Advanced KQL: Advanced Regular Expressions.
The indicator was not obvious.
It was hidden inside thousands of characters: a filename, a command line, a URL, an encoded payload and a trail of suspicious text.
Most analysts saw noise.
Agent Foskett saw a pattern. Sometimes investigations are not about finding more data. They are about recognising the pattern hidden inside it.
Lesson overview
Learn how to use advanced regular expressions in KQL to identify suspicious patterns, extract indicators and improve threat hunting precision across Microsoft Defender XDR telemetry.
Why advanced regular expressions matter
The regular expression mindset
Step 1 — Use matches regex for complex text patterns
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
let TimeFrame = 7d;
DeviceProcessEvents
| where Timestamp > ago(TimeFrame)
| where FileName in~ ("powershell.exe", "pwsh.exe")
| where ProcessCommandLine matches regex @"(?i)powershell.*encoded(command)?"
| project Timestamp,
DeviceName,
InitiatingProcessAccountName,
FileName,
ProcessCommandLine
| order by Timestamp descStep 2 — Use anchors to control where a match occurs
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
let TimeFrame = 14d;
DeviceProcessEvents
| where Timestamp > ago(TimeFrame)
| where FileName matches regex @"(?i)^(powershell|pwsh|cmd)\.exe$"
| project Timestamp,
DeviceName,
FileName,
ProcessCommandLine,
InitiatingProcessFileName
| order by Timestamp descStep 3 — Use character classes for controlled matching
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
let TimeFrame = 7d;
DeviceNetworkEvents
| where Timestamp > ago(TimeFrame)
| where isnotempty(RemoteUrl)
| where RemoteUrl matches regex @"(?i)[a-z0-9-]{8,}\.(top|xyz|click|zip)$"
| project Timestamp,
DeviceName,
InitiatingProcessFileName,
RemoteUrl,
RemoteIP
| order by Timestamp descStep 4 — Use quantifiers to find suspicious repetition
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
let TimeFrame = 14d;
DeviceFileEvents
| where Timestamp > ago(TimeFrame)
| where FileName matches regex @"(?i)^[a-z0-9]{10,}\.(exe|dll|scr|js|vbs)$"
| project Timestamp,
DeviceName,
FolderPath,
FileName,
SHA256,
InitiatingProcessFileName
| order by Timestamp descStep 5 — Extract indicators with capture groups
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
let TimeFrame = 7d;
DeviceProcessEvents
| where Timestamp > ago(TimeFrame)
| where ProcessCommandLine has "http"
| extend ExtractedUrl = extract(@"(https?://[^\s'\"]+)", 1, ProcessCommandLine)
| where isnotempty(ExtractedUrl)
| project Timestamp,
DeviceName,
FileName,
ProcessCommandLine,
ExtractedUrl
| order by Timestamp descStep 6 — Hunt encoded PowerShell more precisely
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
let TimeFrame = 30d;
DeviceProcessEvents
| where Timestamp > ago(TimeFrame)
| where FileName in~ ("powershell.exe", "pwsh.exe")
| where ProcessCommandLine matches regex @"(?i)(-|/)(enc|encodedcommand)\s+[A-Za-z0-9+/=]{20,}"
| extend EncodedBlob = extract(@"(?i)(?:-|/)(?:enc|encodedcommand)\s+([A-Za-z0-9+/=]{20,})", 1, ProcessCommandLine)
| project Timestamp,
DeviceName,
InitiatingProcessAccountName,
ProcessCommandLine,
EncodedBlob
| order by Timestamp descStep 7 — Detect suspicious double extensions
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
let TimeFrame = 30d;
DeviceFileEvents
| where Timestamp > ago(TimeFrame)
| where FileName matches regex @"(?i)\.(pdf|docx|xlsx|jpg|png)\.(exe|scr|js|vbs|ps1|hta)$"
| project Timestamp,
DeviceName,
FolderPath,
FileName,
SHA256,
InitiatingProcessFileName
| order by Timestamp descStep 8 — Extract email domains from sender fields
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
let TimeFrame = 30d;
EmailEvents
| where Timestamp > ago(TimeFrame)
| where SenderFromAddress matches regex @"(?i)^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$"
| extend SenderDomain = tostring(extract(@"(?i)@([A-Z0-9.-]+\.[A-Z]{2,})$", 1, SenderFromAddress))
| summarize Messages = count(), Recipients = dcount(RecipientEmailAddress) by SenderDomain
| order by Messages descStep 9 — Combine parse_json() and regex
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
let TimeFrame = 14d;
DeviceEvents
| where Timestamp > ago(TimeFrame)
| where isnotempty(AdditionalFields)
| extend ParsedFields = parse_json(AdditionalFields)
| extend Command = tostring(ParsedFields.CommandLine)
| where Command matches regex @"(?i)(downloadstring|invoke-webrequest|iwr|iex)"
| project Timestamp,
DeviceName,
ActionType,
Command,
AdditionalFields
| order by Timestamp descStep 10 — Build a reusable regex hunting pattern
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
let SuspiciousCommandLinePattern = @"(?i)(encodedcommand|downloadstring|invoke-webrequest|\biwr\b|\biex\b|frombase64string)";
let SuspiciousCommandLines = (Lookback:timespan) {
DeviceProcessEvents
| where Timestamp > ago(Lookback)
| where ProcessCommandLine matches regex SuspiciousCommandLinePattern
| project Timestamp,
DeviceName,
FileName,
InitiatingProcessAccountName,
ProcessCommandLine
};
SuspiciousCommandLines(7d)
| order by Timestamp descCommon regex mistakes
Regex investigation checklist
Related Agent Foskett Academy lessons
Coming next
Final thought
Advanced KQL Advanced Regular Expressions in Microsoft Defender XDR
Agent Foskett Academy Lesson 127 teaches defenders how to use advanced regular expressions in KQL for Microsoft Defender XDR investigations, including matches regex, extract(), anchors, character classes, quantifiers and capture groups.
Learn regex hunting with KQL and Microsoft Defender XDR
Advanced regular expressions help security analysts identify encoded PowerShell, suspicious filenames, command-line patterns, URLs, email domains and hidden indicators across Defender XDR telemetry.
Microsoft Defender XDR regex tutorial for threat hunting
This lesson explains how to use regex in KQL to match complex strings, extract investigation evidence, reduce false positives and build reusable pattern matching logic for security operations.
