Your First Threat Hunt: A Hypothesis-Driven Walkthrough
A start-to-finish first threat hunt for beginners: pick a hypothesis, gather data, write queries, separate signal from admin noise, document findings, and turn results into a durable detection. No expensive tooling required.
Hunting is not alert triage
Threat hunting is the act of looking for adversary activity that your existing detections missed. It assumes a breach and goes looking for proof, rather than waiting for an alert to fire. That mindset shift trips up a lot of newcomers, so let's ground it in a real, repeatable method.
You do not need a threat intelligence subscription or a six-figure platform for your first hunt. You need a hypothesis, some logs, and the discipline to write down what you find.
Step 1: Form a hypothesis
A hunt without a hypothesis is just scrolling through logs. A good hypothesis is specific, testable, and tied to a technique an adversary would plausibly use in your environment.
Weak: "There might be malware somewhere."
Strong: "An attacker has established persistence by creating a scheduled task that runs a script or living-off-the-land binary."
That is our hunt. Scheduled tasks map to MITRE ATT&CK technique T1053.005 (Scheduled Task), they are a favorite persistence mechanism, and they are noisy enough that defenders often stop looking. Perfect target.
Anchor the hypothesis on something concrete:
- What the adversary does: creates or modifies a scheduled task.
- Where it shows up: process creation logs and the Task Scheduler event log.
- Why it hides: legitimate software and admins create tasks constantly.
Step 2: Know your data
Before writing a single query, confirm you can actually see the behavior. For scheduled task persistence on Windows, the useful sources are:
| Source | Event | What it tells you |
|---|---|---|
| Security log | 4698 | A scheduled task was created |
| Security log | 4702 | A scheduled task was updated |
| Sysmon | 1 | Process creation, including schtasks.exe |
| Task Scheduler operational log | 106 / 140 | Task registered or updated |
If you only have one of these, the hunt still works, you just adjust your queries. Event ID 4698 is the richest because it captures the full task XML, including the command the task runs. Make sure it is enabled; task creation auditing is off by default in many environments.
Step 3: Write the query
Start broad, then narrow. Your first query should just surface every scheduled task creation in the window you care about.
index=windows EventCode=4698
| table _time, host, SubjectUserName, TaskName, Command
That will return a lot. This is expected. The hunt is in the filtering, not the first query. Look at what runs and where it lives. Legitimate tasks tend to point at signed binaries in Program Files or Windows\System32. Suspicious tasks point at user-writable paths or invoke interpreters.
Narrow toward abuse patterns:
index=windows EventCode=4698
| search Command IN ("*powershell*", "*cmd.exe*", "*wscript*",
"*mshta*", "*rundll32*", "*regsvr32*", "*\\Users\\*",
"*\\AppData\\*", "*\\Temp\\*", "*\\ProgramData\\*")
| table _time, host, SubjectUserName, TaskName, Command
Now you are looking at tasks that run interpreters or execute from user-writable directories. This is where persistence hides.
Step 4: Separate signal from admin noise
This is the hardest part of hunting and the part nobody warns you about. Most of what you find will be legitimate. Your job is to build a mental (and then written) baseline of normal.
Work through the results and ask:
- Is this a known deployment tool? Configuration management platforms create tasks by the thousand. Learn their task-name patterns and account names, then set them aside.
- Does the task name look auto-generated? Random GUID-style names or misspelled system-sounding names ("WindowsUpdater", "MicrosoftEdgeUpdateTaskCore") deserve a second look.
- Who created it? A task created by a service account during a patch window is different from one created by a normal user account at 2 a.m.
- Where does the command point? A PowerShell one-liner with an encoded command, a script in
C:\Users\Public, or a binary inAppDatais a red flag.
A practical filtering move: aggregate and count. Rare is interesting.
index=windows EventCode=4698
| stats count by Command
| sort count asc
The commands that appear once, on one host, are your leads. The command that appears on 400 hosts is almost certainly your patch agent.
When a task command references an external URL or downloads a payload, pull the domain or IP out and enrich it. A fast reputation check on mlab.sh tells you whether the destination is known-bad before you go deeper.
Step 5: Run down the leads
Say you find this:
schtasks /create /tn "AdobeUpdateService" /tr
"powershell -w hidden -enc SQBFAFgAKA..." /sc minute /mo 30
Everything about it is wrong. A task named after Adobe running a hidden, base64-encoded PowerShell command every 30 minutes is not how Adobe updates itself. Decode the encoded command (do it in an isolated environment), see what it fetches, and pivot.
From here it becomes an investigation: which host, which user, what did the payload do, when did the task first appear, and does the same task exist elsewhere. Follow the first-seen timestamp back to the initial access.
Not every lead is malicious. Some will be a badly written internal script or an obscure vendor agent. Documenting those is just as valuable, because it shrinks the haystack next time.
Step 6: Document as you go
Write it down while you hunt, not after. A minimal hunt record contains:
- Hypothesis in one sentence.
- Data sources and time window searched.
- Queries you ran, verbatim, so the hunt is repeatable.
- Findings, including the benign ones you ruled out.
- Outcome: confirmed activity, nothing found, or a gap in visibility.
"Nothing found" is a legitimate and useful result, provided you can show you actually looked. A documented negative hunt tells your team where you have coverage.
Step 7: Turn the hunt into a detection
The point of hunting is not to hunt the same thing forever by hand. When you find a reliable pattern, convert it into an automated detection so the machine watches for it and you move on to the next hypothesis.
Our scheduled task hunt becomes a Sigma rule:
title: Suspicious Scheduled Task Running an Interpreter
id: 7c1a4e63-2b8f-4d10-9a5c-3e7b1f2d6a90
status: experimental
logsource:
product: windows
service: security
detection:
selection:
EventID: 4698
suspicious:
TaskContent|contains:
- 'powershell'
- 'mshta'
- 'regsvr32'
- '-enc'
- '\AppData\'
- '\Temp\'
condition: selection and suspicious
falsepositives:
- Legitimate admin scripts; baseline before enabling
level: medium
tags:
- attack.persistence
- attack.t1053.005
Author it, validate the logic, and convert it to your SIEM's query language. Doing that authoring and conversion in a workbench like hunt.mlab.sh catches syntax and field errors before the rule ever reaches production, and it maps the coverage back to ATT&CK so you can see what you just gained.
The loop
That is the whole cycle: hypothesize, gather, query, filter, run down leads, document, and ship a detection. Then pick the next technique and go again. Your first hunt will feel slow because you are building baselines from scratch. The tenth will be fast, because most of the noise is already ruled out.
Start with one testable idea and a week of logs. That is enough to catch something the alerts never would.