Hunting Threats logo

Hunting Threats

Community
trilwu
hunting-threats

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.

Overview

Publishertrilwu
Repositorysecskills
Skill namehunting-threats
Stars
144
Forks
15
Bundled files
Instructions only
LicenseMIT
Links
  • Markdown instructions

    A SKILL.md file the model loads on demand, so it only costs tokens when a request actually matches.

  • Works with any LLM

    AI skills are plain Markdown, not provider-specific code, so this works with GPT, Claude, Gemini, Grok, or a local model.

  • Self-contained

    Everything the model needs lives in the instructions — no extra files to sync.

  • Open source

    Published by trilwu on GitHub. Read the source before you install it.

Installation

Install the Hunting Threats AI skill in TypingMind to use it with any LLM, or drop it into another agent that reads SKILL.md.

1

Install in TypingMind

TypingMind installs a skill straight from its GitHub folder — it reads SKILL.md, bundles the resource files, and stores the result locally.

  1. Open the app and go to Plugins → Skills.
  2. Choose "Install from GitHub".
  3. Paste the skill folder URL below and confirm.
  4. Enable the skill in any chat where you want it available.
Plugins → Skills → Add skill → From GitHub URL, then paste the folder URL and press Continue.
2

Install in another agent

Any agent that reads the Agent Skills format can use this skill — copy the folder into that agent's skills directory.

Claude Code — .claude/skills
git clone --depth 1 https://github.com/trilwu/secskills.git /tmp/secskills
mkdir -p .claude/skills
cp -r /tmp/secskills/secskills-defense/skills/hunting-threats .claude/skills/hunting-threats
Restart Claude Code after copying so it picks up the new skill.

Use it in TypingMind

Enable Hunting Threats in any TypingMind chat and the model takes it from there. Its name and description sit in the system prompt, and the moment a request matches, the model loads the full instructions itself — you never invoke it by hand, and it costs no tokens until it is actually used.

The model loads Hunting Threats on its own as soon as a request matches it.

Works with any AI model

AI skills are plain Markdown instructions rather than provider-specific code, so Hunting Threats is not tied to the model it was written for. Install it once in TypingMind and use it with GPT-5, Claude, Gemini, Grok, DeepSeek, Mistral, Llama, or a local model you run yourself — all on your own API keys.

  • Loaded only when it is needed

    The system prompt carries just the name and description. The instructions are fetched on the first matching request, so an idle skill costs nothing.

  • Switch models mid-chat

    Because the skill is instructions rather than code, changing model does not break it — the next model reads the same SKILL.md.

Skill instructions

This is the SKILL.md content the model loads. Read it before installing — a skill is instructions your model will follow.

Hunting Threats

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

  • Proactively searching for compromise that detection missed
  • Testing a specific hypothesis about attacker behaviour in your environment
  • Operationalizing a threat intel report against your telemetry
  • Validating that a control or detection actually works in production
  • Baselining an environment to enable future outlier analysis

When NOT to Use

  • Working an alert queue rather than a hypothesis — use triaging-security-alerts; a hunt starts from a question, triage from a queue
  • Confirmed incident in progress — use responding-to-incidents
  • Writing the rule for what you found — use engineering-detections
  • Sample analysis — use analyzing-malware
  • A packet capture to work through — use analyzing-network-traffic
  • A confirmed AWS compromise to investigate — use investigating-aws-incidents
  • Pivoting on indicators, tracking an actor, or producing a finished intel product — use producing-threat-intelligence; a hunt consumes intelligence, it does not produce it
  • Offensive testing of defenses — use the red team skills

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.

sql
-- 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
kusto
// 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?

  • A service account that has never used interactive logon, now doing so
  • A workstation talking to an internal subnet it has never touched
  • A user authenticating outside their historical hours and geography
  • A host whose process-count baseline shifted after a specific date
sql
-- 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

  • Match the window to dwell-time reality, not convenience. If you look back 7 days for an actor with a 60-day median dwell time, a clean result is meaningless.
  • Confirm retention before you commit: a 90-day hunt over 30-day retention silently becomes a 30-day hunt.
  • Record which host populations are not covered by the telemetry you used. 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

  • "Nothing found, so we're clean." You searched one hypothesis over one data set for one window. Write down all three.
  • "Too much data to hunt." That is what stacking is for. Aggregate first; you are looking for the rare, not reading the common.
  • "The EDR would have alerted." The premise of hunting is that it did not.
  • "That's just noise." Characterize the noise. "Just noise" is where implants hide, and an uncharacterized benign cluster is an unexamined hypothesis.
  • "I'll remember what I searched." You will not, and neither will your successor. Undocumented hunts get repeated instead of extended.
  • "Let me just log into the suspicious host and look." You are now part of the timeline, and possibly a tripwire.
  • "We hunt when we have time." Ad-hoc hunting produces ad-hoc coverage. Schedule hunts against a prioritized technique backlog.

