Objective

Use Microsoft Defender for Endpoint (MDE/MDATP) Advanced Hunting in KQL with the DeviceProcessEvents table to look up all parent processes and help build a process tree.

Background

As a SOAR engineer working on Microsoft Defender for Endpoint (MDE) feature development, you may notice that alerts from MDE sometimes include multiple process evidence/entities in the additional information. However, there may not always be clear evidence showing how these processes are related to each other.

Therefore, building a process tree visualization is a good way to understand the relationships between these process evidences.

After researching, I found a helpful article: Enterprise Scale Threat Hunting with Process Tree Analysis by Mehmet Ergene. However, the DeviceProcessEvents table now includes more fields, which are not always needed for this kind of search. Also, querying by CreateTime is a best practice, as it is more efficient than using a large time range.

DeviceProcessEvents table study

In the DeviceProcessEvents table, the main index key and foreign key are ProcessUniqueId and InitiatingProcessUniqueId. These represent the unique ID of the current process and its parent process, respectively.

DeviceProcessEvents

Therefore, we can use these two keys to search for each parent level.

Query time frame problem

For better performance and to avoid API limits or timeouts, it is best to use a precise time frame in your KQL queries.

Below are some recommended ways to specify time in your queries.

  • ProcessCreationTime for current process
  • InitiatingProcessCreationTime for parent process

