lib/rex/post/meterpreter/extensions/stdapi/fs/dir.rb
# -*- 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