Hunting Windows Persistence: Registry, Tasks, WMI and More
Find attacker persistence on Windows: Run keys, services, scheduled tasks, WMI, Winlogon, IFEO, COM hijacking and BITS, mapped to MITRE ATT&CK, with tools.
Once attackers get in, they make sure they can come back after a reboot, a password change or a patched vulnerability. This guide walks through the autostart locations that Windows intrusions use most, maps each one to its MITRE ATT&CK technique, and shows where to look on a live host or a disk image. You will finish with a repeatable triage workflow and a way to separate real persistence from the hundreds of benign autostarts on a normal machine.
Persistence map
| Mechanism | ATT&CK | Primary location | Key logs |
|---|---|---|---|
| Run / RunOnce keys | T1547.001 | SOFTWARE and NTUSER.DAT: ...\CurrentVersion\Run, RunOnce | Sysmon 13 |
| Startup folders | T1547.001 | ...\Start Menu\Programs\Startup (per user and all users) | Sysmon 11 |
| Windows services | T1543.003 | SYSTEM\ControlSet00x\Services\<name> | 7045, 4697 |
| Scheduled tasks | T1053.005 | C:\Windows\System32\Tasks, TaskCache in SOFTWARE | 4698, TaskScheduler 106/140/200 |
| WMI event subscription | T1546.003 | C:\Windows\System32\wbem\Repository\OBJECTS.DATA | WMI-Activity 5861, Sysmon 19-21 |
| Winlogon Userinit / Shell | T1547.004 | SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon | Sysmon 13 |
| IFEO Debugger, SilentProcessExit | T1546.012 | ...\Image File Execution Options\<exe>, ...\SilentProcessExit\<exe> | Sysmon 13 |
| AppInit_DLLs | T1546.010 | SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows | Sysmon 13 |
| COM hijacking | T1546.015 | UsrClass.dat: Software\Classes\CLSID\{GUID} | Sysmon 13 |
| BITS jobs | T1197 | C:\ProgramData\Microsoft\Network\Downloader\ | Bits-Client 3/59/60 |
| Accessibility features | T1546.008 | System32\sethc.exe, utilman.exe or their IFEO keys | Prefetch, file hashes, Sysmon 1 |
| Local account creation | T1136.001 | SAM hive | 4720, 4722, 4732 |
| DLL search order hijacking | T1574.001 | Application folders, writable PATH entries | Sysmon 7 |
ATT&CK IDs are current at the time of writing; MITRE occasionally renames or merges sub-techniques (recent versions, for example, broadened T1574.001 to cover DLL side-loading as well). Check attack.mitre.org when you put IDs in a report.
Registry autostarts
Run and RunOnce keys
The classic. Values under these keys launch at logon:
HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Runand\RunOnce(all users)HKLM\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Runand\RunOnce(32-bit view on 64-bit Windows)HKCU\Software\Microsoft\Windows\CurrentVersion\Runand\RunOnce, stored in each user's NTUSER.DAT...\Policies\Explorer\Run, normally only written by Group Policy
RunOnce values are deleted before they execute (unless prefixed with !), so a one-shot payload may leave no trace in the hive afterwards. Values themselves carry no timestamp: the only time you get is the key's last write time, which reflects the latest change to any value in that key. On Windows 8 and later, Explorer\StartupApproved\Run records whether an entry is enabled or disabled in Task Manager. Full details are on the Run keys and Startup folders page.
Winlogon Userinit and Shell
HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon holds Userinit (default C:\Windows\system32\userinit.exe, with a trailing comma) and Shell (default explorer.exe). Anything appended to these values runs at every interactive logon. A per-user Shell value in NTUSER.DAT under the same path is also honoured. Compare exact strings: an extra comma-separated path in Userinit is easy to miss in a GUI.
IFEO Debugger and SilentProcessExit
Image File Execution Options (HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\<name.exe>) exists for debugging. A Debugger value makes Windows launch the debugger instead of the target, with the target path as an argument, so Debugger = C:\ProgramData\x.exe on notepad.exe runs the payload whenever Notepad starts.
The quieter variant is SilentProcessExit: set GlobalFlag to 0x200 on the IFEO key, then create ...\CurrentVersion\SilentProcessExit\<name.exe> with ReportingMode = 1 and MonitorProcess pointing to the payload. The payload runs when the monitored process exits. Both keys are covered on the IFEO and Winlogon page.
AppInit_DLLs
AppInit_DLLs under HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows (and its WOW6432Node twin), with LoadAppInit_DLLs = 1, loads the listed DLLs into every process that loads user32.dll. On Windows 8 and later the mechanism is disabled when Secure Boot is on, so its presence on a modern Secure Boot machine is more a sign of intent than of working persistence. Any non-empty value is still worth an explanation.
COM hijacking
When a program instantiates a COM object, Windows looks up the CLSID in HKCU\Software\Classes\CLSID before HKLM\SOFTWARE\Classes\CLSID. An attacker without admin rights can register the same CLSID under the user hive with an InprocServer32 (DLL) or LocalServer32 (EXE) pointing to their payload, and it loads every time Explorer or a scheduled task instantiates that object. On disk, HKCU\Software\Classes lives in UsrClass.dat, not NTUSER.DAT, so collect both. Hunt for CLSIDs present in a user's UsrClass.dat that also exist in HKLM with a different server path, and for servers under user-writable folders like AppData.
Services
Services live under HKLM\SYSTEM\CurrentControlSet\Services\<name>; on an offline hive, resolve the active control set through Select\Current. The values that matter:
ImagePath: the executable or driver, including arguments. Look forcmd.exe /c,powershell, paths inTemp,ProgramDataor user profiles, and random names.Parameters\ServiceDll(sometimes directly under the service key): the DLL loaded by a sharedsvchost.exeservice. Replacing it on a legitimate service is stealthier than creating a new one.Start: 0 boot, 1 system, 2 automatic, 3 manual, 4 disabled.ObjectName: the account it runs as, usuallyLocalSystem.FailureActionsandFailureCommand: a recovery command can itself be a persistence hook.
Installation leaves 7045 in the System log (service name, image path, start type, account) and, when Audit Security System Extension is on, 4697 in Security. PsExec-style lateral movement produces the same events, which is why the lateral movement guide leans on them too. See the services page.
# Live: services with their binary and account
Get-CimInstance Win32_Service |
Select-Object Name, State, StartMode, StartName, PathName |
Sort-Object PathName
Scheduled tasks
A task on Vista and later leaves three traces:
- An XML definition in
C:\Windows\System32\Tasks\<folder>\<name>(no extension), withActions,Triggers,PrincipalsandRegistrationInfo. - Registry keys in the SOFTWARE hive under
Microsoft\Windows NT\CurrentVersion\Schedule\TaskCache:Tree\<path>(valuesId,Index,SD) andTasks\{GUID}(Path,Actions,Triggers,DynamicInfo). - Events: Security 4698 (created, with the task XML, if Audit Other Object Access Events is on), 4702 (updated), 4699 (deleted), and in
Microsoft-Windows-TaskScheduler/Operational106 (registered), 140 (updated), 141 (deleted), 200 (action started) and 201 (action completed).
The HAFNIUM "Tarrask" technique hides a task by deleting its SD value under TaskCache\Tree. The task keeps running but disappears from schtasks /query and the Task Scheduler console. Any Tree subkey without an SD value is a finding; also compare the Tree, Tasks and XML file lists against each other, since a mismatch indicates tampering. The Author and Date fields in the XML are written by the creator and can be forged. Details on the scheduled tasks page.
# Live: every task with its action, including hidden-flag tasks
Get-ScheduledTask | ForEach-Object {
[pscustomobject]@{
Path = $_.TaskPath + $_.TaskName
State = $_.State
Hidden = $_.Settings.Hidden
Execute = ($_.Actions | ForEach-Object { "$($_.Execute) $($_.Arguments)" }) -join ' | '
}
}
# Tree entries missing their SD value (Tarrask-style hiding)
Get-ChildItem 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Schedule\TaskCache\Tree' -Recurse |
Where-Object { $_.GetValue('Id') -and ($_.GetValueNames() -notcontains 'SD') } |
Select-Object Name
Get-ScheduledTask will not show a task whose SD was removed, which is exactly why the registry check matters.
WMI event subscriptions
A permanent WMI subscription combines three objects, usually in root\subscription:
- an
__EventFilterholding a WQL query (for example a trigger a few minutes after boot, or on a process start), - an
__EventConsumersubclass that acts, typicallyCommandLineEventConsumer(runs a command) orActiveScriptEventConsumer(runs VBScript or JScript), - a
__FilterToConsumerBindingthat links the two.
The consumer runs inside WmiPrvSE.exe as SYSTEM, with no file in any autostart folder. Everything is stored in the CIM repository, C:\Windows\System32\wbem\Repository\OBJECTS.DATA together with INDEX.BTR and the MAPPING*.MAP files. On Windows 10 and later, 5861 in Microsoft-Windows-WMI-Activity/Operational logs each new permanent binding with the filter query and consumer details; Sysmon 19, 20 and 21 capture filter, consumer and binding activity when configured. Deleted subscriptions can survive in unallocated pages of OBJECTS.DATA. See the WMI repository page.
# Live enumeration
Get-CimInstance -Namespace root/subscription -ClassName __EventFilter |
Select-Object Name, Query
Get-CimInstance -Namespace root/subscription -ClassName __EventConsumer |
Select-Object __CLASS, Name, CommandLineTemplate, ScriptText
Get-CimInstance -Namespace root/subscription -ClassName __FilterToConsumerBinding |
Select-Object Filter, Consumer
# Offline: parse the repository copied from an image
python PyWMIPersistenceFinder.py ./Repository/OBJECTS.DATA
Other locations worth checking
Startup folders
Shortcuts or scripts dropped in C:\Users\<user>\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup or C:\ProgramData\Microsoft\Windows\Start Menu\Programs\StartUp run at logon. Files there have normal NTFS timestamps, which gives you a creation time that Run key values lack. Also check User Shell Folders for a redirected Startup path.
BITS jobs
Background Intelligent Transfer Service jobs can download a payload and, through bitsadmin /SetNotifyCmdLine, run a command when the job completes or errors. Jobs can be kept alive for long periods and retried, which makes them a quiet persistence and download mechanism. Evidence sits in the BITS database (qmgr.db on Windows 10 and later, qmgr0.dat/qmgr1.dat before) and in Microsoft-Windows-Bits-Client/Operational (3 created, 59 transfer started with URL, 60 stopped). See the BITS jobs page.
Accessibility feature backdoors
Replacing C:\Windows\System32\sethc.exe (Sticky Keys, Shift five times) or utilman.exe (Win+U) with cmd.exe, or pointing an IFEO Debugger at them, gives a SYSTEM shell from the logon screen, including over RDP. Check the hashes and version info of sethc.exe, utilman.exe, osk.exe, Magnify.exe, Narrator.exe, DisplaySwitch.exe and AtBroker.exe against a clean build of the same version, look for IFEO keys on those names, and review Prefetch for them executing at unusual times.
New local accounts
A new local administrator is persistence that survives any cleanup of malware. Look for 4720 (created), 4722 (enabled) and 4732 (added to a local group such as Administrators) in Security, and parse the SAM hive for account creation times, last logon and group membership (see the SAM user accounts page). Accounts can be hidden from the logon screen with a 0 DWORD under HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon\SpecialAccounts\UserList\<name>, and names ending in $ are skipped by some listing commands.
DLL search order hijacking
When an application loads a DLL by name without a full path, Windows searches a sequence of directories, starting with the application's own folder. Dropping a malicious DLL with the expected name in that folder, or in a writable directory earlier in the search order, gets it loaded by a legitimate and often signed process. KnownDLLs (HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\KnownDLLs) are protected from this. Hunt for DLLs that normally live in System32 appearing next to executables in ProgramData, AppData or Temp, for unsigned DLLs beside signed binaries, and for Sysmon 7 image loads from unusual paths. Amcache can help date when the DLL first appeared.
Tools
Autoruns offline
Sysinternals Autoruns covers most registry and file autostarts and flags unsigned entries. autorunsc can analyze an offline Windows directory with -z, pointing at the Windows folder and a user profile of a mounted image.
# Live, all entry types, CSV with hashes and signature checks
autorunsc.exe -accepteula -nobanner -a * -c -h -s > autoruns_live.csv
# Offline image mounted as F:
autorunsc.exe -accepteula -nobanner -a * -c -h -s -z F:\Windows F:\Users\alice > autoruns_offline.csv
Offline mode depends on the hives it can read, so run it once per user profile of interest.
KAPE and RECmd
KAPE collects the hives, task XML, WMI repository and logs in one pass, then its !EZParser module runs Eric Zimmerman's tools. RECmd with a batch file (such as Kroll_Batch.reb shipped in its BatchExamples folder) extracts known persistence keys from every hive into one CSV, including hive transaction logs when present.
kape.exe --tsource C: --tdest E:\tout --target KapeTriage --mdest E:\mout --module !EZParser
RECmd.exe -d E:\tout\C --bn .\BatchExamples\Kroll_Batch.reb --csv E:\out\registry
Velociraptor
For fleets, Velociraptor runs the same checks everywhere at once. Useful artifacts include Windows.Sysinternals.Autoruns, Windows.Persistence.PermanentWMIEvents, Windows.System.TaskScheduler, Windows.System.Services and Windows.Sys.StartupItems. Stacking results across hosts is often faster than examining one machine in depth. The acquisition and triage guide covers deployment.
Common mistakes
- Only checking HKLM. HKCU Run keys, per-user COM objects in UsrClass.dat and user Startup folders need admin-free access and are favourites for that reason. Parse every user's hives, including service accounts.
- Forgetting WOW6432Node. 32-bit Run keys, IFEO and AppInit entries live in a separate branch on 64-bit systems.
- Trusting signatures. Attackers point autostarts at signed LOLBins (
rundll32,mshta,regsvr32) with malicious arguments. Read the command line, not just the binary. - Over-reading key timestamps. A last write time dates the last change to the key, not to the value you care about.
- Missing tasks hidden from the API. Enumerate
TaskCacheand theTasksfolder directly, not justschtasks. - Stopping at the first finding. Attackers usually set more than one mechanism; keep going through the whole list.
Triage workflow and baseline comparison
- Collect hives (SYSTEM, SOFTWARE, SAM, every NTUSER.DAT and UsrClass.dat, with transaction logs),
System32\Tasks, the WMIRepositoryfolder, BITS database, Startup folders and all event logs. The registry forensics guide explains the hive layout. - Enumerate with Autoruns (live or
-z), RECmd batch output, task XML plus TaskCache, and a WMI parser. - Check the logs for 7045, 4697, 4698, TaskScheduler 106/200, WMI-Activity 5861, Bits-Client 3/59, 4720/4732, following the event log forensics guide.
- Compare against a baseline: Autoruns output from a clean machine built from the same image, or stacked results from the fleet. Entries that appear on one or two hosts out of hundreds float to the top.
- Filter by time: keep entries whose key last write time, file creation time or install event falls inside the intrusion window.
- Validate each candidate: hash and signature of the target, file path, Prefetch or Amcache for execution, and the account that created it.
- Document each mechanism with its ATT&CK ID, location, creation time and evidence of execution, then remove all of them together during remediation so the attacker cannot use one to restore the others.
Area 07
Persistence
The artifact cheat sheets behind this guide: location by Windows version, what each one proves and the tools to parse it.