2012-06-29 00:18:28 -05:00
# -*- coding: binary -*-
2021-09-13 11:51:00 -05:00
2020-06-09 11:38:14 +10:00
require 'rex/post/meterpreter/extensions/stdapi/command_ids'
2021-05-24 21:12:33 +05:30
require 'rex/post/file_stat'
2011-01-11 17:53:24 +00:00
2012-07-17 15:48:06 -06:00
module Msf::Post::File
2021-04-22 17:29:14 -04:00
include Msf :: Post :: Common
def initialize ( info = { } )
2021-07-23 14:18:26 +01:00
super (
update_info (
info ,
2021-09-13 11:51:00 -05:00
'Compat' = > {
'Meterpreter' = > {
'Commands' = > %w[
2021-11-08 12:04:21 +00:00
core_channel_eof
core_channel_open
core_channel_read
core_channel_write
2021-09-13 11:51:00 -05:00
stdapi_fs_chdir
2021-11-08 12:04:21 +00:00
stdapi_fs_chmod
2021-09-13 11:51:00 -05:00
stdapi_fs_delete_dir
stdapi_fs_delete_file
stdapi_fs_file_expand_path
stdapi_fs_file_move
stdapi_fs_getwd
stdapi_fs_ls
stdapi_fs_mkdir
2021-11-08 12:04:21 +00:00
stdapi_fs_separator
2021-09-13 11:51:00 -05:00
stdapi_fs_stat
]
2021-07-23 14:18:26 +01:00
}
2021-09-13 11:51:00 -05:00
}
)
2021-07-23 14:18:26 +01:00
)
2021-04-22 17:29:14 -04:00
end
2012-08-23 23:57:55 -04:00
#
2014-02-18 18:24:23 -06:00
# Change directory in the remote session to +path+, which may be relative or
# absolute.
2012-08-23 23:57:55 -04:00
#
2014-02-18 18:24:23 -06:00
# @return [void]
2012-05-30 13:52:48 -06:00
def cd ( path )
2021-09-13 11:51:00 -05:00
e_path = begin
expand_path ( path )
rescue StandardError
path
end
if session . type == 'meterpreter'
2012-05-30 13:52:48 -06:00
session . fs . dir . chdir ( e_path )
2021-07-19 18:24:16 +05:30
elsif session . type == 'powershell'
2023-05-23 15:18:00 -04:00
cmd_exec ( " Set-Location -Path \" #{ e_path } \" ;[System.IO.Directory]::SetCurrentDirectory($(Get-Location)) " )
2012-05-30 13:52:48 -06:00
else
2015-01-08 16:10:28 -06:00
session . shell_command_token ( " cd \" #{ e_path } \" " )
2012-05-30 13:52:48 -06:00
end
2021-07-22 20:07:06 +05:30
nil
2012-05-30 13:52:48 -06:00
end
2013-08-30 16:28:33 -05:00
2012-08-23 23:57:55 -04:00
#
# Returns the current working directory in the remote session
#
2014-02-18 18:24:23 -06:00
# @note This may be inaccurate on shell sessions running on Windows before
# XP/2k3
#
# @return [String]
2012-05-30 13:52:48 -06:00
def pwd
2021-09-13 11:51:00 -05:00
if session . type == 'meterpreter'
2012-05-30 13:52:48 -06:00
return session . fs . dir . getwd
2021-07-08 16:08:43 +05:30
elsif session . type == 'powershell'
return cmd_exec ( '(Get-Location).Path' ) . strip
2021-09-13 11:51:00 -05:00
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
2012-05-30 13:52:48 -06:00
else
2021-09-13 11:51:00 -05:00
# Result on systems without pwd command
return session . shell_command_token ( 'echo $PWD' ) . to_s . strip
2012-05-30 13:52:48 -06:00
end
end
2013-08-30 16:28:33 -05:00
2014-11-17 12:03:37 -06:00
# Returns a list of the contents of the specified directory
# @param directory [String] the directory to list
# @return [Array] the contents of the directory
2014-11-17 12:15:33 -06:00
def dir ( directory )
2014-07-31 17:51:15 -07:00
if session . type == 'meterpreter'
2014-11-17 09:15:08 -08:00
return session . fs . dir . entries ( directory )
2014-07-31 17:51:15 -07:00
end
2018-07-12 13:54:47 +02:00
2021-06-06 19:42:25 +05:30
if session . type == 'powershell'
2021-09-23 13:29:31 +01:00
return cmd_exec ( " Get-ChildItem \" #{ directory } \" -Name " ) . split ( / [ \ r \ n]+ / )
2021-06-06 19:42:25 +05:30
end
2018-07-12 13:54:47 +02:00
if session . platform == 'windows'
2021-08-13 20:02:26 +05:30
return session . shell_command_token ( " dir /b \" #{ directory } \" " ) & . split ( / [ \ r \ n]+ / )
2018-07-12 13:54:47 +02:00
end
if command_exists? ( 'ls' )
return session . shell_command_token ( " ls #{ directory } " ) . split ( / [ \ r \ n]+ / )
end
# Result on systems without ls command
if directory [ - 1 ] != '/'
2021-09-13 11:51:00 -05:00
directory += '/'
2018-07-12 13:54:47 +02:00
end
result = [ ]
data = session . shell_command_token ( " for fn in #{ directory } *; do echo $fn; done " )
parts = data . split ( " \n " )
parts . each do | line |
2021-09-13 11:51:00 -05:00
line = line . split ( '/' ) [ - 1 ]
2018-07-12 13:54:47 +02:00
result . insert ( - 1 , line )
end
result
2014-07-31 17:51:15 -07:00
end
alias ls dir
2020-04-30 07:47:57 -05:00
# create and mark directory for cleanup
def mkdir ( path )
2021-02-05 09:24:35 -05:00
result = nil
2020-05-06 15:36:53 -05:00
vprint_status ( " Creating directory #{ path } " )
2020-04-30 07:47:57 -05:00
if session . type == 'meterpreter'
2021-02-05 09:24:35 -05:00
# behave like mkdir -p and don't throw an error if the directory exists
result = session . fs . dir . mkdir ( path ) unless directory? ( path )
2021-07-19 18:24:16 +05:30
elsif session . type == 'powershell'
result = cmd_exec ( " New-Item \" #{ path } \" -itemtype directory " )
2021-09-13 11:51:00 -05:00
elsif session . platform == 'windows'
result = cmd_exec ( " mkdir \" #{ path } \" " )
2020-04-30 07:47:57 -05:00
else
2021-09-13 11:51:00 -05:00
result = cmd_exec ( " mkdir -p ' #{ path } ' " )
2020-04-30 07:47:57 -05:00
end
vprint_status ( " #{ path } created " )
register_dir_for_cleanup ( path )
result
end
2012-05-30 13:52:48 -06:00
#
# See if +path+ exists on the remote system and is a directory
#
2014-02-18 18:24:23 -06:00
# @param path [String] Remote filename to check
2012-05-30 13:52:48 -06:00
def directory? ( path )
2018-01-23 01:12:42 -06:00
if session . type == 'meterpreter'
2021-09-13 11:51:00 -05:00
stat = begin
session . fs . file . stat ( path )
rescue StandardError
nil
end
2012-05-30 13:52:48 -06:00
return false unless stat
2021-09-13 11:51:00 -05:00
2012-05-30 13:52:48 -06:00
return stat . directory?
2021-07-19 18:24:16 +05:30
elsif session . type == 'powershell'
2021-07-19 19:17:26 +05:30
return cmd_exec ( " Test-Path -Path \" #{ path } \" -PathType Container " ) . include? ( 'True' )
2012-05-30 13:52:48 -06:00
else
2016-10-29 13:41:20 +10:00
if session . platform == 'windows'
2014-02-02 19:04:38 +00:00
f = cmd_exec ( " cmd.exe /C IF exist \" #{ path } \\ * \" ( echo true ) " )
2012-05-30 13:52:48 -06:00
else
2023-04-21 14:18:28 -04:00
f = session . shell_command_token ( " test -d ' #{ path } ' && echo true " )
2012-05-30 13:52:48 -06:00
end
2018-01-23 01:12:42 -06:00
return false if f . nil? || f . empty?
2014-02-02 19:04:38 +00:00
return false unless f =~ / true /
2021-09-13 11:51:00 -05:00
2018-01-23 01:12:42 -06:00
true
2012-05-30 13:52:48 -06:00
end
end
2013-08-30 16:28:33 -05:00
2012-09-04 22:21:47 -04:00
#
# Expand any environment variables to return the full path specified by +path+.
#
2014-02-18 18:24:23 -06:00
# @return [String]
2012-06-06 19:41:03 -06:00
def expand_path ( path )
2021-09-13 11:51:00 -05:00
if session . type == 'meterpreter'
2012-06-06 19:41:03 -06:00
return session . fs . file . expand_path ( path )
2021-07-19 18:24:16 +05:30
elsif session . type == 'powershell'
2021-07-21 23:37:46 +05:30
return cmd_exec ( " [Environment]::ExpandEnvironmentVariables( \" #{ path } \" ) " )
2012-06-06 19:41:03 -06:00
else
return cmd_exec ( " echo #{ path } " )
end
end
2013-08-30 16:28:33 -05:00
2012-03-19 01:08:03 -06:00
#
2012-05-30 13:52:48 -06:00
# See if +path+ exists on the remote system and is a regular file
2012-03-19 01:08:03 -06:00
#
2014-02-18 18:24:23 -06:00
# @param path [String] Remote filename to check
2012-05-30 13:52:48 -06:00
def file? ( path )
2021-09-21 01:47:59 +01:00
return false if path . nil?
2018-01-23 01:12:42 -06:00
if session . type == 'meterpreter'
2021-09-13 11:51:00 -05:00
stat = begin
session . fs . file . stat ( path )
rescue StandardError
nil
end
2012-03-16 16:27:06 -06:00
return false unless stat
2021-09-13 11:51:00 -05:00
2012-03-16 16:27:06 -06:00
return stat . file?
2021-07-08 16:08:43 +05:30
elsif session . type == 'powershell'
2021-09-13 11:51:00 -05:00
return cmd_exec ( " [System.IO.File]::Exists( \" #{ path } \" ) " ) & . include? ( 'True' )
2012-03-16 16:27:06 -06:00
else
2016-10-29 13:41:20 +10:00
if session . platform == 'windows'
2014-02-02 19:04:38 +00:00
f = cmd_exec ( " cmd.exe /C IF exist \" #{ path } \" ( echo true ) " )
if f =~ / true /
f = cmd_exec ( " cmd.exe /C IF exist \" #{ path } \\ \\ \" ( echo false ) ELSE ( echo true ) " )
end
2012-03-16 16:27:06 -06:00
else
2015-01-08 16:10:28 -06:00
f = session . shell_command_token ( " test -f \" #{ path } \" && echo true " )
2012-05-30 13:52:48 -06:00
end
2018-01-23 01:12:42 -06:00
return false if f . nil? || f . empty?
2014-02-02 19:04:38 +00:00
return false unless f =~ / true /
2021-09-13 11:51:00 -05:00
2018-01-23 01:12:42 -06:00
true
2012-05-30 13:52:48 -06:00
end
end
2013-08-30 16:28:33 -05:00
2012-05-30 13:52:48 -06:00
alias file_exist? file?
2013-08-30 16:28:33 -05:00
2018-01-23 01:12:42 -06:00
#
# See if +path+ on the remote system is a setuid file
#
# @param path [String] Remote filename to check
def setuid? ( path )
2021-05-24 21:12:33 +05:30
stat = stat ( path )
stat . setuid?
2018-01-23 01:12:42 -06:00
end
2019-11-21 10:48:42 +01:00
#
# See if +path+ on the remote system exists and is executable
#
# @param path [String] Remote path to check
#
# @return [Boolean] true if +path+ exists and is executable
#
def executable? ( path )
raise " `executable?' method does not support Windows systems " if session . platform == 'windows'
cmd_exec ( " test -x ' #{ path } ' && echo true " ) . to_s . include? 'true'
end
2018-09-15 06:29:24 +00:00
#
# See if +path+ on the remote system exists and is writable
#
# @param path [String] Remote path to check
#
# @return [Boolean] true if +path+ exists and is writable
#
def writable? ( path )
2021-08-17 16:09:20 +05:30
verification_token = Rex :: Text . rand_text_alpha_upper ( 8 )
if session . type == 'powershell' && file? ( path )
2021-09-13 11:51:00 -05:00
return cmd_exec ( " $a=[System.IO.File]::OpenWrite(' #{ path } ');if($?){echo #{ verification_token } };$a.Close() " ) . include? ( verification_token )
2021-08-17 16:09:20 +05:30
end
2018-09-20 22:49:05 -05:00
raise " `writable?' method does not support Windows systems " if session . platform == 'windows'
2018-09-15 06:29:24 +00:00
2022-05-24 08:44:12 -04:00
cmd_exec ( " (test -w ' #{ path } ' || test -O ' #{ path } ') && echo true " ) . to_s . include? 'true'
2018-09-15 06:29:24 +00:00
end
2020-01-16 13:24:48 -06:00
#
# See if +path+ on the remote system exists and is immutable
#
# @param path [String] Remote path to check
#
# @return [Boolean] true if +path+ exists and is immutable
#
def immutable? ( path )
raise " `immutable?' method does not support Windows systems " if session . platform == 'windows'
attributes ( path ) . include? ( 'Immutable' )
end
2019-02-01 19:44:18 +00:00
#
# See if +path+ on the remote system exists and is readable
#
# @param path [String] Remote path to check
#
# @return [Boolean] true if +path+ exists and is readable
#
def readable? ( path )
2021-09-13 11:51:00 -05:00
verification_token = Rex :: Text . rand_text_alpha ( 8 )
2021-08-11 19:23:26 +05:30
return false unless exists? ( path )
2021-09-13 11:51:00 -05:00
2021-08-11 18:46:52 +05:30
if session . type == 'powershell'
2021-09-13 11:51:00 -05:00
if directory? ( path )
return cmd_exec ( " [System.IO.Directory]::GetFiles(' #{ path } '); if($?) {echo #{ verification_token } } " ) . include? ( verification_token )
else
2021-08-11 18:46:52 +05:30
return cmd_exec ( " [System.IO.File]::OpenRead( \" #{ path } \" );if($?){echo \
#{ verification_token } } " ) . include? ( verification_token )
end
2021-08-11 19:23:26 +05:30
end
2021-08-11 18:46:52 +05:30
2019-02-01 19:44:18 +00:00
raise " `readable?' method does not support Windows systems " if session . platform == 'windows'
2021-08-11 18:46:52 +05:30
cmd_exec ( " test -r ' #{ path } ' && echo #{ verification_token } " ) . to_s . include? ( verification_token )
2019-02-01 19:44:18 +00:00
end
2012-05-30 13:52:48 -06:00
#
# Check for existence of +path+ on the remote file system
#
2014-02-18 18:24:23 -06:00
# @param path [String] Remote filename to check
2012-05-30 13:52:48 -06:00
def exist? ( path )
2018-01-23 01:12:42 -06:00
if session . type == 'meterpreter'
2021-09-13 11:51:00 -05:00
stat = begin
session . fs . file . stat ( path )
rescue StandardError
nil
end
return ! ! stat
2021-07-08 22:03:14 +05:30
elsif session . type == 'powershell'
2023-05-10 11:10:08 -04:00
return cmd_exec ( " Test-Path \" #{ path } \" " ) & . include? ( 'True' )
2012-05-30 13:52:48 -06:00
else
2016-10-29 13:41:20 +10:00
if session . platform == 'windows'
2014-02-18 18:24:23 -06:00
f = cmd_exec ( " cmd.exe /C IF exist \" #{ path } \" ( echo true ) " )
2012-05-30 13:52:48 -06:00
else
2015-01-08 16:10:28 -06:00
f = cmd_exec ( " test -e \" #{ path } \" && echo true " )
2012-03-16 16:27:06 -06:00
end
2018-01-23 01:12:42 -06:00
return false if f . nil? || f . empty?
2014-02-10 23:24:26 +00:00
return false unless f =~ / true /
2021-09-13 11:51:00 -05:00
2018-01-23 01:12:42 -06:00
true
2012-03-16 16:27:06 -06:00
end
end
2013-08-30 16:28:33 -05:00
2021-09-13 11:51:00 -05:00
alias exists? exist?
2016-04-23 17:31:22 -04:00
2019-12-25 07:34:44 +00:00
#
# Retrieve file attributes for +path+ on the remote system
#
# @param path [String] Remote filename to check
def attributes ( path )
raise " `attributes' method does not support Windows systems " if session . platform == 'windows'
cmd_exec ( " lsattr -l ' #{ path } ' " ) . to_s . scan ( / ^ #{ path } \ s+(.+)$ / ) . flatten . first . to_s . split ( ', ' )
end
2011-01-11 17:53:24 +00:00
#
2012-08-23 23:57:55 -04:00
# Writes a given string to a given local file
2011-01-11 17:53:24 +00:00
#
2014-02-18 18:24:23 -06:00
# @param local_file_name [String]
# @param data [String]
# @return [void]
def file_local_write ( local_file_name , data )
2020-08-17 09:50:42 +00:00
fname = Rex :: FileUtils . clean_path ( local_file_name )
unless :: File . exist? ( fname )
:: FileUtils . touch ( fname )
2011-01-11 17:53:24 +00:00
end
2021-09-13 11:51:00 -05:00
output = :: File . open ( fname , 'a' )
2014-02-18 18:24:23 -06:00
data . each_line do | d |
2011-01-11 17:53:24 +00:00
output . puts ( d )
end
output . close
end
2013-08-30 16:28:33 -05:00
2012-01-12 12:47:29 -05:00
#
# Returns a MD5 checksum of a given remote file
#
2022-08-06 18:56:39 +10:00
# @note For shell sessions,
2022-08-06 19:15:30 +10:00
# this method downloads the file from the remote host
# unless a hashing utility for use on the remote host is specified.
2022-08-06 17:42:11 +10:00
#
2014-02-18 18:24:23 -06:00
# @param file_name [String] Remote file name
2022-08-06 19:15:30 +10:00
# @option util [String] Remote file hashing utility
2014-02-18 18:24:23 -06:00
# @return [String] Hex digest of file contents
2022-08-06 19:15:30 +10:00
def file_remote_digestmd5 ( file_name , util : nil )
2022-08-06 17:42:11 +10:00
if session . type == 'meterpreter'
begin
return session . fs . file . md5 ( file_name ) & . unpack ( 'H*' ) . flatten . first
2022-08-13 15:06:57 +10:00
rescue StandardError = > e
print_error ( " Exception while running #{ __method__ } : #{ e } " )
2022-08-06 17:42:11 +10:00
return nil
end
end
2022-08-06 18:56:39 +10:00
# Note: This will fail on files larger than 2GB
if session . type == 'powershell'
data = cmd_exec ( " $md5 = New-Object -TypeName System.Security.Cryptography.MD5CryptoServiceProvider; [System.BitConverter]::ToString($md5.ComputeHash([System.IO.File]::ReadAllBytes(' #{ file_name } '))) " )
return unless data
chksum = data . scan ( / ^([A-F0-9-]+)$ / ) . flatten . first
return chksum & . gsub ( / - / , '' ) & . downcase
end
2022-08-06 19:15:30 +10:00
case util
when 'md5'
chksum = session . shell_command_token ( " md5 -q ' #{ file_name } ' " ) & . strip
when 'md5sum'
chksum = session . shell_command_token ( " md5sum ' #{ file_name } ' " ) & . strip . split . first
when 'certutil'
data = session . shell_command_token ( " certutil -hashfile \" #{ file_name } \" MD5 " )
return unless data
chksum = data . scan ( / ^([a-f0-9 ]{47}) \ r? \ n / ) . flatten . first & . gsub ( / \ s* / , '' )
else
data = read_file ( file_name )
return unless data
2012-01-26 19:35:39 -05:00
chksum = Digest :: MD5 . hexdigest ( data )
end
2022-08-06 19:15:30 +10:00
return unless chksum =~ / \ A[a-f0-9]{32} \ z /
chksum
2012-01-12 12:47:29 -05:00
end
2013-08-30 16:28:33 -05:00
2012-01-12 12:47:29 -05:00
#
# Returns a SHA1 checksum of a given remote file
#
2022-08-06 18:56:39 +10:00
# @note For shell sessions,
2022-08-06 19:15:30 +10:00
# this method downloads the file from the remote host
# unless a hashing utility for use on the remote host is specified.
2022-08-06 17:42:11 +10:00
#
2014-02-18 18:24:23 -06:00
# @param file_name [String] Remote file name
2022-08-06 19:15:30 +10:00
# @option util [String] Remote file hashing utility
2014-02-18 18:24:23 -06:00
# @return [String] Hex digest of file contents
2022-08-06 19:15:30 +10:00
def file_remote_digestsha1 ( file_name , util : nil )
2022-08-06 17:42:11 +10:00
if session . type == 'meterpreter'
begin
return session . fs . file . sha1 ( file_name ) & . unpack ( 'H*' ) . flatten . first
2022-08-13 15:06:57 +10:00
rescue StandardError = > e
print_error ( " Exception while running #{ __method__ } : #{ e } " )
2022-08-06 17:42:11 +10:00
return nil
end
end
2022-08-06 18:56:39 +10:00
# Note: This will fail on files larger than 2GB
if session . type == 'powershell'
data = cmd_exec ( " $sha1 = New-Object -TypeName System.Security.Cryptography.SHA1CryptoServiceProvider; [System.BitConverter]::ToString($sha1.ComputeHash([System.IO.File]::ReadAllBytes(' #{ file_name } '))) " )
return unless data
chksum = data . scan ( / ^([A-F0-9-]+)$ / ) . flatten . first
return chksum & . gsub ( / - / , '' ) & . downcase
end
2022-08-06 19:15:30 +10:00
case util
when 'sha1'
chksum = session . shell_command_token ( " sha1 -q ' #{ file_name } ' " ) & . strip
when 'sha1sum'
chksum = session . shell_command_token ( " sha1sum ' #{ file_name } ' " ) & . strip . split . first
when 'certutil'
data = session . shell_command_token ( " certutil -hashfile \" #{ file_name } \" SHA1 " )
return unless data
chksum = data . scan ( / ^([a-f0-9 ]{59}) \ r? \ n / ) . flatten . first & . gsub ( / \ s* / , '' )
else
data = read_file ( file_name )
return unless data
2012-01-26 19:35:39 -05:00
chksum = Digest :: SHA1 . hexdigest ( data )
end
2022-08-06 19:15:30 +10:00
return unless chksum =~ / \ A[a-f0-9]{40} \ z /
chksum
2012-01-12 12:47:29 -05:00
end
2013-08-30 16:28:33 -05:00
2012-01-12 12:47:29 -05:00
#
# Returns a SHA2 checksum of a given remote file
#
2014-02-18 18:24:23 -06:00
# @note THIS DOWNLOADS THE FILE
# @param file_name [String] Remote file name
# @return [String] Hex digest of file contents
def file_remote_digestsha2 ( file_name )
data = read_file ( file_name )
2012-01-26 19:35:39 -05:00
chksum = nil
if data
chksum = Digest :: SHA256 . hexdigest ( data )
end
2012-01-12 12:47:29 -05:00
return chksum
end
2013-08-30 16:28:33 -05:00
2011-01-11 17:53:24 +00:00
#
# Platform-agnostic file read. Returns contents of remote file +file_name+
# as a String.
#
2014-02-18 18:24:23 -06:00
# @param file_name [String] Remote file name to read
# @return [String] Contents of the file
2018-06-19 12:39:41 +02:00
#
# @return [Array] of strings(lines)
#
2011-01-11 17:53:24 +00:00
def read_file ( file_name )
2018-07-12 13:54:47 +02:00
if session . type == 'meterpreter'
return _read_file_meterpreter ( file_name )
end
2021-06-11 12:58:00 +01:00
return unless %w[ shell powershell ] . include? ( session . type )
2013-08-30 16:28:33 -05:00
2021-07-19 18:24:16 +05:30
if session . type == 'powershell'
2021-07-27 20:17:04 +05:30
return _read_file_powershell ( file_name )
2021-07-19 19:17:26 +05:30
end
2021-07-26 23:52:20 +05:30
2021-07-19 18:24:16 +05:30
if session . platform == 'windows'
2018-07-12 13:54:47 +02:00
return session . shell_command_token ( " type \" #{ file_name } \" " )
2011-01-11 17:53:24 +00:00
end
2018-07-12 13:54:47 +02:00
2019-02-01 19:44:18 +00:00
return nil unless readable? ( file_name )
2018-07-12 13:54:47 +02:00
if command_exists? ( 'cat' )
return session . shell_command_token ( " cat \" #{ file_name } \" " )
end
# Result on systems without cat command
session . shell_command_token ( " while read line; do echo $line; done < #{ file_name } " )
2011-01-11 17:53:24 +00:00
end
2013-08-30 16:28:33 -05:00
2011-05-30 00:15:04 +00:00
# Platform-agnostic file write. Writes given object content to a remote file.
#
2014-02-18 18:24:23 -06:00
# @param file_name [String] Remote file name to write
# @param data [String] Contents to put in the file
2021-11-25 03:29:13 +00:00
# @return bool
2011-05-30 00:15:04 +00:00
def write_file ( file_name , data )
2021-09-13 11:51:00 -05:00
if session . type == 'meterpreter'
2021-11-25 03:29:13 +00:00
return _write_file_meterpreter ( file_name , data )
2021-08-17 01:34:12 +05:30
elsif session . type == 'powershell'
2022-12-14 17:00:16 +11:00
return _write_file_powershell ( file_name , data )
2011-05-30 00:15:04 +00:00
elsif session . respond_to? :shell_command_token
2016-10-29 13:41:20 +10:00
if session . platform == 'windows'
2021-06-29 14:51:40 -05:00
if _can_echo? ( data )
2022-12-14 12:48:46 +11:00
return _win_ansi_write_file ( file_name , data )
2021-06-23 09:19:43 -05:00
else
2022-12-14 12:48:46 +11:00
return _win_bin_write_file ( file_name , data )
2021-06-22 09:34:54 -05:00
end
2011-05-30 22:14:11 +00:00
else
2022-12-14 12:48:46 +11:00
return _write_file_unix_shell ( file_name , data )
2011-05-30 22:14:11 +00:00
end
2021-11-29 09:46:28 +00:00
else
return false
2011-05-21 16:24:34 +00:00
end
2011-05-30 00:15:04 +00:00
end
2013-08-30 16:28:33 -05:00
2011-06-10 03:27:09 +00:00
#
2011-05-30 00:15:04 +00:00
# Platform-agnostic file append. Appends given object content to a remote file.
2012-03-19 01:08:03 -06:00
#
2014-02-18 18:24:23 -06:00
# @param file_name [String] Remote file name to write
# @param data [String] Contents to put in the file
2021-06-23 09:19:43 -05:00
# @return bool
2011-05-30 00:15:04 +00:00
def append_file ( file_name , data )
2021-09-13 11:51:00 -05:00
if session . type == 'meterpreter'
2021-11-25 03:29:13 +00:00
return _write_file_meterpreter ( file_name , data , 'ab' )
2021-09-13 11:20:58 -05:00
elsif session . type == 'powershell'
2022-12-14 17:00:16 +11:00
return _append_file_powershell ( file_name , data )
2011-05-30 00:15:04 +00:00
elsif session . respond_to? :shell_command_token
2016-10-29 13:41:20 +10:00
if session . platform == 'windows'
2021-06-29 14:51:40 -05:00
if _can_echo? ( data )
return _win_ansi_append_file ( file_name , data )
2021-06-23 09:19:43 -05:00
else
2021-06-29 14:51:40 -05:00
return _win_bin_append_file ( file_name , data )
2021-06-22 09:34:54 -05:00
end
2011-05-30 22:14:11 +00:00
else
2022-10-21 23:28:14 +11:00
return _append_file_unix_shell ( file_name , data )
2011-05-30 22:14:11 +00:00
end
2011-05-30 00:15:04 +00:00
end
end
2013-08-30 16:28:33 -05:00
2012-06-06 19:41:03 -06:00
#
2013-02-07 18:37:11 -06:00
# Read a local file +local+ and write it as +remote+ on the remote file
2012-08-23 23:57:55 -04:00
# system
2012-06-06 19:41:03 -06:00
#
2014-02-18 18:24:23 -06:00
# @param remote [String] Destination file name on the remote filesystem
# @param local [String] Local file whose contents will be uploaded
# @return (see #write_file)
2012-06-06 19:41:03 -06:00
def upload_file ( remote , local )
2022-02-12 21:39:12 +00:00
write_file ( remote , :: File . read ( local , mode : 'rb' ) )
2012-06-06 19:41:03 -06:00
end
2013-08-30 16:28:33 -05:00
2019-09-03 17:31:04 +08:00
#
# Upload a binary and write it as an executable file +remote+ on the
# remote filesystem.
#
2023-03-05 20:15:14 -06:00
# @param path [String] Path to the destination file on the remote filesystem
2019-09-03 17:31:04 +08:00
# @param data [String] Data to be uploaded
def upload_and_chmodx ( path , data )
2019-10-23 20:28:13 +08:00
print_status " Writing ' #{ path } ' ( #{ data . size } bytes) ... "
write_file path , data
2019-09-03 17:31:04 +08:00
chmod ( path )
end
2018-09-24 15:23:44 +08:00
#
2018-09-24 12:37:15 -05:00
# Sets the permissions on a remote file
2018-09-24 15:23:44 +08:00
#
2018-09-24 12:37:15 -05:00
# @param path [String] Path on the remote filesystem
# @param mode [Fixnum] Mode as an octal number
2021-09-13 11:51:00 -05:00
def chmod ( path , mode = 0 o700 )
2018-09-24 12:37:15 -05:00
if session . platform == 'windows'
2018-09-24 15:17:59 -05:00
raise " `chmod' method does not support Windows systems "
2018-09-24 12:37:15 -05:00
end
2020-06-09 11:38:14 +10:00
if session . type == 'meterpreter' && session . commands . include? ( Rex :: Post :: Meterpreter :: Extensions :: Stdapi :: COMMAND_ID_STDAPI_FS_CHMOD )
2018-09-24 12:37:15 -05:00
session . fs . file . chmod ( path , mode )
2018-09-24 15:23:44 +08:00
else
2018-09-24 12:37:15 -05:00
cmd_exec ( " chmod #{ mode . to_s ( 8 ) } ' #{ path } ' " )
2018-09-24 15:23:44 +08:00
end
end
2019-09-03 17:31:04 +08:00
#
# Read a local exploit file binary from the data directory
#
2023-03-05 20:15:14 -06:00
# @param data_directory [String] Name of data directory within the exploits folder
# @param file [String] Filename in the data folder to use.
2019-09-03 17:31:04 +08:00
def exploit_data ( data_directory , file )
2021-09-13 11:51:00 -05:00
file_path = :: File . join ( :: Msf :: Config . data_directory , 'exploits' , data_directory , file )
2019-09-03 17:31:04 +08:00
:: File . binread ( file_path )
end
2021-11-09 11:57:04 -06:00
#
# Read a local exploit source file from the external exploits directory
#
2023-03-05 20:15:14 -06:00
# @param source_directory [String] Directory in the external/source/exploits directory to use as the source directory.
# @param file [String] Filename in the source folder to use.
2021-11-09 11:57:04 -06:00
def exploit_source ( source_directory , file )
file_path = :: File . join ( Msf :: Config . install_root , 'external' , 'source' , 'exploits' , source_directory , file )
:: File . read ( file_path )
end
2012-09-10 12:32:14 -05:00
#
# Delete remote files
#
2014-02-18 18:24:23 -06:00
# @param remote_files [Array<String>] List of remote filenames to
# delete
# @return [void]
2012-09-10 12:32:14 -05:00
def rm_f ( * remote_files )
remote_files . each do | remote |
2021-09-13 11:51:00 -05:00
if session . type == 'meterpreter'
2021-07-16 00:52:47 +05:30
session . fs . file . delete ( remote ) if file? ( remote )
2021-07-08 16:08:43 +05:30
elsif session . type == 'powershell'
2021-09-13 11:20:58 -05:00
cmd_exec ( " [System.IO.File]::Delete( \" #{ remote } \" ) " ) if file? ( remote )
2021-09-13 11:51:00 -05:00
elsif session . platform == 'windows'
cmd_exec ( " del /q /f \" #{ remote } \" " )
2012-09-10 12:32:14 -05:00
else
2021-09-13 11:51:00 -05:00
cmd_exec ( " rm -f \" #{ remote } \" " )
2012-09-10 12:32:14 -05:00
end
end
end
2013-08-30 16:28:33 -05:00
2017-12-31 00:14:57 -06:00
#
# Delete remote directories
#
# @param remote_dirs [Array<String>] List of remote directories to
# delete
# @return [void]
def rm_rf ( * remote_dirs )
remote_dirs . each do | remote |
2021-09-13 11:51:00 -05:00
if session . type == 'meterpreter'
2017-12-31 00:14:57 -06:00
session . fs . dir . rmdir ( remote ) if exist? ( remote )
2021-07-08 16:08:43 +05:30
elsif session . type == 'powershell'
2021-07-13 20:07:00 +05:30
cmd_exec ( " Remove-Item -Path \" #{ remote } \" -Force -Recurse " )
2021-09-13 11:51:00 -05:00
elsif session . platform == 'windows'
cmd_exec ( " rd /s /q \" #{ remote } \" " )
2017-12-31 00:14:57 -06:00
else
2021-09-13 11:51:00 -05:00
cmd_exec ( " rm -rf \" #{ remote } \" " )
2017-12-31 00:14:57 -06:00
end
end
end
2021-09-13 11:51:00 -05:00
alias file_rm rm_f
alias dir_rm rm_rf
2014-02-18 18:24:23 -06:00
2012-10-01 18:14:44 -05:00
#
2021-08-19 12:07:52 -04:00
# Renames a remote file. If the new file path is a directory, the file will be
# moved into that directory with the same name.
2012-10-01 18:14:44 -05:00
#
2014-02-18 18:24:23 -06:00
# @param old_file [String] Remote file name to move
# @param new_file [String] The new name for the remote file
2021-08-19 12:07:52 -04:00
# @return [Boolean] Return true on success and false on failure
2013-04-05 01:24:24 -05:00
def rename_file ( old_file , new_file )
2021-08-16 19:40:37 +05:30
verification_token = Rex :: Text . rand_text_alphanumeric ( 8 )
2021-09-13 11:51:00 -05:00
if session . type == 'meterpreter'
2021-08-16 19:40:37 +05:30
begin
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 )
rescue Rex :: Post :: Meterpreter :: RequestError = > e
return false
2014-02-18 18:24:23 -06:00
end
2021-08-16 19:40:37 +05:30
elsif session . type == 'powershell'
cmd_exec ( " Move-Item \" #{ old_file } \" \" #{ new_file } \" -Force; if($?){echo #{ verification_token } } " ) . include? ( verification_token )
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.
2021-09-13 11:51:00 -05:00
cmd_exec ( %( move #{ directory? ( new_file ) ? '' : '/y' } " #{ old_file } " " #{ new_file } " & if not errorlevel 1 echo #{ verification_token } ) ) . include? ( verification_token )
2021-08-16 19:40:37 +05:30
else
2021-09-13 11:51:00 -05:00
cmd_exec ( %( mv #{ directory? ( new_file ) ? '' : '-f' } " #{ old_file } " " #{ new_file } " && echo #{ verification_token } ) ) . include? ( verification_token )
2013-04-05 01:24:24 -05:00
end
2012-10-01 18:14:44 -05:00
end
2021-09-13 11:51:00 -05:00
alias move_file rename_file
alias mv_file rename_file
2011-05-21 16:24:34 +00:00
2021-08-16 19:40:37 +05:30
#
2021-04-28 12:46:30 +02:00
#
# Copy a remote file.
#
# @param src_file [String] Remote source file name to copy
# @param dst_file [String] The name for the remote destination file
2021-07-22 19:16:06 +05:30
# @return [Boolean] Return true on success and false on failure
2021-04-28 12:46:30 +02:00
def copy_file ( src_file , dst_file )
2021-09-13 11:51:00 -05:00
return false if directory? ( dst_file ) || directory? ( src_file )
2021-07-20 19:31:21 +05:30
verification_token = Rex :: Text . rand_text_alpha_upper ( 8 )
2021-09-13 11:51:00 -05:00
if session . type == 'meterpreter'
2021-07-20 19:31:21 +05:30
begin
return ( session . fs . file . cp ( src_file , dst_file ) . result == 0 )
2021-07-22 19:16:06 +05:30
rescue Rex :: Post :: Meterpreter :: RequestError = > e # when the source file is not present meterpreter will raise an error
2021-07-20 19:31:21 +05:30
return false
end
2021-07-19 18:24:16 +05:30
elsif session . type == 'powershell'
2021-07-20 19:31:21 +05:30
cmd_exec ( " Copy-Item \" #{ src_file } \" -Destination \" #{ dst_file } \" ; if($?){echo #{ verification_token } } " ) . include? ( verification_token )
2021-09-13 11:51:00 -05:00
elsif session . platform == 'windows'
cmd_exec ( %( copy /y " #{ src_file } " " #{ dst_file } " & if not errorlevel 1 echo #{ verification_token } ) ) . include? ( verification_token )
2021-04-28 12:46:30 +02:00
else
2021-09-13 11:51:00 -05:00
cmd_exec ( %( cp -f " #{ src_file } " " #{ dst_file } " && echo #{ verification_token } ) ) . include? ( verification_token )
2021-04-28 12:46:30 +02:00
end
end
2021-09-13 11:51:00 -05:00
alias cp_file copy_file
2021-04-28 12:46:30 +02:00
2021-09-13 11:51:00 -05:00
protected
2014-02-18 18:24:23 -06:00
2021-09-13 11:20:58 -05:00
def _append_file_powershell ( file_name , data )
_write_file_powershell ( file_name , data , true )
end
2021-08-26 07:27:07 +05:30
2021-09-13 11:20:58 -05:00
def _write_file_powershell ( file_name , data , append = false )
2021-08-17 03:44:57 +05:30
offset = 0
2022-12-14 20:46:31 +11:00
chunk_size = 1000
2021-08-17 03:44:57 +05:30
loop do
2022-12-14 17:00:16 +11:00
success = _write_file_powershell_fragment ( file_name , data , offset , chunk_size , append )
unless success
unless offset == 0
2022-12-16 10:36:26 +11:00
print_warning ( " Write partially succeeded then failed. May need to manually clean up #{ file_name } " )
2022-12-14 17:00:16 +11:00
end
return false
end
2022-12-14 20:46:31 +11:00
# Future writes will then append, regardless of whether this is an append or write operation
append = true
offset += chunk_size
2021-08-17 20:14:00 +05:30
break if offset > = data . length
2021-08-17 03:44:57 +05:30
end
2022-12-14 17:00:16 +11:00
true
2021-08-17 03:44:57 +05:30
end
2021-09-13 11:20:58 -05:00
def _write_file_powershell_fragment ( file_name , data , offset , chunk_size , append = false )
2022-12-16 10:36:26 +11:00
token = " _ #{ :: Rex :: Text . rand_text_alpha ( 32 ) } "
2022-12-14 20:46:31 +11:00
chunk = data [ offset .. ( offset + chunk_size - 1 ) ]
2021-09-13 11:51:00 -05:00
length = chunk . length
compressed_chunk = Rex :: Text . gzip ( chunk )
encoded_chunk = Base64 . strict_encode64 ( compressed_chunk )
2022-12-14 20:46:31 +11:00
if append
2021-09-13 11:51:00 -05:00
file_mode = 'Append'
2021-09-13 11:20:58 -05:00
else
2021-09-13 11:51:00 -05:00
file_mode = 'Create'
2021-09-13 11:20:58 -05:00
end
2021-10-29 12:36:27 +01:00
pwsh_code = << ~ PSH
2022-12-14 17:00:16 +11:00
try {
2021-10-29 12:36:27 +01:00
$encoded = '#{encoded_chunk}' ;
$gzip_bytes = [ System . Convert ] :: FromBase64String ( $encoded ) ;
$mstream = New - Object System . IO . MemoryStream ( , $gzip_bytes ) ;
$gzipstream = New - Object System . IO . Compression . GzipStream $mstream , ( [ System . IO . Compression . CompressionMode ] :: Decompress ) ;
$filestream = [ System . IO . File ] :: Open ( '#{file_name}' , [ System . IO . FileMode ] :: #{file_mode});
2021-10-29 15:26:03 +01:00
$file_bytes = [ System . Byte [ ] ] :: CreateInstance ( [ System . Byte ] , #{length});
$gzipstream . Read ( $file_bytes , 0 , $file_bytes . Length ) ;
$filestream . Write ( $file_bytes , 0 , $file_bytes . Length ) ;
2021-10-29 12:36:27 +01:00
$filestream . Close ( ) ;
$gzipstream . Close ( ) ;
2022-12-14 20:46:31 +11:00
echo Done
2022-12-14 17:00:16 +11:00
} catch {
echo #{token}
}
2021-10-29 12:36:27 +01:00
PSH
2022-12-14 17:00:16 +11:00
result = cmd_exec ( pwsh_code )
2022-12-14 20:46:31 +11:00
return result . include? ( length . to_s ) && ! result . include? ( token ) && result . include? ( 'Done' )
2021-09-08 12:53:41 -05:00
end
2021-08-26 07:27:07 +05:30
2021-08-02 18:39:16 +05:30
def _read_file_powershell ( filename )
data = ''
2021-08-10 18:45:26 +05:30
offset = 0
2021-08-10 20:07:37 +05:30
chunk_size = 65536
2021-08-02 18:39:16 +05:30
loop do
2021-08-10 18:45:26 +05:30
chunk = _read_file_powershell_fragment ( filename , chunk_size , offset )
break if chunk . nil?
2021-09-13 11:51:00 -05:00
2021-08-10 18:45:26 +05:30
data << chunk
offset += chunk_size
break if chunk . length < chunk_size
2021-08-02 18:39:16 +05:30
end
return data
end
2021-09-13 11:51:00 -05:00
def _read_file_powershell_fragment ( filename , chunk_size , offset = 0 )
2021-10-29 12:36:27 +01:00
pwsh_code = << ~ PSH
$mstream = New - Object System . IO . MemoryStream ;
$gzipstream = New - Object System . IO . Compression . GZipStream ( $mstream , [ System . IO . Compression . CompressionMode ] :: Compress ) ;
$get_bytes = [ System . IO . File ] :: ReadAllBytes ( \ " #{ filename } \" )[ #{ offset } .. #{ offset + chunk_size - 1 } ];
$gzipstream.Write($get_bytes, 0, $get_bytes.Length);
$gzipstream.Close();
2021-10-29 15:26:03 +01:00
[System.Convert]::ToBase64String($mstream.ToArray());
2021-10-29 12:36:27 +01:00
PSH
b64_data = cmd_exec(pwsh_code)
2021-08-10 18:45:26 +05:30
return nil if b64_data.empty?
2021-09-13 11:51:00 -05:00
2021-08-10 18:45:26 +05:30
uncompressed_fragment = Zlib::GzipReader.new(StringIO.new(Base64.decode64(b64_data))).read
return uncompressed_fragment
2021-07-26 23:52:20 +05:30
end
2021-08-02 18:39:16 +05:30
2021-08-16 14:17:51 +01:00
protected
2022-10-27 01:49:07 +01:00
# Checks to see if there are non-printable characters in a given string
2021-06-29 14:51:40 -05:00
#
2022-10-27 01:49:07 +01:00
# @param data [String] String to check for non-printable characters
2021-06-29 14:51:40 -05:00
# @return bool
def _can_echo?(data)
2022-10-27 01:49:07 +01:00
# Ensure all bytes are between ascii 0x20 to 0x7e (ie. [[:print]]), excluding quotes etc
data.bytes.all? do|b|
(b >= 0x20 && b <= 0x7e) &&
b != ' " '.ord &&
b != ' %'.ord &&
b != ' $' . ord
2021-06-29 14:51:40 -05:00
end
end
2011-01-11 17:53:24 +00:00
#
2021-11-25 03:29:13 +00:00
# Meterpreter-specific file write. Returns true on success
#
def _write_file_meterpreter ( file_name , data , mode = 'wb' )
fd = session . fs . file . new ( file_name , mode )
fd . write ( data )
fd . close
return true
rescue :: Rex :: Post :: Meterpreter :: RequestError = > e
return false
end
2011-01-11 17:53:24 +00:00
# Meterpreter-specific file read. Returns contents of remote file
2011-07-14 21:59:35 +00:00
# +file_name+ as a String or nil if there was an error
2011-01-11 17:53:24 +00:00
#
2013-02-07 18:37:11 -06:00
# You should never call this method directly. Instead, call {#read_file}
2012-03-19 01:08:03 -06:00
# which will call this if it is appropriate for the given session.
#
2014-02-18 18:24:23 -06:00
# @return [String]
2012-03-19 01:08:03 -06:00
def _read_file_meterpreter ( file_name )
2021-09-13 11:51:00 -05:00
fd = session . fs . file . new ( file_name , 'rb' )
2013-08-30 16:28:33 -05:00
2023-06-15 00:05:18 +01:00
data = '' . b
data << fd . read
2021-09-13 11:51:00 -05:00
data << fd . read until fd . eof?
2018-05-30 15:26:34 -05:00
2011-01-11 17:53:24 +00:00
data
2018-05-30 15:26:34 -05:00
rescue EOFError
# Sometimes fd isn't marked EOF in time?
2023-06-14 17:50:54 +01:00
data
2018-05-30 15:26:34 -05:00
rescue :: Rex :: Post :: Meterpreter :: RequestError = > e
print_error ( " Failed to open file: #{ file_name } : #{ e } " )
return nil
ensure
fd . close if fd
2011-01-11 17:53:24 +00:00
end
2021-09-13 11:51:00 -05:00
2021-06-29 14:51:40 -05:00
# 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!*
#
# @param file_name [String] Remote file name to write
# @param data [String] Contents to put in the file
# @param chunk_size [int] max size for the data chunk to write at a time
# @return [void]
def _win_ansi_write_file ( file_name , data , chunk_size = 5000 )
start_index = 0
2021-09-13 11:51:00 -05:00
write_length = [ chunk_size , data . length ] . min
2022-12-15 09:05:16 +11:00
success = _shell_command_with_success_code ( " echo | set /p x= \" #{ data [ 0 , write_length ] } \" > \" #{ file_name } \" " )
2022-12-14 17:00:16 +11:00
return false unless success
2021-06-29 14:51:40 -05:00
if data . length > write_length
# just use append to finish the rest
2022-12-15 09:05:16 +11:00
return _win_ansi_append_file ( file_name , data [ write_length , data . length ] , chunk_size )
2021-06-29 14:51:40 -05:00
end
2022-12-14 17:00:16 +11:00
true
2021-06-29 14:51:40 -05:00
end
# Windows ansi file append for shell sessions. Writes given object content to a remote file.
#
# NOTE: *This is not binary-safe on Windows shell sessions!*
#
# @param file_name [String] Remote file name to write
# @param data [String] Contents to put in the file
# @param chunk_size [int] max size for the data chunk to write at a time
# @return [void]
def _win_ansi_append_file ( file_name , data , chunk_size = 5000 )
start_index = 0
2021-09-13 11:51:00 -05:00
write_length = [ chunk_size , data . length ] . min
2021-06-29 14:51:40 -05:00
while start_index < data . length
begin
2022-12-15 09:05:16 +11:00
success = _shell_command_with_success_code ( " echo | set /p x= \" #{ data [ start_index , write_length ] } \" >> \" #{ file_name } \" " )
2022-12-14 17:00:16 +11:00
unless success
2022-12-16 10:36:26 +11:00
print_warning ( " Write partially succeeded then failed. May need to manually clean up #{ file_name } " ) unless start_index == 0
2022-12-14 17:00:16 +11:00
return false
end
2021-09-13 11:51:00 -05:00
start_index += write_length
2021-06-29 14:51:40 -05:00
write_length = [ chunk_size , data . length - start_index ] . min
rescue :: Exception = > e
2021-09-13 11:51:00 -05:00
print_error ( " Exception while running #{ __method__ } : #{ e } " )
2022-12-16 10:36:26 +11:00
print_warning ( " May need to manually clean up #{ file_name } " ) unless start_index == 0
2021-06-29 14:51:40 -05:00
file_rm ( file_name )
2022-12-14 17:00:16 +11:00
return false
2021-06-29 14:51:40 -05:00
end
end
2022-12-14 17:00:16 +11:00
true
2021-06-29 14:51:40 -05:00
end
# Windows binary file write for shell sessions. Writes given object content to a remote file.
#
# @param file_name [String] Remote file name to write
# @param data [String] Contents to put in the file
# @param chunk_size [int] max size for the data chunk to write at a time
# @return [void]
def _win_bin_write_file ( file_name , data , chunk_size = 5000 )
b64_data = Base64 . strict_encode64 ( data )
b64_filename = " #{ file_name } .b64 "
begin
2022-12-14 17:00:16 +11:00
success = _win_ansi_write_file ( b64_filename , b64_data , chunk_size )
return false unless success
2022-12-15 09:05:16 +11:00
vprint_status ( " Uploaded Base64-encoded file. Decoding using certutil " )
2022-12-14 17:00:16 +11:00
success = _shell_command_with_success_code ( " certutil -f -decode #{ b64_filename } #{ file_name } " )
return false unless success
2021-06-29 14:51:40 -05:00
rescue :: Exception = > e
2021-09-13 11:51:00 -05:00
print_error ( " Exception while running #{ __method__ } : #{ e } " )
2022-12-14 17:00:16 +11:00
return false
2021-06-29 14:51:40 -05:00
ensure
file_rm ( b64_filename )
end
2022-12-14 17:00:16 +11:00
true
2021-06-29 14:51:40 -05:00
end
# Windows binary file append for shell sessions. Appends given object content to a remote file.
#
# @param file_name [String] Remote file name to write
# @param data [String] Contents to put in the file
# @param chunk_size [int] max size for the data chunk to write at a time
# @return [void]
def _win_bin_append_file ( file_name , data , chunk_size = 5000 )
b64_data = Base64 . strict_encode64 ( data )
b64_filename = " #{ file_name } .b64 "
tmp_filename = " #{ file_name } .tmp "
begin
2022-12-14 17:00:16 +11:00
success = _win_ansi_write_file ( b64_filename , b64_data , chunk_size )
return false unless success
2022-12-15 09:05:16 +11:00
vprint_status ( " Uploaded Base64-encoded file. Decoding using certutil " )
2022-12-14 17:00:16 +11:00
success = _shell_command_with_success_code ( " certutil -decode #{ b64_filename } #{ tmp_filename } " )
return false unless success
2022-12-15 09:05:16 +11:00
vprint_status ( " Certutil succeeded. Appending using copy " )
2022-12-14 17:00:16 +11:00
success = _shell_command_with_success_code ( " copy /b #{ file_name } + #{ tmp_filename } #{ file_name } " )
return false unless success
2021-06-29 14:51:40 -05:00
rescue :: Exception = > e
2021-09-13 11:51:00 -05:00
print_error ( " Exception while running #{ __method__ } : #{ e } " )
2022-12-14 17:00:16 +11:00
return false
2021-06-29 14:51:40 -05:00
ensure
file_rm ( b64_filename )
file_rm ( tmp_filename )
end
2022-12-14 17:00:16 +11:00
true
2021-06-29 14:51:40 -05:00
end
2022-10-21 23:28:14 +11:00
#
# Append +data+ to the remote file +file_name+.
#
# You should never call this method directly. Instead, call {#append_file}
# which will call this method if it is appropriate for the given session.
#
# @return [void]
def _append_file_unix_shell ( file_name , data )
_write_file_unix_shell ( file_name , data , true )
end
2012-03-19 01:08:03 -06:00
#
# Write +data+ to the remote file +file_name+.
#
# Truncates if +append+ is false, appends otherwise.
#
2014-02-18 18:24:23 -06:00
# You should never call this method directly. Instead, call {#write_file}
# or {#append_file} which will call this if it is appropriate for the given
2012-03-19 01:08:03 -06:00
# session.
#
2014-02-18 18:24:23 -06:00
# @return [void]
2021-09-13 11:51:00 -05:00
def _write_file_unix_shell ( file_name , data , append = false )
redirect = ( append ? '>>' : '>' )
2013-08-30 16:28:33 -05:00
2012-03-19 01:08:03 -06:00
# Short-circuit an empty string. The : builtin is part of posix
# standard and should theoretically exist everywhere.
2021-09-13 11:51:00 -05:00
if data . empty?
2022-12-14 17:00:16 +11:00
return _shell_command_with_success_code ( " : #{ redirect } #{ file_name } " )
2012-03-19 01:08:03 -06:00
end
2013-08-30 16:28:33 -05:00
2012-03-19 01:08:03 -06:00
d = data . dup
2021-09-13 11:51:00 -05:00
d . force_encoding ( 'binary' ) if d . respond_to? :force_encoding
2013-08-30 16:28:33 -05:00
2012-03-19 01:08:03 -06:00
chunks = [ ]
command = nil
2012-05-15 17:00:02 -06:00
encoding = :hex
2021-09-13 11:51:00 -05:00
cmd_name = ''
2013-08-30 16:28:33 -05:00
2012-03-19 01:08:03 -06:00
line_max = _unix_max_line_length
# Leave plenty of room for the filename we're writing to and the
# command to echo it out
2012-07-08 20:46:37 -06:00
line_max -= file_name . length
line_max -= 64
2013-08-30 16:28:33 -05:00
2012-05-15 17:00:02 -06:00
# Ordered by descending likeliness to work
[
# POSIX standard requires %b which expands octal (but not hex)
2013-06-25 17:19:00 -05:00
# escapes in the argument. However, some versions (notably
# FreeBSD) truncate input on nulls, so "printf %b '\0\101'"
# produces a 0-length string. Some also allow octal escapes
# without a format string, and do not truncate, so start with
# that and try %b if it doesn't work. The standalone version seems
2023-09-24 17:42:00 -04:00
# to be more likely to work than the builtin version, so try it
2013-06-25 17:19:00 -05:00
# first.
#
# Both of these work for sure on Linux and FreeBSD
2021-09-13 11:51:00 -05:00
{ cmd : %q{ /usr/bin/printf 'CONTENTS' } , enc : :octal , name : 'printf' } ,
{ cmd : %q{ printf 'CONTENTS' } , enc : :octal , name : 'printf' } ,
2013-06-25 17:19:00 -05:00
# Works on Solaris
2021-09-13 11:51:00 -05:00
{ cmd : %q{ /usr/bin/printf %b 'CONTENTS' } , enc : :octal , name : 'printf' } ,
{ cmd : %q{ printf %b 'CONTENTS' } , enc : :octal , name : 'printf' } ,
2012-05-15 17:00:02 -06:00
# Perl supports both octal and hex escapes, but octal is usually
# shorter (e.g. 0 becomes \0 instead of \x00)
2021-09-13 11:51:00 -05:00
{ cmd : %q{ perl -e 'print("CONTENTS")' } , enc : :octal , name : 'perl' } ,
2012-05-15 17:00:02 -06:00
# POSIX awk doesn't have \xNN escapes, use gawk to ensure we're
# getting the GNU version.
2021-09-13 11:51:00 -05:00
{ cmd : %q^gawk 'BEGIN {ORS="";print "CONTENTS"}' </dev/null^ , enc : :hex , name : 'awk' } ,
2012-07-08 20:45:25 -06:00
# xxd's -p flag specifies a postscript-style hexdump of unadorned hex
# digits, e.g. ABCD would be 41424344
2021-09-13 11:51:00 -05:00
{ cmd : %q{ echo 'CONTENTS'|xxd -p -r } , enc : :bare_hex , name : 'xxd' } ,
2012-05-15 17:00:02 -06:00
# 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
# that support both. Most others treat all options as just more
# arguments to print. In particular, the standalone /bin/echo or
# /usr/bin/echo appear never to have -e so don't bother trying
# them.
2021-09-13 11:51:00 -05:00
{ cmd : %q{ echo -ne 'CONTENTS' } , enc : :hex } ,
] . each do | foo |
2012-05-15 17:00:02 -06:00
# Some versions of printf mangle %.
2019-12-25 05:15:33 +00:00
test_str = " \0 \xff \xfe #{ Rex :: Text . rand_text_alpha_upper ( 4 ) } \x7f %% \r \n "
2021-09-13 11:51:00 -05:00
# test_str = "\0\xff\xfe"
2012-07-08 20:45:25 -06:00
case foo [ :enc ]
when :hex
2021-09-13 11:51:00 -05:00
cmd = foo [ :cmd ] . sub ( 'CONTENTS' ) { Rex :: Text . to_hex ( test_str ) }
2012-07-08 20:45:25 -06:00
when :octal
2021-09-13 11:51:00 -05:00
cmd = foo [ :cmd ] . sub ( 'CONTENTS' ) { Rex :: Text . to_octal ( test_str ) }
2012-07-08 20:45:25 -06:00
when :bare_hex
2021-09-13 11:51:00 -05:00
cmd = foo [ :cmd ] . sub ( 'CONTENTS' ) { Rex :: Text . to_hex ( test_str , '' ) }
2012-05-15 17:00:02 -06:00
end
2021-09-13 11:51:00 -05:00
a = session . shell_command_token ( cmd . to_s )
2013-08-30 16:28:33 -05:00
2012-05-15 17:00:02 -06:00
if test_str == a
command = foo [ :cmd ]
encoding = foo [ :enc ]
2012-07-08 20:45:25 -06:00
cmd_name = foo [ :name ]
2012-05-15 17:00:02 -06:00
break
else
2012-07-08 20:45:25 -06:00
vprint_status ( " #{ cmd } Failed: #{ a . inspect } != #{ test_str . inspect } " )
2012-03-19 01:08:03 -06:00
end
2021-09-13 11:51:00 -05:00
end
2013-08-30 16:28:33 -05:00
2012-05-15 17:00:02 -06:00
if command . nil?
raise RuntimeError , " Can't find command on the victim for writing binary data " , caller
end
2013-08-30 16:28:33 -05:00
2012-05-15 17:00:02 -06:00
# each byte will balloon up to 4 when we encode
# (A becomes \x41 or \101)
2021-09-13 11:51:00 -05:00
max = line_max / 4
2013-08-30 16:28:33 -05:00
2012-05-15 17:00:02 -06:00
i = 0
while ( i < d . length )
2021-09-13 11:51:00 -05:00
slice = d . slice ( i ... ( i + max ) )
2012-07-08 20:45:25 -06:00
case encoding
when :hex
chunks << Rex :: Text . to_hex ( slice )
when :octal
chunks << Rex :: Text . to_octal ( slice )
when :bare_hex
2021-09-13 11:51:00 -05:00
chunks << Rex :: Text . to_hex ( slice , '' )
2012-03-19 01:08:03 -06:00
end
2012-05-15 17:00:02 -06:00
i += max
2012-03-19 01:08:03 -06:00
end
2013-08-30 16:28:33 -05:00
2012-07-08 20:45:25 -06:00
vprint_status ( " Writing #{ d . length } bytes in #{ chunks . length } chunks of #{ chunks . first . length } bytes ( #{ encoding } -encoded), using #{ cmd_name } " )
2013-08-30 16:28:33 -05:00
2012-03-19 01:08:03 -06:00
# The first command needs to use the provided redirection for either
# appending or truncating.
2021-09-13 11:51:00 -05:00
cmd = command . sub ( 'CONTENTS' ) { chunks . shift }
2022-12-14 17:00:16 +11:00
succeeded = _shell_command_with_success_code ( " #{ cmd } #{ redirect } \" #{ file_name } \" " )
2022-12-14 12:48:46 +11:00
return false unless succeeded
2013-08-30 16:28:33 -05:00
2012-03-19 01:08:03 -06:00
# After creating/truncating or appending with the first command, we
# need to append from here on out.
2021-09-13 11:51:00 -05:00
chunks . each do | chunk |
2012-05-15 17:00:02 -06:00
vprint_status ( " Next chunk is #{ chunk . length } bytes " )
2021-09-13 11:51:00 -05:00
cmd = command . sub ( 'CONTENTS' ) { chunk }
2013-08-30 16:28:33 -05:00
2022-12-14 17:00:16 +11:00
succeeded = _shell_command_with_success_code ( " #{ cmd } >> ' #{ file_name } ' " )
2022-12-14 12:48:46 +11:00
unless succeeded
2022-12-16 10:36:26 +11:00
print_warning ( " Write partially succeeded then failed. May need to manually clean up #{ file_name } " )
2022-12-14 12:48:46 +11:00
return false
end
2021-09-13 11:51:00 -05:00
end
2013-08-30 16:28:33 -05:00
2012-03-19 01:08:03 -06:00
true
end
2013-08-30 16:28:33 -05:00
2022-12-14 17:00:16 +11:00
def _shell_command_with_success_code ( cmd )
2022-12-16 10:36:26 +11:00
token = " _ #{ :: Rex :: Text . rand_text_alpha ( 32 ) } "
2022-12-14 12:48:46 +11:00
result = session . shell_command_token ( " #{ cmd } && echo #{ token } " )
2023-07-13 11:24:44 -04:00
return result & . include? ( token )
2022-12-14 12:48:46 +11:00
end
2012-09-04 22:21:47 -04:00
#
# Calculate the maximum line length for a unix shell.
#
2017-01-17 14:09:27 -06:00
# @return [Integer]
2012-03-19 01:08:03 -06:00
def _unix_max_line_length
# Based on autoconf's arg_max calculator, see
# http://www.in-ulm.de/~mascheck/various/argmax/autoconf_check.html
calc_line_max = 'i=0 max= new= str=abcd; \
while (test "X"`echo "X$str" 2>/dev/null` = "XX$str") >/dev/null 2>&1 && \
new=`expr "X$str" : ".*" 2>&1` && \
test "$i" != 17 && \
max=$new; do \
i=`expr $i + 1`; str=$str$str;\
done; echo $max'
line_max = session . shell_command_token ( calc_line_max ) . to_i
2013-08-30 16:28:33 -05:00
2012-05-15 17:00:02 -06:00
# Fall back to a conservative 4k which should work on even the most
# restrictive of embedded shells.
2012-03-19 01:08:03 -06:00
line_max = ( line_max == 0 ? 4096 : line_max )
2012-05-15 17:00:02 -06:00
vprint_status ( " Max line length is #{ line_max } " )
2013-08-30 16:28:33 -05:00
2012-03-19 01:08:03 -06:00
line_max
end
2021-05-24 21:12:33 +05:30
def stat ( filename )
if session . type == 'meterpreter'
return session . fs . file . stat ( filename )
else
raise NotImplementedError if session . platform == 'windows'
raise " `stat' command doesn't exist on target system " unless command_exists? ( 'stat' )
2021-09-13 11:51:00 -05:00
2021-05-24 21:12:33 +05:30
return FileStat . new ( filename , session )
end
end
class FileStat < Rex :: Post :: FileStat
2021-06-09 20:22:15 +01:00
2021-05-24 21:12:33 +05:30
attr_accessor :stathash
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
raise 'format argument of stat command not behaving as expected' unless data =~ / ( \ d+,){12} \ w+ /
2021-09-13 11:51:00 -05:00
data = data . split ( ',' )
2021-05-24 21:12:33 +05:30
@stathash = Hash . new
@stathash [ 'st_dev' ] = data [ 0 ] . to_i
@stathash [ 'st_ino' ] = data [ 1 ] . to_i
@stathash [ 'st_nlink' ] = data [ 2 ] . to_i
@stathash [ 'st_uid' ] = data [ 3 ] . to_i
@stathash [ 'st_gid' ] = data [ 4 ] . to_i
@stathash [ 'st_rdev' ] = data [ 5 ] . to_i
@stathash [ 'st_size' ] = data [ 6 ] . to_i
@stathash [ 'st_blksize' ] = data [ 7 ] . to_i
@stathash [ 'st_blocks' ] = data [ 8 ] . to_i
@stathash [ 'st_atime' ] = data [ 9 ] . to_i
@stathash [ 'st_mtime' ] = data [ 10 ] . to_i
@stathash [ 'st_ctime' ] = data [ 11 ] . to_i
2021-09-13 11:51:00 -05:00
@stathash [ 'st_mode' ] = data [ 12 ] . to_i ( 16 ) # stat command returns hex value of mode"
2021-05-24 21:12:33 +05:30
end
end
2011-01-11 17:53:24 +00:00
end