WMI event subscriptions sit in a blind spot for many detection stacks. The technique doesn’t write a registry run key. It doesn’t create a scheduled task. It doesn’t drop a service. Instead, it buries persistence inside the WMI repository — a structured database that most EDR agents query but fewer analysts know how to interpret. Threat actors including APT29, FIN7, and numerous ransomware affiliates have used it to maintain long-term access while staying invisible to the usual persistence hunts.
This guide covers what WMI event subscriptions are, how attackers build them, and the detection logic that catches them.
The Three-Component Model
WMI persistence requires three objects working together:
__EventFilter — defines the trigger condition. This is a WQL (WMI Query Language) query that fires when something happens: a process starts, a time interval elapses, a file is created, a service changes state.
__EventConsumer — defines the action to take when the filter fires. Two consumer types matter for attackers:
CommandLineEventConsumer: executes an arbitrary command lineActiveScriptEventConsumer: runs embedded VBScript or JScript
__FilterToConsumerBinding — wires the filter to the consumer. Without this binding, the filter and consumer sit in the repository but do nothing.
All three objects persist in the WMI repository at %windir%\System32\wbem\Repository. They survive reboots and are not visible to autoruns, msconfig, or scheduled task enumeration. The WMI service (winmgmt) evaluates filters continuously and fires consumers automatically.
How Attackers Build Subscriptions
A typical implant sets up a __TimerEvent subscription that runs a payload every 60 seconds:
# Filter: fires every 60 seconds
$filter = Set-WmiInstance -Namespace root\subscription -Class __EventFilter -Arguments @{
Name = "SysHealthMonitor"
EventNamespace = "root\cimv2"
QueryLanguage = "WQL"
Query = "SELECT * FROM __TimerEvent WHERE TimerID='SysHealth'"
}
# Consumer: runs a command
$consumer = Set-WmiInstance -Namespace root\subscription -Class CommandLineEventConsumer -Arguments @{
Name = "SysHealthConsumer"
CommandLineTemplate = "powershell.exe -enc <base64payload>"
}
# Binding: connects filter to consumer
Set-WmiInstance -Namespace root\subscription -Class __FilterToConsumerBinding -Arguments @{
Filter = $filter
Consumer = $consumer
}
Attackers frequently use innocuous-looking names (WindowsUpdate, SystemHealthMonitor, DefragScheduler) to blend in with the handful of legitimate subscriptions that exist on Windows systems. The payload is often base64-encoded PowerShell, a path to a dropped binary, or a VBScript that reaches out to a C2.
Detection: Sysmon
Sysmon 6.20+ logs WMI activity across three event IDs:
| Event ID | Description |
|---|---|
| 19 | WmiEventFilter activity — filter created or deleted |
| 20 | WmiEventConsumer activity — consumer created or deleted |
| 21 | WmiEventConsumerToFilter activity — binding created or deleted |
All three should be enabled in your Sysmon configuration. A single persistence implant generates all three events in sequence, usually within the same second. Correlating on User, Operation (Created), and timestamp gives you a reliable signal.
Key fields to inspect in Event ID 20:
Destination: contains the consumer type and its command template- For
CommandLineEventConsumer, theCommandLineTemplatefield will contain the payload - For
ActiveScriptEventConsumer, theScriptTextfield contains embedded code
Detection: WMI-Activity/Operational Log
The Microsoft-Windows-WMI-Activity/Operational log records subscription activity independently of Sysmon:
| Event ID | Meaning |
|---|---|
| 5859 | Slow query or subscription registration |
| 5860 | Temporary subscription registered |
| 5861 | Permanent subscription registered |
Event 5861 is the one to alert on. The PossibleCause field in 5861 contains the consumer command. This log is disabled by default on older Windows versions — enable it via Group Policy (Computer Configuration > Administrative Templates > Windows Components > Event Forwarding).
Detection: PowerShell Enumeration
To enumerate all permanent WMI subscriptions on a live host:
# List all event filters
Get-WMIObject -Namespace root\subscription -Class __EventFilter |
Select-Object Name, Query, EventNamespace
# List all consumers
Get-WMIObject -Namespace root\subscription -Class __EventConsumer |
Select-Object Name, __CLASS, CommandLineTemplate, ScriptText
# List all bindings
Get-WMIObject -Namespace root\subscription -Class __FilterToConsumerBinding |
Select-Object Filter, Consumer
Legitimate Windows subscriptions are limited. On a clean endpoint, you should see only SCM Event Log Filter (used by the Service Control Manager) and possibly a few antivirus-related subscriptions. Anything else warrants investigation.
Sigma Rule
title: WMI Permanent Event Subscription Created
id: 1b2b3c4d-0000-0000-0000-000000000001
status: stable
description: Detects creation of WMI permanent event subscriptions via Sysmon events
references:
- https://attack.mitre.org/techniques/T1546/003/
logsource:
product: windows
service: sysmon
detection:
filter_create:
EventID: 19
Operation: 'Created'
consumer_create:
EventID: 20
Operation: 'Created'
binding_create:
EventID: 21
Operation: 'Created'
filter_exclude_legit:
Name|contains:
- 'SCM Event Log Filter'
- 'BVTFilter'
condition: (filter_create or consumer_create or binding_create) and not filter_exclude_legit
falsepositives:
- Security software, SCOM, WMI-based monitoring tools
level: medium
tags:
- attack.persistence
- attack.t1546.003
Tune the false positive exclusions for your environment before promoting this to a high-confidence alert.
Threat Hunting Queries
KQL (Microsoft Sentinel / Defender for Endpoint)
// Hunt for CommandLineEventConsumer with encoded or suspicious payloads
SysmonEvent
| where EventID == 20
| where EventData has_any ("CommandLineEventConsumer", "ActiveScriptEventConsumer")
| extend ConsumerData = parse_xml(EventData)
| where ConsumerData has_any ("-enc", "-encoded", "powershell", "cmd.exe", "wscript", "cscript", "mshta")
| project TimeGenerated, Computer, User, EventData
| order by TimeGenerated desc
Splunk
index=sysmon EventCode IN (19, 20, 21) Operation=Created
| eval consumer_cmd = coalesce(CommandLineTemplate, ScriptText)
| where isnotnull(consumer_cmd)
| search consumer_cmd IN ("*powershell*", "*cmd.exe*", "*mshta*", "*wscript*", "*-enc*")
| table _time, host, User, Name, consumer_cmd
Response Playbook
When you get a hit:
- Confirm — query the live host with the PowerShell enumeration commands above to verify the subscription still exists.
- Capture — export the filter, consumer, and binding objects before removal (they’re evidence).
- Remove —
Remove-WmiObjecton the binding first, then the consumer, then the filter. Removing only the binding leaves orphaned objects. - Scope — check for the same subscription name across all endpoints (implicates a worm or mass deployment).
- Recover — review process creation logs for
WmiPrvSE.exespawning child processes (the consumer host process) in the preceding days to estimate dwell time.
WMI event subscriptions are not complex to build, and the detection telemetry exists on every modern Windows deployment. The gap is almost always configuration — Sysmon not collecting Event IDs 19-21, or the WMI-Activity log not forwarded to SIEM. Fix the collection, and the technique loses most of its stealth advantage.