TH2.11 Graph Semantics for Process Tree Analysis

4-5 hours · Module 2 · Free
Operational Objective
Endpoint hunting requires tracing execution chains: parent process → child process → grandchild process. An attacker's process tree — Word spawning PowerShell spawning cmd spawning certutil — tells a story that individual process events cannot. KQL does not have native graph traversal, but recursive join patterns achieve the same result. This subsection teaches process tree reconstruction for endpoint hunt campaigns.
Deliverable: The ability to reconstruct process execution trees from DeviceProcessEvents and identify suspicious parent-child relationships that indicate malicious execution chains.
⏱ Estimated completion: 25 minutes

Process trees reveal intent

A single powershell.exe execution is noise — it runs thousands of times per day. But WINWORD.EXE → powershell.exe → cmd.exe → certutil.exe -urlcache -split -f http://... is a malicious document delivering a payload. The individual events are unremarkable. The chain is the indicator.

Building a process tree from DeviceProcessEvents

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
// Reconstruct the process tree for a suspect process
// Start: you have a suspicious process (from a hunt finding or alert)
let suspectProcess = "certutil.exe";
let suspectDevice = "DESKTOP-NGE042";
let pivotWindow = 1d;
// Step 1: Find the suspect process execution
let target = DeviceProcessEvents
| where TimeGenerated > ago(pivotWindow)
| where DeviceName == suspectDevice
| where FileName =~ suspectProcess
| project TargetTime = TimeGenerated,
    TargetPID = ProcessId,
    TargetFile = FileName,
    TargetCmd = ProcessCommandLine,
    ParentPID = InitiatingProcessId,
    ParentFile = InitiatingProcessFileName;
// Step 2: Find the parent process
let parent = DeviceProcessEvents
| where TimeGenerated > ago(pivotWindow)
| where DeviceName == suspectDevice
| join kind=inner target
    on $left.ProcessId == $right.ParentPID
| project ParentTime = TimeGenerated,
    ParentPID = ProcessId,
    ParentFile = FileName,
    ParentCmd = ProcessCommandLine,
    GrandparentPID = InitiatingProcessId,
    GrandparentFile = InitiatingProcessFileName;
// Step 3: Find the grandparent
DeviceProcessEvents
| where TimeGenerated > ago(pivotWindow)
| where DeviceName == suspectDevice
| join kind=inner parent
    on $left.ProcessId == $right.GrandparentPID
| project
    GrandparentFile = FileName,
    GrandparentCmd = ProcessCommandLine,
    ParentFile1 = ParentFile,
    ParentCmd1 = ParentCmd,
    TargetFile1 = target[0].TargetFile,
    TargetCmd1 = target[0].TargetCmd
// The tree: Grandparent  Parent  Target
// Example output:
//   WINWORD.EXE  powershell.exe -enc ...  certutil.exe -urlcache ...
// That tree = malicious document executing a download cradle

Hunting for suspicious parent-child relationships

Instead of starting from a known suspect process, hunt for process trees that match known-malicious patterns:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
// Hunt: Office applications spawning script interpreters
// MITRE ATT&CK: T1204.002 (User Execution: Malicious File)
DeviceProcessEvents
| where TimeGenerated > ago(7d)
| where InitiatingProcessFileName in~
    ("WINWORD.EXE", "EXCEL.EXE", "POWERPNT.EXE",
     "OUTLOOK.EXE", "MSPUB.EXE")
| where FileName in~
    ("powershell.exe", "cmd.exe", "wscript.exe",
     "cscript.exe", "mshta.exe", "certutil.exe",
     "bitsadmin.exe", "regsvr32.exe")
| project TimeGenerated, DeviceName,
    Parent = InitiatingProcessFileName,
    Child = FileName,
    CommandLine = ProcessCommandLine,
    User = AccountUpn
