← All posts

WMI Event Subscriptions: The Fileless Persistence That Survives a Reboot and Leaves No File

WMI event subscriptions let attackers run code on triggers with no file on disk and no Run key. Here is how the __EventFilter/__EventConsumer/__FilterToConsumerBinding trio works, and how to hunt it on your own machines.

Most persistence techniques leave a mark you can point at: a Run key, a Startup shortcut, a service, a scheduled task. WMI event subscription persistence leaves none of those. There is no file dropped in a folder, no autostart value in the registry that a casual sweep would flag, and nothing in the Task Scheduler UI. The payload lives inside the Windows Management Instrumentation (WMI) repository — a binary database at C:\Windows\System32\wbem\Repository — and it fires when a condition you specify becomes true. That combination of fileless and trigger-driven is exactly why it has been a favorite of everyone from commodity crimeware to APTs, and why it is one of the persistence mechanisms defenders most often miss.

MITRE ATT&CK tracks this as T1546.003 (Event Triggered Execution: Windows Management Instrumentation Event Subscription). This post explains the three objects that make it work, shows what a malicious subscription actually looks like, and gives you commands to hunt every subscription on a machine right now.

The three-object trio that makes it run

A permanent WMI event subscription is always three objects wired together. Understanding the trio is the whole game, because each one is a place to hunt:

Why is this so effective? Because __EventConsumer instances registered in the root\subscription (or root\default) namespace run as SYSTEM, they survive reboots without any file or service, and the actual payload — especially with ActiveScriptEventConsumer — can be an inline script that never touches the filesystem. A defender grepping for dropped files or reading autoruns will see nothing.

What a malicious subscription looks like

An attacker with admin can create the whole chain in a few lines of PowerShell. Read this the way an incident responder would:

$filter = Set-WmiInstance -Namespace root\subscription -Class __EventFilter -Arguments @{
  Name = "Updater"; EventNameSpace = "root\cimv2"; QueryLanguage = "WQL";
  Query = "SELECT * FROM __InstanceModificationEvent WITHIN 60 " +
          "WHERE TargetInstance ISA 'Win32_PerfFormattedData_PerfOS_System' " +
          "AND TargetInstance.SystemUpTime >= 200 AND TargetInstance.SystemUpTime < 320"
}

$consumer = Set-WmiInstance -Namespace root\subscription -Class CommandLineEventConsumer -Arguments @{
  Name = "Updater"; ExecutablePath = "powershell.exe";
  CommandLineTemplate = "powershell -nop -w hidden -enc SQBFAFgAI..."
}

Set-WmiInstance -Namespace root\subscription -Class __FilterToConsumerBinding -Arguments @{
  Filter = $filter; Consumer = $consumer
}

Every choice here is deliberate cover. The names are the boring word Updater. The filter fires only a few minutes after boot, so behavior looks like a normal startup task rather than something hammering constantly. The consumer launches hidden, profile-less, base64-encoded PowerShell — the exact fingerprint of a payload that does not want to be read. And it all lives in the WMI repository, so there is no .ps1, no .exe, and no Run key to find. An ActiveScriptEventConsumer variant is even quieter: the entire script body is a property on the WMI object, executed by scrcons.exe with never a file written.

The tell in process telemetry is WmiPrvSE.exe or scrcons.exe spawning something unexpected — a shell, a LOLBin, a network client. A script-consumer host process launching PowerShell minutes after boot, with no parent that explains it, is a strong lead.

What a WMI persistence audit inspects

A proper check does not just dump every subscription — a clean machine has a handful of legitimate ones (Microsoft, Dell/HP/Lenovo management agents, SCOM). It scores each subscription against the signals a responder would weigh by hand:

The reason to automate this is the same reason we automate scheduled-task and autostart hunting: consistency. Almost nobody opens wbemtest and reads their subscriptions by hand, and even fewer do it weekly. A scanner reads all of them every run and surfaces only the ones that match a risk pattern.

Hunt every subscription yourself, right now

You need no third-party tooling to start. These three commands enumerate the entire trio on the local machine:

# All permanent event subscriptions, in the namespace they live in
Get-WmiObject -Namespace root\subscription -Class __EventFilter |
  Select-Object Name, Query
Get-WmiObject -Namespace root\subscription -Class __EventConsumer |
  Select-Object __CLASS, Name, CommandLineTemplate, ScriptText
Get-WmiObject -Namespace root\subscription -Class __FilterToConsumerBinding |
  Select-Object Filter, Consumer

The consumer query is the one that matters most: it prints the actual CommandLineTemplate (for command-line consumers) and ScriptText (for script consumers), so you see the payload in the clear. Anything with hidden, encoded PowerShell, or inline VBScript that you cannot attribute to installed software is a lead. Also check root\default, another namespace attackers use:

Get-WmiObject -Namespace root\default -Class __EventConsumer |
  Select-Object __CLASS, Name, ScriptText, CommandLineTemplate

If you find a subscription that is clearly malicious, the chain is removed by deleting all three objects (filter, consumer, binding) with Remove-WmiObject. On a live compromise, capture the objects first — the query, the script body, the command line are evidence — then remove them.

Hardening: make it louder and harder

Where this fits in WinSentinel

Persistence hunting — across WMI subscriptions, autostart locations, services, and scheduled tasks — is part of WinSentinel's audit surface, and like every module it is completely free on a single machine: the CLI, the audit, one-click removal of what is fixable, scheduled re-scans, and PDF reports, all local, with no node cap and no time limit. If a hidden ActiveScriptEventConsumer is wired to a boot-time filter on your box, winsentinel --audit surfaces it next to your other findings, mapped to severity and to the relevant CIS / ATT&CK context — instead of you remembering to open wbemtest.

What is paid is the fleet story. Running persistence audits across dozens or hundreds of machines, WinSentinel Pro rolls those results into one place: which nodes carry unexplained subscriptions, drift alerts when a new one appears anywhere in the estate, remote "scan now" dispatch, and compliance rollups across the whole fleet. The single-machine depth is identical either way — Pro does not unlock extra checks; it solves the org and over-time problem of seeing every machine at once.

If you have never read the WMI subscriptions on your own laptop, that is where to start. Run the three Get-WmiObject queries above, or install the free tool, and look at what is set to fire the next time you boot. It is usually stock vendor telemetry. The point of an audit is to be sure it is only that.

Browse all 33 audit modules →  ·  Read: Hunting persistence in autostart locations →