Chapter 217

Incident Response and Threat Hunting

Incident response is structured investigation: from an alert or tip, reconstruct what happened, determine scope, and drive remediation. Threat hunting is proactive: given a hypothesis about attacker behavior, search logs for evidence of that behavior without a triggering alert. Both skills require fluency in the same data sources — process creation, network, registry, file, and authentication events — and the same analytical tool: KQL or SPL queries that trace a behavior backward and forward in time from a pivot point.

Scenario

Alert fires: MDE detected a suspicious PowerShell encoded command on WORKSTATION-42 at 14:32. The user claims they did nothing. You have 30 minutes to determine: was this a true positive, what was the full attack chain, are other hosts compromised, and does the attacker still have access?

Incident Response Phases

NIST IR PHASES → PRACTICAL WORKFLOW ═══════════════════════════════════════════════════════════════════════ 1. PREPARATION - Sysmon deployed, MDE onboarded, baseline behavior known - Runbooks for common alert types (phishing, ransomware, C2) - Isolation capability: network contain in MDE one click 2. IDENTIFICATION (first 30 minutes) a. Validate alert: is this a TP or FP? (check parent process, user context) b. Scope: how many hosts have the same indicator? c. Determine entry point: first seen on which host/user? d. Determine timeline: when did attacker first appear in logs? e. Determine objective: credential access? Lateral movement? Exfil started? 3. CONTAINMENT - Network isolate confirmed compromised hosts (MDE: Isolate Device) - Reset credentials for compromised accounts (force sign-out all sessions) - Block C2 indicators at proxy/firewall - Preserve evidence: collect memory dump, volatile data BEFORE remediation 4. ERADICATION - Remove persistence mechanisms found - Rebuild or AV-clean affected hosts - Patch vulnerability used for initial access 5. RECOVERY - Restore from clean backup or rebuild - Re-enroll to MDM/EDR - Verify no persistence survived rebuild 6. POST-INCIDENT (within 72 hours) - Root cause analysis - New detection rules from IOCs/TTPs observed - Lessons learned: which rules fired? What was missed? ═══════════════════════════════════════════════════════════════════════

Initial Triage Queries

// MDE KQL: 30-minute triage on WORKSTATION-42 after PowerShell alert

let targetHost = "WORKSTATION-42";
let alertTime  = datetime(2026-09-18 14:32:00);
let window     = 2h;

// 1. What ran around the alert time?
DeviceProcessEvents
| where DeviceName == targetHost
| where Timestamp between ((alertTime - window) .. (alertTime + window))
| project Timestamp, AccountName, FileName, ProcessCommandLine, InitiatingProcessFileName
| order by Timestamp asc

// 2. Network connections around alert time
DeviceNetworkEvents
| where DeviceName == targetHost
| where Timestamp between ((alertTime - 30m) .. (alertTime + 1h))
| where RemotePort !in (80, 443, 53, 137, 138, 139, 445)
| project Timestamp, RemoteIP, RemotePort, RemoteUrl, InitiatingProcessFileName

// 3. Files created (payload drops)
DeviceFileEvents
| where DeviceName == targetHost
| where Timestamp between ((alertTime - 30m) .. (alertTime + 2h))
| where ActionType == "FileCreated"
| where FolderPath !startswith @"C:\Windows\Prefetch"
| project Timestamp, FileName, FolderPath, InitiatingProcessFileName, SHA256

// 4. Registry modifications (persistence)
DeviceRegistryEvents
| where DeviceName == targetHost
| where Timestamp between ((alertTime - 30m) .. (alertTime + 2h))
| where RegistryKey contains_any ("Run", "Services", "Startup", "Winlogon")
| project Timestamp, RegistryKey, RegistryValueName, RegistryValueData

// 5. Logon events (credential use / lateral movement)
DeviceLogonEvents
| where DeviceName == targetHost
| where Timestamp between ((alertTime - 1h) .. (alertTime + 2h))
| where LogonType in (3, 10)  // network + remote interactive
| project Timestamp, AccountName, LogonType, RemoteIP, AuthenticationPackage

Threat Hunting Methodology

// HYPOTHESIS-DRIVEN HUNTING:
// Hypothesis: An attacker used WMI for lateral movement and established persistence
//             via a WMI subscription, evading scheduled-task-based detection.
// Data needed: WMI activity logs (EID 5861), process trees from WmiPrvSE.exe
// Query: hunt for all hosts with WmiPrvSE spawning suspicious children

// Hunt query 1: WMI-spawned commands in last 7 days
DeviceProcessEvents
| where Timestamp > ago(7d)
| where InitiatingProcessFileName =~ "WmiPrvSE.exe"
| where FileName in~ ("cmd.exe","powershell.exe","wscript.exe","mshta.exe","cscript.exe","regsvr32.exe")
| summarize count(), commands=makeset(ProcessCommandLine, 5) by DeviceName, FileName
| order by count_ desc

