82a22ad38c
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.
404 lines
9.5 KiB
Ruby
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
|
|
|