Deliverable

markdown
# 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)

  • T1546.003 Windows Management Instrumentation Event Subscription — see also establishing-persistence

Defense Evasion (TA0005)

  • T1036 Masquerading — see also establishing-persistence
  • T1070.001 Clear Windows Event Logs — see also responding-to-incidents
  • T1218 System Binary Proxy Execution — see also escalating-windows-privileges
  • T1218.011 Rundll32 — see also analyzing-malware
  • T1562 Impair Defenses — see also responding-to-incidents
  • T1562.001 Disable or Modify Tools — see also responding-to-incidents

Collection (TA0009)

  • T1074 Data Staged — see also transferring-files
  • T1560 Archive Collected Data — see also transferring-files

Command and Control (TA0011)

  • T1071.004 DNS — see also engineering-detections, analyzing-network-traffic
  • T1219 Remote Access Software
  • T1568 Dynamic Resolution — see also analyzing-malware, analyzing-network-traffic
  • T1572 Protocol Tunneling — see also transferring-files

Exfiltration (TA0010)

  • T1030 Data Transfer Size Limits
  • T1048 Exfiltration Over Alternative Protocol — see also transferring-files, analyzing-network-traffic
  • T1567 Exfiltration Over Web Service — see also transferring-files

Impact (TA0040)

  • T1490 Inhibit System Recovery — see also responding-to-incidents
  • T1496 Resource Hijacking — see also exploiting-cloud-platforms

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

Reading External Sources

Fetch public advisories, specifications, and vendor reports as Markdown:

bash
curl -sL "https://defuddle.md/<url>"      # scheme in the path is optional

This strips page boilerplate — roughly 78% fewer tokens on a prose page — and returns the full text rather than a summary, so you can grep it and trust a negative result.

Three things it is not for. Fetch JSON and API responses raw, because readability extraction mangles structured data. Fetch authenticated or JavaScript-rendered pages directly, because it retrieves them anonymously. And never route adversary infrastructure (phishing links, C2, malware hosting), client-owned hosts, or engagement URLs through it — the request leaves your machine to a third party, and for live adversary infrastructure it also tips off the operator.

Some sites block the extractor and return an error blob rather than the page — {"error":"Failed to fetch: 418 I'm a teapot"} from freedesktop.org, for instance. That is the fetch being refused, not the source saying the thing does not exist. Re-fetch the URL directly before drawing any conclusion from it.

References

  • engineering-detections — converting a successful hunt into a tested rule
  • responding-to-incidents — the handoff when a hunt confirms compromise
  • MITRE ATT&CK for hypothesis generation; PEAK and TaHiTI hunting frameworks
  • Sysmon, Zeek, osquery, Velociraptor, and cloud audit logs as core telemetry

Frequently asked questions

What does the Hunting Threats AI skill do?

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.

Why use Hunting Threats on TypingMind?

Because you install it once and use it with any model. Hunting Threats is plain Markdown rather than provider-specific code, so the same skill runs on GPT-5, Claude, Gemini, Grok, or a local model — and you can switch model mid-chat without it breaking. TypingMind runs on your own API keys, so you pay providers directly instead of a per-seat subscription, and your skills and chats stay in your own storage.

How do I install Hunting Threats in TypingMind?

Open Plugins → Skills → Install from GitHub in TypingMind and paste https://github.com/trilwu/secskills/tree/main/secskills-defense/skills/hunting-threats. TypingMind reads its SKILL.md and installs it as a skill you can enable per chat.

Which AI models can use Hunting Threats?

Any model you connect in TypingMind. AI skills are plain Markdown instructions rather than provider-specific code, so GPT, Claude, Gemini, Grok, and local models can all load this skill when a request matches it.

How many AI models can I use with Hunting Threats?

As many as you like. As long as a model supports skills, you can use Hunting Threats with it — GPT, Claude, Gemini, Grok, DeepSeek, Mistral, Llama and more — all on TypingMind with your own API keys.

Is the Hunting Threats AI skill free?

Yes. It is published on GitHub by trilwu under the MIT license. You only pay your own AI provider for the tokens you use.

What are AI skills?

An AI skill is a reusable instruction bundle that teaches an AI model how to do one specific task. It follows the open Agent Skills format: a SKILL.md file with a name and description, plus any scripts, templates or reference files the model may need. The model reads the instructions only when your request matches the skill, so an installed skill costs nothing until it is used.

How are AI skills different from plugins or MCP servers?

A plugin or MCP server gives a model new tools to call — code that runs somewhere and returns a result. An AI skill gives the model knowledge and process instead: how to approach a task, which steps to follow, what good output looks like. Skills are plain Markdown, so they need no server, no API key and no runtime, and they work with any model.

View all

Set up your own AI workspace now

Get notified about new features and future giveaways by subscribing to our newsletter 👇