Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Path: blob/master/lib/rex/post/meterpreter/extensions/stdapi/fs/file.rb
Views: 11795
# -*- coding: binary -*-12require 'rex/post/file'3require 'rex/post/meterpreter/channel'4require 'rex/post/meterpreter/channels/pools/file'5require 'rex/post/meterpreter/extensions/stdapi/stdapi'6require 'rex/post/meterpreter/extensions/stdapi/fs/io'7require 'rex/post/meterpreter/extensions/stdapi/fs/file_stat'8require 'fileutils'9require 'filesize'1011module Rex12module Post13module Meterpreter14module Extensions15module Stdapi16module Fs1718###19#20# This class implements the Rex::Post::File interface and wraps interaction21# with files on the remote machine.22#23###24class File < Rex::Post::Meterpreter::Extensions::Stdapi::Fs::IO2526include Rex::Post::File2728MIN_BLOCK_SIZE = 10242930class << self31attr_accessor :client32end3334#35# Return the directory separator, i.e.: "/" on unix, "\\" on windows36#37def File.separator()38# The separator won't change, so cache it to prevent sending39# unnecessary requests.40return @separator if @separator4142request = Packet.create_request(COMMAND_ID_STDAPI_FS_SEPARATOR)4344# Fall back to the old behavior of always assuming windows. This45# allows meterpreter executables built before the addition of this46# command to continue functioning.47begin48response = client.send_request(request)49@separator = response.get_tlv_value(TLV_TYPE_STRING)50rescue RequestError51@separator = "\\"52end5354return @separator55end5657class << self58alias :Separator :separator59alias :SEPARATOR :separator60end6162#63# Search for files matching +glob+ starting in directory +root+.64#65# Returns an Array (possibly empty) of Hashes. Each element has the following66# keys:67# 'path':: The directory in which the file was found68# 'name':: File name69# 'size':: Size of the file, in bytes70#71# Example:72# client.fs.file.search(client.fs.dir.pwd, "*.txt")73# # => [{"path"=>"C:\\Documents and Settings\\user\\Desktop", "name"=>"foo.txt", "size"=>0}]74#75# Raises a RequestError if +root+ is not a directory.76#77def File.search( root=nil, glob="*.*", recurse=true, timeout=-1, modified_start_date=nil, modified_end_date=nil)7879files = ::Array.new8081request = Packet.create_request( COMMAND_ID_STDAPI_FS_SEARCH )8283root = client.unicode_filter_decode(root) if root84root = root.chomp( self.separator ) if root && !root.eql?('/')8586request.add_tlv( TLV_TYPE_SEARCH_ROOT, root )87request.add_tlv( TLV_TYPE_SEARCH_GLOB, glob )88request.add_tlv( TLV_TYPE_SEARCH_RECURSE, recurse )89request.add_tlv( TLV_TYPE_SEARCH_M_START_DATE, modified_start_date) if modified_start_date90request.add_tlv( TLV_TYPE_SEARCH_M_END_DATE, modified_end_date) if modified_end_date9192# we set the response timeout to -1 to wait indefinitely as a93# search could take an indeterminate amount of time to complete.94response = client.send_request( request, timeout )95if( response.result == 0 )96response.each( TLV_TYPE_SEARCH_RESULTS ) do | results |97files << {98'path' => client.unicode_filter_encode(results.get_tlv_value(TLV_TYPE_FILE_PATH).chomp( self.separator )),99'name' => client.unicode_filter_encode(results.get_tlv_value(TLV_TYPE_FILE_NAME)),100'size' => results.get_tlv_value(TLV_TYPE_FILE_SIZE),101'mtime'=> results.get_tlv_value(TLV_TYPE_SEARCH_MTIME)102}103end104end105106return files107end108109#110# Returns the base name of the supplied file path to the caller.111#112def File.basename(*a)113path = a[0]114115# Allow both kinds of dir serparators since lots and lots of code116# assumes one or the other so this ends up getting called with strings117# like: "C:\\foo/bar"118path =~ %r#.*[/\\](.*)$#119120Rex::FileUtils.clean_path($1 || path)121end122123#124# Expands a file path, substituting all environment variables, such as125# %TEMP% on Windows or $HOME on Unix126#127# Examples:128# client.fs.file.expand_path("%appdata%")129# # => "C:\\Documents and Settings\\user\\Application Data"130# client.fs.file.expand_path("~")131# # => "/home/user"132# client.fs.file.expand_path("$HOME/dir")133# # => "/home/user/dir"134# client.fs.file.expand_path("asdf")135# # => "asdf"136#137def File.expand_path(path)138case client.platform139when 'osx', 'freebsd', 'bsd', 'linux', 'android', 'apple_ios'140# For unix-based systems, do some of the work here141# First check for ~142path_components = path.split(separator)143if path_components.length > 0 && path_components[0] == '~'144path = "$HOME#{path[1..-1]}"145end146147# Now find the environment variables we'll need from the client148env_regex = /\$(?:([A-Za-z0-9_]+)|\{([A-Za-z0-9_]+)\})/149matches = path.to_enum(:scan, env_regex).map { Regexp.last_match }150env_vars = matches.map { |match| (match[1] || match[2]).to_s }.uniq151152# Retrieve them153env_vals = client.sys.config.getenvs(*env_vars)154155# Now fill them in156path.gsub(env_regex) { |_z| envvar = $1; envvar = $2 if envvar == nil; env_vals[envvar] }157else158request = Packet.create_request(COMMAND_ID_STDAPI_FS_FILE_EXPAND_PATH)159160request.add_tlv(TLV_TYPE_FILE_PATH, client.unicode_filter_decode( path ))161162response = client.send_request(request)163164return client.unicode_filter_encode(response.get_tlv_value(TLV_TYPE_FILE_PATH))165end166end167168169#170# Calculates the MD5 (16-bytes raw) of a remote file171#172def File.md5(path)173request = Packet.create_request(COMMAND_ID_STDAPI_FS_MD5)174175request.add_tlv(TLV_TYPE_FILE_PATH, client.unicode_filter_decode( path ))176177response = client.send_request(request)178179# older meterpreter binaries will send FILE_NAME containing the hash180hash = response.get_tlv_value(TLV_TYPE_FILE_HASH) ||181response.get_tlv_value(TLV_TYPE_FILE_NAME)182return hash183end184185#186# Calculates the SHA1 (20-bytes raw) of a remote file187#188def File.sha1(path)189request = Packet.create_request(COMMAND_ID_STDAPI_FS_SHA1)190191request.add_tlv(TLV_TYPE_FILE_PATH, client.unicode_filter_decode( path ))192193response = client.send_request(request)194195# older meterpreter binaries will send FILE_NAME containing the hash196hash = response.get_tlv_value(TLV_TYPE_FILE_HASH) ||197response.get_tlv_value(TLV_TYPE_FILE_NAME)198return hash199end200201#202# Performs a stat on a file and returns a FileStat instance.203#204def File.stat(name)205return client.fs.filestat.new( name )206end207208#209# Returns true if the remote file +name+ exists, false otherwise210#211def File.exist?(name)212r = client.fs.filestat.new(name) rescue nil213r ? true : false214end215216#217# Performs a delete on the remote file +name+218#219def File.rm(name)220request = Packet.create_request(COMMAND_ID_STDAPI_FS_DELETE_FILE)221222request.add_tlv(TLV_TYPE_FILE_PATH, client.unicode_filter_decode( name ))223224response = client.send_request(request)225226return response227end228229class << self230alias unlink rm231alias delete rm232end233234#235# Performs a rename from oldname to newname236#237def File.mv(oldname, newname)238request = Packet.create_request(COMMAND_ID_STDAPI_FS_FILE_MOVE)239240request.add_tlv(TLV_TYPE_FILE_NAME, client.unicode_filter_decode( oldname ))241request.add_tlv(TLV_TYPE_FILE_PATH, client.unicode_filter_decode( newname ))242243response = client.send_request(request)244245return response246end247248class << self249alias move mv250alias rename mv251end252253#254# Performs a copy from oldname to newname255#256def File.cp(oldname, newname)257request = Packet.create_request(COMMAND_ID_STDAPI_FS_FILE_COPY)258259request.add_tlv(TLV_TYPE_FILE_NAME, client.unicode_filter_decode( oldname ))260request.add_tlv(TLV_TYPE_FILE_PATH, client.unicode_filter_decode( newname ))261262response = client.send_request(request)263264return response265end266267class << self268alias copy cp269end270271#272# Performs a chmod on the remote file273#274def File.chmod(name, mode)275request = Packet.create_request(COMMAND_ID_STDAPI_FS_CHMOD)276277request.add_tlv(TLV_TYPE_FILE_PATH, client.unicode_filter_decode( name ))278request.add_tlv(TLV_TYPE_FILE_MODE_T, mode)279280response = client.send_request(request)281282return response283end284285#286# Upload one or more files to the remote remote directory supplied in287# +destination+.288#289# If a block is given, it will be called before each file is uploaded and290# again when each upload is complete.291#292def File.upload(dest, *src_files, &stat)293src_files.each { |src|294if (self.basename(dest) != ::File.basename(src))295dest += self.separator unless dest.end_with?(self.separator)296dest += ::File.basename(src)297end298stat.call('Uploading', src, dest) if (stat)299300upload_file(dest, src)301stat.call('Completed', src, dest) if (stat)302}303end304305#306# Upload a single file.307#308def File.upload_file(dest_file, src_file, &stat)309# Open the file on the remote side for writing and read310# all of the contents of the local file311stat.call('Uploading', src_file, dest_file) if stat312dest_fd = nil313src_fd = nil314buf_size = 8 * 1024 * 1024315316begin317dest_fd = client.fs.file.new(dest_file, "wb")318src_fd = ::File.open(src_file, "rb")319src_size = src_fd.stat.size320while (buf = src_fd.read(buf_size))321dest_fd.write(buf)322percent = dest_fd.pos.to_f / src_size.to_f * 100.0323msg = "Uploaded #{Filesize.new(dest_fd.pos).pretty} of " \324"#{Filesize.new(src_size).pretty} (#{percent.round(2)}%)"325stat.call(msg, src_file, dest_file) if stat326end327ensure328src_fd.close unless src_fd.nil?329dest_fd.close unless dest_fd.nil?330end331stat.call('Completed', src_file, dest_file) if stat332end333334def File.is_glob?(name)335/\*|\[|\?/ === name336end337338#339# Download one or more files from the remote computer to the local340# directory supplied in destination.341#342# If a block is given, it will be called before each file is downloaded and343# again when each download is complete.344#345def File.download(dest, src_files, opts = {}, &stat)346dest.force_encoding('UTF-8')347timestamp = opts["timestamp"]348[*src_files].each { |src|349src.force_encoding('UTF-8')350if (::File.basename(dest) != File.basename(src))351# The destination when downloading is a local file so use this352# system's separator353dest += ::File::SEPARATOR unless dest.end_with?(::File::SEPARATOR)354dest += File.basename(src)355end356357# XXX: dest can be the same object as src, so we use += instead of <<358if timestamp359dest += timestamp360end361362stat.call('Downloading', src, dest) if (stat)363result = download_file(dest, src, opts, &stat)364stat.call(result, src, dest) if (stat)365}366end367368#369# Download a single file.370#371def File.download_file(dest_file, src_file, opts = {}, &stat)372stat ||= lambda { |a,b,c| }373374adaptive = opts["adaptive"]375block_size = opts["block_size"] || 1024 * 1024376continue = opts["continue"]377tries_no = opts["tries_no"]378tries = opts["tries"]379380src_fd = client.fs.file.new(src_file, "rb")381382# Check for changes383src_stat = client.fs.filestat.new(src_file)384if ::File.exist?(dest_file)385dst_stat = ::File.stat(dest_file)386if src_stat.size == dst_stat.size && src_stat.mtime == dst_stat.mtime387src_fd.close388return 'Skipped'389end390end391392# Make the destination path if necessary393dir = ::File.dirname(dest_file)394::FileUtils.mkdir_p(dir) if dir and not ::File.directory?(dir)395396src_size = Filesize.new(src_stat.size).pretty397398if continue399# continue downloading the file - skip downloaded part in the source400dst_fd = ::File.new(dest_file, "ab")401begin402dst_fd.seek(0, ::IO::SEEK_END)403in_pos = dst_fd.pos404src_fd.seek(in_pos)405stat.call("Continuing from #{Filesize.new(in_pos).pretty} of #{src_size}", src_file, dest_file)406rescue407# if we can't seek, download again408stat.call('Error continuing - downloading from scratch', src_file, dest_file)409dst_fd.close410dst_fd = ::File.new(dest_file, "wb")411end412else413dst_fd = ::File.new(dest_file, "wb")414end415416# Keep transferring until EOF is reached...417begin418if tries419# resume when timeouts encountered420seek_back = false421adjust_block = false422tries_cnt = 0423begin # while424begin # exception425if seek_back426in_pos = dst_fd.pos427src_fd.seek(in_pos)428seek_back = false429stat.call("Resuming at #{Filesize.new(in_pos).pretty} of #{src_size}", src_file, dest_file)430else431# successfully read and wrote - reset the counter432tries_cnt = 0433end434adjust_block = true435data = src_fd.read(block_size)436adjust_block = false437rescue Rex::TimeoutError438# timeout encountered - either seek back and retry or quit439if (tries && (tries_no == 0 || tries_cnt < tries_no))440tries_cnt += 1441seek_back = true442# try a smaller block size for the next round443if adaptive && adjust_block444block_size = [block_size >> 1, MIN_BLOCK_SIZE].max445adjust_block = false446msg = "Error downloading, block size set to #{block_size} - retry # #{tries_cnt}"447stat.call(msg, src_file, dest_file)448else449stat.call("Error downloading - retry # #{tries_cnt}", src_file, dest_file)450end451retry452else453stat.call('Error downloading - giving up', src_file, dest_file)454raise455end456end457dst_fd.write(data) if (data != nil)458percent = dst_fd.pos.to_f / src_stat.size.to_f * 100.0459msg = "Downloaded #{Filesize.new(dst_fd.pos).pretty} of #{src_size} (#{percent.round(2)}%)"460stat.call(msg, src_file, dest_file)461end while (data != nil)462else463# do the simple copying quitting on the first error464while ((data = src_fd.read(block_size)) != nil)465dst_fd.write(data)466percent = dst_fd.pos.to_f / src_stat.size.to_f * 100.0467msg = "Downloaded #{Filesize.new(dst_fd.pos).pretty} of #{src_size} (#{percent.round(2)}%)"468stat.call(msg, src_file, dest_file)469end470end471rescue EOFError472ensure473src_fd.close474dst_fd.close475end476477# Clone the times from the remote file478::File.utime(src_stat.atime, src_stat.mtime, dest_file)479return 'Completed'480end481482#483# With no associated block, File.open is a synonym for ::new. If the optional484# code block is given, it will be passed the opened file as an argument, and485# the File object will automatically be closed when the block terminates. In486# this instance, File.open returns the value of the block.487#488# (doc stolen from http://www.ruby-doc.org/core-1.9.3/File.html#method-c-open)489#490def File.open(name, mode="r", perms=0)491f = new(name, mode, perms)492if block_given?493ret = yield f494f.close495return ret496else497return f498end499end500501##502#503# Constructor504#505##506507#508# Initializes and opens the specified file with the specified permissions.509#510def initialize(name, mode = "r", perms = 0)511self.client = self.class.client512self.filed = _open(name, mode, perms)513end514515##516#517# IO implementers518#519##520521#522# Returns whether or not the file has reach EOF.523#524def eof525return self.filed.eof526end527528#529# Returns the current position of the file pointer.530#531def pos532return self.filed.tell533end534535#536# Synonym for sysseek.537#538def seek(offset, whence = ::IO::SEEK_SET)539return self.sysseek(offset, whence)540end541542#543# Seeks to the supplied offset based on the supplied relativity.544#545def sysseek(offset, whence = ::IO::SEEK_SET)546return self.filed.seek(offset, whence)547end548549protected550551##552#553# Internal methods554#555##556557#558# Creates a File channel using the supplied information.559#560def _open(name, mode = "r", perms = 0)561return Rex::Post::Meterpreter::Channels::Pools::File.open(562self.client, name, mode, perms)563end564565attr_accessor :client # :nodoc:566567end568569end; end; end; end; end; end570571572573