Files
metasploit-gs/lib/rex/post/meterpreter/extensions/stdapi/fs/dir.rb
T
Spencer McIntyre 82a22ad38c Skip empty stat buffers
This skips empty stat buffers, allowing Meterpreter to return empty ones
for entries that can not be stat'ed and thus maintain the array
alignment.
2021-12-09 13:43:00 -05:00

404 lines
9.5 KiB
Ruby

# -*- coding: binary -*-
require 'rex/post/dir'
require 'rex/post/meterpreter/extensions/stdapi/stdapi'
module Rex
module Post
module Meterpreter
module Extensions
module Stdapi
module Fs
###
#
# This class implements directory operations against the remote endpoint. It
# implements the Rex::Post::Dir interface.
#
###
class Dir < Rex::Post::Dir
class << self
attr_accessor :client
end
##
#
# Constructor
#
##
#
# Initializes the directory instance.
#
def initialize(path)
self.path = path
self.client = self.class.client
end
##
#
# Enumeration
#
##
#
# Enumerates all of the contents of the directory.
#
def each(&block)
client.fs.dir.foreach(self.path, &block)
end
#
# Enumerates all of the files/folders in a given directory.
#
def Dir.entries(name = getwd, glob = nil)
request = Packet.create_request(COMMAND_ID_STDAPI_FS_LS)
files = []
name = name + ::File::SEPARATOR + glob if glob
request.add_tlv(TLV_TYPE_DIRECTORY_PATH, client.unicode_filter_decode(name))
response = client.send_request(request)
response.each(TLV_TYPE_FILE_NAME) { |file_name|
files << client.unicode_filter_encode(file_name.value)
}
return files
end
#
# Enumerates files with a bit more information than the default entries.
#
def Dir.entries_with_info(name = getwd)
request = Packet.create_request(COMMAND_ID_STDAPI_FS_LS)
files = []
sbuf = nil
new_stat_buf = true
request.add_tlv(TLV_TYPE_DIRECTORY_PATH, client.unicode_filter_decode(name))
response = client.send_request(request)
fname = response.get_tlvs(TLV_TYPE_FILE_NAME)
fsname = response.get_tlvs(TLV_TYPE_FILE_SHORT_NAME)
fpath = response.get_tlvs(TLV_TYPE_FILE_PATH)
if response.has_tlv?(TLV_TYPE_STAT_BUF)
sbuf = response.get_tlvs(TLV_TYPE_STAT_BUF)
else
sbuf = response.get_tlvs(TLV_TYPE_STAT_BUF32)
new_stat_buf = false
end
if (!fname or !sbuf)
return []
end
fname.each_with_index { |file_name, idx|
st = nil
if sbuf[idx] && sbuf[idx].value.length > 0
st = ::Rex::Post::FileStat.new
if new_stat_buf
st.update(sbuf[idx].value)
else
st.update32(sbuf[idx].value)
end
end
files <<
{
'FileName' => client.unicode_filter_encode(file_name.value),
'FilePath' => client.unicode_filter_encode(fpath[idx].value),
'FileShortName' => fsname[idx] ? fsname[idx].value : nil,
'StatBuf' => st,
}
}
return files
end
#
# Enumerates all of the files and folders matched with name.
# When option match_dir is true, return matched folders.
#
def Dir.match(name, match_dir = false)
path = name + '*'
files = []
sbuf = nil
new_stat_buf = true
request = Packet.create_request(COMMAND_ID_STDAPI_FS_LS)
request.add_tlv(TLV_TYPE_DIRECTORY_PATH, client.unicode_filter_decode(path))
response = client.send_request(request)
fpath = response.get_tlvs(TLV_TYPE_FILE_PATH)
if response.has_tlv?(TLV_TYPE_STAT_BUF)
sbuf = response.get_tlvs(TLV_TYPE_STAT_BUF)
else
sbuf = response.get_tlvs(TLV_TYPE_STAT_BUF32)
new_stat_buf = false
end
unless fpath && sbuf
return []
end
fpath.each_with_index do |file_name, idx|
is_dir = false
if sbuf[idx]
st = ::Rex::Post::FileStat.new
if new_stat_buf
st.update(sbuf[idx].value)
else
st.update32(sbuf[idx].value)
end
is_dir = st.ftype == 'directory'
next if (match_dir && !is_dir) # if file_name isn't directory
end
if !file_name.value.end_with?('.', '\\', '/') # Exclude current and parent directory
name = client.unicode_filter_encode(file_name.value)
if is_dir
name += client.fs.file.separator
end
files << name
end
end
files
end
##
#
# General directory operations
#
##
#
# Changes the working directory of the remote process.
#
def Dir.chdir(path)
request = Packet.create_request(COMMAND_ID_STDAPI_FS_CHDIR)
request.add_tlv(TLV_TYPE_DIRECTORY_PATH, client.unicode_filter_decode( path ))
client.send_request(request)
getwd(refresh: true)
return 0
end
#
# Creates a directory.
#
def Dir.mkdir(path)
request = Packet.create_request(COMMAND_ID_STDAPI_FS_MKDIR)
request.add_tlv(TLV_TYPE_DIRECTORY_PATH, client.unicode_filter_decode( path ))
client.send_request(request)
return 0
end
#
# Returns the current working directory of the remote process.
#
def Dir.pwd(refresh: true)
if @working_directory.nil? || refresh
request = Packet.create_request(COMMAND_ID_STDAPI_FS_GETWD)
response = client.send_request(request)
@working_directory = client.unicode_filter_encode(response.get_tlv(TLV_TYPE_DIRECTORY_PATH).value)
end
@working_directory
end
#
# Synonym for pwd.
#
def Dir.getwd(refresh: true)
pwd(refresh: refresh)
end
#
# Removes the supplied directory if it's empty.
#
def Dir.delete(path)
request = Packet.create_request(COMMAND_ID_STDAPI_FS_DELETE_DIR)
request.add_tlv(TLV_TYPE_DIRECTORY_PATH, client.unicode_filter_decode( path ))
client.send_request(request)
return 0
end
#
# Synonyms for delete.
#
def Dir.rmdir(path)
delete(path)
end
#
# Synonyms for delete.
#
def Dir.unlink(path)
delete(path)
end
##
#
# Directory mirroring
#
##
#
# Downloads the contents of a remote directory a
# local directory, optionally in a recursive fashion.
#
def Dir.download(dst, src, opts = {}, force = true, glob = nil, &stat)
src.force_encoding('UTF-8')
dst.force_encoding('UTF-8')
tries_cnt = 0
continue = opts["continue"]
recursive = opts["recursive"]
timestamp = opts["timestamp"]
tries_no = opts["tries_no"] || 0
tries = opts["tries"]
begin
dir_files = self.entries(src, glob)
rescue Rex::TimeoutError
if (tries && (tries_no == 0 || tries_cnt < tries_no))
tries_cnt += 1
stat.call('error listing - retry #', tries_cnt, src) if (stat)
retry
else
stat.call('error listing directory - giving up', src, dst) if (stat)
raise
end
end
dir_files.each { |src_sub|
src_sub.force_encoding('UTF-8')
dst_sub = src_sub.dup
dst_sub.gsub!(::File::SEPARATOR, '_') # '/' on all systems
dst_sub.gsub!(::File::ALT_SEPARATOR, '_') if ::File::ALT_SEPARATOR # nil on Linux, '\' on Windows
dst_item = ::File.join(dst, client.unicode_filter_encode(dst_sub))
src_item = src + client.fs.file.separator + client.unicode_filter_encode(src_sub)
if (src_sub == '.' or src_sub == '..')
next
end
tries_cnt = 0
begin
src_stat = client.fs.filestat.new(src_item)
rescue Rex::TimeoutError
if (tries && (tries_no == 0 || tries_cnt < tries_no))
tries_cnt += 1
stat.call('error opening file - retry #', tries_cnt, src_item) if (stat)
retry
else
stat.call('error opening file - giving up', tries_cnt, src_item) if (stat)
raise
end
end
if (src_stat.file?)
if timestamp
dst_item << timestamp
end
stat.call('downloading', src_item, dst_item) if (stat)
begin
if (continue || tries) # allow to file.download to log messages
result = client.fs.file.download_file(dst_item, src_item, opts, &stat)
else
result = client.fs.file.download_file(dst_item, src_item, opts)
end
stat.call(result, src_item, dst_item) if (stat)
rescue ::Rex::Post::Meterpreter::RequestError => e
if force
stat.call('failed', src_item, dst_item) if (stat)
else
raise e
end
end
elsif (src_stat.directory?)
if (recursive == false)
next
end
begin
::Dir.mkdir(dst_item)
rescue
end
stat.call('mirroring', src_item, dst_item) if (stat)
download(dst_item, src_item, opts, force, glob, &stat)
stat.call('mirrored', src_item, dst_item) if (stat)
end
} # entries
end
#
# Uploads the contents of a local directory to a remote
# directory, optionally in a recursive fashion.
#
def Dir.upload(dst, src, recursive = false, &stat)
::Dir.entries(src).each { |src_sub|
dst_item = dst + client.fs.file.separator + client.unicode_filter_encode(src_sub)
src_item = src + ::File::SEPARATOR + client.unicode_filter_encode(src_sub)
if (src_sub == '.' or src_sub == '..')
next
end
src_stat = ::File.stat(src_item)
if (src_stat.file?)
stat.call('uploading', src_item, dst_item) if (stat)
client.fs.file.upload(dst_item, src_item)
stat.call('uploaded', src_item, dst_item) if (stat)
elsif (src_stat.directory?)
if (recursive == false)
next
end
begin
self.mkdir(dst_item)
rescue
end
stat.call('mirroring', src_item, dst_item) if (stat)
upload(dst_item, src_item, recursive, &stat)
stat.call('mirrored', src_item, dst_item) if (stat)
end
}
end
#
# The path of the directory that was opened.
#
attr_reader :path
protected
attr_accessor :client # :nodoc:
attr_writer :path # :nodoc:
end
end; end; end; end; end; end