Files
metasploit-gs/lib/rex/post/meterpreter/extensions/stdapi/fs/file.rb
T
2013-11-07 18:51:59 -05:00

400 lines
9.9 KiB
Ruby

# -*- coding: binary -*-
require 'rex/post/file'
require 'rex/post/meterpreter/channel'
require 'rex/post/meterpreter/channels/pools/file'
require 'rex/post/meterpreter/extensions/stdapi/stdapi'
require 'rex/post/meterpreter/extensions/stdapi/fs/io'
require 'rex/post/meterpreter/extensions/stdapi/fs/file_stat'
require 'fileutils'
module Rex
module Post
module Meterpreter
module Extensions
module Stdapi
module Fs
###
#
# This class implements the Rex::Post::File interface and wraps interaction
# with files on the remote machine.
#
###
class File < Rex::Post::Meterpreter::Extensions::Stdapi::Fs::IO
include Rex::Post::File
class << self
attr_accessor :client
end
#
# Return the directory separator, i.e.: "/" on unix, "\\" on windows
#
def File.separator()
# The separator won't change, so cache it to prevent sending
# unnecessary requests.
return @separator if @separator
request = Packet.create_request('stdapi_fs_separator')
# Fall back to the old behavior of always assuming windows. This
# allows meterpreter executables built before the addition of this
# command to continue functioning.
begin
response = client.send_request(request)
@separator = response.get_tlv_value(TLV_TYPE_STRING)
rescue RequestError
@separator = "\\"
end
return @separator
end
class << self
alias :Separator :separator
alias :SEPARATOR :separator
end
#
# Search for files matching +glob+ starting in directory +root+.
#
# Returns an Array (possibly empty) of Hashes. Each element has the following
# keys:
# 'path':: The directory in which the file was found
# 'name':: File name
# 'size':: Size of the file, in bytes
#
# Example:
# client.fs.file.search(client.fs.dir.pwd, "*.txt")
# # => [{"path"=>"C:\\Documents and Settings\\user\\Desktop", "name"=>"foo.txt", "size"=>0}]
#
# Raises a RequestError if +root+ is not a directory.
#
def File.search( root=nil, glob="*.*", recurse=true, timeout=-1 )
files = ::Array.new
request = Packet.create_request( 'stdapi_fs_search' )
root = client.unicode_filter_decode(root) if root
root = root.chomp( '\\' ) if root
request.add_tlv( TLV_TYPE_SEARCH_ROOT, root )
request.add_tlv( TLV_TYPE_SEARCH_GLOB, glob )
request.add_tlv( TLV_TYPE_SEARCH_RECURSE, recurse )
# we set the response timeout to -1 to wait indefinatly as a
# search could take an indeterminate ammount of time to complete.
response = client.send_request( request, timeout )
if( response.result == 0 )
response.each( TLV_TYPE_SEARCH_RESULTS ) do | results |
files << {
'path' => client.unicode_filter_encode( results.get_tlv_value( TLV_TYPE_FILE_PATH ).chomp( '\\' ) ),
'name' => client.unicode_filter_encode( results.get_tlv_value( TLV_TYPE_FILE_NAME ) ),
'size' => results.get_tlv_value( TLV_TYPE_FILE_SIZE )
}
end
end
return files
end
#
# Returns the base name of the supplied file path to the caller.
#
def File.basename(*a)
path = a[0]
# Allow both kinds of dir serparators since lots and lots of code
# assumes one or the other so this ends up getting called with strings
# like: "C:\\foo/bar"
path =~ %r#.*[/\\](.*)$#
Rex::FileUtils.clean_path($1 || path)
end
#
# Expands a file path, substituting all environment variables, such as
# %TEMP%.
#
# Examples:
# client.fs.file.expand_path("%appdata%")
# # => "C:\\Documents and Settings\\user\\Application Data"
# client.fs.file.expand_path("asdf")
# # => "asdf"
#
# NOTE: This method is fairly specific to Windows. It has next to no relation
# to the ::File.expand_path method! In particular, it does *not* do ~
# expansion or environment variable expansion on non-Windows systems. For
# these reasons, this method may be deprecated in the future. Use it with
# caution.
#
def File.expand_path(path)
request = Packet.create_request('stdapi_fs_file_expand_path')
request.add_tlv(TLV_TYPE_FILE_PATH, client.unicode_filter_decode( path ))
response = client.send_request(request)
return client.unicode_filter_encode( response.get_tlv_value(TLV_TYPE_FILE_PATH) )
end
#
# Calculates the MD5 (16-bytes raw) of a remote file
#
def File.md5(path)
request = Packet.create_request('stdapi_fs_md5')
request.add_tlv(TLV_TYPE_FILE_PATH, client.unicode_filter_decode( path ))
response = client.send_request(request)
# This is not really a file name, but a raw hash in bytes
return response.get_tlv_value(TLV_TYPE_FILE_NAME)
end
#
# Calculates the SHA1 (20-bytes raw) of a remote file
#
def File.sha1(path)
request = Packet.create_request('stdapi_fs_sha1')
request.add_tlv(TLV_TYPE_FILE_PATH, client.unicode_filter_decode( path ))
response = client.send_request(request)
# This is not really a file name, but a raw hash in bytes
return response.get_tlv_value(TLV_TYPE_FILE_NAME)
end
#
# Performs a stat on a file and returns a FileStat instance.
#
def File.stat(name)
return client.fs.filestat.new( name )
end
#
# Returns true if the remote file +name+ exists, false otherwise
#
def File.exists?(name)
r = client.fs.filestat.new(name) rescue nil
r ? true : false
end
#
# Performs a delete on the remote file +name+
#
def File.rm(name)
request = Packet.create_request('stdapi_fs_delete_file')
request.add_tlv(TLV_TYPE_FILE_PATH, client.unicode_filter_decode( name ))
response = client.send_request(request)
return response
end
class << self
alias unlink rm
alias delete rm
end
#
# Performs a rename from oldname to newname
#
def File.mv(oldname, newname)
request = Packet.create_request('stdapi_fs_file_move')
request.add_tlv(TLV_TYPE_FILE_NAME, client.unicode_filter_decode( oldname ))
request.add_tlv(TLV_TYPE_FILE_PATH, client.unicode_filter_decode( newname ))
response = client.send_request(request)
return response
end
class << self
alias move mv
alias rename mv
end
#
# Upload one or more files to the remote remote directory supplied in
# +destination+.
#
# If a block is given, it will be called before each file is uploaded and
# again when each upload is complete.
#
def File.upload(destination, *src_files, &stat)
src_files.each { |src|
dest = destination
stat.call('uploading', src, dest) if (stat)
if (self.basename(destination) != ::File.basename(src))
dest += self.separator + ::File.basename(src)
end
upload_file(dest, src)
stat.call('uploaded', src, dest) if (stat)
}
end
#
# Upload a single file.
#
def File.upload_file(dest_file, src_file)
# Open the file on the remote side for writing and read
# all of the contents of the local file
dest_fd = client.fs.file.new(dest_file, "wb")
src_buf = ''
::File.open(src_file, 'rb') { |f|
src_buf = f.read(f.stat.size)
}
begin
dest_fd.write(src_buf)
ensure
dest_fd.close
end
end
#
# Download one or more files from the remote computer to the local
# directory supplied in destination.
#
# If a block is given, it will be called before each file is downloaded and
# again when each download is complete.
#
def File.download(dest, *src_files, &stat)
src_files.each { |src|
if (::File.basename(dest) != File.basename(src))
# The destination when downloading is a local file so use this
# system's separator
dest += ::File::SEPARATOR + File.basename(src)
end
stat.call('downloading', src, dest) if (stat)
download_file(dest, src)
stat.call('downloaded', src, dest) if (stat)
}
end
#
# Download a single file.
#
def File.download_file(dest_file, src_file)
src_fd = client.fs.file.new(src_file, "rb")
dir = ::File.dirname(dest_file)
::FileUtils.mkdir_p(dir) if dir and not ::File.directory?(dir)
dst_fd = ::File.new(dest_file, "wb")
# Keep transferring until EOF is reached...
begin
while ((data = src_fd.read) != nil)
dst_fd.write(data)
end
rescue EOFError
ensure
src_fd.close
dst_fd.close
end
end
#
# With no associated block, File.open is a synonym for ::new. If the optional
# code block is given, it will be passed the opened file as an argument, and
# the File object will automatically be closed when the block terminates. In
# this instance, File.open returns the value of the block.
#
# (doc stolen from http://www.ruby-doc.org/core-1.9.3/File.html#method-c-open)
#
def File.open(name, mode="r", perms=0)
f = new(name, mode, perms)
if block_given?
ret = yield f
f.close
return ret
else
return f
end
end
##
#
# Constructor
#
##
#
# Initializes and opens the specified file with the specified permissions.
#
def initialize(name, mode = "r", perms = 0)
self.client = self.class.client
self.filed = _open(name, mode, perms)
end
##
#
# IO implementators
#
##
#
# Returns whether or not the file has reach EOF.
#
def eof
return self.filed.eof
end
#
# Returns the current position of the file pointer.
#
def pos
return self.filed.tell
end
#
# Synonym for sysseek.
#
def seek(offset, whence = ::IO::SEEK_SET)
return self.sysseek(offset, whence)
end
#
# Seeks to the supplied offset based on the supplied relativity.
#
def sysseek(offset, whence = ::IO::SEEK_SET)
return self.filed.seek(offset, whence)
end
protected
##
#
# Internal methods
#
##
#
# Creates a File channel using the supplied information.
#
def _open(name, mode = "r", perms = 0)
return Rex::Post::Meterpreter::Channels::Pools::File.open(
self.client, name, mode, perms)
end
attr_accessor :client # :nodoc:
end
end; end; end; end; end; end