Talk is Cheap, Show me the KQL

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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
let L0_startTime = datetime_add("hour", -1, datetime(2025-08-06T12:03:07.2751588Z)); // from alert first activity 
let L0_endTime = datetime(2025-08-06T12:03:07.2927671Z); // from alert last activity
let L0_DeviceId = "XXXX";
// Level 0 and 1, which means current (start) and parent process
let Level_0_1 = DeviceProcessEvents
| where DeviceId contains L0_DeviceId
| where ProcessId == ????
| where Timestamp between (L0_startTime .. L0_endTime)
| project L0_CreationTime=ProcessCreationTime, L0_Process=FileName, L0_FolderPath=FolderPath, L0_PId=ProcessId, L0_CommandLine=ProcessCommandLine, L0_AccountUpn=AccountUpn, L0_IntegrityLevel=ProcessIntegrityLevel, L0_ProcessTokenElevation=ProcessTokenElevation, L0_SHA256=SHA256, L0_IsRemoteSession=IsProcessRemoteSession, L0_ProcessUniqueId = ProcessUniqueId,
L1_CreationTime=InitiatingProcessCreationTime, L1_Process=InitiatingProcessFileName, L1_FolderPath=InitiatingProcessFolderPath, L1_PId=InitiatingProcessId, L1_CommandLine=InitiatingProcessCommandLine, L1_AccountUpn=InitiatingProcessAccountUpn, L1_IntegrityLevel=InitiatingProcessIntegrityLevel, L1_TokenElevation=InitiatingProcessTokenElevation, L1_SHA256=InitiatingProcessSHA256, L1_IsRemoteSession=IsInitiatingProcessRemoteSession, L1_UniqueId = InitiatingProcessUniqueId,
L2_CreationTime=InitiatingProcessParentCreationTime, L2_Process=InitiatingProcessParentFileName, L2_PId=InitiatingProcessParentId
| take 1;
// Level B1, which means child process
let L0_UniqueId = toscalar(Level_0_1 | project L0_ProcessUniqueId | take 1);
let Level_B1 = DeviceProcessEvents
| where DeviceId contains L0_DeviceId
| where InitiatingProcessUniqueId == L0_UniqueId
| where Timestamp between (L0_startTime .. L0_endTime)
| project B1_CreationTime=ProcessCreationTime, B1_Process=FileName, B1_FolderPath=FolderPath, B1_PId=ProcessId, B1_CommandLine=ProcessCommandLine, B1_AccountUpn=AccountUpn, B1_IntegrityLevel=ProcessIntegrityLevel, B1_ProcessTokenElevation=ProcessTokenElevation, B1_SHA256=SHA256, B1_IsRemoteSession=IsProcessRemoteSession, B1_ProcessUniqueId = ProcessUniqueId,
L0_CreationTime=InitiatingProcessCreationTime, L0_Process=InitiatingProcessFileName, L0_FolderPath=InitiatingProcessFolderPath, L0_PId=InitiatingProcessId, L0_CommandLine=InitiatingProcessCommandLine, L0_AccountUpn=InitiatingProcessAccountUpn, L0_IntegrityLevel=InitiatingProcessIntegrityLevel, L0_TokenElevation=InitiatingProcessTokenElevation, L0_SHA256=InitiatingProcessSHA256, L0_IsRemoteSession=IsInitiatingProcessRemoteSession, L0_UniqueId = InitiatingProcessUniqueId,
L1_CreationTime=InitiatingProcessParentCreationTime, L1_Process=InitiatingProcessParentFileName, L1_PId=InitiatingProcessParentId
| take 1;
// Level 2, which means grandparent process
let L1_UniqueId = toscalar(Level_0_1 | project L1_UniqueId | take 1);
let L1_CreationTime = toscalar(Level_0_1 | project L1_CreationTime | take 1);
let L1_startTime = datetime_add('minute', -1, L1_CreationTime);
let Level_2 = DeviceProcessEvents
| where DeviceId contains L0_DeviceId
| where ProcessUniqueId == L1_UniqueId
| where Timestamp between (L1_startTime .. L0_endTime)
| project L1_CreationTime=ProcessCreationTime, L1_Process=FileName, L1_FolderPath=FolderPath, L1_PId=ProcessId, L1_CommandLine=ProcessCommandLine, L1_AccountUpn=AccountUpn, L1_IntegrityLevel=ProcessIntegrityLevel, L1_ProcessTokenElevation=ProcessTokenElevation, L1_SHA256=SHA256, L1_IsRemoteSession=IsProcessRemoteSession, L1_ProcessUniqueId = ProcessUniqueId,
L2_CreationTime=InitiatingProcessCreationTime, L2_Process=InitiatingProcessFileName, L2_FolderPath=InitiatingProcessFolderPath, L2_PId=InitiatingProcessId, L2_CommandLine=InitiatingProcessCommandLine, L2_AccountUpn=InitiatingProcessAccountUpn, L2_IntegrityLevel=InitiatingProcessIntegrityLevel, L2_TokenElevation=InitiatingProcessTokenElevation, L2_SHA256=InitiatingProcessSHA256, L2_IsRemoteSession=IsInitiatingProcessRemoteSession, L2_UniqueId = InitiatingProcessUniqueId,
L3_CreationTime=InitiatingProcessParentCreationTime, L3_Process=InitiatingProcessParentFileName, L3_PId=InitiatingProcessParentId
| take 1;
// Level 3
let L2_UniqueId = toscalar(Level_2 | project L2_UniqueId | take 1);
let L2_CreationTime = toscalar(Level_2 | project L2_CreationTime | take 1);
let L2_startTime = datetime_add('minute', -1, L2_CreationTime);
let Level_3 = DeviceProcessEvents
| where DeviceId contains L0_DeviceId
| where ProcessUniqueId == L2_UniqueId
| where Timestamp between (L2_startTime .. L0_endTime)
| project L2_CreationTime=ProcessCreationTime, L2_Process=FileName, L2_FolderPath=FolderPath, L2_PId=ProcessId, L2_CommandLine=ProcessCommandLine, L2_AccountUpn=AccountUpn, L2_IntegrityLevel=ProcessIntegrityLevel, L2_ProcessTokenElevation=ProcessTokenElevation, L2_SHA256=SHA256, L2_IsRemoteSession=IsProcessRemoteSession, L2_ProcessUniqueId = ProcessUniqueId,
L3_CreationTime=InitiatingProcessCreationTime, L3_Process=InitiatingProcessFileName, L3_FolderPath=InitiatingProcessFolderPath, L3_PId=InitiatingProcessId, L3_CommandLine=InitiatingProcessCommandLine, L3_AccountUpn=InitiatingProcessAccountUpn, L3_IntegrityLevel=InitiatingProcessIntegrityLevel, L3_TokenElevation=InitiatingProcessTokenElevation, L3_SHA256=InitiatingProcessSHA256, L3_IsRemoteSession=IsInitiatingProcessRemoteSession, L3_UniqueId = InitiatingProcessUniqueId,
L4_CreationTime=InitiatingProcessParentCreationTime, L4_Process=InitiatingProcessParentFileName, L4_PId=InitiatingProcessParentId
| take 1;
// Level 4
let L3_UniqueId = toscalar(Level_3 | project L3_UniqueId | take 1);
let L3_CreationTime = toscalar(Level_3 | project L3_CreationTime | take 1);
let L3_startTime = datetime_add('minute', -1, L3_CreationTime);
let Level_4 = DeviceProcessEvents
| where DeviceId contains L0_DeviceId
| where ProcessUniqueId == L3_UniqueId
| where Timestamp between (L3_startTime .. L0_endTime)
| project L3_CreationTime=ProcessCreationTime, L3_Process=FileName, L3_FolderPath=FolderPath, L3_PId=ProcessId, L3_CommandLine=ProcessCommandLine, L3_AccountUpn=AccountUpn, L3_IntegrityLevel=ProcessIntegrityLevel, L3_ProcessTokenElevation=ProcessTokenElevation, L3_SHA256=SHA256, L3_IsRemoteSession=IsProcessRemoteSession, L3_ProcessUniqueId = ProcessUniqueId,
L4_CreationTime=InitiatingProcessCreationTime, L4_Process=InitiatingProcessFileName, L4_FolderPath=InitiatingProcessFolderPath, L4_PId=InitiatingProcessId, L4_CommandLine=InitiatingProcessCommandLine, L4_AccountUpn=InitiatingProcessAccountUpn, L4_IntegrityLevel=InitiatingProcessIntegrityLevel, L4_TokenElevation=InitiatingProcessTokenElevation, L4_SHA256=InitiatingProcessSHA256, L4_IsRemoteSession=IsInitiatingProcessRemoteSession, L4_UniqueId = InitiatingProcessUniqueId,
L5_CreationTime=InitiatingProcessParentCreationTime, L5_Process=InitiatingProcessParentFileName, L5_PId=InitiatingProcessParentId
| take 1;
// Level 5
let L4_UniqueId = toscalar(Level_4 | project L4_UniqueId | take 1);
let L4_CreationTime = toscalar(Level_4 | project L4_CreationTime | take 1);
let L4_startTime = datetime_add('minute', -1, L4_CreationTime);
let Level_5 = DeviceProcessEvents
| where DeviceId contains L0_DeviceId
| where ProcessUniqueId == L4_UniqueId
| where Timestamp between (L4_startTime .. L0_endTime)
| project L5_CreationTime=InitiatingProcessCreationTime, L5_Process=InitiatingProcessFileName, L5_FolderPath=InitiatingProcessFolderPath, L5_PId=InitiatingProcessId, L5_CommandLine=InitiatingProcessCommandLine, L5_AccountUpn=InitiatingProcessAccountUpn, L5_IntegrityLevel=InitiatingProcessIntegrityLevel, L5_TokenElevation=InitiatingProcessTokenElevation, L5_SHA256=InitiatingProcessSHA256, L5_IsRemoteSession=IsInitiatingProcessRemoteSession, L5_UniqueId = InitiatingProcessUniqueId
| take 1;
union Level_B1, Level_0_1, Level_2, Level_3, Level_4, Level_5

Questions?

  1. Why dont search from the L2_CreationTime=InitiatingProcessParentCreationTime and L2_Process=InitiatingProcessParentFileName in Level_0_1 table?

Yes, I tried this method too, but sometimes it doesn’t find the correct results. I’m not sure why. I think this approach is still a kind of fuzzy search but with a smaller scope.

  1. How about more than 5 level?

I use a SOAR workflow with a while loop to handle more levels. KQL is just a query language and has its own limitations.

  1. Level_0_1, Level_2, Level_3, Level_4, Level_5 are ugly.

Yes, they are not ideal. Please try to improve it if possible. =]

  1. Why do you reduce 1 minute from the process start time?

Just to be safe, or maybe I don’t fully trust Microsoft Security’s timestamp accuracy.

Reference

  1. Enterprise Scale Threat Hunting with Process Tree Analysis by Mehmet Ergene
  2. DeviceProcessEvents