Rubocop -A

This commit is contained in:
bwatters 2021-09-13 11:51:00 -05:00
parent ddebdbc770
commit 07204dc99e
2 changed files with 247 additions and 223 deletions

View File

@ -1,33 +1,32 @@
# -*- coding: binary -*- # -*- coding: binary -*-
#
require 'rex/post/meterpreter/extensions/stdapi/command_ids' require 'rex/post/meterpreter/extensions/stdapi/command_ids'
require 'rex/post/file_stat' require 'rex/post/file_stat'
module Msf::Post::File module Msf::Post::File
include Msf::Post::Common include Msf::Post::Common
def initialize(info = {}) def initialize(info = {})
super( super(
update_info( update_info(
info, info,
'Compat' => { 'Compat' => {
'Meterpreter' => { 'Meterpreter' => {
'Commands' => %w[ 'Commands' => %w[
core_channel_* core_channel_*
stdapi_fs_chdir stdapi_fs_chdir
stdapi_fs_delete_dir stdapi_fs_delete_dir
stdapi_fs_delete_file stdapi_fs_delete_file
stdapi_fs_file_expand_path stdapi_fs_file_expand_path
stdapi_fs_file_move stdapi_fs_file_move
stdapi_fs_getwd stdapi_fs_getwd
stdapi_fs_ls stdapi_fs_ls
stdapi_fs_mkdir stdapi_fs_mkdir
stdapi_fs_stat stdapi_fs_stat
] ]
}
} }
) }
)
) )
end end
@ -37,8 +36,12 @@ module Msf::Post::File
# #
# @return [void] # @return [void]
def cd(path) def cd(path)
e_path = expand_path(path) rescue path e_path = begin
if session.type == "meterpreter" expand_path(path)
rescue StandardError
path
end
if session.type == 'meterpreter'
session.fs.dir.chdir(e_path) session.fs.dir.chdir(e_path)
elsif session.type == 'powershell' elsif session.type == 'powershell'
cmd_exec("Set-Location -Path \"#{e_path}\"") cmd_exec("Set-Location -Path \"#{e_path}\"")
@ -56,23 +59,19 @@ module Msf::Post::File
# #
# @return [String] # @return [String]
def pwd def pwd
if session.type == "meterpreter" if session.type == 'meterpreter'
return session.fs.dir.getwd return session.fs.dir.getwd
elsif session.type == 'powershell' elsif session.type == 'powershell'
return cmd_exec('(Get-Location).Path').strip return cmd_exec('(Get-Location).Path').strip
elsif session.platform == 'windows'
return session.shell_command_token('echo %CD%').to_s.strip
# XXX: %CD% only exists on XP and newer, figure something out for NT4
# and 2k
elsif command_exists?('pwd')
return session.shell_command_token('pwd').to_s.strip
else else
if session.platform == 'windows' # Result on systems without pwd command
# XXX: %CD% only exists on XP and newer, figure something out for NT4 return session.shell_command_token('echo $PWD').to_s.strip
# and 2k
return session.shell_command_token("echo %CD%").to_s.strip
else
if command_exists?("pwd")
return session.shell_command_token("pwd").to_s.strip
else
# Result on systems without pwd command
return session.shell_command_token("echo $PWD").to_s.strip
end
end
end end
end end
@ -100,13 +99,13 @@ module Msf::Post::File
# Result on systems without ls command # Result on systems without ls command
if directory[-1] != '/' if directory[-1] != '/'
directory = directory + "/" directory += '/'
end end
result = [] result = []
data = session.shell_command_token("for fn in #{directory}*; do echo $fn; done") data = session.shell_command_token("for fn in #{directory}*; do echo $fn; done")
parts = data.split("\n") parts = data.split("\n")
parts.each do |line| parts.each do |line|
line = line.split("/")[-1] line = line.split('/')[-1]
result.insert(-1, line) result.insert(-1, line)
end end
@ -124,12 +123,10 @@ module Msf::Post::File
result = session.fs.dir.mkdir(path) unless directory?(path) result = session.fs.dir.mkdir(path) unless directory?(path)
elsif session.type == 'powershell' elsif session.type == 'powershell'
result = cmd_exec("New-Item \"#{path}\" -itemtype directory") result = cmd_exec("New-Item \"#{path}\" -itemtype directory")
elsif session.platform == 'windows'
result = cmd_exec("mkdir \"#{path}\"")
else else
if session.platform == 'windows' result = cmd_exec("mkdir -p '#{path}'")
result = cmd_exec("mkdir \"#{path}\"")
else
result = cmd_exec("mkdir -p '#{path}'")
end
end end
vprint_status("#{path} created") vprint_status("#{path} created")
register_dir_for_cleanup(path) register_dir_for_cleanup(path)
@ -142,8 +139,13 @@ module Msf::Post::File
# @param path [String] Remote filename to check # @param path [String] Remote filename to check
def directory?(path) def directory?(path)
if session.type == 'meterpreter' if session.type == 'meterpreter'
stat = session.fs.file.stat(path) rescue nil stat = begin
session.fs.file.stat(path)
rescue StandardError
nil
end
return false unless stat return false unless stat
return stat.directory? return stat.directory?
elsif session.type == 'powershell' elsif session.type == 'powershell'
return cmd_exec("Test-Path -Path \"#{path}\" -PathType Container").include?('True') return cmd_exec("Test-Path -Path \"#{path}\" -PathType Container").include?('True')
@ -155,6 +157,7 @@ module Msf::Post::File
end end
return false if f.nil? || f.empty? return false if f.nil? || f.empty?
return false unless f =~ /true/ return false unless f =~ /true/
true true
end end
end end
@ -164,7 +167,7 @@ module Msf::Post::File
# #
# @return [String] # @return [String]
def expand_path(path) def expand_path(path)
if session.type == "meterpreter" if session.type == 'meterpreter'
return session.fs.file.expand_path(path) return session.fs.file.expand_path(path)
elsif session.type == 'powershell' elsif session.type == 'powershell'
return cmd_exec("[Environment]::ExpandEnvironmentVariables(\"#{path}\")") return cmd_exec("[Environment]::ExpandEnvironmentVariables(\"#{path}\")")
@ -179,11 +182,16 @@ module Msf::Post::File
# @param path [String] Remote filename to check # @param path [String] Remote filename to check
def file?(path) def file?(path)
if session.type == 'meterpreter' if session.type == 'meterpreter'
stat = session.fs.file.stat(path) rescue nil stat = begin
session.fs.file.stat(path)
rescue StandardError
nil
end
return false unless stat return false unless stat
return stat.file? return stat.file?
elsif session.type == 'powershell' elsif session.type == 'powershell'
return cmd_exec("[System.IO.File]::Exists( \"#{path}\")")&.include?("True") return cmd_exec("[System.IO.File]::Exists( \"#{path}\")")&.include?('True')
else else
if session.platform == 'windows' if session.platform == 'windows'
f = cmd_exec("cmd.exe /C IF exist \"#{path}\" ( echo true )") f = cmd_exec("cmd.exe /C IF exist \"#{path}\" ( echo true )")
@ -195,6 +203,7 @@ module Msf::Post::File
end end
return false if f.nil? || f.empty? return false if f.nil? || f.empty?
return false unless f =~ /true/ return false unless f =~ /true/
true true
end end
end end
@ -233,7 +242,7 @@ module Msf::Post::File
def writable?(path) def writable?(path)
verification_token = Rex::Text.rand_text_alpha_upper(8) verification_token = Rex::Text.rand_text_alpha_upper(8)
if session.type == 'powershell' && file?(path) if session.type == 'powershell' && file?(path)
return cmd_exec("$a=[System.IO.File]::OpenWrite('#{path}');if($?){echo #{verification_token}};$a.Close()").include?(verification_token) return cmd_exec("$a=[System.IO.File]::OpenWrite('#{path}');if($?){echo #{verification_token}};$a.Close()").include?(verification_token)
end end
raise "`writable?' method does not support Windows systems" if session.platform == 'windows' raise "`writable?' method does not support Windows systems" if session.platform == 'windows'
@ -261,14 +270,15 @@ module Msf::Post::File
# @return [Boolean] true if +path+ exists and is readable # @return [Boolean] true if +path+ exists and is readable
# #
def readable?(path) def readable?(path)
verification_token = Rex::Text::rand_text_alpha(8) verification_token = Rex::Text.rand_text_alpha(8)
return false unless exists?(path) return false unless exists?(path)
if session.type == 'powershell' if session.type == 'powershell'
unless directory?(path) if directory?(path)
return cmd_exec("[System.IO.Directory]::GetFiles('#{path}'); if($?) {echo #{verification_token}}").include?(verification_token)
else
return cmd_exec("[System.IO.File]::OpenRead(\"#{path}\");if($?){echo\ return cmd_exec("[System.IO.File]::OpenRead(\"#{path}\");if($?){echo\
#{verification_token}}").include?(verification_token) #{verification_token}}").include?(verification_token)
else
return cmd_exec("[System.IO.Directory]::GetFiles('#{path}'); if($?) {echo #{verification_token}}").include?(verification_token)
end end
end end
@ -283,10 +293,14 @@ module Msf::Post::File
# @param path [String] Remote filename to check # @param path [String] Remote filename to check
def exist?(path) def exist?(path)
if session.type == 'meterpreter' if session.type == 'meterpreter'
stat = session.fs.file.stat(path) rescue nil stat = begin
return !!(stat) session.fs.file.stat(path)
rescue StandardError
nil
end
return !!stat
elsif session.type == 'powershell' elsif session.type == 'powershell'
return cmd_exec("[System.IO.File]::Exists( \"#{path}\")")&.include?("True") return cmd_exec("[System.IO.File]::Exists( \"#{path}\")")&.include?('True')
else else
if session.platform == 'windows' if session.platform == 'windows'
f = cmd_exec("cmd.exe /C IF exist \"#{path}\" ( echo true )") f = cmd_exec("cmd.exe /C IF exist \"#{path}\" ( echo true )")
@ -295,11 +309,12 @@ module Msf::Post::File
end end
return false if f.nil? || f.empty? return false if f.nil? || f.empty?
return false unless f =~ /true/ return false unless f =~ /true/
true true
end end
end end
alias :exists? :exist? alias exists? exist?
# #
# Retrieve file attributes for +path+ on the remote system # Retrieve file attributes for +path+ on the remote system
@ -322,7 +337,7 @@ module Msf::Post::File
unless ::File.exist?(fname) unless ::File.exist?(fname)
::FileUtils.touch(fname) ::FileUtils.touch(fname)
end end
output = ::File.open(fname, "a") output = ::File.open(fname, 'a')
data.each_line do |d| data.each_line do |d|
output.puts(d) output.puts(d)
end end
@ -414,8 +429,8 @@ module Msf::Post::File
# @param data [String] Contents to put in the file # @param data [String] Contents to put in the file
# @return [void] # @return [void]
def write_file(file_name, data) def write_file(file_name, data)
if session.type == "meterpreter" if session.type == 'meterpreter'
fd = session.fs.file.new(file_name, "wb") fd = session.fs.file.new(file_name, 'wb')
fd.write(data) fd.write(data)
fd.close fd.close
elsif session.type == 'powershell' elsif session.type == 'powershell'
@ -440,8 +455,8 @@ module Msf::Post::File
# @param data [String] Contents to put in the file # @param data [String] Contents to put in the file
# @return bool # @return bool
def append_file(file_name, data) def append_file(file_name, data)
if session.type == "meterpreter" if session.type == 'meterpreter'
fd = session.fs.file.new(file_name, "ab") fd = session.fs.file.new(file_name, 'ab')
fd.write(data) fd.write(data)
fd.close fd.close
elsif session.type == 'powershell' elsif session.type == 'powershell'
@ -488,7 +503,7 @@ module Msf::Post::File
# #
# @param path [String] Path on the remote filesystem # @param path [String] Path on the remote filesystem
# @param mode [Fixnum] Mode as an octal number # @param mode [Fixnum] Mode as an octal number
def chmod(path, mode = 0700) def chmod(path, mode = 0o700)
if session.platform == 'windows' if session.platform == 'windows'
raise "`chmod' method does not support Windows systems" raise "`chmod' method does not support Windows systems"
end end
@ -506,7 +521,7 @@ module Msf::Post::File
# @param path [String] Directory in the exploits folder # @param path [String] Directory in the exploits folder
# @param path [String] Filename in the data folder # @param path [String] Filename in the data folder
def exploit_data(data_directory, file) def exploit_data(data_directory, file)
file_path = ::File.join(::Msf::Config.data_directory, "exploits", data_directory, file) file_path = ::File.join(::Msf::Config.data_directory, 'exploits', data_directory, file)
::File.binread(file_path) ::File.binread(file_path)
end end
@ -518,16 +533,14 @@ module Msf::Post::File
# @return [void] # @return [void]
def rm_f(*remote_files) def rm_f(*remote_files)
remote_files.each do |remote| remote_files.each do |remote|
if session.type == "meterpreter" if session.type == 'meterpreter'
session.fs.file.delete(remote) if file?(remote) session.fs.file.delete(remote) if file?(remote)
elsif session.type == 'powershell' elsif session.type == 'powershell'
cmd_exec("[System.IO.File]::Delete(\"#{remote}\")") if file?(remote) cmd_exec("[System.IO.File]::Delete(\"#{remote}\")") if file?(remote)
elsif session.platform == 'windows'
cmd_exec("del /q /f \"#{remote}\"")
else else
if session.platform == 'windows' cmd_exec("rm -f \"#{remote}\"")
cmd_exec("del /q /f \"#{remote}\"")
else
cmd_exec("rm -f \"#{remote}\"")
end
end end
end end
end end
@ -540,21 +553,19 @@ module Msf::Post::File
# @return [void] # @return [void]
def rm_rf(*remote_dirs) def rm_rf(*remote_dirs)
remote_dirs.each do |remote| remote_dirs.each do |remote|
if session.type == "meterpreter" if session.type == 'meterpreter'
session.fs.dir.rmdir(remote) if exist?(remote) session.fs.dir.rmdir(remote) if exist?(remote)
elsif session.type == 'powershell' elsif session.type == 'powershell'
cmd_exec("Remove-Item -Path \"#{remote}\" -Force -Recurse") cmd_exec("Remove-Item -Path \"#{remote}\" -Force -Recurse")
elsif session.platform == 'windows'
cmd_exec("rd /s /q \"#{remote}\"")
else else
if session.platform == 'windows' cmd_exec("rm -rf \"#{remote}\"")
cmd_exec("rd /s /q \"#{remote}\"")
else
cmd_exec("rm -rf \"#{remote}\"")
end
end end
end end
end end
alias :file_rm :rm_f alias file_rm rm_f
alias :dir_rm :rm_rf alias dir_rm rm_rf
# #
# Renames a remote file. If the new file path is a directory, the file will be # Renames a remote file. If the new file path is a directory, the file will be
@ -564,9 +575,8 @@ module Msf::Post::File
# @param new_file [String] The new name for the remote file # @param new_file [String] The new name for the remote file
# @return [Boolean] Return true on success and false on failure # @return [Boolean] Return true on success and false on failure
def rename_file(old_file, new_file) def rename_file(old_file, new_file)
verification_token = Rex::Text.rand_text_alphanumeric(8) verification_token = Rex::Text.rand_text_alphanumeric(8)
if session.type == "meterpreter" if session.type == 'meterpreter'
begin begin
new_file = new_file + session.fs.file.separator + session.fs.file.basename(old_file) if directory?(new_file) new_file = new_file + session.fs.file.separator + session.fs.file.basename(old_file) if directory?(new_file)
return (session.fs.file.mv(old_file, new_file).result == 0) return (session.fs.file.mv(old_file, new_file).result == 0)
@ -577,13 +587,14 @@ module Msf::Post::File
cmd_exec("Move-Item \"#{old_file}\" \"#{new_file}\" -Force; if($?){echo #{verification_token}}").include?(verification_token) cmd_exec("Move-Item \"#{old_file}\" \"#{new_file}\" -Force; if($?){echo #{verification_token}}").include?(verification_token)
elsif session.platform == 'windows' elsif session.platform == 'windows'
return false unless file?(old_file) # adding this because when the old_file is not present it hangs for a while, should be removed after this issue is fixed. return false unless file?(old_file) # adding this because when the old_file is not present it hangs for a while, should be removed after this issue is fixed.
cmd_exec(%Q|move #{directory?(new_file) ? "" : "/y"} "#{old_file}" "#{new_file}" & if not errorlevel 1 echo #{verification_token}|).include?(verification_token)
cmd_exec(%(move #{directory?(new_file) ? '' : '/y'} "#{old_file}" "#{new_file}" & if not errorlevel 1 echo #{verification_token})).include?(verification_token)
else else
cmd_exec(%Q|mv #{directory?(new_file) ? "" : "-f"} "#{old_file}" "#{new_file}" && echo #{verification_token}|).include?(verification_token) cmd_exec(%(mv #{directory?(new_file) ? '' : '-f'} "#{old_file}" "#{new_file}" && echo #{verification_token})).include?(verification_token)
end end
end end
alias :move_file :rename_file alias move_file rename_file
alias :mv_file :rename_file alias mv_file rename_file
# #
# #
@ -593,9 +604,10 @@ module Msf::Post::File
# @param dst_file [String] The name for the remote destination file # @param dst_file [String] The name for the remote destination file
# @return [Boolean] Return true on success and false on failure # @return [Boolean] Return true on success and false on failure
def copy_file(src_file, dst_file) def copy_file(src_file, dst_file)
return false if directory?(dst_file) or directory?(src_file) return false if directory?(dst_file) || directory?(src_file)
verification_token = Rex::Text.rand_text_alpha_upper(8) verification_token = Rex::Text.rand_text_alpha_upper(8)
if session.type == "meterpreter" if session.type == 'meterpreter'
begin begin
return (session.fs.file.cp(src_file, dst_file).result == 0) return (session.fs.file.cp(src_file, dst_file).result == 0)
rescue Rex::Post::Meterpreter::RequestError => e # when the source file is not present meterpreter will raise an error rescue Rex::Post::Meterpreter::RequestError => e # when the source file is not present meterpreter will raise an error
@ -603,17 +615,15 @@ module Msf::Post::File
end end
elsif session.type == 'powershell' elsif session.type == 'powershell'
cmd_exec("Copy-Item \"#{src_file}\" -Destination \"#{dst_file}\"; if($?){echo #{verification_token}}").include?(verification_token) cmd_exec("Copy-Item \"#{src_file}\" -Destination \"#{dst_file}\"; if($?){echo #{verification_token}}").include?(verification_token)
elsif session.platform == 'windows'
cmd_exec(%(copy /y "#{src_file}" "#{dst_file}" & if not errorlevel 1 echo #{verification_token})).include?(verification_token)
else else
if session.platform == 'windows' cmd_exec(%(cp -f "#{src_file}" "#{dst_file}" && echo #{verification_token})).include?(verification_token)
cmd_exec(%Q|copy /y "#{src_file}" "#{dst_file}" & if not errorlevel 1 echo #{verification_token}|).include?(verification_token)
else
cmd_exec(%Q|cp -f "#{src_file}" "#{dst_file}" && echo #{verification_token}|).include?(verification_token)
end
end end
end end
alias :cp_file :copy_file alias cp_file copy_file
protected protected
def _append_file_powershell(file_name, data) def _append_file_powershell(file_name, data)
_write_file_powershell(file_name, data, true) _write_file_powershell(file_name, data, true)
@ -630,14 +640,14 @@ protected
end end
def _write_file_powershell_fragment(file_name, data, offset, chunk_size, append = false) def _write_file_powershell_fragment(file_name, data, offset, chunk_size, append = false)
chunk = data[offset..offset+chunk_size] chunk = data[offset..offset + chunk_size]
length = chunk.length length = chunk.length
compressed_chunk = Rex::Text.gzip(chunk) compressed_chunk = Rex::Text.gzip(chunk)
encoded_chunk = Base64.strict_encode64(compressed_chunk) encoded_chunk = Base64.strict_encode64(compressed_chunk)
if offset > 0 || append if offset > 0 || append
file_mode = "Append" file_mode = 'Append'
else else
file_mode = "Create" file_mode = 'Create'
end end
pwsh_code = %($encoded=\"#{encoded_chunk}\"; pwsh_code = %($encoded=\"#{encoded_chunk}\";
$mstream = [System.IO.MemoryStream]::new([System.Convert]::FromBase64String($encoded)); $mstream = [System.IO.MemoryStream]::new([System.Convert]::FromBase64String($encoded));
@ -659,6 +669,7 @@ protected
loop do loop do
chunk = _read_file_powershell_fragment(filename, chunk_size, offset) chunk = _read_file_powershell_fragment(filename, chunk_size, offset)
break if chunk.nil? break if chunk.nil?
data << chunk data << chunk
offset += chunk_size offset += chunk_size
break if chunk.length < chunk_size break if chunk.length < chunk_size
@ -666,17 +677,17 @@ protected
return data return data
end end
def _read_file_powershell_fragment(filename, chunk_size, offset=0) def _read_file_powershell_fragment(filename, chunk_size, offset = 0)
b64_data= cmd_exec("$mstream = [System.IO.MemoryStream]::new();\ b64_data = cmd_exec("$mstream = [System.IO.MemoryStream]::new();\
$gzipstream = [System.IO.Compression.GZipStream]::new($mstream, [System.IO.Compression.CompressionMode]::Compress);\ $gzipstream = [System.IO.Compression.GZipStream]::new($mstream, [System.IO.Compression.CompressionMode]::Compress);\
$get_bytes = [System.IO.File]::ReadAllBytes(\"#{filename}\")[#{offset}..#{offset + chunk_size -1}];\ $get_bytes = [System.IO.File]::ReadAllBytes(\"#{filename}\")[#{offset}..#{offset + chunk_size - 1}];\
$gzipstream.Write($get_bytes, 0 , $get_bytes.Length);\ $gzipstream.Write($get_bytes, 0 , $get_bytes.Length);\
$gzipstream.Close();\ $gzipstream.Close();\
[Convert]::ToBase64String($mstream.ToArray())") [Convert]::ToBase64String($mstream.ToArray())")
return nil if b64_data.empty? return nil if b64_data.empty?
uncompressed_fragment = Zlib::GzipReader.new(StringIO.new(Base64.decode64(b64_data))).read uncompressed_fragment = Zlib::GzipReader.new(StringIO.new(Base64.decode64(b64_data))).read
return uncompressed_fragment return uncompressed_fragment
end end
# Checks to see if there are non-ansi or newline characters in a given string # Checks to see if there are non-ansi or newline characters in a given string
@ -701,12 +712,10 @@ protected
# #
# @return [String] # @return [String]
def _read_file_meterpreter(file_name) def _read_file_meterpreter(file_name)
fd = session.fs.file.new(file_name, "rb") fd = session.fs.file.new(file_name, 'rb')
data = fd.read data = fd.read
until fd.eof? data << fd.read until fd.eof?
data << fd.read
end
data data
rescue EOFError rescue EOFError
@ -718,6 +727,7 @@ protected
ensure ensure
fd.close if fd fd.close if fd
end end
# Windows ANSI file write for shell sessions. Writes given object content to a remote file. # Windows ANSI file write for shell sessions. Writes given object content to a remote file.
# #
# NOTE: *This is not binary-safe on Windows shell sessions!* # NOTE: *This is not binary-safe on Windows shell sessions!*
@ -728,7 +738,7 @@ protected
# @return [void] # @return [void]
def _win_ansi_write_file(file_name, data, chunk_size = 5000) def _win_ansi_write_file(file_name, data, chunk_size = 5000)
start_index = 0 start_index = 0
write_length =[chunk_size, data.length].min write_length = [chunk_size, data.length].min
session.shell_command_token("echo | set /p=\"#{data[0, write_length]}\"> \"#{file_name}\"") session.shell_command_token("echo | set /p=\"#{data[0, write_length]}\"> \"#{file_name}\"")
if data.length > write_length if data.length > write_length
# just use append to finish the rest # just use append to finish the rest
@ -746,14 +756,14 @@ protected
# @return [void] # @return [void]
def _win_ansi_append_file(file_name, data, chunk_size = 5000) def _win_ansi_append_file(file_name, data, chunk_size = 5000)
start_index = 0 start_index = 0
write_length =[chunk_size, data.length].min write_length = [chunk_size, data.length].min
while start_index < data.length while start_index < data.length
begin begin
session.shell_command_token("<nul set /p=\"#{data[start_index, write_length]}\" >> \"#{file_name}\"") session.shell_command_token("<nul set /p=\"#{data[start_index, write_length]}\" >> \"#{file_name}\"")
start_index = start_index + write_length start_index += write_length
write_length = [chunk_size, data.length - start_index].min write_length = [chunk_size, data.length - start_index].min
rescue ::Exception => e rescue ::Exception => e
print_error("Exception while running #{__method__.to_s}: #{e.to_s}") print_error("Exception while running #{__method__}: #{e}")
file_rm(file_name) file_rm(file_name)
end end
end end
@ -772,7 +782,7 @@ protected
_win_ansi_write_file(b64_filename, b64_data, chunk_size) _win_ansi_write_file(b64_filename, b64_data, chunk_size)
cmd_exec("certutil -decode #{b64_filename} #{file_name}") cmd_exec("certutil -decode #{b64_filename} #{file_name}")
rescue ::Exception => e rescue ::Exception => e
print_error("Exception while running #{__method__.to_s}: #{e.to_s}") print_error("Exception while running #{__method__}: #{e}")
ensure ensure
file_rm(b64_filename) file_rm(b64_filename)
end end
@ -793,14 +803,13 @@ protected
cmd_exec("certutil -decode #{b64_filename} #{tmp_filename}") cmd_exec("certutil -decode #{b64_filename} #{tmp_filename}")
cmd_exec("copy /b #{file_name}+#{tmp_filename} #{file_name}") cmd_exec("copy /b #{file_name}+#{tmp_filename} #{file_name}")
rescue ::Exception => e rescue ::Exception => e
print_error("Exception while running #{__method__.to_s}: #{e.to_s}") print_error("Exception while running #{__method__}: #{e}")
ensure ensure
file_rm(b64_filename) file_rm(b64_filename)
file_rm(tmp_filename) file_rm(tmp_filename)
end end
end end
# #
# Write +data+ to the remote file +file_name+. # Write +data+ to the remote file +file_name+.
# #
@ -811,23 +820,23 @@ protected
# session. # session.
# #
# @return [void] # @return [void]
def _write_file_unix_shell(file_name, data, append=false) def _write_file_unix_shell(file_name, data, append = false)
redirect = (append ? ">>" : ">") redirect = (append ? '>>' : '>')
# Short-circuit an empty string. The : builtin is part of posix # Short-circuit an empty string. The : builtin is part of posix
# standard and should theoretically exist everywhere. # standard and should theoretically exist everywhere.
if data.length == 0 if data.empty?
session.shell_command_token(": #{redirect} #{file_name}") session.shell_command_token(": #{redirect} #{file_name}")
return return
end end
d = data.dup d = data.dup
d.force_encoding("binary") if d.respond_to? :force_encoding d.force_encoding('binary') if d.respond_to? :force_encoding
chunks = [] chunks = []
command = nil command = nil
encoding = :hex encoding = :hex
cmd_name = "" cmd_name = ''
line_max = _unix_max_line_length line_max = _unix_max_line_length
# Leave plenty of room for the filename we're writing to and the # Leave plenty of room for the filename we're writing to and the
@ -847,40 +856,40 @@ protected
# first. # first.
# #
# Both of these work for sure on Linux and FreeBSD # Both of these work for sure on Linux and FreeBSD
{ :cmd => %q^/usr/bin/printf 'CONTENTS'^ , :enc => :octal, :name => "printf" }, { cmd: %q{/usr/bin/printf 'CONTENTS'}, enc: :octal, name: 'printf' },
{ :cmd => %q^printf 'CONTENTS'^ , :enc => :octal, :name => "printf" }, { cmd: %q{printf 'CONTENTS'}, enc: :octal, name: 'printf' },
# Works on Solaris # Works on Solaris
{ :cmd => %q^/usr/bin/printf %b 'CONTENTS'^ , :enc => :octal, :name => "printf" }, { cmd: %q{/usr/bin/printf %b 'CONTENTS'}, enc: :octal, name: 'printf' },
{ :cmd => %q^printf %b 'CONTENTS'^ , :enc => :octal, :name => "printf" }, { cmd: %q{printf %b 'CONTENTS'}, enc: :octal, name: 'printf' },
# Perl supports both octal and hex escapes, but octal is usually # Perl supports both octal and hex escapes, but octal is usually
# shorter (e.g. 0 becomes \0 instead of \x00) # shorter (e.g. 0 becomes \0 instead of \x00)
{ :cmd => %q^perl -e 'print("CONTENTS")'^ , :enc => :octal, :name => "perl" }, { cmd: %q{perl -e 'print("CONTENTS")'}, enc: :octal, name: 'perl' },
# POSIX awk doesn't have \xNN escapes, use gawk to ensure we're # POSIX awk doesn't have \xNN escapes, use gawk to ensure we're
# getting the GNU version. # getting the GNU version.
{ :cmd => %q^gawk 'BEGIN {ORS="";print "CONTENTS"}' </dev/null^ , :enc => :hex, :name => "awk" }, { cmd: %q^gawk 'BEGIN {ORS="";print "CONTENTS"}' </dev/null^, enc: :hex, name: 'awk' },
# xxd's -p flag specifies a postscript-style hexdump of unadorned hex # xxd's -p flag specifies a postscript-style hexdump of unadorned hex
# digits, e.g. ABCD would be 41424344 # digits, e.g. ABCD would be 41424344
{ :cmd => %q^echo 'CONTENTS'|xxd -p -r^ , :enc => :bare_hex, :name => "xxd" }, { cmd: %q{echo 'CONTENTS'|xxd -p -r}, enc: :bare_hex, name: 'xxd' },
# Use echo as a last resort since it frequently doesn't support -e # Use echo as a last resort since it frequently doesn't support -e
# or -n. bash and zsh's echo builtins are apparently the only ones # or -n. bash and zsh's echo builtins are apparently the only ones
# that support both. Most others treat all options as just more # that support both. Most others treat all options as just more
# arguments to print. In particular, the standalone /bin/echo or # arguments to print. In particular, the standalone /bin/echo or
# /usr/bin/echo appear never to have -e so don't bother trying # /usr/bin/echo appear never to have -e so don't bother trying
# them. # them.
{ :cmd => %q^echo -ne 'CONTENTS'^ , :enc => :hex }, { cmd: %q{echo -ne 'CONTENTS'}, enc: :hex },
].each { |foo| ].each do |foo|
# Some versions of printf mangle %. # Some versions of printf mangle %.
test_str = "\0\xff\xfe#{Rex::Text.rand_text_alpha_upper(4)}\x7f%%\r\n" test_str = "\0\xff\xfe#{Rex::Text.rand_text_alpha_upper(4)}\x7f%%\r\n"
#test_str = "\0\xff\xfe" # test_str = "\0\xff\xfe"
case foo[:enc] case foo[:enc]
when :hex when :hex
cmd = foo[:cmd].sub("CONTENTS"){ Rex::Text.to_hex(test_str) } cmd = foo[:cmd].sub('CONTENTS') { Rex::Text.to_hex(test_str) }
when :octal when :octal
cmd = foo[:cmd].sub("CONTENTS"){ Rex::Text.to_octal(test_str) } cmd = foo[:cmd].sub('CONTENTS') { Rex::Text.to_octal(test_str) }
when :bare_hex when :bare_hex
cmd = foo[:cmd].sub("CONTENTS"){ Rex::Text.to_hex(test_str,'') } cmd = foo[:cmd].sub('CONTENTS') { Rex::Text.to_hex(test_str, '') }
end end
a = session.shell_command_token("#{cmd}") a = session.shell_command_token(cmd.to_s)
if test_str == a if test_str == a
command = foo[:cmd] command = foo[:cmd]
@ -890,7 +899,7 @@ protected
else else
vprint_status("#{cmd} Failed: #{a.inspect} != #{test_str.inspect}") vprint_status("#{cmd} Failed: #{a.inspect} != #{test_str.inspect}")
end end
} end
if command.nil? if command.nil?
raise RuntimeError, "Can't find command on the victim for writing binary data", caller raise RuntimeError, "Can't find command on the victim for writing binary data", caller
@ -898,18 +907,18 @@ protected
# each byte will balloon up to 4 when we encode # each byte will balloon up to 4 when we encode
# (A becomes \x41 or \101) # (A becomes \x41 or \101)
max = line_max/4 max = line_max / 4
i = 0 i = 0
while (i < d.length) while (i < d.length)
slice = d.slice(i...(i+max)) slice = d.slice(i...(i + max))
case encoding case encoding
when :hex when :hex
chunks << Rex::Text.to_hex(slice) chunks << Rex::Text.to_hex(slice)
when :octal when :octal
chunks << Rex::Text.to_octal(slice) chunks << Rex::Text.to_octal(slice)
when :bare_hex when :bare_hex
chunks << Rex::Text.to_hex(slice,'') chunks << Rex::Text.to_hex(slice, '')
end end
i += max i += max
end end
@ -918,17 +927,17 @@ protected
# The first command needs to use the provided redirection for either # The first command needs to use the provided redirection for either
# appending or truncating. # appending or truncating.
cmd = command.sub("CONTENTS") { chunks.shift } cmd = command.sub('CONTENTS') { chunks.shift }
session.shell_command_token("#{cmd} #{redirect} \"#{file_name}\"") session.shell_command_token("#{cmd} #{redirect} \"#{file_name}\"")
# After creating/truncating or appending with the first command, we # After creating/truncating or appending with the first command, we
# need to append from here on out. # need to append from here on out.
chunks.each { |chunk| chunks.each do |chunk|
vprint_status("Next chunk is #{chunk.length} bytes") vprint_status("Next chunk is #{chunk.length} bytes")
cmd = command.sub("CONTENTS") { chunk } cmd = command.sub('CONTENTS') { chunk }
session.shell_command_token("#{cmd} >> '#{file_name}'") session.shell_command_token("#{cmd} >> '#{file_name}'")
} end
true true
end end
@ -963,11 +972,11 @@ protected
else else
raise NotImplementedError if session.platform == 'windows' raise NotImplementedError if session.platform == 'windows'
raise "`stat' command doesn't exist on target system" unless command_exists?('stat') raise "`stat' command doesn't exist on target system" unless command_exists?('stat')
return FileStat.new(filename, session) return FileStat.new(filename, session)
end end
end end
class FileStat < Rex::Post::FileStat class FileStat < Rex::Post::FileStat
attr_accessor :stathash attr_accessor :stathash
@ -975,7 +984,8 @@ protected
def initialize(filename, session) def initialize(filename, session)
data = session.shell_command_token("stat --format='%d,%i,%h,%u,%g,%t,%s,%B,%o,%X,%Y,%Z,%f' '#{filename}'").to_s.chomp data = session.shell_command_token("stat --format='%d,%i,%h,%u,%g,%t,%s,%B,%o,%X,%Y,%Z,%f' '#{filename}'").to_s.chomp
raise 'format argument of stat command not behaving as expected' unless data =~ /(\d+,){12}\w+/ raise 'format argument of stat command not behaving as expected' unless data =~ /(\d+,){12}\w+/
data = data.split(",")
data = data.split(',')
@stathash = Hash.new @stathash = Hash.new
@stathash['st_dev'] = data[0].to_i @stathash['st_dev'] = data[0].to_i
@stathash['st_ino'] = data[1].to_i @stathash['st_ino'] = data[1].to_i
@ -989,8 +999,7 @@ protected
@stathash['st_atime'] = data[9].to_i @stathash['st_atime'] = data[9].to_i
@stathash['st_mtime'] = data[10].to_i @stathash['st_mtime'] = data[10].to_i
@stathash['st_ctime'] = data[11].to_i @stathash['st_ctime'] = data[11].to_i
@stathash['st_mode'] = data[12].to_i(16) #stat command returns hex value of mode" @stathash['st_mode'] = data[12].to_i(16) # stat command returns hex value of mode"
end end
end end
end end

View File

@ -1,11 +1,10 @@
lib = File.join(Msf::Config.install_root, 'test', 'lib')
lib = File.join(Msf::Config.install_root, "test", "lib") $LOAD_PATH.push(lib) unless $LOAD_PATH.include?(lib)
$:.push(lib) unless $:.include?(lib)
require 'module_test' require 'module_test'
#load 'test/lib/module_test.rb' # load 'test/lib/module_test.rb'
#load 'lib/rex/text.rb' # load 'lib/rex/text.rb'
#load 'lib/msf/core/post/file.rb' # load 'lib/msf/core/post/file.rb'
class MetasploitModule < Msf::Post class MetasploitModule < Msf::Post
@ -13,20 +12,24 @@ class MetasploitModule < Msf::Post
include Msf::Post::Common include Msf::Post::Common
include Msf::Post::File include Msf::Post::File
def initialize(info={}) def initialize(info = {})
super( update_info( info, super(
'Name' => 'Testing Remote File Manipulation', update_info(
'Description' => %q{ This module will test Post::File API methods }, info,
'License' => MSF_LICENSE, 'Name' => 'Testing Remote File Manipulation',
'Author' => [ 'egypt'], 'Description' => %q{ This module will test Post::File API methods },
'Platform' => [ 'windows', 'linux', 'java' ], 'License' => MSF_LICENSE,
'SessionTypes' => [ 'meterpreter', 'shell' ] 'Author' => [ 'egypt'],
)) 'Platform' => [ 'windows', 'linux', 'java' ],
'SessionTypes' => [ 'meterpreter', 'shell' ]
)
)
register_options( register_options(
[ [
OptString.new("BaseFileName" , [true, "File name to create", "meterpreter-test"]) OptString.new('BaseFileName', [true, 'File name to create', 'meterpreter-test'])
], self.class) ], self.class
)
end end
# #
@ -36,7 +39,7 @@ class MetasploitModule < Msf::Post
# #
def setup def setup
@old_pwd = pwd @old_pwd = pwd
tmp = (directory?("/tmp")) ? "/tmp" : "%TEMP%" tmp = directory?('/tmp') ? '/tmp' : '%TEMP%'
vprint_status("Setup: changing working directory to #{tmp}") vprint_status("Setup: changing working directory to #{tmp}")
cd(tmp) cd(tmp)
@ -44,45 +47,45 @@ class MetasploitModule < Msf::Post
end end
def test_file def test_file
it "should test for file existence" do it 'should test for file existence' do
ret = false ret = false
[ [
"c:\\boot.ini", 'c:\\boot.ini',
"c:\\pagefile.sys", 'c:\\pagefile.sys',
"/etc/passwd", '/etc/passwd',
"/etc/master.passwd", '/etc/master.passwd',
"%WINDIR%\\system32\\notepad.exe", '%WINDIR%\\system32\\notepad.exe',
"%WINDIR%\\system32\\calc.exe" '%WINDIR%\\system32\\calc.exe'
].each { |path| ].each do |path|
ret = true if file?(path) ret = true if file?(path)
} end
ret ret
end end
it "should test for directory existence" do it 'should test for directory existence' do
ret = false ret = false
[ [
"c:\\", 'c:\\',
"/etc/", '/etc/',
"/tmp" '/tmp'
].each { |path| ].each do |path|
ret = true if directory?(path) ret = true if directory?(path)
} end
ret ret
end end
it "should create text files" do it 'should create text files' do
rm_f(datastore["BaseFileName"]) rm_f(datastore['BaseFileName'])
write_file(datastore["BaseFileName"], "foo") write_file(datastore['BaseFileName'], 'foo')
file?(datastore["BaseFileName"]) file?(datastore['BaseFileName'])
end end
it "should read the text we just wrote" do it 'should read the text we just wrote' do
f = read_file(datastore["BaseFileName"]) f = read_file(datastore['BaseFileName'])
ret = ("foo" == f) ret = (f == 'foo')
unless ret unless ret
print_error("Didn't read what we wrote, actual file on target: |#{f}|") print_error("Didn't read what we wrote, actual file on target: |#{f}|")
end end
@ -90,14 +93,14 @@ class MetasploitModule < Msf::Post
ret ret
end end
it "should append text files" do it 'should append text files' do
ret = true ret = true
append_file(datastore["BaseFileName"], "bar") append_file(datastore['BaseFileName'], 'bar')
ret &&= read_file(datastore["BaseFileName"]) == "foobar" ret &&= read_file(datastore['BaseFileName']) == 'foobar'
append_file(datastore["BaseFileName"], "baz") append_file(datastore['BaseFileName'], 'baz')
final_contents = read_file(datastore["BaseFileName"]) final_contents = read_file(datastore['BaseFileName'])
ret &&= final_contents == "foobarbaz" ret &&= final_contents == 'foobarbaz'
unless ret unless ret
print_error("Didn't read what we wrote, actual file on target: #{final_contents}") print_error("Didn't read what we wrote, actual file on target: #{final_contents}")
end end
@ -105,68 +108,81 @@ class MetasploitModule < Msf::Post
ret ret
end end
it "should delete text files" do it 'should delete text files' do
rm_f(datastore["BaseFileName"]) rm_f(datastore['BaseFileName'])
not file_exist?(datastore["BaseFileName"]) !file_exist?(datastore['BaseFileName'])
end end
it "should move files" do it 'should move files' do
# Make sure we don't have leftovers from a previous run # Make sure we don't have leftovers from a previous run
moved_file = datastore["BaseFileName"] + "-moved" moved_file = datastore['BaseFileName'] + '-moved'
rm _f(datastore["BaseFileName"]) rescue nil begin
rm_f(moved_file) rescue nil rm _f(datastore['BaseFileName'])
rescue StandardError
nil
end
begin
rm_f(moved_file)
rescue StandardError
nil
end
# touch a new file # touch a new file
write_file(datastore["BaseFileName"], "") write_file(datastore['BaseFileName'], '')
rename_file(datastore["BaseFileName"], moved_file) rename_file(datastore['BaseFileName'], moved_file)
res &&= exist?(moved_file) res &&= exist?(moved_file)
res &&= !exist?(datastore["BaseFileName"]) res &&= !exist?(datastore['BaseFileName'])
# clean up # clean up
rm_f(datastore["BaseFileName"]) rescue nil begin
rm_f(moved_file) rescue nil rm_f(datastore['BaseFileName'])
rescue StandardError
nil
end
begin
rm_f(moved_file)
rescue StandardError
nil
end
end end
end end
def test_binary_files def test_binary_files
# binary_data = ::File.read("/bin/ls")
#binary_data = ::File.read("/bin/ls") binary_data = ::File.read('/bin/echo')
binary_data = ::File.read("/bin/echo") # binary_data = "\xff\x00\xff\xfe\xff\`$(echo blha)\`"
#binary_data = "\xff\x00\xff\xfe\xff\`$(echo blha)\`" it 'should write binary data' do
it "should write binary data" do
vprint_status "Writing #{binary_data.length} bytes" vprint_status "Writing #{binary_data.length} bytes"
t = Time.now t = Time.now
write_file(datastore["BaseFileName"], binary_data) write_file(datastore['BaseFileName'], binary_data)
vprint_status("Finished in #{Time.now - t}") vprint_status("Finished in #{Time.now - t}")
file_exist?(datastore["BaseFileName"]) file_exist?(datastore['BaseFileName'])
end end
it "should read the binary data we just wrote" do it 'should read the binary data we just wrote' do
bin = read_file(datastore["BaseFileName"]) bin = read_file(datastore['BaseFileName'])
vprint_status "Read #{bin.length} bytes" vprint_status "Read #{bin.length} bytes"
bin == binary_data bin == binary_data
end end
it "should delete binary files" do it 'should delete binary files' do
rm_f(datastore["BaseFileName"]) rm_f(datastore['BaseFileName'])
not file_exist?(datastore["BaseFileName"]) !file_exist?(datastore['BaseFileName'])
end end
it "should append binary data" do it 'should append binary data' do
write_file(datastore["BaseFileName"], "\xde\xad") write_file(datastore['BaseFileName'], "\xde\xad")
append_file(datastore["BaseFileName"], "\xbe\xef") append_file(datastore['BaseFileName'], "\xbe\xef")
bin = read_file(datastore["BaseFileName"]) bin = read_file(datastore['BaseFileName'])
rm_f(datastore["BaseFileName"]) rm_f(datastore['BaseFileName'])
bin == "\xde\xad\xbe\xef" bin == "\xde\xad\xbe\xef"
end end
end end
def cleanup def cleanup
@ -176,4 +192,3 @@ class MetasploitModule < Msf::Post
end end
end end