Class: Rex::Post::Meterpreter::Extensions::Stdapi::Fs::File
- Includes:
- File
- Defined in:
- lib/rex/post/meterpreter/extensions/stdapi/fs/file.rb
Overview
This class implements the Rex::Post::File interface and wraps interaction with files on the remote machine.
Constant Summary collapse
- MIN_BLOCK_SIZE =
1024
Class Attribute Summary collapse
-
.client ⇒ Object
Returns the value of attribute client.
Instance Attribute Summary collapse
-
#client ⇒ Object
protected
:nodoc:.
Attributes included from File
Attributes inherited from IO
Class Method Summary collapse
-
.basename(*a) ⇒ Object
Returns the base name of the supplied file path to the caller.
-
.chmod(name, mode) ⇒ Object
Performs a chmod on the remote file.
-
.cp(oldname, newname) ⇒ Object
(also: copy)
Performs a copy from oldname to newname.
-
.download(dest, src_files, opts = {}, &stat) ⇒ Object
Download one or more files from the remote computer to the local directory supplied in destination.
-
.download_file(dest_file, src_file, opts = {}, &stat) ⇒ Object
Download a single file.
-
.exist?(name) ⇒ Boolean
Returns true if the remote file
name
exists, false otherwise. -
.expand_path(path) ⇒ Object
Expands a file path, substituting all environment variables, such as %TEMP% on Windows or $HOME on Unix.
- .is_glob?(name) ⇒ Boolean
-
.md5(path) ⇒ Object
Calculates the MD5 (16-bytes raw) of a remote file.
-
.mv(oldname, newname) ⇒ Object
(also: move, rename)
Performs a rename from oldname to newname.
-
.open(name, mode = "r", perms = 0) ⇒ Object
With no associated block, File.open is a synonym for ::new.
-
.rm(name) ⇒ Object
(also: unlink, delete)
Performs a delete on the remote file
name
. -
.search(root = nil, glob = "*.*", recurse = true, timeout = -1,, modified_start_date = nil, modified_end_date = nil) ⇒ Object
Search for files matching
glob
starting in directoryroot
. -
.separator ⇒ Object
(also: Separator, SEPARATOR)
Return the directory separator, i.e.: “/” on unix, “\” on windows.
-
.sha1(path) ⇒ Object
Calculates the SHA1 (20-bytes raw) of a remote file.
-
.stat(name) ⇒ Object
Performs a stat on a file and returns a FileStat instance.
-
.upload(dest, *src_files, &stat) ⇒ Object
Upload one or more files to the remote remote directory supplied in
destination
. -
.upload_file(dest_file, src_file, &stat) ⇒ Object
Upload a single file.
Instance Method Summary collapse
-
#_open(name, mode = "r", perms = 0) ⇒ Object
protected
Creates a File channel using the supplied information.
-
#eof ⇒ Object
Returns whether or not the file has reach EOF.
-
#initialize(name, mode = "r", perms = 0) ⇒ File
constructor
Initializes and opens the specified file with the specified permissions.
-
#pos ⇒ Object
Returns the current position of the file pointer.
-
#seek(offset, whence = ::IO::SEEK_SET) ⇒ Object
Synonym for sysseek.
-
#sysseek(offset, whence = ::IO::SEEK_SET) ⇒ Object
Seeks to the supplied offset based on the supplied relativity.
Methods included from File
atime, blockdev?, chardev?, chown, ctime, directory?, dirname, executable?, executable_real?, extname, file?, fnmatch, fnmatch?, ftype, grpowned?, join, lchmod, lchown, link, lstat, mtime, owned?, #path, pipe?, readable?, readable_real?, setgid?, setuid?, size, socket?, sticky?, symlink?, writeable?, writeable_real?, zero?
Methods inherited from IO
Methods inherited from IO
#binmode, #close, #close_read, #close_write, #closed?, #each, #each_byte, #each_line, #eof?, #fcntl, #flush, #fsync, #getc, #gets, #ioctl, #isatty, #lineno, #print, #printf, #putc, #puts, #read, #readchar, #readline, #readlines, #rewind, #stat, #sync, #sysread, #syswrite, #tell, #tty?, #ungetc, #write
Constructor Details
#initialize(name, mode = "r", perms = 0) ⇒ File
Initializes and opens the specified file with the specified permissions.
511 512 513 514 |
# File 'lib/rex/post/meterpreter/extensions/stdapi/fs/file.rb', line 511 def initialize(name, mode = "r", perms = 0) self.client = self.class.client self.filed = _open(name, mode, perms) end |
Class Attribute Details
.client ⇒ Object
Returns the value of attribute client.
32 33 34 |
# File 'lib/rex/post/meterpreter/extensions/stdapi/fs/file.rb', line 32 def client @client end |
Instance Attribute Details
#client ⇒ Object (protected)
:nodoc:
566 567 568 |
# File 'lib/rex/post/meterpreter/extensions/stdapi/fs/file.rb', line 566 def client @client end |
Class Method Details
.basename(*a) ⇒ Object
Returns the base name of the supplied file path to the caller.
113 114 115 116 117 118 119 120 121 122 |
# File 'lib/rex/post/meterpreter/extensions/stdapi/fs/file.rb', line 113 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 |
.chmod(name, mode) ⇒ Object
Performs a chmod on the remote file
275 276 277 278 279 280 281 282 283 284 |
# File 'lib/rex/post/meterpreter/extensions/stdapi/fs/file.rb', line 275 def File.chmod(name, mode) request = Packet.create_request(COMMAND_ID_STDAPI_FS_CHMOD) request.add_tlv(TLV_TYPE_FILE_PATH, client.unicode_filter_decode( name )) request.add_tlv(TLV_TYPE_FILE_MODE_T, mode) response = client.send_request(request) return response end |
.cp(oldname, newname) ⇒ Object Also known as: copy
Performs a copy from oldname to newname
257 258 259 260 261 262 263 264 265 266 |
# File 'lib/rex/post/meterpreter/extensions/stdapi/fs/file.rb', line 257 def File.cp(oldname, newname) request = Packet.create_request(COMMAND_ID_STDAPI_FS_FILE_COPY) 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 |
.download(dest, src_files, opts = {}, &stat) ⇒ Object
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.
346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 |
# File 'lib/rex/post/meterpreter/extensions/stdapi/fs/file.rb', line 346 def File.download(dest, src_files, opts = {}, &stat) dest.force_encoding('UTF-8') = opts["timestamp"] [*src_files].each { |src| src.force_encoding('UTF-8') if (::File.basename(dest) != File.basename(src)) # The destination when downloading is a local file so use this # system's separator dest += ::File::SEPARATOR unless dest.end_with?(::File::SEPARATOR) dest += File.basename(src) end # XXX: dest can be the same object as src, so we use += instead of << if dest += end stat.call('Downloading', src, dest) if (stat) result = download_file(dest, src, opts, &stat) stat.call(result, src, dest) if (stat) } end |
.download_file(dest_file, src_file, opts = {}, &stat) ⇒ Object
Download a single file.
372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 |
# File 'lib/rex/post/meterpreter/extensions/stdapi/fs/file.rb', line 372 def File.download_file(dest_file, src_file, opts = {}, &stat) stat ||= lambda { |a,b,c| } adaptive = opts["adaptive"] block_size = opts["block_size"] || 1024 * 1024 continue = opts["continue"] tries_no = opts["tries_no"] tries = opts["tries"] src_fd = client.fs.file.new(src_file, "rb") # Check for changes src_stat = client.fs.filestat.new(src_file) if ::File.exist?(dest_file) dst_stat = ::File.stat(dest_file) if src_stat.size == dst_stat.size && src_stat.mtime == dst_stat.mtime src_fd.close return 'Skipped' end end # Make the destination path if necessary dir = ::File.dirname(dest_file) ::FileUtils.mkdir_p(dir) if dir and not ::File.directory?(dir) src_size = Filesize.new(src_stat.size).pretty if continue # continue downloading the file - skip downloaded part in the source dst_fd = ::File.new(dest_file, "ab") begin dst_fd.seek(0, ::IO::SEEK_END) in_pos = dst_fd.pos src_fd.seek(in_pos) stat.call("Continuing from #{Filesize.new(in_pos).pretty} of #{src_size}", src_file, dest_file) rescue # if we can't seek, download again stat.call('Error continuing - downloading from scratch', src_file, dest_file) dst_fd.close dst_fd = ::File.new(dest_file, "wb") end else dst_fd = ::File.new(dest_file, "wb") end # Keep transferring until EOF is reached... begin if tries # resume when timeouts encountered seek_back = false adjust_block = false tries_cnt = 0 begin # while begin # exception if seek_back in_pos = dst_fd.pos src_fd.seek(in_pos) seek_back = false stat.call("Resuming at #{Filesize.new(in_pos).pretty} of #{src_size}", src_file, dest_file) else # successfully read and wrote - reset the counter tries_cnt = 0 end adjust_block = true data = src_fd.read(block_size) adjust_block = false rescue Rex::TimeoutError # timeout encountered - either seek back and retry or quit if (tries && (tries_no == 0 || tries_cnt < tries_no)) tries_cnt += 1 seek_back = true # try a smaller block size for the next round if adaptive && adjust_block block_size = [block_size >> 1, MIN_BLOCK_SIZE].max adjust_block = false msg = "Error downloading, block size set to #{block_size} - retry # #{tries_cnt}" stat.call(msg, src_file, dest_file) else stat.call("Error downloading - retry # #{tries_cnt}", src_file, dest_file) end retry else stat.call('Error downloading - giving up', src_file, dest_file) raise end end dst_fd.write(data) if (data != nil) percent = dst_fd.pos.to_f / src_stat.size.to_f * 100.0 msg = "Downloaded #{Filesize.new(dst_fd.pos).pretty} of #{src_size} (#{percent.round(2)}%)" stat.call(msg, src_file, dest_file) end while (data != nil) else # do the simple copying quitting on the first error while ((data = src_fd.read(block_size)) != nil) dst_fd.write(data) percent = dst_fd.pos.to_f / src_stat.size.to_f * 100.0 msg = "Downloaded #{Filesize.new(dst_fd.pos).pretty} of #{src_size} (#{percent.round(2)}%)" stat.call(msg, src_file, dest_file) end end rescue EOFError ensure src_fd.close dst_fd.close end # Clone the times from the remote file ::File.utime(src_stat.atime, src_stat.mtime, dest_file) return 'Completed' end |
.exist?(name) ⇒ Boolean
Returns true if the remote file name
exists, false otherwise
212 213 214 215 |
# File 'lib/rex/post/meterpreter/extensions/stdapi/fs/file.rb', line 212 def File.exist?(name) r = client.fs.filestat.new(name) rescue nil r ? true : false end |
.expand_path(path) ⇒ Object
Expands a file path, substituting all environment variables, such as %TEMP% on Windows or $HOME on Unix
Examples:
client.fs.file.("%appdata%")
# => "C:\\Documents and Settings\\user\\Application Data"
client.fs.file.("~")
# => "/home/user"
client.fs.file.("$HOME/dir")
# => "/home/user/dir"
client.fs.file.("asdf")
# => "asdf"
138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 |
# File 'lib/rex/post/meterpreter/extensions/stdapi/fs/file.rb', line 138 def File.(path) case client.platform when 'osx', 'freebsd', 'bsd', 'linux', 'android', 'apple_ios' # For unix-based systems, do some of the work here # First check for ~ path_components = path.split(separator) if path_components.length > 0 && path_components[0] == '~' path = "$HOME#{path[1..-1]}" end # Now find the environment variables we'll need from the client env_regex = /\$(?:([A-Za-z0-9_]+)|\{([A-Za-z0-9_]+)\})/ matches = path.to_enum(:scan, env_regex).map { Regexp.last_match } env_vars = matches.map { |match| (match[1] || match[2]).to_s }.uniq # Retrieve them env_vals = client.sys.config.getenvs(*env_vars) # Now fill them in path.gsub(env_regex) { |_z| envvar = $1; envvar = $2 if envvar == nil; env_vals[envvar] } else request = Packet.create_request(COMMAND_ID_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 end |
.is_glob?(name) ⇒ Boolean
335 336 337 |
# File 'lib/rex/post/meterpreter/extensions/stdapi/fs/file.rb', line 335 def File.is_glob?(name) /\*|\[|\?/ === name end |
.md5(path) ⇒ Object
Calculates the MD5 (16-bytes raw) of a remote file
173 174 175 176 177 178 179 180 181 182 183 184 |
# File 'lib/rex/post/meterpreter/extensions/stdapi/fs/file.rb', line 173 def File.md5(path) request = Packet.create_request(COMMAND_ID_STDAPI_FS_MD5) request.add_tlv(TLV_TYPE_FILE_PATH, client.unicode_filter_decode( path )) response = client.send_request(request) # older meterpreter binaries will send FILE_NAME containing the hash hash = response.get_tlv_value(TLV_TYPE_FILE_HASH) || response.get_tlv_value(TLV_TYPE_FILE_NAME) return hash end |
.mv(oldname, newname) ⇒ Object Also known as: move, rename
Performs a rename from oldname to newname
238 239 240 241 242 243 244 245 246 247 |
# File 'lib/rex/post/meterpreter/extensions/stdapi/fs/file.rb', line 238 def File.mv(oldname, newname) request = Packet.create_request(COMMAND_ID_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 |
.open(name, mode = "r", perms = 0) ⇒ Object
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 www.ruby-doc.org/core-1.9.3/File.html#method-c-open)
491 492 493 494 495 496 497 498 499 500 |
# File 'lib/rex/post/meterpreter/extensions/stdapi/fs/file.rb', line 491 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 |
.rm(name) ⇒ Object Also known as: unlink, delete
Performs a delete on the remote file name
220 221 222 223 224 225 226 227 228 |
# File 'lib/rex/post/meterpreter/extensions/stdapi/fs/file.rb', line 220 def File.rm(name) request = Packet.create_request(COMMAND_ID_STDAPI_FS_DELETE_FILE) request.add_tlv(TLV_TYPE_FILE_PATH, client.unicode_filter_decode( name )) response = client.send_request(request) return response end |
.search(root = nil, glob = "*.*", recurse = true, timeout = -1,, modified_start_date = nil, modified_end_date = nil) ⇒ Object
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.
78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 |
# File 'lib/rex/post/meterpreter/extensions/stdapi/fs/file.rb', line 78 def File.search( root=nil, glob="*.*", recurse=true, timeout=-1, modified_start_date=nil, modified_end_date=nil) files = ::Array.new request = Packet.create_request( COMMAND_ID_STDAPI_FS_SEARCH ) root = client.unicode_filter_decode(root) if root root = root.chomp( self.separator ) if root && !root.eql?('/') request.add_tlv( TLV_TYPE_SEARCH_ROOT, root ) request.add_tlv( TLV_TYPE_SEARCH_GLOB, glob ) request.add_tlv( TLV_TYPE_SEARCH_RECURSE, recurse ) request.add_tlv( TLV_TYPE_SEARCH_M_START_DATE, modified_start_date) if modified_start_date request.add_tlv( TLV_TYPE_SEARCH_M_END_DATE, modified_end_date) if modified_end_date # we set the response timeout to -1 to wait indefinitely as a # search could take an indeterminate amount 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( self.separator )), 'name' => client.unicode_filter_encode(results.get_tlv_value(TLV_TYPE_FILE_NAME)), 'size' => results.get_tlv_value(TLV_TYPE_FILE_SIZE), 'mtime'=> results.get_tlv_value(TLV_TYPE_SEARCH_MTIME) } end end return files end |
.separator ⇒ Object Also known as: Separator, SEPARATOR
Return the directory separator, i.e.: “/” on unix, “\” on windows
38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
# File 'lib/rex/post/meterpreter/extensions/stdapi/fs/file.rb', line 38 def File.separator() # The separator won't change, so cache it to prevent sending # unnecessary requests. return @separator if @separator request = Packet.create_request(COMMAND_ID_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 |
.sha1(path) ⇒ Object
Calculates the SHA1 (20-bytes raw) of a remote file
189 190 191 192 193 194 195 196 197 198 199 200 |
# File 'lib/rex/post/meterpreter/extensions/stdapi/fs/file.rb', line 189 def File.sha1(path) request = Packet.create_request(COMMAND_ID_STDAPI_FS_SHA1) request.add_tlv(TLV_TYPE_FILE_PATH, client.unicode_filter_decode( path )) response = client.send_request(request) # older meterpreter binaries will send FILE_NAME containing the hash hash = response.get_tlv_value(TLV_TYPE_FILE_HASH) || response.get_tlv_value(TLV_TYPE_FILE_NAME) return hash end |
.stat(name) ⇒ Object
Performs a stat on a file and returns a FileStat instance.
205 206 207 |
# File 'lib/rex/post/meterpreter/extensions/stdapi/fs/file.rb', line 205 def File.stat(name) return client.fs.filestat.new( name ) end |
.upload(dest, *src_files, &stat) ⇒ Object
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.
293 294 295 296 297 298 299 300 301 302 303 304 |
# File 'lib/rex/post/meterpreter/extensions/stdapi/fs/file.rb', line 293 def File.upload(dest, *src_files, &stat) src_files.each { |src| if (self.basename(dest) != ::File.basename(src)) dest += self.separator unless dest.end_with?(self.separator) dest += ::File.basename(src) end stat.call('Uploading', src, dest) if (stat) upload_file(dest, src) stat.call('Completed', src, dest) if (stat) } end |
.upload_file(dest_file, src_file, &stat) ⇒ Object
Upload a single file.
309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 |
# File 'lib/rex/post/meterpreter/extensions/stdapi/fs/file.rb', line 309 def File.upload_file(dest_file, src_file, &stat) # Open the file on the remote side for writing and read # all of the contents of the local file stat.call('Uploading', src_file, dest_file) if stat dest_fd = nil src_fd = nil buf_size = 8 * 1024 * 1024 begin dest_fd = client.fs.file.new(dest_file, "wb") src_fd = ::File.open(src_file, "rb") src_size = src_fd.stat.size while (buf = src_fd.read(buf_size)) dest_fd.write(buf) percent = dest_fd.pos.to_f / src_size.to_f * 100.0 msg = "Uploaded #{Filesize.new(dest_fd.pos).pretty} of " \ "#{Filesize.new(src_size).pretty} (#{percent.round(2)}%)" stat.call(msg, src_file, dest_file) if stat end ensure src_fd.close unless src_fd.nil? dest_fd.close unless dest_fd.nil? end stat.call('Completed', src_file, dest_file) if stat end |
Instance Method Details
#_open(name, mode = "r", perms = 0) ⇒ Object (protected)
Creates a File channel using the supplied information.
561 562 563 564 |
# File 'lib/rex/post/meterpreter/extensions/stdapi/fs/file.rb', line 561 def _open(name, mode = "r", perms = 0) return Rex::Post::Meterpreter::Channels::Pools::File.open( self.client, name, mode, perms) end |
#eof ⇒ Object
Returns whether or not the file has reach EOF.
525 526 527 |
# File 'lib/rex/post/meterpreter/extensions/stdapi/fs/file.rb', line 525 def eof return self.filed.eof end |
#pos ⇒ Object
Returns the current position of the file pointer.
532 533 534 |
# File 'lib/rex/post/meterpreter/extensions/stdapi/fs/file.rb', line 532 def pos return self.filed.tell end |
#seek(offset, whence = ::IO::SEEK_SET) ⇒ Object
Synonym for sysseek.
539 540 541 |
# File 'lib/rex/post/meterpreter/extensions/stdapi/fs/file.rb', line 539 def seek(offset, whence = ::IO::SEEK_SET) return self.sysseek(offset, whence) end |
#sysseek(offset, whence = ::IO::SEEK_SET) ⇒ Object
Seeks to the supplied offset based on the supplied relativity.
546 547 548 |
# File 'lib/rex/post/meterpreter/extensions/stdapi/fs/file.rb', line 546 def sysseek(offset, whence = ::IO::SEEK_SET) return self.filed.seek(offset, whence) end |