In this section
Windows Persistence Mechanisms
What you already know
Process analysis tells you what is running now. Persistence is how the attacker guarantees it runs again after reboot, and Windows offers more ways to do that than any other OS.
Scenario
The account is reset, the session revoked, the malicious binary deleted. Two days later the endpoint beacons again. The attacker left a WMI event subscription that fires on a schedule and rebuilds the foothold, and nothing in the Run keys or the startup folder ever showed it. Persistence you do not find is persistence that brings the attacker back.
Windows offers more persistence mechanisms than any other operating system. The brief requires the triage responder to "deconstruct WMI event subscriptions, anomalous scheduled tasks, and rogue service creation", but these are only 3 of the 10+ persistence vectors available to a Windows attacker. Registry Run keys, DLL sideloading, COM object hijacking, startup folder shortcuts, AppInit_DLLs, Image File Execution Options, and Accessibility Features backdoors all provide boot-persistent or login-persistent execution. The responder who checks only scheduled tasks and services misses half the landscape.
Figure TR3.5. Eight Windows persistence mechanisms. Red = most common in incidents. Amber = frequently overlooked. Blue = advanced. Autoruns scans ALL mechanisms in a single execution: the recommended first-pass tool for triage.
Mechanism 1: WMI Event Subscriptions: the fileless persistence
Six-step method. WMI persistence detection:
What to look for: WMI permanent event subscriptions consisting of three components: an EventFilter (the trigger condition), an EventConsumer (the action to execute), and a FilterToConsumerBinding (linking the two). A WMI subscription persists across reboots, executes under the SYSTEM context, and does NOT require a file on disk: the subscription is stored in the WMI repository.
Where to find it: The WMI repository at C:\Windows\System32\wbem\Repository\. Queryable via WMI/CIM PowerShell cmdlets.
How to extract it:
# Detect WMI event subscriptions
Get-CimInstance -Namespace root/subscription -ClassName __EventFilter |
Select-Object Name, Query, QueryLanguage | Format-List
Get-CimInstance -Namespace root/subscription -ClassName __EventConsumer |
Select-Object __CLASS, Name, CommandLineTemplate, ScriptText | Format-List
Get-CimInstance -Namespace root/subscription -ClassName __FilterToConsumerBinding |
Select-Object Filter, Consumer | Format-List
How to interpret: the three components:
EventFilter (the trigger):
Name: SystemHealthCheck
Query: SELECT * FROM __InstanceModificationEvent WITHIN 300 WHERE TargetInstance ISA 'Win32_PerfFormattedData_PerfOS_System'
This filter fires every 300 seconds (5 minutes), checking for system performance data changes (which always change). This is a disguised TIMER: the filter condition is always true, so the consumer executes every 5 minutes. Legitimate WMI filters target specific events (hardware changes, service failures). A filter with WITHIN 300 on a condition that is always true is a persistence timer.
EventConsumer (the action):
__CLASS: CommandLineEventConsumer
Name: SystemHealthMonitor
CommandLineTemplate: C:\Windows\Temp\update-service.exe
The CommandLineEventConsumer executes a command line when the filter triggers. C:\Windows\Temp\update-service.exe is the attacker's binary. Other consumer types: ActiveScriptEventConsumer (executes VBScript/JScript inline, completely fileless) and LogFileEventConsumer (writes to a log file, less commonly used for attacks).
FilterToConsumerBinding (the link):
Filter: __EventFilter.Name="SystemHealthCheck"
Consumer: CommandLineEventConsumer.Name="SystemHealthMonitor"
The binding connects the filter to the consumer. All three components must exist for the subscription to function. Removing any one component disables the subscription.
KQL corroboration:
// WMI event subscription creation in Defender
DeviceEvents
| where DeviceName == "DESKTOP-NGE042"
| where ActionType == "WmiEventSubscriptionCreated"
| project Timestamp, ActionType,
AdditionalFields = parse_json(AdditionalFields)
| extend FilterName = tostring(AdditionalFields.FilterName),
ConsumerName = tostring(AdditionalFields.ConsumerName),
ConsumerType = tostring(AdditionalFields.ConsumerType)
// Sysmon Event ID 19-21 (WMI subscription events)
DeviceEvents
| where ActionType has "Wmi"
| where Timestamp > ago(7d)
| project Timestamp, DeviceName, ActionType, AdditionalFields
Mechanism 2: Scheduled Tasks, field-by-field XML analysis
Six-step method, scheduled task triage:
# Export ALL scheduled tasks to CSV with full details
schtasks /query /fo csv /v > "$env:TEMP\IR_tasks_full.csv"
# Filter for non-Microsoft tasks (attacker tasks are not signed by Microsoft)
Get-ScheduledTask | Where-Object { $_.TaskPath -notlike "\Microsoft\*" } |
Select-Object TaskName, TaskPath, State,
@{N='Action';E={$_.Actions.Execute}},
@{N='Arguments';E={$_.Actions.Arguments}},
@{N='Author';E={$_.Author}},
@{N='Date';E={$_.Date}},
@{N='Principal';E={$_.Principal.UserId}} |
Format-Table -AutoSize
How to interpret scheduled task fields:
TaskPath not under \Microsoft\: Legitimate Windows tasks reside under \Microsoft\Windows\*. A task at \ (root) or under a custom path was created by an administrator or an attacker. Tasks at the root level with generic names ("SystemUpdate", "WindowsHealthCheck", "UpdateHelper") are classic attacker naming patterns.
Author field: Legitimate tasks show "Microsoft Corporation" or the installing application's name. An empty Author field or a username-style author ("DESKTOP-NGE042\admin") indicates manual creation, investigate.
Date field: The task creation timestamp. Cross-reference with the incident timeline. A task created within the incident window is a prime suspect.
Action.Execute: The binary the task runs. Flag: paths in %TEMP%, C:\Users\*\AppData\, C:\ProgramData\, or any path with a dot-prefixed directory.
At NE, the CHAIN-MESH attacker created scheduled task "UpdateOrchestrator" at \ three days before ransomware execution. The task ran C:\ProgramData\Microsoft\UpdateHelper\svchelper.exe: the ransomware binary, at 02:48 daily. The "UpdateOrchestrator" name was chosen to mimic the legitimate \Microsoft\Windows\UpdateOrchestrator\* tasks.
Mechanism 3: Services, rogue service detection
# Services with binaries outside standard paths
Get-CimInstance Win32_Service | Where-Object {
$_.PathName -and
$_.PathName -notmatch "C:\\Windows\\|C:\\Program Files" -and
$_.PathName -notmatch "svchost\.exe"
} | Select-Object Name, DisplayName, State, StartMode,
PathName, StartName | Format-Table -AutoSize
# Recently created services (Event ID 7045 in System log)
Get-WinEvent -FilterHashtable @{LogName='System'; ID=7045} -MaxEvents 20 |
Select-Object TimeCreated,
@{N='ServiceName';E={$_.Properties[0].Value}},
@{N='ImagePath';E={$_.Properties[1].Value}},
@{N='ServiceType';E={$_.Properties[2].Value}},
@{N='StartType';E={$_.Properties[3].Value}},
@{N='AccountName';E={$_.Properties[4].Value}} |
Format-Table -AutoSize
Event ID 7045 field interpretation:
ServiceName: SystemHealthMonitor: the service name. Generic names mimicking system services are suspicious.
ImagePath: C:\Windows\Temp\beacon.exe: the binary. ANY service binary in a user-writable directory is a definitive persistence indicator.
ServiceType: user mode service, vs kernel driver. User mode services are more common for C2 persistence. Kernel drivers indicate rootkit deployment.
StartType: auto start: the service starts at boot. Manual start requires the attacker to trigger it remotely.
AccountName: LocalSystem: the highest-privilege service account. The attacker's beacon runs with SYSTEM privileges.
Mechanisms 4-8: Registry, DLL sideloading, COM hijacking, startup folder, IFEO
Mechanism 4: Registry Run keys
# Check ALL autostart registry locations
$runKeys = @(
"HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Run",
"HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce",
"HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Run",
"HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce",
"HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\RunServices",
"HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\Explorer\Run",
"HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\Explorer\Run"
)
foreach ($key in $runKeys) {
$values = Get-ItemProperty -Path $key -ErrorAction SilentlyContinue
if ($values) {
Write-Output "=== $key ==="
$values | Format-List
}
}
Flag: values pointing to user-writable paths, encoded PowerShell commands, or executables the responder cannot identify by name.
Mechanism 5: DLL sideloading: the attacker places a malicious DLL alongside a legitimate application that loads it. Detection: check for recently modified DLLs in application directories: Get-ChildItem "C:\Program Files\\.dll" -Recurse | Where-Object LastWriteTime -gt (Get-Date).AddDays(-7).
Mechanism 6: COM object hijacking: the attacker registers a COM object that points to their binary. Detection: Get-ItemProperty "HKCU:\SOFTWARE\Classes\CLSID\\InProcServer32" -ErrorAction SilentlyContinue | Where-Object { $_.'(default)' -notlike "C:\Windows\" -and $_.'(default)' -notlike "C:\Program Files*" }.
Mechanism 7: Startup folder: the simplest persistence: a shortcut (.lnk) or executable in C:\Users\USERNAME\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup\. Detection: Get-ChildItem "$env:APPDATA\Microsoft\Windows\Start Menu\Programs\Startup\" -Recurse.
Mechanism 8: Image File Execution Options (IFEO): the attacker sets a "Debugger" registry value for a common application (notepad.exe, calc.exe). When the application launches, Windows executes the "debugger" (the attacker's binary) instead. Detection: Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\*" -Name Debugger -ErrorAction SilentlyContinue.
Anti-Pattern
Checking only the obvious persistence locations
Run keys, the startup folder, and scheduled tasks are the first places everyone looks, so they are not where a capable attacker hides. WMI event subscriptions, COM hijacks, and IFEO debuggers leave nothing in those locations and survive every reboot. A persistence check that stops at the obvious keys reports clean while the foothold stays live.
The Autoruns approach, scan everything in one pass
Sysinternals Autoruns examines ALL persistence mechanisms simultaneously:
# If Autoruns is pre-staged on the triage USB:
.\autorunsc64.exe -a * -c -h -nobanner -vt > "$env:TEMP\IR_autoruns.csv"
# Flags: -a * (all categories), -c (CSV), -h (include hashes), -vt (VirusTotal lookup)
The Autoruns CSV output includes: Entry Location (which mechanism), Image Path (the binary), Publisher (code signing), VirusTotal detection ratio, and a "Time" column for creation timestamp. Filter for unsigned entries (Publisher is empty) and entries with VirusTotal detections, these are the highest-priority persistence findings.
Investigation Finding. IF-2026-TR3-PERSIST-001
Artifact: Autoruns + WMI subscription analysis for DESKTOP-NGE042 Source: Autoruns CSV export + WMI CIM queries. Findings:3 persistence mechanisms identified:
- WMI subscription (T1546.003): EventFilter "SystemHealthCheck" (5-minute timer) → CommandLineEventConsumer executing
C:\Windows\Temp\update-service.exe. Fileless persistence: no scheduled task, no service, no registry key. The subscription was created at 14:33 (3 minutes after beacon deployment).
- Scheduled task (T1053.005):
\UpdateOrchestratorrunningC:\ProgramData\Microsoft\UpdateHelper\svchelper.exedaily at 02:48. Created 3 days before the triage.
- Registry Run key (T1547.001): HKCU Run key "WindowsUpdate" →
powershell.exe -ep bypass -w hidden -e [BASE64]. The encoded PowerShell downloads and executes a stager from the C2 URL.
- Proves: The attacker deployed 3 independent persistence mechanisms spanning WMI, scheduled tasks, and registry. Each uses a different execution path: WMI (SYSTEM context, fileless), scheduled task (SYSTEM context, file-based), registry Run (user context, PowerShell stager). Removing any single mechanism leaves the other 2 active.
- Does not prove: Whether additional persistence exists beyond these 8 checked mechanisms (BITS jobs, Group Policy, boot configuration modifications are not covered by this audit).
- Next step: Remove all 3: (1)
Get-CimInstance -Namespace root/subscription -ClassName __EventFilter | Where-Object Name -eq "SystemHealthCheck" | Remove-CimInstance, (2)Unregister-ScheduledTask -TaskName "UpdateOrchestrator" -Confirm:$false, (3)Remove-ItemProperty -Path "HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Run" -Name "WindowsUpdate". THEN kill the beacon process. Verify with Autoruns re-scan.
⚖ Decision Point
Suspicious Run key AND scheduled task on the same endpoint. Document both (1 minute) but defer deep analysis to investigation. The triage deliverable: 'Two persistence mechanisms identified. Details in evidence package.'
Download Autoruns from Sysinternals. Run autorunsc64.exe -a * -c -h -nobanner > autoruns.csv. Open in Excel. Sort by Publisher: any entry where Publisher is empty is unsigned. Can you explain each unsigned entry? On a standard corporate workstation, you should have 3-10 unsigned autostart entries (custom LOB applications, admin tools). Any entry you cannot explain warrants investigation.
The 8 mechanisms in this subsection show that scheduled tasks and services are only 2 of 8+ persistence vectors. WMI event subscriptions are COMPLETELY INVISIBLE to Task Scheduler and services.msc, they exist in the WMI repository, not in the task database or service control manager. Registry Run keys execute at user login, not as services or tasks. COM object hijacking triggers when specific applications launch. The Autoruns approach is the only practical way to check ALL mechanisms in a single pass. The triage responder who checks only tasks and services produces an incomplete persistence audit, and the missed WMI subscription or registry Run key re-establishes the attacker's access after containment.
Troubleshooting
"Get-CimInstance for WMI subscriptions returns nothing." On systems without WMI persistence, the root/subscription namespace may have no instances, this is NORMAL and means no WMI subscriptions exist (clean). If the command throws an error: the WMI repository may be corrupted. Fall back to direct repository examination: Get-ChildItem C:\Windows\System32\wbem\Repository\ -Recurse | Where-Object LastWriteTime -gt (Get-Date).AddDays(-7).
"Autoruns is not available on the target system." Download from the triage USB or Sysinternals Live: \\live.sysinternals.com\tools\autorunsc64.exe. If network access is restricted: use the manual PowerShell checks per mechanism above. Autoruns is a convenience tool: every check it performs can be replicated with individual PowerShell commands.