// Hunt query 2: Kerberoasting hunt — all RC4 TGS-REPs for service accounts
IdentityLogonEvents
| where Timestamp > ago(30d)
| where Protocol == "Kerberos" and EncryptionType == "RC4-HMAC"
| where not(AccountName endswith "$")  // exclude computer accounts
| summarize count(), first_seen=min(Timestamp), source_hosts=dcount(IPAddress)
    by AccountName, TargetDeviceName
| where count_ > 2
| order by count_ desc

// Hunt query 3: Beaconing detection — regular outbound connection intervals
DeviceNetworkEvents
| where Timestamp > ago(24h)
| where InitiatingProcessFileName !in~ ("svchost.exe","MsMpEng.exe","chrome.exe","msedge.exe")
| summarize intervals=make_list(Timestamp, 1000), count_=count()
    by DeviceName, RemoteIP, InitiatingProcessFileName
| where count_ between (10 .. 500)
// Add jitter analysis: compute standard deviation of intervals; low StdDev = beaconing

Timeline Reconstruction

// Unified timeline: merge process, file, network, registry, logon events by timestamp
let Host = "WORKSTATION-42";
let Start = datetime(2026-09-18 13:00:00);
let End   = datetime(2026-09-18 17:00:00);

union
(DeviceProcessEvents
 | where DeviceName == Host and Timestamp between (Start .. End)
 | project Timestamp, Type="Process", Detail=strcat(FileName, " | ", ProcessCommandLine)),
(DeviceNetworkEvents
 | where DeviceName == Host and Timestamp between (Start .. End)
 | project Timestamp, Type="Network", Detail=strcat(RemoteIP, ":", tostring(RemotePort), " | ", InitiatingProcessFileName)),
(DeviceFileEvents
 | where DeviceName == Host and Timestamp between (Start .. End)
 | where ActionType == "FileCreated"
 | project Timestamp, Type="File", Detail=strcat(FolderPath, "\\", FileName)),
(DeviceRegistryEvents
 | where DeviceName == Host and Timestamp between (Start .. End)
 | project Timestamp, Type="Registry", Detail=strcat(RegistryKey, " = ", RegistryValueData)),
(DeviceLogonEvents
 | where DeviceName == Host and Timestamp between (Start .. End)
 | project Timestamp, Type="Logon", Detail=strcat(AccountName, " type=", tostring(LogonType), " from=", RemoteIP))
| order by Timestamp asc

IOC Pivoting

Starting IOCPivot queryWhat it finds
IP address 1.2.3.4DeviceNetworkEvents | where RemoteIP == "1.2.3.4"All hosts that connected to C2
File SHA256DeviceFileEvents | where SHA256 == "..."All hosts with the same binary
UsernameDeviceLogonEvents | where AccountName == "..."All hosts the user authenticated to
Parent process nameDeviceProcessEvents | where InitiatingProcessFileName == "..."All children of the suspicious parent
Registry keyDeviceRegistryEvents | where RegistryKey has "..."All hosts with that persistence key
Domain in DNS queryDeviceNetworkEvents | where RemoteUrl has "c2domain"Full network scope of C2 usage

Q&A

During threat hunting, you find beaconing behavior in DNS — a workstation making a query to the same domain every 60 seconds for 8 hours. The domain resolves to a legitimate CDN IP. How do you determine whether this is malicious C2 or legitimate software telemetry, and what additional evidence would drive a containment decision?

Regular high-frequency DNS queries to a legitimate CDN IP is a classic C2 evasion pattern: the attacker fronts their C2 through a CDN provider (Cloudflare, Azure Front Door, Fastly) so the resolved IP is shared across thousands of legitimate domains. The IP by itself is not indicative. The determination requires investigation along three axes.

First, identify the domain behind the CDN query. Use passive DNS or perform the resolution in a sandbox to get the domain. Then query VirusTotal, Shodan, or WHOIS for registration age, registrar, and community detections. A domain registered within the last 60 days with a privacy-protected registrar and no historical DNS resolutions is suspicious regardless of what IP it resolves to today. A domain that is three years old with hundreds of DNS resolvers reporting legitimate categories is likely legitimate software telemetry.

Second, identify the process making the queries. DeviceNetworkEvents | where RemoteUrl has "domain.example" | project InitiatingProcessFileName, ProcessCommandLine reveals whether it is a known application (Chrome, Windows Update, Defender) or an unknown binary. For an unknown process, check its file hash against VirusTotal and examine its parent process chain — where did it come from, who created it? A binary that is unsigned, located in %APPDATA%, and spawned by powershell.exe -enc is high confidence C2 regardless of CDN fronting.

The containment decision is driven by the combination: (1) unknown or recently-dropped binary + (2) newly-registered or low-reputation domain + (3) regular interval (low jitter) consistent with beacon sleep = isolate and collect. If process is a known vendor binary with digital signature and the domain is old and reputable, the behavior is likely telemetry and should be added to a hunting exclusion list. The key data point that drives containment over exclusion is the process identity — CDN fronting can deceive network-layer analysis, but the endpoint process chain cannot be spoofed without a separate endpoint compromise.