Adversaries may use complex process trees to evade detection by splitting malicious activity across multiple processes. SOC teams should proactively hunt for anomalous process trees in Azure Sentinel to identify potential command and control or persistence mechanisms that bypass traditional detection methods.
KQL Query
let timeframe = 48h;
// Define of which processes you want to generate process tree
let _selected_processes = dynamic(["winword.exe","excel.exe","powerpnt.exe","acrord32.exe", "FoxitPhantomPDF.exe","MicrosoftPdfReader.exe","SumatraPDF.exe"]);
// First, generate the process tree and store it in the cache.
// Renaming fields accordingly to generate a tree up to 7th level
// In each step, project only the required fields to optimize resource usage
let _process_tree_data= materialize
( DeviceProcessEvents
| where Timestamp > ago(timeframe)
| where InitiatingProcessFileName in~ (_selected_processes )
| project DeviceId,DeviceName,
InitiatingProcessG3ParentFileName=FileName,InitiatingProcessG3ParentSHA1=SHA1,InitiatingProcessG3ParentId=ProcessId, InitiatingProcessG3ParentCommandLine=ProcessCommandLine,InitiatingProcessG3ParentCreationTime=todatetime(ProcessCreationTime),
InitiatingProcessG4ParentFileName=InitiatingProcessFileName,InitiatingProcessG4ParentSHA1=InitiatingProcessSHA1,InitiatingProcessG4ParentId=InitiatingProcessId,InitiatingProcessG4ParentCommandLine=InitiatingProcessCommandLine, InitiatingProcessG4ParentCreationTime=todatetime(InitiatingProcessCreationTime)
// Start iteration
// 1st iteration of join. From now on, query all processes, rename fields, and join accordingly
| join kind=leftouter (
DeviceProcessEvents
| where Timestamp > ago(timeframe)
| project DeviceId, InitiatingProcessG2ParentFileName=FileName,InitiatingProcessG2ParentFolderPath=FolderPath,InitiatingProcessG2ParentSHA1=SHA1, InitiatingProcessG2ParentId=ProcessId, InitiatingProcessG2ParentCommandLine=ProcessCommandLine, InitiatingProcessG2ParentCreationTime=todatetime(ProcessCreationTime),
InitiatingProcessG3ParentFileName=InitiatingProcessFileName,InitiatingProcessG3ParentFolderPath=InitiatingProcessFolderPath,InitiatingProcessG3ParentSHA1=InitiatingProcessSHA1, InitiatingProcessG3ParentId=InitiatingProcessId, InitiatingProcessG3ParentCommandLine=InitiatingProcessCommandLine, InitiatingProcessG3ParentCreationTime=todatetime(InitiatingProcessCreationTime)
)
on DeviceId , InitiatingProcessG3ParentFileName, InitiatingProcessG3ParentId, InitiatingProcessG3ParentCreationTime
// 2nd iteration of join.
| join kind=leftouter (
DeviceProcessEvents
| where Timestamp > ago(timeframe)
| project DeviceId, InitiatingProcessG1ParentFileName=FileName,InitiatingProcessG1ParentFolderPath=FolderPath,InitiatingProcessG1ParentSHA1=SHA1, InitiatingProcessG1ParentId=ProcessId, InitiatingProcessG1ParentCommandLine=ProcessCommandLine, InitiatingProcessG1ParentCreationTime=todatetime(ProcessCreationTime),
InitiatingProcessG2ParentFileName=InitiatingProcessFileName,InitiatingProcessG2ParentFolderPath=InitiatingProcessFolderPath,InitiatingProcessG2ParentSHA1=InitiatingProcessSHA1, InitiatingProcessG2ParentId=InitiatingProcessId, InitiatingProcessG2ParentCommandLine=InitiatingProcessCommandLine, InitiatingProcessG2ParentCreationTime=todatetime(InitiatingProcessCreationTime)
)
on DeviceId , InitiatingProcessG2ParentFileName , InitiatingProcessG2ParentId, InitiatingProcessG2ParentCreationTime
// 3rd iteration of join.
| join kind=leftouter (
DeviceProcessEvents
| where Timestamp > ago(timeframe)
| project DeviceId, InitiatingProcessParentFileName=FileName,InitiatingProcessParentFolderPath=FolderPath,InitiatingProcessParentSHA1=SHA1, InitiatingProcessParentId=ProcessId, InitiatingProcessParentCommandLine=ProcessCommandLine, InitiatingProcessParentCreationTime=ProcessCreationTime,
InitiatingProcessG1ParentFileName=InitiatingProcessFileName,InitiatingProcessG1ParentFolderPath=InitiatingProcessFolderPath,InitiatingProcessG1ParentSHA1=InitiatingProcessSHA1, InitiatingProcessG1ParentId=InitiatingProcessId, InitiatingProcessG1ParentCommandLine=InitiatingProcessCommandLine, InitiatingProcessG1ParentCreationTime=todatetime(InitiatingProcessCreationTime)
)
on DeviceId , InitiatingProcessG1ParentFileName , InitiatingProcessG1ParentId, InitiatingProcessG1ParentCreationTime
// 4th iteration of join
| join kind=leftouter (
DeviceProcessEvents
| where Timestamp > ago(timeframe)
| project DeviceId, InitiatingProcessFileName=FileName,InitiatingProcessSHA1=SHA1, InitiatingProcessId=ProcessId, InitiatingProcessCommandLine=ProcessCommandLine, InitiatingProcessCreationTime=ProcessCreationTime,
InitiatingProcessParentFileName=InitiatingProcessFileName,InitiatingProcessParentSHA1=InitiatingProcessSHA1, InitiatingProcessParentId=InitiatingProcessId, InitiatingProcessParentCommandLine=InitiatingProcessCommandLine, InitiatingProcessParentCreationTime=InitiatingProcessCreationTime
)
on DeviceId , InitiatingProcessParentFileName , InitiatingProcessParentId, InitiatingProcessParentCreationTime
// 5th iteration of join
| join kind=leftouter (
DeviceProcessEvents
| where Timestamp > ago(timeframe)
| project Timestamp, DeviceId, FileName,SHA1, ProcessId, ProcessCommandLine, ProcessCreationTime,
InitiatingProcessFileName,InitiatingProcessSHA1, InitiatingProcessId, InitiatingProcessCommandLine, InitiatingProcessCreationTime
)
on DeviceId , InitiatingProcessFileName , InitiatingProcessId, InitiatingProcessCreationTime
);
// Use the cached results and find the rare patterns based on process names.
_process_tree_data
|summarize count() by FileName,InitiatingProcessFileName,InitiatingProcessParentFileName,InitiatingProcessG1ParentFileName,InitiatingProcessG2ParentFileName,InitiatingProcessG3ParentFileName,InitiatingProcessG4ParentFileName
| where count_ < 10 // If the count of a pattern is less than 10, it is anomalous. Threshold can be changed.
// Now, join the anomalous patterns with the original results to get the details.
| join kind=inner _process_tree_data on FileName,InitiatingProcessFileName,InitiatingProcessParentFileName,InitiatingProcessG1ParentFileName,InitiatingProcessG2ParentFileName,InitiatingProcessG3ParentFileName,InitiatingProcessG4ParentFileName
// Now, join the anomalous patterns with the original results to get the details.
|project Timestamp=case(isnotempty(Timestamp),Timestamp,isnotempty(InitiatingProcessParentCreationTime),InitiatingProcessParentCreationTime,isnotempty(InitiatingProcessG1ParentCreationTime),InitiatingProcessG1ParentCreationTime,
isnotempty(InitiatingProcessG2ParentCreationTime),InitiatingProcessG2ParentCreationTime,isnotempty(InitiatingProcessG3ParentCreationTime),InitiatingProcessG3ParentCreationTime,InitiatingProcessG4ParentCreationTime),
count_ , DeviceId, DeviceName,
InitiatingProcessG4ParentFileName,InitiatingProcessG3ParentFileName,InitiatingProcessG2ParentFileName,InitiatingProcessG1ParentFileName,InitiatingProcessParentFileName,InitiatingProcessFileName,FileName,
InitiatingProcessG4ParentCommandLine, InitiatingProcessG3ParentCommandLine, InitiatingProcessG2ParentCommandLine, InitiatingProcessG1ParentCommandLine, InitiatingProcessCommandLine, ProcessCommandLine,
InitiatingProcessG4ParentId, InitiatingProcessG4ParentCreationTime,
InitiatingProcessG3ParentId, InitiatingProcessG3ParentFolderPath ,InitiatingProcessG3ParentSHA1, InitiatingProcessG3ParentCreationTime,
InitiatingProcessG2ParentId,InitiatingProcessG2ParentFolderPath,InitiatingProcessG2ParentSHA1, InitiatingProcessG2ParentCreationTime,
InitiatingProcessG1ParentId,InitiatingProcessG1ParentFolderPath,InitiatingProcessG1ParentSHA1, InitiatingProcessG1ParentCreationTime,
InitiatingProcessParentId, InitiatingProcessParentFolderPath,InitiatingProcessParentSHA1, InitiatingProcessParentCommandLine ,InitiatingProcessParentCreationTime,
InitiatingProcessId, InitiatingProcessSHA1, InitiatingProcessCreationTime,
ProcessId, SHA1, ProcessCreationTime
| order by Timestamp, DeviceName, InitiatingProcessG4ParentCreationTime , InitiatingProcessG3ParentCreationTime , InitiatingProcessG2ParentCreationTime , InitiatingProcessG1ParentCreationTime , InitiatingProcessCreationTime
id: a3bbacd9-7e8a-4dbc-a168-d08740f9904e
name: detect-anomalous-process-trees
description: |
This query generates process trees of given processes and performs anomaly detection on the process trees. It generates process trees up to 7th level.
The query can be used as a template to perform anomaly detection on specific processes like winword.exe, powerpnt.exe, w3wp.exe, etc. The query runs without any performance issues in large environments.
Detailed explanation can be found here
Reference - https://mergene.medium.com/detecting-threats-with-process-tree-analysis-without-machine-learning-838d85f78b2c
requiredDataConnectors:
- connectorId: MicrosoftThreatProtection
dataTypes:
- DeviceProcessEvents
tactics:
- Initial access
- Execution
- Persistence
- Discovery
- Lateral movement
query: |
let timeframe = 48h;
// Define of which processes you want to generate process tree
let _selected_processes = dynamic(["winword.exe","excel.exe","powerpnt.exe","acrord32.exe", "FoxitPhantomPDF.exe","MicrosoftPdfReader.exe","SumatraPDF.exe"]);
// First, generate the process tree and store it in the cache.
// Renaming fields accordingly to generate a tree up to 7th level
// In each step, project only the required fields to optimize resource usage
let _process_tree_data= materialize
( DeviceProcessEvents
| where Timestamp > ago(timeframe)
| where InitiatingProcessFileName in~ (_selected_processes )
| project DeviceId,DeviceName,
InitiatingProcessG3ParentFileName=FileName,InitiatingProcessG3ParentSHA1=SHA1,InitiatingProcessG3ParentId=ProcessId, InitiatingProcessG3ParentCommandLine=ProcessCommandLine,InitiatingProcessG3ParentCreationTime=todatetime(ProcessCreationTime),
InitiatingProcessG4ParentFileName=InitiatingProcessFileName,InitiatingProcessG4ParentSHA1=InitiatingProcessSHA1,InitiatingProcessG4ParentId=InitiatingProcessId,InitiatingProcessG4ParentCommandLine=InitiatingProcessCommandLine, InitiatingProcessG4ParentCreationTime=todatetime(InitiatingProcessCreationTime)
// Start iteration
// 1st iteration of join. From now on, query all processes, rename fields, and join accordingly
| join kind=leftouter (
DeviceProcessEvents
| where Timestamp > ago(timeframe)
| project DeviceId, InitiatingProcessG2ParentFileName=FileName,InitiatingProcessG2ParentFolderPath=FolderPath,InitiatingProcessG2ParentSHA1=SHA1, InitiatingProcessG2ParentId=ProcessId, InitiatingProcessG2ParentCommandLine=ProcessCommandLine, InitiatingProcessG2ParentCreationTime=todatetime(ProcessCreationTime),
InitiatingProcessG3ParentFileName=InitiatingProcessFileName,InitiatingProcessG3ParentFolderPath=InitiatingProcessFolderPath,InitiatingProcessG3ParentSHA1=InitiatingProcessSHA1, InitiatingProcessG3ParentId=InitiatingProcessId, InitiatingProcessG3ParentCommandLine=InitiatingProcessCommandLine, InitiatingProcessG3ParentCre
| Sentinel Table | Notes |
|---|---|
DeviceProcessEvents | Ensure this data connector is enabled |
Scenario: Scheduled System Maintenance Task
Description: A legitimate scheduled task (e.g., schtasks.exe) runs a process tree that includes tools like taskkill.exe or net.exe to clean up temporary files or stop services.
Filter/Exclusion: Exclude processes associated with known system maintenance tasks (e.g., schtasks.exe with command-line arguments like /RUN or /CREATE for maintenance jobs).
Scenario: Software Update Deployment
Description: A patching tool like Windows Update or third-party tools like WSUS or SCCM may spawn a process tree that includes msiexec.exe, setup.exe, or patch.exe to apply updates.
Filter/Exclusion: Exclude processes related to known update mechanisms (e.g., msiexec.exe with arguments like /i or /uninstall, or setup.exe from trusted update sources).
Scenario: Backup and Restore Operations
Description: A backup tool like Veeam, Acronis, or rsync may generate a process tree that includes backup.exe, restore.exe, or rsync.exe with elevated privileges.
Filter/Exclusion: Exclude processes associated with backup/restore tools (e.g., backup.exe with known command-line arguments, or rsync.exe with specific flags like --backup).
Scenario: Administrative Task via PowerShell
Description: An admin may use PowerShell (powershell.exe) to run a script that spawns multiple processes (e.g., Get-Service, Stop-Service, or Invoke-Command) as part of routine system management.
Filter/Exclusion: Exclude PowerShell scripts or commands associated with known administrative tasks (e.g., powershell.exe with -Command or `-