Building Effective Threat Hunting Queries
- Threat Hunting
- SIEM Queries
Automated detections cover the behavior somebody already wrote a rule for. Hunting queries are for everything else, and the ones that work start from a specific idea about adversary behavior rather than a broad sweep across a data source.
Start from a hypothesis
“Check for PowerShell activity” is a data pull rather than a hunt, since it produces volume without defining what would count as a finding. “Threat actors may use encoded PowerShell commands to download payloads” gives you a specific TTP to test, a set of fields worth aggregating on, and a clear answer when the results come back empty.
Write for anomalies, not matches
The query below hunts encoded PowerShell and download cradles in Sysmon process creation events, then ranks what comes back by how rare it is.
| |
Stacking on the raw command line does not work for this, because an encoded payload is unique to every execution and each row comes back with a count of one. The sed replacement strips the base64 blob and any URLs out first, which leaves a skeleton that does repeat, so the aggregation has something real to count. Ranking by distinct hosts rather than raw volume follows from the same idea, since a shape appearing ten thousand times across the fleet is your build automation and a shape appearing twice on one host is worth reading. Two hosts is a starting threshold and wants tuning to the size of the environment.
The parameter match is loose on purpose. PowerShell accepts any unambiguous prefix of -EncodedCommand, so -e, -ec, -en, and -enco all execute, and a literal *-enc* wildcard misses most of them. Requiring a long base64 string immediately after the flag is what keeps that loose match from flooding the results. OriginalFileName is in the search for the same reason, since it comes out of the PE version resource and a copy of powershell.exe renamed to something unremarkable still matches on it.
The rarity stacking above is a hunt technique, not something a single Sigma rule can express, but the selection criteria underneath it travel fine. This is the same encoded-command and download-cradle logic as a standing, backend-portable detection:
| |
Scope the window to the hypothesis
An incident investigation scopes to the hours around the suspicious activity. Proactive hunting works better sampled incrementally across recent data than run over a full retention period at once, since a query returning thousands of rows gets skimmed instead of reviewed, and the reason to hunt at all is that every result gets looked at.
Evaluating results
A result earns its place if it confirms or refutes the hypothesis, exposes a pattern you had not considered, or gives you a pivot point into other data. Anything else is noise, and noise usually means the scope is too broad or the hypothesis was not specific enough to begin with. Both are worth fixing before running the query again.
Document the hunt
Record the hypothesis, the queries, the findings, and the conclusion, including the hunts that turned up nothing. Knowing which patterns have already been checked and came back clean is most of what makes the next hunt faster, and it is the part that gets skipped first.