Most intrusions do not end at the initial payload — they persist, and they try to blend in by abusing binaries that already ship with Windows. Hunting persistence with KQL in Microsoft Defender is how you find both: the autostart entries and scheduled tasks that keep an attacker's code running across reboots, and the living-off-the-land binaries (LOLBins) they lean on to avoid dropping obvious malware. This post walks through four Advanced Hunting queries I run against Defender for Endpoint telemetry — suspicious parent-child process chains, LOLBin download and proxy execution, registry Run key persistence, and scheduled task persistence — each shown with the real results it returns in my lab.
Key Takeaways
- Hunting persistence with KQL comes down to a couple of tables —
DeviceProcessEventsandDeviceRegistryEvents— filtered on the behaviours attackers reuse rather than on specific malware files. - LOLBin abuse through
certutil,bitsadmin,mshta,regsvr32, andrundll32is caught most reliably by pairing the binary name with the suspicious arguments it rarely uses legitimately. - Registry Run key and scheduled task persistence both map to well-documented MITRE ATT&CK techniques and surface cleanly in Advanced Hunting once you project the right fields.
- Suspicious parent-child chains — Office or a script host spawning PowerShell — are one of the highest-signal, lowest-noise hunts available to a defender.
- Every query here is a starting point: tune the thresholds and exclusions to your own baseline before promoting any of them to a custom detection rule.
Environment
- Microsoft Defender XDR portal at security.microsoft.com, under Hunting → Advanced hunting.
- Defender for Endpoint Plan 2 onboarding on at least one Windows client, which is what populates
DeviceProcessEventsandDeviceRegistryEvents. - A Security Reader or Security Operator role to run the queries; Security Administrator to promote one to a custom detection rule.
- Advanced Hunting retains roughly 30 days of data, so every time filter below lives inside that window.
- All screenshots come from an isolated lab tenant with a single onboarded client and benign, self-generated test activity — the device and account names (
dwm-1,dwm-2, and so on) are throwaway test identities, not production data.
The Problem — Behaviour, Not Files
Antivirus is good at catching known-bad files. It is far weaker against an attacker who never drops one. Persistence through a registry Run key is just a string pointing at a signed interpreter. Downloading a second stage with certutil.exe uses a Microsoft-signed binary that is on every Windows box for legitimate certificate work. A scheduled task that launches PowerShell at logon looks, byte for byte, like a hundred benign tasks. None of these are a file you can hash and block — they are behaviours, and behaviour is exactly what endpoint detection and response telemetry records.
That is why this is a hunting problem rather than a signature problem. The DeviceProcessEvents and DeviceRegistryEvents tables in Defender for Endpoint capture process creations and registry writes across every onboarded device, which is enough raw material to ask precise questions: which process spawned PowerShell, which built-in binary reached out to the internet, what got written under a Run key. To exercise the queries honestly I generated the telemetry myself on a lab client with inert commands — an encoded Write-Output, a download from a benign Microsoft test URL, a Run key and scheduled task that were removed afterward. Run the offensive side only against systems you own or are authorized to test; everything below is written for the defender sitting in the Defender portal, not the person on the other end. The operator mechanics — where, project, summarize — are covered in depth in my complete guide to KQL threat hunting in Microsoft Defender, so here I focus on the hunts themselves.
The Solution
Step 1 — Catch suspicious parent-child process chains
The single highest-signal process hunt is a script host or Office application spawning a command interpreter. A macro-laden document that launches PowerShell, or cmd.exe kicking off an encoded command, is the classic initial-access shape and it rarely happens during legitimate work. The query filters on the parent as much as the child:
DeviceProcessEvents
| where Timestamp > ago(24h)
| where InitiatingProcessFileName in~ ("cmd.exe", "wscript.exe", "cscript.exe", "winword.exe", "excel.exe", "mshta.exe")
| where FileName in~ ("powershell.exe", "pwsh.exe")
| project Timestamp, DeviceName, InitiatingProcessFileName, FileName, ProcessCommandLine, AccountName
| order by Timestamp desc
The parent-child hunt catching cmd.exe launching an encoded PowerShell command in the lab. Captured in an isolated test tenant.
This maps to MITRE ATT&CK T1059.001, PowerShell, usually as the execution stage of a phishing chain. The false positives you will see are management tooling — some software genuinely shells out to PowerShell from a script host — so build an exclusion list of the specific parent-and-command combinations you confirm are benign rather than dropping whole parents from the query. If you want to go a layer deeper on the child, the encoded-command angle ties into PowerShell script block logging with Event ID 4104, which de-obfuscates what actually ran.
Step 2 — Hunt LOLBin download and proxy execution
Living-off-the-land binaries are Microsoft-signed tools that can be coerced into downloading files or proxying execution of attacker code. certutil becomes a downloader, bitsadmin transfers a payload, mshta and regsvr32 run remote script. The catalogue of these techniques lives at the LOLBAS project. Hunting them means pairing the binary with the arguments it only uses when abused:
DeviceProcessEvents
| where Timestamp > ago(24h)
| where FileName in~ ("certutil.exe", "bitsadmin.exe", "mshta.exe", "regsvr32.exe", "rundll32.exe")
| where ProcessCommandLine has_any ("urlcache", "-split", "/transfer", "http", "javascript:", "vbscript:", "scrobj")
| project Timestamp, DeviceName, FileName, ProcessCommandLine, InitiatingProcessFileName
| order by Timestamp desc
The LOLBin hunt returning certutil and bitsadmin pulling a file from a benign test URL. Captured in an isolated test tenant.
These fall under MITRE ATT&CK T1218, System Binary Proxy Execution, with certutil downloads sitting closer to T1105, Ingress Tool Transfer. The honest caveat is that some of these binaries have real day-job uses — rundll32 in particular runs constantly and legitimately — which is why the has_any argument filter matters so much: it is the difference between a handful of interesting rows and thousands of benign ones. Sort the rare invocations to the top and investigate the ones that ran once, on one device.
Step 3 — Surface registry Run key persistence
The registry Run keys are the oldest autostart trick in the book and still one of the most common, because they need no elevation and survive a reboot. A value under ...\CurrentVersion\Run that points at PowerShell, an encoded blob, or a URL is worth a look. DeviceRegistryEvents records the write as it happens:
DeviceRegistryEvents
| where Timestamp > ago(24h)
| where ActionType == "RegistryValueSet"
| where RegistryKey has @"CurrentVersion\Run"
| where RegistryValueData has_any ("powershell", "-enc", "-nop", "http", ".vbs", "mshta")
| project Timestamp, DeviceName, RegistryKey, RegistryValueName, RegistryValueData, InitiatingProcessFileName
A Run key write pointing at an encoded PowerShell command, caught the moment it lands. Captured in an isolated test tenant.
This is MITRE ATT&CK T1547.001, Registry Run Keys / Startup Folder. Plenty of legitimate software writes Run keys at install time, so the RegistryValueData filter is doing the heavy lifting again — a value that contains -enc or a raw URL is far more suspicious than one pointing at a signed executable in Program Files. The same detection idea from the log-collection side appears in my Sigma work; the point here is that Defender records the write centrally, so you can hunt it fleet-wide instead of pulling autoruns off one machine at a time.
Step 4 — Find scheduled task persistence
Scheduled tasks are the other workhorse of persistence: durable, flexible, and able to run as SYSTEM. Creation happens through schtasks.exe (or the Task Scheduler COM interface, which shows differently), so hunting the command-line creation catches the common case:
DeviceProcessEvents
| where Timestamp > ago(24h)
| where FileName =~ "schtasks.exe"
| where ProcessCommandLine has "/create"
| project Timestamp, DeviceName, AccountName, ProcessCommandLine, InitiatingProcessFileName
| order by Timestamp desc
The scheduled-task hunt catching a schtasks /create that launches PowerShell at logon. Captured in an isolated test tenant.
This is MITRE ATT&CK T1053.005, Scheduled Task. Software installers and management agents create tasks legitimately, so refine by the task action rather than the creation alone — a task whose command line contains PowerShell, an encoded blob, or a path in a user-writable directory like %TEMP% or %APPDATA% is the interesting subset. I covered the endpoint-log view of this same technique in detecting malicious scheduled tasks on Windows, which pairs the Security event log with the hunt above.
Step 5 — From ad-hoc hunt to standing detection
Each of these run by hand finds yesterday's activity; the value multiplies when you promote the reliable ones to custom detection rules that run on a schedule. The rules engine has firm requirements — the query has to return Timestamp, ReportId, and a strong entity column like DeviceId, and it should stop hard-filtering on time so the frequency window can do that job. The parent-child and scheduled-task hunts, being single-table, even qualify for near-real-time evaluation. The full mechanics of turning a hunt into a rule are in the complete KQL threat hunting guide; the short version is to tune each query against your own baseline first, because a rule that fires on every legitimate installer is a rule people mute.
Frequently Asked Questions
Which tables do I need for persistence hunting in Microsoft Defender?
Mostly two: DeviceProcessEvents for process creation (parent-child chains, LOLBins, schtasks) and DeviceRegistryEvents for autostart writes like Run keys. Both require Defender for Endpoint Plan 2 on the onboarded devices. Network-based follow-up uses DeviceNetworkEvents.
Are LOLBins like certutil and rundll32 always malicious?
No. They are legitimate Windows binaries with real uses, which is exactly why attackers abuse them. The signal is not the binary itself but the argument pattern — certutil -urlcache pulling an HTTP URL, or rundll32 loading script — so always pair the file name with a suspicious-argument filter and tune out your environment's known-good usage.
Which MITRE ATT&CK techniques do these hunts cover?
Parent-child PowerShell maps to T1059.001; LOLBin proxy execution to T1218 (and T1105 for downloads); registry Run keys to T1547.001; and scheduled tasks to T1053.005. Tagging your detection rules with the technique makes the coverage legible when you review it later.
How do I cut false positives on the Run key query?
Filter on RegistryValueData content rather than the key alone, so you only see values containing PowerShell, encoded flags, URLs, or script extensions. Then exclude the specific signed-software paths you confirm are benign. Legitimate installers write Run keys constantly; the goal is to surface the ones that point at an interpreter or a user-writable path.
Can I run these queries in Microsoft Sentinel?
The KQL operators are identical, but the schema differs. Defender XDR uses DeviceProcessEvents and DeviceRegistryEvents; in Sentinel the equivalent data may live in SecurityEvent, DeviceEvents, or a connector-specific table. The hunting logic ports directly; the table and column names may need adjusting.
Conclusion
None of these four queries is clever. They are a filter on a table and a short list of suspicious strings. What makes them useful is that they target the parts of an intrusion that signatures miss — the persistence that keeps an attacker resident and the signed binaries they use to stay quiet — and they do it across the whole onboarded fleet from one query editor.
Treat them as a baseline to adapt, not a finished product. The tuning — which parents to exclude, which LOLBin arguments matter in your environment, which Run key paths are noise — is where the real detection engineering happens, and it is worth doing before you turn any of these into an alert that pages someone. Get the queries returning clean results first, then let Defender run them around the clock.
Related Posts
- KQL Threat Hunting in Microsoft Defender: A Complete Guide — the operator-by-operator foundation and how to ship a custom detection rule.
- Useful Advanced Hunting KQL Queries in Microsoft Defender — more short, practical queries for day-to-day investigation.
- Windows Security: Detecting Malicious Scheduled Tasks — the event-log view of the persistence technique in Step 4.
Editorial note: posts on this blog are drafted with AI assistance and then reviewed, edited, and tested against a real environment before publishing. Commands, output, and screenshots come from systems I actually ran the work on.
0 comments:
Post a Comment