Hunting Living-off-the-Land Binaries on Windows with KQL
Production-ready KQL queries for Microsoft Defender and Sentinel to hunt LOLBin abuse from rundll32, mshta, and certutil in real environments.

A capable attacker does not need to drop their own binary on the victim's disk: they use what is already there. Rundll32, mshta, certutil, bitsadmin, and regsvr32 are legitimate tools signed by Microsoft itself, which leads many EDRs to treat their executions as noise. This technique is called Living off the Land, the binaries themselves LOLBins, cataloged in the public LOLBAS project. The Basilisk team spent the last six months reviewing 47 incidents, and 31 of them had at least one LOLBin somewhere in the kill chain. This post delivers the KQL queries we run in Microsoft Defender for Endpoint and Sentinel to turn those executions into actionable detections without drowning the SOC in false positives.
What LOLBins are and why they slip through
A LOLBin is a preinstalled, signed program that carries an unintended, attacker-useful side function: download, code execution, persistence, or bypassing application control. Because the signature comes from Microsoft, naive allowlists do not fire, and a bare process name as an indicator is worthless. Detection therefore shifts from which binary to which command line, which parent process, which downstream behavior. That is exactly why the process command line (ProcessCommandLine) is the most important field in any hunting query, followed by InitiatingProcessFileName and the network connections made by the same process.
Understand the baseline first
Before writing any rule, you must understand the environment baseline. We run a broad hunt against DeviceProcessEvents grouping by FileName and counting executions over 30 days per host, surfacing what is normal in that fleet. At a customer with 8,200 endpoints, mshta.exe showed up on just 0.4% of machines, all marketing boxes running a legacy report. That means any mshta execution outside that group earns a P2 alert. This statistical-rarity approach is the bedrock of modern hunting and lines up with Threat Hunting with Sigma and Elastic: From Indicator to Detection Rule, where we walk from raw indicator to Git-versioned Sigma rule.
Rundll32 with a suspicious command line
The base query we recommend for rundll32 is: DeviceProcessEvents | where FileName =~ 'rundll32.exe' | where ProcessCommandLine has_any ('javascript:', 'shell32.dll,Control_RunDLL', 'mshtml,RunHTMLApplication', '.cpl,', 'url.dll,OpenURL') | where InitiatingProcessFileName !in~ ('explorer.exe', 'svchost.exe') | project Timestamp, DeviceName, AccountName, ProcessCommandLine, InitiatingProcessFileName. Tested against MITRE ATT&CK T1218.011, it caught 9 of 10 Cobalt Strike beacon executions in SMB pivot mode. Fine tuning comes from excluding legitimate parent processes per department, something we document in Adversary Emulation with Caldera and MITRE ATT&CK in a Corporate Lab. Pay special attention to rundll32 with no DLL argument, a strong sign of process hollowing.
Certutil: the Swiss Army knife
Certutil deserves its own chapter. It downloads files (-urlcache), decodes base64 (-decode), and computes hashes. Our hunting query is: DeviceProcessEvents | where FileName =~ 'certutil.exe' | where ProcessCommandLine has_any ('-urlcache', '-decode', '-decodehex', '-ping', 'http://', 'https://') | where ProcessCommandLine !contains 'CertificateServices' | extend Stage = case(ProcessCommandLine has '-decode', 'staging', ProcessCommandLine has 'http', 'download', 'recon'). Through 2025 we saw certutil used as a second stage in campaigns starting with macro phishing, a flow described from the offensive side in Simulated Initial Access: Macros, LNK and ISO in an Isolated Windows 11 Lab that blue teams must correlate against AD event 4688.
Mshta and regsvr32: remote scriptlets
For mshta and regsvr32 (T1218.010) the logic shifts. Mshta executing a URL is almost always malicious outside help systems: DeviceProcessEvents | where FileName =~ 'mshta.exe' | where ProcessCommandLine has_any ('http://','https://','.hta','javascript:','vbscript:') yields a false positive rate below 2% in most fleets. Regsvr32 with /i: and a URL (the Squiblydoo technique) you catch similarly via ProcessCommandLine has_any ('/i:http','scrobj.dll'). Both benefit hugely from correlation with the network layer, because a locally harmless call becomes a real alert through an outbound connection.
Multi-table correlation with the network
Combine the process find with DeviceNetworkEvents in the same 60-second window through a join on DeviceId and the process ID to confirm the connection actually came from the suspicious process: DeviceProcessEvents | where FileName =~ 'mshta.exe' | join kind=inner (DeviceNetworkEvents) on DeviceId, InitiatingProcessId | where NetworkTimestamp between (Timestamp .. Timestamp + 60s). This kind of multi-table correlation separates hunting from pure log mining and connects to Lateral Movement in the Lab: SMB, WMI and WinRM with a Detection Focus, where attackers chain LOLBins through remote WMI and the network view supplies the decisive context.
The trio: bitsadmin, msiexec, and wmic
Bitsadmin, msiexec /i http, and remote wmic are the trio that most often slips past default rules. For msiexec, our favorite heuristic uses ProcessVersionInfoOriginalFileName to detect renamed binaries, something tools like Sliver do by default: where FileName =~ 'msiexec.exe' and ProcessVersionInfoOriginalFileName !~ 'msiexec.exe'. See the operator perspective in Building C2 Infra with Sliver in an Isolated Lab for Defensive Research. Catch bitsadmin via ProcessCommandLine has_any ('/transfer','/create','/addfile'), and wmic via process call create and /node: for remote execution.
PowerShell and encoded commands as neighbors
LOLBins rarely appear alone; usually a PowerShell stage sits right next to them. Hunt encoded commands with DeviceProcessEvents | where FileName in~ ('powershell.exe','pwsh.exe') | where ProcessCommandLine has_any ('-enc','-EncodedCommand','-e ','FromBase64String','IEX','Invoke-Expression','-w hidden','-nop'). The real gold mine, though, is module logging: enable ScriptBlock telemetry (event 4104) via DeviceEvents and search there for the cleartext payload that -enc hides on the command line. A rundll32 or mshta whose parent is an encoded PowerShell is a near-certain beacon loader and deserves immediate high severity rather than a silent archive.
Correlating the payload drop via DeviceFileEvents
A certutil download without the file it subsequently writes is only half the story. Join the execution with DeviceFileEvents to see the payload actually dropped: join on DeviceId and InitiatingProcessId and filter for fresh FileCreated events in %TEMP%, %APPDATA%, or C:\Users\Public. Enrich the hit with the file SHA256 and look it up against your threat-intel sources; an unsigned executable just written by a LOLBin is a finding that justifies isolating the host. It is exactly this chain of process, network, and file that turns a weak indicator into a solid story for the incident report.
Operationalizing in Sentinel and Sigma
In Sentinel, we materialize these hunts as Analytics Rules with entity mapping on Account and Host, 5-minute frequency, and 1-hour per-entity suppression, which keeps the noise tractable. Every rule gets a MITRE tactic/technique tag so coverage becomes measurable. We always export the logic to Sigma for SIEM portability, the same philosophy from Purple Team in Practice: Building a Red vs Blue Feedback Loop. That way the same detection runs whether the customer is on Defender, Elastic, or Splunk, and the red team validates it directly against emulated attacks.
Taming false positives and pitfalls
The most common mistake is trying to eliminate LOLBins; they are part of the OS and used legitimately. SCCM calls msiexec routinely, software distribution uses bitsadmin, backup jobs launch certutil for hashing. Tune by parent process and context, never by binary alone. Another pitfall: has versus contains in KQL; has is token-based and faster but does not match substrings, which leaves gaps on concatenated command lines. Measure the false positive rate of each rule before arming it, and document every exception with a justification.
Checklist
Before rolling out any LOLBin rule: (1) baseline collected over at least 30 days; (2) filter on ProcessCommandLine, not just FileName; (3) parent-process exclusions documented per department; (4) network correlation where it makes sense; (5) renamed binaries covered via OriginalFileName; (6) MITRE tag set; (7) per-entity suppression configured; (8) exported to Sigma; (9) validated against an emulated attack; (10) false positive rate measured and under threshold.
FAQ
Is blocking LOLBins via WDAC enough? Rarely, because many are business critical; better to block the dangerous invocations (like certutil -urlcache) plus detection. Why not just alert on every certutil download? In large environments that produces hundreds of daily hits from legitimate automation; only the combination of a rare parent process, an external URL, and an unsigned target file makes the alert reliable. Do I need P1 for these tables? DeviceProcessEvents and DeviceNetworkEvents belong to Defender for Endpoint P2, and reach Sentinel through the data connector.
How do I handle persistence LOLBins? schtasks.exe and sc.exe are themselves LOLBins when they create new tasks or services pointing at a script or an unusual path. Hunt them with DeviceProcessEvents | where FileName in~ ('schtasks.exe','sc.exe') | where ProcessCommandLine has_any ('/create','create','binPath=') | where ProcessCommandLine has_any ('powershell','cmd /c','\Users\','\Temp\','http'). What if the attacker renames the binary? That is why you never filter on FileName alone; the combination of ProcessVersionInfoOriginalFileName, the command-line signature, and the parent process survives the rename, while a pure name filter goes blind.
Closing with immediate practice: run this in your tenant today: DeviceProcessEvents | where Timestamp > ago(30d) | where FileName in~ ('rundll32.exe','mshta.exe','certutil.exe','regsvr32.exe','bitsadmin.exe','msiexec.exe') | summarize Total=count(), Hosts=dcount(DeviceName) by FileName, bin(Timestamp, 1d) | order by Timestamp desc. The output is your baseline map. Anything that breaks that pattern by 3 standard deviations becomes a rule candidate. You do not eliminate a LOLBin, you observe it with discipline, and teams that treat the baseline as a living product detect intrusions in hours instead of months.


