commit
1a396ba955
|
@ -10,3 +10,4 @@
|
||||||
|
|
||||||
require 'rex/proto/tftp/constants'
|
require 'rex/proto/tftp/constants'
|
||||||
require 'rex/proto/tftp/server'
|
require 'rex/proto/tftp/server'
|
||||||
|
require 'rex/proto/tftp/client'
|
||||||
|
|
|
@ -0,0 +1,343 @@
|
||||||
|
require 'rex/socket'
|
||||||
|
require 'rex/proto/tftp'
|
||||||
|
require 'tempfile'
|
||||||
|
|
||||||
|
module Rex
|
||||||
|
module Proto
|
||||||
|
module TFTP
|
||||||
|
|
||||||
|
#
|
||||||
|
# TFTP Client class
|
||||||
|
#
|
||||||
|
# Note that TFTP has blocks, and so does Ruby. Watch out with the variable names!
|
||||||
|
#
|
||||||
|
# The big gotcha right now is that setting the mode between octet, netascii, or
|
||||||
|
# anything else doesn't actually do anything other than declare it to the
|
||||||
|
# server.
|
||||||
|
#
|
||||||
|
# Also, since TFTP clients act as both clients and servers, we use two
|
||||||
|
# threads to handle transfers, regardless of the direction. For this reason,
|
||||||
|
# the transfer actions are nonblocking; if you need to see the
|
||||||
|
# results of a transfer before doing something else, check the boolean complete
|
||||||
|
# attribute and any return data in the :status attribute. It's a little
|
||||||
|
# weird like that.
|
||||||
|
#
|
||||||
|
# Finally, most (all?) clients will alter the data in netascii mode in order
|
||||||
|
# to try to conform to the RFC standard for what "netascii" means, but there are
|
||||||
|
# ambiguities in implementations on things like if nulls are allowed, what
|
||||||
|
# to do with Unicode, and all that. For this reason, "octet" is default, and
|
||||||
|
# if you want to send "netascii" data, it's on you to fix up your source data
|
||||||
|
# prior to sending it.
|
||||||
|
#
|
||||||
|
class Client
|
||||||
|
|
||||||
|
attr_accessor :local_host, :local_port, :peer_host, :peer_port
|
||||||
|
attr_accessor :threads, :context, :server_sock, :client_sock
|
||||||
|
attr_accessor :local_file, :remote_file, :mode, :action
|
||||||
|
attr_accessor :complete, :recv_tempfile, :status
|
||||||
|
attr_accessor :block_size # This definitely breaks spec, should only use for fuzz/sploit.
|
||||||
|
|
||||||
|
# Returns an array of [code, type, msg]. Data packets
|
||||||
|
# specifically will /not/ unpack, since that would drop any trailing spaces or nulls.
|
||||||
|
def parse_tftp_response(str)
|
||||||
|
return nil unless str.length >= 4
|
||||||
|
ret = str.unpack("nnA*")
|
||||||
|
ret[2] = str[4,str.size] if ret[0] == OpData
|
||||||
|
return ret
|
||||||
|
end
|
||||||
|
|
||||||
|
def initialize(params)
|
||||||
|
self.threads = []
|
||||||
|
self.local_host = params["LocalHost"] || "0.0.0.0"
|
||||||
|
self.local_port = params["LocalPort"] || (1025 + rand(0xffff-1025))
|
||||||
|
self.peer_host = params["PeerHost"] || (raise ArgumentError, "Need a peer host.")
|
||||||
|
self.peer_port = params["PeerPort"] || 69
|
||||||
|
self.context = params["Context"] || {}
|
||||||
|
self.local_file = params["LocalFile"]
|
||||||
|
self.remote_file = params["RemoteFile"] || ::File.split(self.local_file).last
|
||||||
|
self.mode = params["Mode"] || "octet"
|
||||||
|
self.action = params["Action"] || (raise ArgumentError, "Need an action.")
|
||||||
|
self.block_size = params["BlockSize"] || 512
|
||||||
|
end
|
||||||
|
|
||||||
|
#
|
||||||
|
# Methods for both upload and download
|
||||||
|
#
|
||||||
|
|
||||||
|
def start_server_socket
|
||||||
|
self.server_sock = Rex::Socket::Udp.create(
|
||||||
|
'LocalHost' => local_host,
|
||||||
|
'LocalPort' => local_port,
|
||||||
|
'Context' => context
|
||||||
|
)
|
||||||
|
if self.server_sock and block_given?
|
||||||
|
yield "Started TFTP client listener on #{local_host}:#{local_port}"
|
||||||
|
end
|
||||||
|
self.threads << Rex::ThreadFactory.spawn("TFTPServerMonitor", false) {
|
||||||
|
if block_given?
|
||||||
|
monitor_server_sock {|msg| yield msg}
|
||||||
|
else
|
||||||
|
monitor_server_sock
|
||||||
|
end
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def monitor_server_sock
|
||||||
|
yield "Listening for incoming ACKs" if block_given?
|
||||||
|
res = self.server_sock.recvfrom(65535)
|
||||||
|
if res and res[0]
|
||||||
|
code, type, data = parse_tftp_response(res[0])
|
||||||
|
if code == OpAck and self.action == :upload
|
||||||
|
if block_given?
|
||||||
|
yield "WRQ accepted, sending the file." if type == 0
|
||||||
|
send_data(res[1], res[2]) {|msg| yield msg}
|
||||||
|
else
|
||||||
|
send_data(res[1], res[2])
|
||||||
|
end
|
||||||
|
elsif code == OpData and self.action == :download
|
||||||
|
if block_given?
|
||||||
|
recv_data(res[1], res[2], data) {|msg| yield msg}
|
||||||
|
else
|
||||||
|
recv_data(res[1], res[2], data)
|
||||||
|
end
|
||||||
|
elsif code == OpError
|
||||||
|
yield("Aborting, got error type:%d, message:'%s'" % [type, data]) if block_given?
|
||||||
|
self.status = {:error => [code, type, data]}
|
||||||
|
else
|
||||||
|
yield("Aborting, got code:%d, type:%d, message:'%s'" % [code, type, data]) if block_given?
|
||||||
|
self.status = {:error => [code, type, data]}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
stop
|
||||||
|
end
|
||||||
|
|
||||||
|
def monitor_client_sock
|
||||||
|
res = self.client_sock.recvfrom(65535)
|
||||||
|
if res[1] # Got a response back, so that's never good; Acks come back on server_sock.
|
||||||
|
code, type, data = parse_tftp_response(res[0])
|
||||||
|
yield("Aborting, got code:%d, type:%d, message:'%s'" % [code, type, data]) if block_given?
|
||||||
|
self.status = {:error => [code, type, data]}
|
||||||
|
stop
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def stop
|
||||||
|
self.complete = true
|
||||||
|
begin
|
||||||
|
self.server_sock.close
|
||||||
|
self.client_sock.close
|
||||||
|
self.server_sock = nil
|
||||||
|
self.client_sock = nil
|
||||||
|
self.threads.each {|t| t.kill}
|
||||||
|
rescue
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
#
|
||||||
|
# Methods for download
|
||||||
|
#
|
||||||
|
|
||||||
|
def rrq_packet
|
||||||
|
req = [OpRead, self.remote_file, self.mode]
|
||||||
|
packstr = "na#{self.remote_file.length+1}a#{self.mode.length+1}"
|
||||||
|
req.pack(packstr)
|
||||||
|
end
|
||||||
|
|
||||||
|
def ack_packet(blocknum=0)
|
||||||
|
req = [OpAck, blocknum].pack("nn")
|
||||||
|
end
|
||||||
|
|
||||||
|
def send_read_request(&block)
|
||||||
|
self.status = nil
|
||||||
|
self.complete = false
|
||||||
|
if block_given?
|
||||||
|
start_server_socket {|msg| yield msg}
|
||||||
|
else
|
||||||
|
start_server_socket
|
||||||
|
end
|
||||||
|
self.client_sock = Rex::Socket::Udp.create(
|
||||||
|
'PeerHost' => peer_host,
|
||||||
|
'PeerPort' => peer_port,
|
||||||
|
'LocalHost' => local_host,
|
||||||
|
'LocalPort' => local_port,
|
||||||
|
'Context' => context
|
||||||
|
)
|
||||||
|
self.client_sock.sendto(rrq_packet, peer_host, peer_port)
|
||||||
|
self.threads << Rex::ThreadFactory.spawn("TFTPClientMonitor", false) {
|
||||||
|
if block_given?
|
||||||
|
monitor_client_sock {|msg| yield msg}
|
||||||
|
else
|
||||||
|
monitor_client_sock
|
||||||
|
end
|
||||||
|
}
|
||||||
|
until self.complete
|
||||||
|
return self.status
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def recv_data(host, port, first_block)
|
||||||
|
self.recv_tempfile = Rex::Quickfile.new('msf-tftp')
|
||||||
|
recvd_blocks = 1
|
||||||
|
if block_given?
|
||||||
|
yield "Source file: #{self.remote_file}, destination file: #{self.local_file}"
|
||||||
|
yield "Received and acknowledged #{first_block.size} in block #{recvd_blocks}"
|
||||||
|
end
|
||||||
|
if block_given?
|
||||||
|
write_and_ack_data(first_block,1,host,port) {|msg| yield msg}
|
||||||
|
else
|
||||||
|
write_and_ack_data(first_block,1,host,port)
|
||||||
|
end
|
||||||
|
current_block = first_block
|
||||||
|
while current_block.size == 512
|
||||||
|
res = self.server_sock.recvfrom(65535)
|
||||||
|
if res and res[0]
|
||||||
|
code, block_num, current_block = parse_tftp_response(res[0])
|
||||||
|
if code == 3
|
||||||
|
if block_given?
|
||||||
|
write_and_ack_data(current_block,block_num,host,port) {|msg| yield msg}
|
||||||
|
else
|
||||||
|
write_and_ack_data(current_block,block_num,host,port)
|
||||||
|
end
|
||||||
|
recvd_blocks += 1
|
||||||
|
else
|
||||||
|
yield("Aborting, got code:%d, type:%d, message:'%s'" % [code, type, msg]) if block_given?
|
||||||
|
stop
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if block_given?
|
||||||
|
yield("Transferred #{self.recv_tempfile.size} bytes in #{recvd_blocks} blocks, download complete!")
|
||||||
|
end
|
||||||
|
self.status = {:success => [
|
||||||
|
self.local_file,
|
||||||
|
self.remote_file,
|
||||||
|
self.recv_tempfile.size,
|
||||||
|
recvd_blocks.size]
|
||||||
|
}
|
||||||
|
self.recv_tempfile.close
|
||||||
|
stop
|
||||||
|
end
|
||||||
|
|
||||||
|
def write_and_ack_data(data,blocknum,host,port)
|
||||||
|
self.recv_tempfile.write(data)
|
||||||
|
self.recv_tempfile.flush
|
||||||
|
req = ack_packet(blocknum)
|
||||||
|
self.server_sock.sendto(req, host, port)
|
||||||
|
yield "Received and acknowledged #{data.size} in block #{blocknum}" if block_given?
|
||||||
|
end
|
||||||
|
|
||||||
|
#
|
||||||
|
# Methods for upload
|
||||||
|
#
|
||||||
|
|
||||||
|
def wrq_packet
|
||||||
|
req = [OpWrite, self.remote_file, self.mode]
|
||||||
|
packstr = "na#{self.remote_file.length+1}a#{self.mode.length+1}"
|
||||||
|
req.pack(packstr)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Note that the local filename for uploading need not be a real filename --
|
||||||
|
# if it begins with DATA: it can be any old string of bytes. If it's missing
|
||||||
|
# completely, then just quit.
|
||||||
|
def blockify_file_or_data
|
||||||
|
if self.local_file =~ /^DATA:(.*)/m
|
||||||
|
data = $1
|
||||||
|
elsif ::File.file?(self.local_file) and ::File.readable?(self.local_file)
|
||||||
|
data = ::File.open(self.local_file, "rb") {|f| f.read f.stat.size} rescue []
|
||||||
|
else
|
||||||
|
return []
|
||||||
|
end
|
||||||
|
data_blocks = data.scan(/.{1,#{block_size}}/m)
|
||||||
|
# Drop any trailing empty blocks
|
||||||
|
if data_blocks.size > 1 and data_blocks.last.empty?
|
||||||
|
data_blocks.pop
|
||||||
|
end
|
||||||
|
return data_blocks
|
||||||
|
end
|
||||||
|
|
||||||
|
def send_write_request(&block)
|
||||||
|
self.status = nil
|
||||||
|
self.complete = false
|
||||||
|
if block_given?
|
||||||
|
start_server_socket {|msg| yield msg}
|
||||||
|
else
|
||||||
|
start_server_socket
|
||||||
|
end
|
||||||
|
self.client_sock = Rex::Socket::Udp.create(
|
||||||
|
'PeerHost' => peer_host,
|
||||||
|
'PeerPort' => peer_port,
|
||||||
|
'LocalHost' => local_host,
|
||||||
|
'LocalPort' => local_port,
|
||||||
|
'Context' => context
|
||||||
|
)
|
||||||
|
self.client_sock.sendto(wrq_packet, peer_host, peer_port)
|
||||||
|
self.threads << Rex::ThreadFactory.spawn("TFTPClientMonitor", false) {
|
||||||
|
if block_given?
|
||||||
|
monitor_client_sock {|msg| yield msg}
|
||||||
|
else
|
||||||
|
monitor_client_sock
|
||||||
|
end
|
||||||
|
}
|
||||||
|
until self.complete
|
||||||
|
return self.status
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def send_data(host,port)
|
||||||
|
self.status = {:write_allowed => true}
|
||||||
|
data_blocks = blockify_file_or_data()
|
||||||
|
if data_blocks.empty?
|
||||||
|
yield "Closing down since there is no data to send." if block_given?
|
||||||
|
self.status = {:success => [self.local_file, self.local_file, 0, 0]}
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
sent_data = 0
|
||||||
|
sent_blocks = 0
|
||||||
|
expected_blocks = data_blocks.size
|
||||||
|
expected_size = data_blocks.join.size
|
||||||
|
if block_given?
|
||||||
|
yield "Source file: #{self.local_file =~ /^DATA:/ ? "(Data)" : self.remote_file}, destination file: #{self.remote_file}"
|
||||||
|
yield "Sending #{expected_size} bytes (#{expected_blocks} blocks)"
|
||||||
|
end
|
||||||
|
data_blocks.each_with_index do |data_block,idx|
|
||||||
|
req = [OpData, (idx + 1), data_block].pack("nnA*")
|
||||||
|
if self.server_sock.sendto(req, host, port) > 0
|
||||||
|
sent_data += data_block.size
|
||||||
|
end
|
||||||
|
res = self.server_sock.recvfrom(65535)
|
||||||
|
if res
|
||||||
|
code, type, msg = parse_tftp_response(res[0])
|
||||||
|
if code == 4
|
||||||
|
sent_blocks += 1
|
||||||
|
yield "Sent #{data_block.size} bytes in block #{sent_blocks}" if block_given?
|
||||||
|
else
|
||||||
|
if block_given?
|
||||||
|
yield "Got an unexpected response: Code:%d, Type:%d, Message:'%s'. Aborting." % [code, type, msg]
|
||||||
|
end
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if block_given?
|
||||||
|
if(sent_data == expected_size)
|
||||||
|
yield("Transferred #{sent_data} bytes in #{sent_blocks} blocks, upload complete!")
|
||||||
|
else
|
||||||
|
yield "Upload complete, but with errors."
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if sent_data == expected_size
|
||||||
|
self.status = {:success => [
|
||||||
|
self.local_file,
|
||||||
|
self.remote_file,
|
||||||
|
sent_data,
|
||||||
|
sent_blocks
|
||||||
|
] }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,209 @@
|
||||||
|
##
|
||||||
|
# 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/
|
||||||
|
##
|
||||||
|
|
||||||
|
|
||||||
|
require 'msf/core'
|
||||||
|
|
||||||
|
class Metasploit3 < Msf::Auxiliary
|
||||||
|
|
||||||
|
include Rex::Proto::TFTP
|
||||||
|
include Msf::Auxiliary::Report
|
||||||
|
|
||||||
|
def initialize
|
||||||
|
super(
|
||||||
|
'Name' => 'TFTP File Transfer Utility',
|
||||||
|
'Description' => %q{
|
||||||
|
This module will transfer a file to or from a remote TFTP server.
|
||||||
|
Note that the target must be able to connect back to the Metasploit system,
|
||||||
|
and NAT traversal for TFTP is often unsupported.
|
||||||
|
|
||||||
|
Two actions are supported: "Upload" and "Download," which behave as one might
|
||||||
|
expect -- use 'set action Actionname' to use either mode of operation.
|
||||||
|
|
||||||
|
If "Download" is selected, at least one of FILENAME or REMOTE_FILENAME
|
||||||
|
must be set. If "Upload" is selected, either FILENAME must be set to a valid path to
|
||||||
|
a source file, or FILEDATA must be populated. FILENAME may be a fully qualified path,
|
||||||
|
or the name of a file in the Msf::Config.local_directory or Msf::Config.data_directory.
|
||||||
|
},
|
||||||
|
'Author' => [ 'todb' ],
|
||||||
|
'References' =>
|
||||||
|
[
|
||||||
|
['URL', 'http://www.faqs.org/rfcs/rfc1350.html'],
|
||||||
|
['URL', 'http://www.networksorcery.com/enp/protocol/tftp.htm']
|
||||||
|
],
|
||||||
|
'Actions' => [
|
||||||
|
[ 'Download', {'Description' => "Download REMOTE_FILENAME as FILENAME from the server."}],
|
||||||
|
[ 'Upload', {'Description' => "Upload FILENAME as REMOTE_FILENAME to the server."}]
|
||||||
|
],
|
||||||
|
'DefaultAction' => 'Upload',
|
||||||
|
'License' => MSF_LICENSE
|
||||||
|
)
|
||||||
|
register_options([
|
||||||
|
OptString.new( 'FILENAME', [false, "The local filename" ]),
|
||||||
|
OptString.new( 'FILEDATA', [false, "Data to upload in lieu of a real local file." ]),
|
||||||
|
OptString.new( 'REMOTE_FILENAME', [false, "The remote filename"]),
|
||||||
|
OptAddress.new('RHOST', [true, "The remote TFTP server"]),
|
||||||
|
OptPort.new( 'LPORT', [false, "The local port the TFTP client should listen on (default is random)" ]),
|
||||||
|
OptAddress.new('LHOST', [false, "The local address the TFTP client should bind to"]),
|
||||||
|
OptBool.new( 'VERBOSE', [false, "Display verbose details about the transfer", false]),
|
||||||
|
OptString.new( 'MODE', [false, "The TFTP mode; usual choices are netascii and octet.", "octet"]),
|
||||||
|
Opt::RPORT(69)
|
||||||
|
], self.class)
|
||||||
|
end
|
||||||
|
|
||||||
|
def mode
|
||||||
|
datastore['MODE'] || "octect"
|
||||||
|
end
|
||||||
|
|
||||||
|
def remote_file
|
||||||
|
datastore['REMOTE_FILENAME'] || ::File.split(datastore['FILENAME']).last
|
||||||
|
end
|
||||||
|
|
||||||
|
def rport
|
||||||
|
datastore['RPORT'] || 69
|
||||||
|
end
|
||||||
|
|
||||||
|
def rhost
|
||||||
|
datastore['RHOST']
|
||||||
|
end
|
||||||
|
|
||||||
|
# Used only to store loot, doesn't actually have any semantic meaning
|
||||||
|
# for the TFTP protocol.
|
||||||
|
def datatype
|
||||||
|
case datastore['MODE']
|
||||||
|
when "netascii"
|
||||||
|
"text/plain"
|
||||||
|
else
|
||||||
|
"application/octet-stream"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def file
|
||||||
|
if action.name == "Upload"
|
||||||
|
fdata = datastore['FILEDATA'].to_s
|
||||||
|
fname = datastore['FILENAME'].to_s
|
||||||
|
if not fdata.empty?
|
||||||
|
fdata_decorated = "DATA:#{datastore['FILEDATA']}"
|
||||||
|
elsif ::File.readable? fname
|
||||||
|
fname
|
||||||
|
else
|
||||||
|
fname_local = ::File.join(Msf::Config.local_directory,fname)
|
||||||
|
fname_data = ::File.join(Msf::Config.data_directory,fname)
|
||||||
|
return fname_local if ::File.file?(fname_local) and ::File.readable?(fname_local)
|
||||||
|
return fname_data if ::File.file?(fname_data) and ::File.readable?(fname_data)
|
||||||
|
return nil # Couldn't find it, giving up.
|
||||||
|
end
|
||||||
|
else # "Download"
|
||||||
|
fname = ::File.split(datastore['FILENAME'] || datastore['REMOTE_FILENAME']).last rescue nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Experimental message prepending thinger. Might make it up into the
|
||||||
|
# standard Metasploit lib like vprint_status and friends.
|
||||||
|
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
|
||||||
|
|
||||||
|
# This all happens before run(), and should give an idea on how to use
|
||||||
|
# the TFTP client mixin. Essentially, you create an instance of the
|
||||||
|
# Rex::Proto::TFTP::Client class, fill it up with the relevant host and
|
||||||
|
# file data, set it to either :upload or :download, then kick off the
|
||||||
|
# transfer as you like.
|
||||||
|
def setup
|
||||||
|
@lport = datastore['LPORT'] || (1025 + rand(0xffff-1025))
|
||||||
|
@lhost = datastore['LHOST'] || "0.0.0.0"
|
||||||
|
@local_file = file
|
||||||
|
@remote_file = remote_file
|
||||||
|
|
||||||
|
@tftp_client = Rex::Proto::TFTP::Client.new(
|
||||||
|
"LocalHost" => @lhost,
|
||||||
|
"LocalPort" => @lport,
|
||||||
|
"PeerHost" => rhost,
|
||||||
|
"PeerPort" => rport,
|
||||||
|
"LocalFile" => @local_file,
|
||||||
|
"RemoteFile" => @remote_file,
|
||||||
|
"Mode" => mode,
|
||||||
|
"Action" => action.name.to_s.downcase.intern
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def run
|
||||||
|
run_upload() if action.name == 'Upload'
|
||||||
|
run_download() if action.name == 'Download'
|
||||||
|
while not @tftp_client.complete
|
||||||
|
select(nil,nil,nil,1)
|
||||||
|
print_status [rtarget,"TFTP transfer operation complete."].join
|
||||||
|
save_downloaded_file() if action.name == 'Download'
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Run in case something untoward happend with the connection and the
|
||||||
|
# client object didn't get stopped on its own. This can happen with
|
||||||
|
# transfers that got interrupted or malformed (like sending a 0 byte
|
||||||
|
# file).
|
||||||
|
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 run_upload
|
||||||
|
print_status "Sending '#{file}' to #{rhost}:#{rport} as '#{remote_file}'"
|
||||||
|
ret = @tftp_client.send_write_request { |msg| print_tftp_status(msg) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def run_download
|
||||||
|
print_status "Receiving '#{remote_file}' from #{rhost}:#{rport} as '#{file}'"
|
||||||
|
ret = @tftp_client.send_read_request { |msg| print_tftp_status(msg) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def save_downloaded_file
|
||||||
|
print_status "Saving #{remote_file} as '#{file}'"
|
||||||
|
fh = @tftp_client.recv_tempfile
|
||||||
|
data = File.open(fh,"rb") {|f| f.read f.stat.size} rescue nil
|
||||||
|
if data and not data.empty?
|
||||||
|
unless framework.db.active
|
||||||
|
print_status "No database connected, so not actually saving the data:"
|
||||||
|
print_line data
|
||||||
|
end
|
||||||
|
this_service = report_service(
|
||||||
|
:host => rhost,
|
||||||
|
:port => rport,
|
||||||
|
:name => "tftp",
|
||||||
|
:proto => "udp"
|
||||||
|
)
|
||||||
|
store_loot("tftp.file",datatype,rhost,data,file,remote_file,this_service)
|
||||||
|
else
|
||||||
|
print_status [rtarget,"Did not find any data, so nothing to save."].join
|
||||||
|
end
|
||||||
|
fh.unlink rescue nil # Windows often complains about unlinking tempfiles
|
||||||
|
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
|
||||||
|
|
||||||
|
end
|
||||||
|
|
Loading…
Reference in New Issue