Expert in Kusto Query Language (KQL) and Microsoft Defender for Endpoint (MDE) / Microsoft 365 Defender XDR...
CRITICAL: You MUST read the relevant reference files BEFORE writing any query. Do not rely on memory - always verify field names, ActionTypes, and table schemas from the reference documentation.
| File | Description |
|---|---|
references/alerts.md |
MS365D alert and behavior tables (AlertInfo, AlertEvidence, BehaviorInfo, BehaviorEntities) |
references/identity.md |
MDA/MDI identity and cloud app tables (AAD sign-in events, IdentityInfo, IdentityLogonEvents, IdentityQueryEvents, IdentityDirectoryEvents, CloudAppEvents) |
references/email.md |
MDO email security tables (EmailEvents, EmailAttachmentInfo, EmailUrlInfo, EmailPostDeliveryEvents, UrlClickEvents) |
references/devices-core.md |
Core MDE device tables (DeviceInfo, DeviceNetworkInfo, DeviceProcessEvents, DeviceNetworkEvents, DeviceFileEvents) |
references/devices-security.md |
Security-focused MDE tables (DeviceRegistryEvents, DeviceLogonEvents, DeviceImageLoadEvents, DeviceEvents, DeviceFileCertificateInfo) |
references/devices-linux-macos.md |
Linux and macOS platform-specific guidance, ActionTypes, and detection patterns |
references/tvm.md |
TVM vulnerability management tables (DeviceTvmSoftwareInventory, DeviceTvmSoftwareVulnerabilities, DeviceTvmSecureConfigurationAssessment, baseline compliance, browser extensions, hardware/firmware) |
references/hunting-ioc.md |
IOC hunting patterns (hash, IP, domain), process ancestry chains, behavioral anomalies, cross-table correlation |
references/hunting-mitre.md |
MITRE ATT&CK hunting queries (TA0001-TA0011: Initial Access through Exfiltration) |
Before using string manipulation functions like split(), extract(), or parse(), check if the data you need already exists as a native field in the table schema. For example:
FileName directly instead of extracting from FolderPathInitiatingProcessFileName instead of parsing command linesAdditionalFields for structured data before regex parsingWhen detecting suspicious activity, consider which entity matters most for detection:
InitiatingProcess* fields (what created/modified the persistence)FileName, FolderPath)TableName
| where TimeGenerated > ago(24h)
| where <condition>
| project <columns>
| summarize <aggregation> by <grouping>
| order by <column> desc
| Operator | Purpose | Example |
|---|---|---|
where |
Filter rows | where ActionType == "ProcessCreated" |
project |
Select columns | project Timestamp, DeviceName, FileName |
extend |
Add computed columns | extend FileExt = tostring(split(FileName, ".")[-1]) |
summarize |
Aggregate data | summarize count() by DeviceName |
join |
Combine tables | join kind=inner (Table2) on $left.Id == $right.Id |
union |
Combine table rows | union DeviceProcessEvents, DeviceFileEvents |
parse |
Extract from strings | parse CommandLine with * "/c " Command |
mv-expand |
Expand arrays | mv-expand parsed=parse_json(AdditionalFields) |
// Case-insensitive contains
| where FileName contains "mimikatz"
// Case-sensitive contains
| where FileName contains_cs "Mimikatz"
// Starts/ends with
| where FileName startswith "cmd" or FileName endswith ".ps1"
// Regex matching
| where FileName matches regex @"(?i).*mimi.*"
// String extraction
| extend Domain = extract(@"https?://([^/]+)", 1, Url)
// Relative time
| where Timestamp > ago(7d)
| where Timestamp between (ago(48h) .. ago(24h))
// Time binning
| summarize count() by bin(Timestamp, 1h)
// Time formatting
| extend Hour = datetime_part("hour", Timestamp)
where clauses first to reduce dataset sizehas over contains - has is faster for word boundaries* in project - Select only needed columnsjoin scope - Filter tables before joining// INEFFICIENT - late filtering, uses regex
DeviceProcessEvents
| project-away ReportId
| extend lower_cmd = tolower(ProcessCommandLine)
| where lower_cmd matches regex ".*mimikatz.*"
// OPTIMIZED - early filtering, uses has
DeviceProcessEvents
| where Timestamp > ago(24h)
| where ProcessCommandLine has "mimikatz"
| project Timestamp, DeviceName, FileName, ProcessCommandLine
let suspiciousDevices = materialize(
DeviceProcessEvents
| where Timestamp > ago(1h)
| where FileName =~ "powershell.exe"
| where ProcessCommandLine has "-enc"
| distinct DeviceId
);
DeviceFileEvents
| where DeviceId in (suspiciousDevices)
| where Timestamp > ago(1h)
let statements for readability and reuse| take 100 before running full scoperender for visualization: | render timechartTableName | getschemadatatable for inline reference lists