secskills
secskills / defense / hunting-threats

hunting-threats

defense verified 2026-07-27

Run hypothesis-driven threat hunts across endpoint, network, cloud, and identity telemetry using stack counting, outlier analysis, and ATT&CK-based hypotheses, with SIEM query patterns for Splunk, KQL, and Elastic. Use when proactively searching for undetected compromise, validating an intel report against your environment, or converting a hunch into a repeatable hunt.

$ /plugin install secskills-defense $ /plugin install secskills-core

Hunting starts from an assumption of failure: the controls are deployed, no alert has fired, and the adversary may still be present. The output is not usually a compromise — it is a detection, a telemetry gap, or a documented negative result. Hunts that only count as successful when they find something degrade into confirmation bias.

When to Use

When NOT to Use

triaging-security-alerts; a hunt starts from a question, triage from a queue

product** — use producing-threat-intelligence; a hunt consumes intelligence, it does not produce it

Hypothesis Before Query

An unstructured search through logs is browsing, not hunting. Every hunt gets a written hypothesis in this shape:

Hypothesis: An adversary with [access level] is using [technique] to [objective], which would produce [observable] in [data source], which is distinguishable from normal because [discriminator]. If true, I expect to see: ... If false, I expect: ... Telemetry required: ... (verified present: yes/no)

If you cannot name the discriminator — what makes the malicious instance look different from the thousands of benign ones — the hunt is not ready. Go find the discriminator first; that research is the hunt.

Hypotheses come from: recent intel on actors targeting your sector, ATT&CK techniques with no detection coverage, crown-jewel assets and the paths to them, anomalies noticed during other work, and post-incident "what else would this actor have done."

Hunting Techniques

Stack counting (frequency analysis)

The workhorse. Aggregate a field, sort ascending, investigate the rare values. Malicious activity is usually rare; commodity noise is common.

-- Splunk: rarest parent-child process pairs
index=sysmon EventCode=1
| stats count dc(host) as hosts by ParentImage, Image
| where count < 10 AND hosts < 3
| sort count
// KQL: rarely-seen signed binaries making external connections
DeviceNetworkEvents
| where RemoteIPType == "Public"
| summarize Count=count(), Hosts=dcount(DeviceName) by InitiatingProcessFolderPath
| where Hosts <= 2 and Count < 20
| order by Count asc

Stack the right field. Stacking Image finds unusual binaries; stacking ParentImage, Image finds unusual relationships, which is where living-off the-land abuse shows up (winword.exepowershell.exe).

Outlier analysis

Same shape, different axis: what is normal for this entity?

-- Elastic ES|QL: first-seen external destinations per host
FROM logs-network-*
| WHERE destination.ip NOT IN CIDR("10.0.0.0/8","172.16.0.0/12","192.168.0.0/16")
| STATS first_seen = MIN(@timestamp), n = COUNT(*) BY host.name, destination.domain
| WHERE first_seen > NOW() - 7 days AND n > 20

Grouping and clustering

Cluster on a shared attribute to surface campaigns: same JA3/JA4 across unrelated hosts, same rare user agent, same certificate serial, same working hours, same directory of execution.

Intel-driven hunting

Take a report, extract the TTPs rather than the IOCs, and hunt those. The report's hashes and IPs are dead; its described behaviour is not.

Report says:  "uses schtasks to create a task running a DLL via rundll32"
Bad hunt:     search for the report's hash
Good hunt:    every scheduled task created in the last 90 days whose action
              references rundll32, stacked by task name and DLL path

High-Yield Hunting Grounds

Hypothesis areaWhat to look for
Execution via LOLBinsrundll32, regsvr32, mshta, certutil, bitsadmin, msiexec with network or unusual arguments; curl/wget piping to a shell on Linux
PersistenceScheduled tasks/cron/systemd units created recently; WMI event subscriptions (rare and almost always malicious); run keys; new services; authorized_keys modifications
Credential accessLSASS handle opens, ntds.dit copies, shadow-copy creation, Kerberos RC4 requests (4769 with encryption type 0x17), DCSync replication rights use
Lateral movementAdmin share writes followed by service creation, WinRM/WMI from non-admin hosts, SSH from workstations to servers, RDP chains
C2Beacon timing regularity, long-lived connections, DNS with high entropy or high subdomain cardinality, TLS with rare JA3/JA4
ExfiltrationOutbound volume outliers per host, archive creation followed by upload, cloud storage domains from servers, DNS TXT volume
Identity/cloudNew OAuth grants and consented apps, service principal credential additions, mail forwarding rules, role assignments outside change windows, StopLogging/trail deletion
Defense evasionEvent log clears (1102/104), Sysmon or EDR service stops, AMSI/ETW patch indicators, timestomping ($SI vs $FN mismatch)

The Hunt Loop

1. Hypothesis   (written, with a discriminator)
2. Scope        (data sources, time window, host population — decided up front)
3. Verify       (does the telemetry exist and cover the population?)
4. Query        (broad, then narrow — expect several iterations)
5. Investigate  (every candidate resolved to benign-explained or escalated)
6. Conclude     (found / not found / could-not-determine)
7. Convert      (detection rule, telemetry gap ticket, or documented baseline)
8. Document     (so the next person can re-run it, not re-derive it)

Every hunt produces an artifact, including hunts that find nothing. A negative result is a finding when it is documented with its scope and limitations: "no evidence of X across 4,200 endpoints over 90 days; note that 620 hosts lack the required telemetry." That sentence is worth more than an undocumented clean bill of health.

Scoping and Time Windows

7 days for an actor with a 60-day median dwell time, a clean result is meaningless.

silently becomes a 30-day hunt.

This is where the next intrusion will live.

When a Hunt Hits

Stop hunting and switch modes. Preserve first: pull the memory and triage package before anyone touches the host. Then hand to responding-to-incidents with the query, the raw results, and the timestamp of your first look — the response team needs to know what you touched and when, so your own activity does not contaminate the timeline.

Do not "just check one more thing" on a live suspect host. Interactive commands on a compromised box change evidence and can alert the operator.

Rationalizations to Reject

set for one window. Write down all three.

you are looking for the rare, not reading the common.

hide, and an uncharacterized benign cluster is an unexamined hypothesis.

successor. Undocumented hunts get repeated instead of extended.

the timeline, and possibly a tripwire.

Schedule hunts against a prioritized technique backlog.

Deliverable

# Hunt: <name>          Date: <UTC>   Analyst: <name>
Hypothesis:             <as written above>
ATT&CK:                 T####.###
Scope:                  <data sources, host population, time window>
Telemetry verified:     <present / partial — name the gaps>
Queries:                <verbatim, so this is reproducible>
Results:                <candidates found, how each was resolved>
Conclusion:             found / not found / could-not-determine
Outputs:                <detection rule ID, telemetry gap ticket, baseline doc>
Limitations:            <what this hunt could not have seen>

ATT&CK Coverage

Generated from secskills-core/ttp-index.json — edit that file, then run python3 scripts/sync_attack.py --write. Re-verify IDs against the current ATT&CK release before citing them in a report.

Persistence (TA0003)

Defense Evasion (TA0005)

Collection (TA0009)

Command and Control (TA0011)

Exfiltration (TA0010)

Impact (TA0040)

Detection content for any of these: engineering-detections. Proactive search: hunting-threats. Post-compromise: responding-to-incidents.

References