Windows Persistence: 10 Documented Techniques and Their Countermeasures
Defensive catalog of 10 Windows persistence mechanisms with ready-to-run KQL hunting queries and hardening measures any SOC can deploy this week.
Persistence is not the glamorous part of an engagement, but it is what separates an adversary who loses access on the first reboot from the one who comes back three weeks later when the team has forgotten about the alert. At Basilisk OffSec we run exercises against customers where more than 70% of detections that turned into real cases started with trivial mechanisms: Run keys, scheduled tasks with cute names, services with unquoted paths. The goal of this catalog is not to teach implant hiding. It is to document ten real techniques we see every week and ship KQL queries your Defender XDR or Sentinel can run tomorrow morning.
The first family worth covering is the autorun registry keys. HKCU\Software\Microsoft\Windows\CurrentVersion\Run remains the most exploited T1547.001 in commodity campaigns, not because attackers are lazy but because it works in any profile without elevation. A simple query like DeviceRegistryEvents | where RegistryKey has "CurrentVersion\\Run" and InitiatingProcessFileName !in ("explorer.exe","msiexec.exe") catches 80% of cases once you baseline by hostname. To understand the offensive context of these paths, revisit Simulated Initial Access: Macros, LNK and ISO in an Isolated Windows 11 Lab because most macros we see in the lab end up writing to exactly these keys.
Scheduled Tasks (T1053.005) are the second most abused technique and the most underreported. The challenge is not detecting task creation, it is filtering the noise: a corporate Windows 11 creates about 40 legitimate tasks during update cycles on a quiet day. The query that has worked best for us is DeviceProcessEvents | where FileName == "schtasks.exe" and ProcessCommandLine has_any ("/create","/change") | where InitiatingProcessParentFileName !in (~"msiexec.exe",~"trustedinstaller.exe") combined with Get-ScheduledTask enrichment filtering empty Author or unsigned author. In parallel, monitoring Security event 4698 and walking TaskCache\Tree in the registry catches hidden tasks invisible to schtasks /query.
Windows services (T1543.003) remain the preferred vector once the attacker already has SYSTEM. Blue team focus should sit on three signals: service creation pointing to unsigned binary, ImagePath in world-writable directories like ProgramData or Public, and the classic unquoted service path. A good hunt starts with DeviceEvents | where ActionType == "ServiceInstalled" | where FolderPath !startswith "C:\\Windows\\" and FolderPath !startswith "C:\\Program Files". To avoid confusing with legitimate installs, join with the certificate table and drop anything signed by a known corporate signer. This pattern echoes the baseline-driven detection we covered in Hunting Living-off-the-Land Binaries on Windows with KQL.
WMI Event Subscription (T1546.003) is where things get serious. It is silent, survives reboot and rarely appears in junior playbooks. The recipe: monitor __EventFilter, __EventConsumer and __FilterToConsumerBinding in the root\subscription class. In Defender, DeviceEvents | where ActionType == "WmiBindEventFilterToConsumer" works, but in environments without MDE you need Sysmon with events 19, 20 and 21 enabled. Combine with Get-WmiObject -Namespace root\subscription -Class __EventConsumer running weekly as a compliance check. Lateral movement techniques that end in WMI persistence are illustrated well in Lateral Movement in the Lab: SMB, WMI and WinRM with a Detection Focus.
COM hijacking (T1546.015) abuses HKCU precedence over HKLM for CLSIDs. The attacker registers an InProcServer32 under HKCU pointing at a controlled DLL, and any process loading that CLSID in user session executes the code. To hunt, focus on unexpected writes to HKCU\Software\Classes\CLSID\*\InProcServer32 where the default value does not resolve to a Microsoft-signed DLL. AppInit_DLLs (T1546.010) and Image File Execution Options (T1546.012) belong to the same category of registry-based execution flow abuse and deserve dedicated queries. Anyone wondering why modern evasion still goes through here should read EDR Evasion for Research: Direct Syscalls Explained Without the Hype.
Startup folder, Logon Scripts (T1037), BITS Jobs (T1197) and Print Processors (T1547.012) close the practical list. BITS deserves special attention because it survives logoff and can pull payloads via trusted process: bitsadmin /list /allusers /verbose as a weekly check already catches 95% of cases. Print Processors are back on the map after PrintNightmare and monitoring HKLM\SYSTEM\CurrentControlSet\Control\Print\Environments is cheap. To correlate this with the full offensive cycle, Adversary Emulation with Caldera and MITRE ATT&CK in a Corporate Lab and Purple Team in Practice: Building a Red vs Blue Feedback Loop show how to turn these detections into continuous exercises with the red team.
Practical takeaway: stack the ten queries above in a single Sentinel watchlist, configure a 14-day per-host baseline before alerting and review hits in a weekly threat hunting window. Persistence is not detected with one brilliant rule, it is detected with review discipline. Start tomorrow by running the first two queries (Run keys and schtasks) in your production environment in read-only mode, count how many hits you get, and you will discover legitimate persistence nobody documented. That is the real starting point of the program.