// Office app  script interpreter = classic malicious document execution
// Low false positive rate because legitimate Office workflows
//   rarely spawn these interpreters
// Each result is a high-priority investigation target
| sort by TimeGenerated desc
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
// Hunt: processes with network connections from unusual parents
// Combines process tree + network behavior
DeviceProcessEvents
| where TimeGenerated > ago(7d)
| where InitiatingProcessFileName in~
    ("cmd.exe", "powershell.exe", "wscript.exe")
| project ProcessTime = TimeGenerated, DeviceName,
    ProcessId, FileName, ProcessCommandLine,
    ParentFile = InitiatingProcessFileName
| join kind=inner (
    DeviceNetworkEvents
    | where TimeGenerated > ago(7d)
    | where RemotePort in (80, 443, 8080, 4443)
    | project NetTime = TimeGenerated, DeviceName,
        ProcessId = InitiatingProcessId,
        RemoteIP, RemotePort, RemoteUrl
) on DeviceName, ProcessId
| where abs(datetime_diff('minute', ProcessTime, NetTime)) < 5
// Script interpreter execution followed by network connection
//   within 5 minutes = potential download cradle or C2 callback
| project ProcessTime, DeviceName, ParentFile,
    FileName, ProcessCommandLine, RemoteIP, RemoteUrl
PROCESS TREE — THE CHAIN TELLS THE STORYLEGITIMATE TREEexplorer.exe → WINWORD.EXE → splwow64.exeUser opens Word → prints documentNormal process tree — no investigation neededMALICIOUS TREEOUTLOOK.EXE → powershell.exe → certutil.exeEmail client spawns PowerShell → downloads payloadAnomalous tree — escalate immediatelyIndividual processes are noise. Parent-child relationships are signal.The same executable (powershell.exe) appears in both trees — the parent determines the meaning.

Figure TH2.11 — Process tree interpretation. The same child process (powershell.exe) is normal when spawned by explorer.exe and suspicious when spawned by OUTLOOK.EXE. Context is in the parent.

Try it yourself

Exercise: Hunt for Office child process spawning

Run the Office → script interpreter query against your environment's DeviceProcessEvents. How many results appear in 7 days? Examine each: is there a legitimate Office automation workflow that spawns these interpreters, or are the results genuine anomalies?

In most environments, this query returns very few results (if any) because legitimate Office workflows rarely spawn script interpreters. Any result is worth investigating.

⚠ Compliance Myth: "Process monitoring data is only useful for EDR alerts — not for hunting"

The myth: DeviceProcessEvents data is consumed by Defender for Endpoint’s built-in detections. Hunting against it is redundant.

The reality: Defender for Endpoint’s built-in detections cover known-malicious process patterns. Living-off-the-land techniques (LOLBins) use legitimate binaries in legitimate parent-child relationships — certutil downloading files, mshta executing scripts, regsvr32 loading DLLs. These are not inherently malicious, so the built-in detections may not flag them. Hunting for specific parent-child combinations (Office → certutil, cmd → bitsadmin with download parameters) catches the technique-specific patterns that broad behavioral detections miss. TH9 applies this systematically across the full LOLBin landscape.

Extend this approach

KQL does not support native recursive graph traversal (unlike Cypher or Gremlin). The manual join pattern shown here works for 2–3 levels of depth. For deeper trees (5+ levels), use Sentinel notebooks with NetworkX or MSTICPy's process tree visualization. For most hunting campaigns, 3 levels (grandparent → parent → target) is sufficient to identify malicious chains. TH9 and TH12 provide the specific parent-child patterns relevant to their respective techniques.


References Used in This Subsection

  • MITRE ATT&CK Techniques referenced: T1204.002 (User Execution: Malicious File), T1059 (Command and Scripting Interpreter), T1218 (System Binary Proxy Execution)
  • Course cross-references: TH9 (endpoint persistence), TH12 (ransomware pre-encryption)

You're reading the free modules of this course

The full course continues with advanced topics, production detection rules, worked investigation scenarios, and deployable artifacts. Premium subscribers get access to all courses.

View Pricing See Full Syllabus