Skip to content

WMI Persistence Investigation Checklist

Step-by-step checklist for WMI persistence: live queries, the repository, event IDs 5860 and 5861, Sysmon 19-21, execution evidence and a safe clean-up.

Published on 4 min read

TL;DR. Collect the repository first, then look at the host live, then correlate. The objects tell you what runs and when; Microsoft-Windows-WMI-Activity/Operational (event ID 5861) and Sysmon (19–21) tell you when it was registered; Prefetch, Amcache and process telemetry tell you whether it ran. Clean up only after evidence is preserved.

1. Preserve the repository

Copy C:\Windows\System32\wbem\Repository from a shadow copy before touching WMI: how to collect the WMI repository. Removing a subscription, installing software or rebuilding the repository can overwrite deleted records you have not read yet.

2. Look at the host live

List what WMI currently runs, across the usual namespaces:

'root/subscription','root/cimv2','root/default' | ForEach-Object {
  $ns = $_
  '__EventFilter','__EventConsumer','__FilterToConsumerBinding' | ForEach-Object {
    Get-CimInstance -Namespace $ns -ClassName $_ -ErrorAction SilentlyContinue
  }
} | Format-List *

Sysinternals Autoruns has a WMI tab with the same live view. Both show only live objects and rely on a WMI service an attacker may control.

3. Parse the collected files

Drop the Repository folder on WMI Parser. Check, in order:

  1. Bindings that are not Windows defaults. The SCM Event Log and BVT subscriptions are expected — when their content matches (details).
  2. The consumer's action: command line, script text or file, and paths. Encoded PowerShell, binaries under C:\ProgramData, C:\Users\… or %TEMP%, and script consumers deserve a close read.
  3. The filter's trigger: uptime (fires after each boot), logon, timers, process start or device events.
  4. CreatorSID on each object: which account registered it.
  5. Recovered objects: deleted bindings, older versions, unbound consumers. They show what existed and what was cleaned up (how recovery works).
  6. Namespaces other than root\subscription.

4. Correlate with event logs

LogEventWhat it gives
Microsoft-Windows-WMI-Activity/Operational5861A permanent consumer bound to a filter: namespace, filter, consumer and their content
Microsoft-Windows-WMI-Activity/Operational5860A temporary subscription (lives only while its process runs)
Microsoft-Windows-WMI-Activity/Operational5857A WMI provider loaded, with its DLL path
Sysmon19, 20, 21Filter, consumer and binding created or deleted, with the user
Security4688Process creation (if enabled): children of WmiPrvSE.exe and scrcons.exe

The repository itself has no documented creation time for its objects; these events are how you date them. The two FILETIMEs WMI Parser shows for each instance header are leads to check against them.

5. Look for execution

A subscription proves configuration, not execution. For the consumer's payload, check Prefetch, Amcache, ShimCache, SRUM and EDR telemetry; for scripts, the process tree under scrcons.exe; for command lines, processes whose parent is WmiPrvSE.exe. Line their times up with reboots (for uptime triggers) or logons (for logon triggers).

6. Check the neighbours

  • MOF files and AutoRecover: a .mof compiled with #pragma autorecover is replayed when the repository is rebuilt. Review C:\Windows\System32\wbem\AutoRecover and the Autorecover MOFs value under HKLM\SOFTWARE\Microsoft\Wbem\CIMOM.
  • Custom classes: data or code stashed in the properties of an attacker-defined class, outside the subscription triad.
  • Other hosts: the same filter and consumer names across machines point to remote deployment.

7. Clean up safely

When evidence is preserved and the scope is known, remove the three objects in the namespace where they live — binding first:

$ns = 'root/subscription'
Get-CimInstance -Namespace $ns -ClassName __FilterToConsumerBinding | Where-Object { $_.Filter.Name -eq 'IntelPerfMonitor' } | Remove-CimInstance
Get-CimInstance -Namespace $ns -ClassName CommandLineEventConsumer -Filter "Name='IntelPerfMonitor'" | Remove-CimInstance
Get-CimInstance -Namespace $ns -ClassName __EventFilter -Filter "Name='IntelPerfMonitor'" | Remove-CimInstance

Replace the names with yours (these are from the fictional sample). Then delete the payload the consumer pointed to and re-run the live query to confirm.

FAQ

Which event ID shows a new WMI persistence?

Event ID 5861 in Microsoft-Windows-WMI-Activity/Operational is logged when a permanent event consumer is bound to a filter. Sysmon, when configured, adds events 19, 20 and 21 for filters, consumers and bindings.

How do I remove WMI persistence?

After collecting evidence, remove the binding, then the consumer and the filter with Get-CimInstance … | Remove-CimInstance in the namespace where they live, and delete the payload the consumer pointed to.

Is the live query enough?

No. It shows only live objects of the namespace you query, never deleted ones, and it relies on the WMI service of a possibly compromised host. Parse the collected repository as well.

Related articles

The two WMI subscriptions Windows ships — SCM Event Log and BVTFilter/BVTConsumer — what they contain, why they are harmless, and how attackers can abuse the names.
Why deleted WMI filters, consumers and bindings survive in OBJECTS.DATA, how index walking and carving differ, and how python-cim and PyWMIPersistenceFinder compare.
How WMI event subscription persistence works: __EventFilter, event consumers and __FilterToConsumerBinding, where they are stored and how to find them.