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 line
  • ActiveScriptEventConsumer: 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 IDDescription
19WmiEventFilter activity — filter created or deleted
20WmiEventConsumer activity — consumer created or deleted
21WmiEventConsumerToFilter 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, the CommandLineTemplate field will contain the payload
  • For ActiveScriptEventConsumer, the ScriptText field contains embedded code

Detection: WMI-Activity/Operational Log

The Microsoft-Windows-WMI-Activity/Operational log records subscription activity independently of Sysmon:

Event IDMeaning
5859Slow query or subscription registration
5860Temporary subscription registered
5861Permanent 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:

  1. Confirm — query the live host with the PowerShell enumeration commands above to verify the subscription still exists.
  2. Capture — export the filter, consumer, and binding objects before removal (they’re evidence).
  3. RemoveRemove-WmiObject on the binding first, then the consumer, then the filter. Removing only the binding leaves orphaned objects.
  4. Scope — check for the same subscription name across all endpoints (implicates a worm or mass deployment).
  5. Recover — review process creation logs for WmiPrvSE.exe spawning 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.