395 lines
9.2 KiB
Ruby
395 lines
9.2 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('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('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])
|
|
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 dir is true, return matched folders.
|
|
#
|
|
def Dir.match(name, dir = false)
|
|
path = name + '*'
|
|
files = []
|
|
sbuf = nil
|
|
new_stat_buf = true
|
|
|
|
request = Packet.create_request('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|
|
|
if dir && sbuf[idx]
|
|
st = ::Rex::Post::FileStat.new
|
|
if new_stat_buf
|
|
st.update(sbuf[idx].value)
|
|
else
|
|
st.update32(sbuf[idx].value)
|
|
end
|
|
next if st.ftype != 'directory' # if file_name isn't directory
|
|
end
|
|
|
|
if !file_name.value.end_with?('.', '\\', '/') # Exclude current and parent directory
|
|
files << client.unicode_filter_encode(file_name.value)
|
|
end
|
|
end
|
|
|
|
files
|
|
end
|
|
|
|
##
|
|
#
|
|
# General directory operations
|
|
#
|
|
##
|
|
|
|
#
|
|
# Changes the working directory of the remote process.
|
|
#
|
|
def Dir.chdir(path)
|
|
request = Packet.create_request('stdapi_fs_chdir')
|
|
|
|
request.add_tlv(TLV_TYPE_DIRECTORY_PATH, client.unicode_filter_decode( path ))
|
|
|
|
response = client.send_request(request)
|
|
|
|
getwd(refresh: true)
|
|
return 0
|
|
end
|
|
|
|
#
|
|
# Creates a directory.
|
|
#
|
|
def Dir.mkdir(path)
|
|
request = Packet.create_request('stdapi_fs_mkdir')
|
|
|
|
request.add_tlv(TLV_TYPE_DIRECTORY_PATH, client.unicode_filter_decode( path ))
|
|
|
|
response = 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('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('stdapi_fs_delete_dir')
|
|
|
|
request.add_tlv(TLV_TYPE_DIRECTORY_PATH, client.unicode_filter_decode( path ))
|
|
|
|
response = 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)
|
|
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|
|
|
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
|
|
|