Reading width
Wide uses the full column for everything, text, diagrams, code, and exercises. Narrow keeps the standard reading width.
Text size
Scales the body text. Headings and code blocks keep their size.
In this section
0.1 What Threat Hunting Is and Why It Finds What Alerts Miss
A threat hunt is a search for an attacker that no alert has reported. It starts from a question, runs that question against the raw activity the organization records, and ends with an answer someone can act on: found, not found, explained, or not decidable from the data.
This course teaches that work on one organization's month of Microsoft security data, and this lesson opens it by looking at what the organization's alerts did that month.
Microsoft's own description of the discipline is a good starting point. "Proactive threat hunting is a process where security analysts seek out undetected threats and malicious behaviors." The analyst does it "by creating a hypothesis, searching through data, and validating that hypothesis".
Three words in those sentences carry the course: undetected, hypothesis and validating. A hunt looks for what detection missed, it starts from a specific idea rather than a browse, and it ends only when the idea has been tested against the rows.
The difference from alert triage matters because the two are often confused. Triage starts from an alert and asks whether it is real. Hunting starts from the absence of an alert and asks whether something is there anyway.
Triage is bounded by what the detections were written to find. Hunting is bounded by what the data records, which is much more, and by the hunter's ability to ask good questions of it.
Hunting is also different from searching. A search for an indicator, an address or a file hash from a threat report, is a useful task and often part of a hunt, but it can only find what someone already knew to look for.
A hunt reasons from behavior: what would an attacker using this technique have to do here, and what rows would that leave? That reasoning is what lets a hunt find an intrusion nobody has written up yet.
The card above is how every hunt in this course begins. It names why the hunt starts, the question it will answer, the data and window it will read, and the platform the queries run on.
This first one is a question about the organization rather than an attacker, which is the right first hunt for anyone new to an environment: before looking for what is wrong, find out what is normally there.
Read the alert stream by product
Every alert in the month, grouped by the product that raised itNorthgate runs Microsoft's security products and sends their alerts to a Sentinel workspace. One query groups the month's alerts by the product that raised them, with how many alert types, entities and high-severity alerts each produced:
SecurityAlert
| where TimeGenerated >= datetime(2026-02-13 12:00)
and TimeGenerated < datetime(2026-03-15 12:00)
| summarize Alerts = count(), AlertTypes = dcount(AlertName),
Entities = dcount(CompromisedEntity), High = countif(AlertSeverity == "High")
by ProviderName
| order by Alerts desc
Seven products raised alerts across the month, in these numbers:
ProviderName Alerts AlertTypes Entities High
------------------------------- ------ ---------- -------- ----
MCAS 418 5 88 1
IPC 148 7 28 5
OATP 72 2 52 0
Microsoft Defender for Identity 49 16 37 17
MDATP 7 7 3 6
ASI Scheduled Alerts 3 1 3 0
Microsoft Defender XDR 1 1 1 1
698 alerts in a month, 30 of them high severity. Defender for Cloud Apps (MCAS) raised the most, then Entra ID Protection (IPC), Defender for Office 365 (OATP), Defender for Identity, and Defender for Endpoint (MDATP) with only 7. A handful came from Sentinel's own scheduled rules and from Defender XDR's correlation.
The distribution is typical of a Microsoft tenant, and it already says something about where hunting is needed. The products that alert most are the ones built on behavioral baselines of users, and their alerts are mostly low severity.
The product watching the endpoints raised seven alerts all month. That is not evidence that the endpoints were quiet; it is evidence that the endpoint detections fire rarely, and whatever they do not fire on is only visible to someone reading the endpoint rows.
Reading a stream by product is the first thing a hunter does in an unfamiliar tenant, because each product sees a different part of the estate and alerts on a different kind of evidence. Defender for Cloud Apps watches what users do in Microsoft 365 and other connected services. Entra ID Protection scores sign-ins.
Defender for Office 365 judges mail. Defender for Identity watches the domain controllers. Defender for Endpoint watches processes, files and connections on the devices it is installed on. A product with few alerts is either watching a quiet part of the estate or watching it with rules that rarely fire, and the alert count alone cannot say which.
The entities column adds a second reading. Defender for Cloud Apps raised its alerts across dozens of distinct users, while the endpoint product named only a handful of devices. A hunter new to the tenant now knows where the alerting is broad and shallow and where it is narrow, and narrow alerting over a rich source of rows is where hunting usually pays off first.
Check coverage The stream is read. Check what happens to alerts once they arrive.
Follow the alerts into incidents
Incidents by their latest statusAlerts are grouped into incidents, and an incident is what an analyst works. Each incident's latest status shows what the team did with the month's alerts:
SecurityIncident
| where TimeGenerated >= datetime(2026-02-13 12:00)
and TimeGenerated < datetime(2026-03-15 12:00)
| summarize arg_max(TimeGenerated, Status) by IncidentNumber
| summarize Incidents = count() by Status
| order by Incidents desc
The month's incidents, counted by the status where each one had ended up by 15 March:
Status Incidents
------ ---------
Closed 433
New 99
Active 1
533 incidents in the month. 433 were closed, 99 were still new, untouched, when the month ended, and 1 was being worked. A team that closes most of its incidents and leaves a fifth untouched is a team keeping up with its queue, mostly, and doing little beyond it.
That is the normal state of a security operations team, and it is exactly why hunting is a separate job: the queue never empties enough to leave time for looking at what is not in it.
Incident status is also a reminder that alerts have a life after they fire. Each one is opened, read, sometimes escalated, and closed with a classification, and each of those steps consumes an analyst's time.
A tenant that raises hundreds of alerts a month spends most of its analyst time on them. Hunting has to justify its share of that time by finding things the queue would never surface, and by turning what it finds into detections so the queue gets better.
The untouched incidents are not this lesson's concern, and opening them would be triage, not hunting. What matters here is the shape: the team's attention is spent on what the alerts present, and the alerts present only what the rules were written for.
The incident table also shows how the course will treat other people's work. A closed incident is a decision someone made, usually for a good reason, and a hunter does not reopen it on a hunch.
If a later hunt finds rows that contradict a closure, the record says so with the rows, and the team decides what to do. Hunting adds evidence to the team's decisions rather than overriding them.
Check coverage Most alerts become incidents someone closes. Check how much an individual alert actually says.
Measure how common the commonest alerts are
The alert types that touch the most accountsAn alert's value as evidence depends on how rarely it fires where nothing is wrong. The three alert types that touched the most accounts in the month:
SecurityAlert
| where TimeGenerated >= datetime(2026-02-13 12:00)
and TimeGenerated < datetime(2026-03-15 12:00)
| summarize Alerts = count(), Accounts = dcount(CompromisedEntity) by AlertName
| top 3 by Accounts
Three alert types appear, and each of them touched dozens of different accounts in the month:
AlertName Alerts Accounts
------------------------------------------ ------ --------
Suspicious inbox manipulation rule 106 67
Mass download by a single user 82 60
Email reported by user as malware or phish 71 51
Each of these fired on more than forty accounts in an organization of 97 accounts in the directory. An alert that fires on half the workforce in a month cannot, on its own, tell anyone that a particular account is compromised. It is a pointer worth following, and the following is a hunt: read the rows the alert summarizes and decide whether they show anything the alert's name suggests.
This is not a criticism of the products. Behavioral alerts are designed to cast a wide net and let a person decide; they trade precision for coverage on purpose. The consequence for a hunter is that the presence of such an alert on an account is weak evidence, and its absence is weaker still.
A hunt that starts from one of these alerts has to read past it to the rows, and a hunt that starts from no alert at all is reading the same rows from a different direction.
The course comes back to this repeatedly. An alert is a rule's summary of some rows, written by someone who could not see this tenant. A hunter treats it as a hypothesis to test, and the rows as the evidence.
Base rates are the first statistical habit the course asks of you, and the cheapest. Before weighing any signal, count how often it appears across the whole tenant.
A signal that appears everywhere has to be read in context to mean anything; a signal that appears once in a month is worth reading on its own. The query that measures this is a single summarize, and it changes how every later result is read.
Widen Alerts are pointers. Widen to what they point at: the rows of activity underneath.
Count the rows the alerts stand on
Rows of activity in the month by sourceEvery alert is raised from rows of activity: processes, connections, sign-ins, mailbox operations. The same month, counted in those rows:
let start = datetime(2026-02-13 12:00);
let stop = datetime(2026-03-15 12:00);
union
(DeviceProcessEvents | where Timestamp >= start and Timestamp < stop
| summarize Rows = count() | extend Source = "Endpoint"),
(DeviceNetworkEvents | where Timestamp >= start and Timestamp < stop
| summarize Rows = count() | extend Source = "Endpoint"),
(DeviceFileEvents | where Timestamp >= start and Timestamp < stop
| summarize Rows = count() | extend Source = "Endpoint"),
(DeviceLogonEvents | where Timestamp >= start and Timestamp < stop
| summarize Rows = count() | extend Source = "Endpoint"),
(SigninLogs | where TimeGenerated >= start and TimeGenerated < stop
| summarize Rows = count() | extend Source = "Sign-ins"),
(OfficeActivity | where TimeGenerated >= start and TimeGenerated < stop
| summarize Rows = count() | extend Source = "Mail and files"),
(EmailEvents | where Timestamp >= start and Timestamp < stop
| summarize Rows = count() | extend Source = "Mail delivery")
| summarize Rows = sum(Rows) by Source
| order by Rows desc
The month's activity, by the kind of source that recorded it:
Source Rows
-------------- -----
Endpoint 21485
Mail and files 15960
Mail delivery 15188
Sign-ins 8062
60,695 rows of activity against 698 alerts. The endpoints alone recorded 21,485 rows and raised 7 endpoint alerts. Advanced hunting exists to read those rows directly: Microsoft describes it as a tool "to explore up to 30 days of raw Defender XDR data", and in the Defender portal it can query the Sentinel workspace's tables as well.
The rows are also uneven, and the unevenness matters. The endpoint tables hold the most rows because every process start, connection and file change on a sensor-equipped device is recorded. Mail and file activity is next, because every mailbox access and SharePoint operation is audited.
Sign-ins are fewer because a person signs in a few times a day. Each source answers different questions, and Modules 5 to 7 take them one at a time before Module 8 reads them together.
The ratio is the case for hunting in one number. An attacker who avoids every rule still leaves rows, because every process they start and every sign-in they make is recorded whether a rule matches it or not. The rows are where the course's hunts look, and the rest of the course is about how to ask them good questions.
The window matters as much as the counts. Every query in this lesson reads the same thirty days, from 12:00 on 13 February to 12:00 on 15 March, in UTC, with the start included and the end excluded.
Microsoft's advanced hunting works in UTC throughout, and the course follows it, so a time in a result is a UTC time unless a lesson says otherwise. Keeping one window across every query is what lets results from different tables be compared at all.
Check coverage The rows dwarf the alerts. Check the last coverage question: which devices raised no alert at all.
Count the devices no alert mentions
Devices that report activity, and how many appear in an alertAn alert names the entity it is about. Devices that record activity but never appear in an alert are devices the alerting has said nothing about, for better or worse:
let start = datetime(2026-02-13 12:00);
let stop = datetime(2026-03-15 12:00);
union
(DeviceProcessEvents | where Timestamp >= start and Timestamp < stop
| distinct DeviceName),
(DeviceNetworkEvents | where Timestamp >= start and Timestamp < stop
| distinct DeviceName),
(DeviceLogonEvents | where Timestamp >= start and Timestamp < stop
| distinct DeviceName)
| distinct DeviceName
| join kind=leftouter (SecurityAlert
| where TimeGenerated >= start and TimeGenerated < stop
| summarize Alerts = count() by DeviceName = CompromisedEntity) on DeviceName
| summarize Devices = count(), WithAnAlert = countif(Alerts > 0),
WithNone = countif(isempty(Alerts) or Alerts == 0)
How many reporting devices an alert ever named:
Devices WithAnAlert WithNone
------- ----------- --------
24 8 16
24 devices recorded endpoint activity in the month; 8 were named in at least one alert and 16 were never named. Most of those are probably quiet, ordinary laptops and servers doing ordinary work. Some may not be, and the alerting cannot say which. A hunt is how the team finds out, by asking each device a question the rules never asked.
Coverage has a second meaning that the course treats separately. Some devices in the estate report no process activity at all, only logons or network connections, and a hunt over process rows cannot see them whatever it asks.
Module 3 measures that kind of gap and Module 9 turns it into a scorecard. For now it is enough to know that "no alert" and "no row" are different statements, and that a hunter has to know which one a silence is.
Writing a hunt down is what separates it from looking around. A query run once and forgotten leaves nothing behind; a hunt written with its question, data, steps and outcome can be rerun next month, handed to a colleague, reviewed, or turned into a detection.
Microsoft's own hunting workflow in Sentinel makes the same point with bookmarks, which preserve "the specific row results, KQL query, and time range that generated the result", so that a finding can be found again.
The hunts the course teaches are written down in one consistent shape, so that someone else can rerun them:
A hunt, as this course writes one
Trigger
Why the hunt starts: an intelligence report, a gap, a question from outside the team
Question
One question the data can answer, with the answer that would end the hunt
Data
The tables that would hold the rows, and the window, half-open, in UTC
Steps
Each a query, its result, what the result means, and the decision it leads to
Record
The finding, the rows that support it, the bounds of what was searched, the next steps
Outcome
Proven, disproven, explained, inconclusive, escalated, failed or planned
Every lesson in the course is a hunt written this way, and every lesson ends on a record with one of these outcomes.
Every lesson after this one is a hunt in that shape, and each ends on a record. This lesson's own record follows, and it is short, because the hunt was a description. Later records carry findings, bookmarked rows and next steps that other people act on, and the same six parts hold every one of them.
The hunt record
Outcome: explainedThe record closes the hunt. Explained means the question is answered and nothing in the answer calls for action.
Northgate's products raised 698 alerts in the month, 30 high severity, from seven sources; the endpoint product raised 7. They became 533 incidents, 433 closed and 99 untouched at month end. The commonest alert types each touch more than forty of 97 accounts. Underneath, the month holds 60,695 rows of activity, and 16 of 24 reporting devices were never named in an alert.
- Alerts: 698 from 7 products; MDATP 7
- Incidents: 533; closed 433, new 99
- Commonest types: 67, 60, 51 accounts each
- Rows: 60,695; endpoint 21,485
- Devices: 24 reporting, 8 ever alerted on
Alerts, incidents and activity in the month. The lesson counts and does not open any alert, incident or device.
- Meet the organization the rows describe (0.2)
- Learn the tables the rows live in (0.3)
A hunt whose outcome is explained still produced something: a baseline. The next hunter who asks how many alerts a month is normal for Northgate, or how many devices usually go without one, can start from this record instead of from nothing. Most of a hunting program's value accumulates this way, in baselines and descriptions that make the next anomaly easier to see.
The answer to the brief is a description, not a finding, and that is the right outcome for a first hunt in a new place. It tells the new hunter that the alerts are numerous and mostly behavioral, that the endpoints say little through alerts and a great deal through rows, and that most devices have never been the subject of an alert.
Those three facts point to where the course's hunts go.
The record's bounds line is worth noticing even here. It says what the hunt looked at, alerts, incidents and counts of activity, and what it deliberately did not do: open any of them.
A reader of the record knows that "nothing to act on" means nothing in the counts, not that every alert was checked. That habit of stating what was searched, and therefore what the answer covers, runs through every record in the course.
The course is built for analysts who already know what an alert is and want to find what alerts miss. It assumes you can read a query and are willing to learn KQL properly, which Module 4 teaches from the ground up for hunting. It does not assume you have hunted before.
Each module adds one part of the work: the method in Modules 1 and 2, the platform in Module 3, the language in Module 4, the three main data domains in Modules 5 to 7, correlation in Module 8, turning hunts into lasting value in Module 9, and the whole of it on two cases in Module 10.
Every query in the course runs in your browser against the same month of Northgate's data, and every result shown in a lesson is the result that query returns. That is a deliberate choice. A hunter learns by running queries and comparing what comes back with what they expected, and a course whose results were typed in by hand would teach the opposite habit.
0.2 introduces Northgate itself: its people, its devices, its network edge and its cloud, and the hunting team you join.
Practice
What does the alert stream look like? hands onSteps 1 and 3 read the alert stream by product and by type. Build both views yourself, then find the alert type that fires most often per account.
Write down the product that raised the most alerts and the one that raised the fewest. Then write one sentence on what that difference does and does not mean.
Read the evidence
What alerts can and cannot tell youThe check gives you the month's alerts by product. Decide what each reading supports.
Resources
Publishers revise these pages; where a page and this lesson disagree, the page is newer.