Command History with Process Tree
Correlates CommandHistory events with ProcessRollup2 to show PowerShell command history together with the execution chain (parent → child process with PID). Provides context on how each PowerShell session was launched. ## How It Works 1. **Collects two event types** (Windows only): - CommandHistory: the console history of a PowerShell process, written when the process exits - ProcessRollup2: the process execution event, including parent process information 2. **Correlates via selfJoinFilter**: - Joins on aid + TargetProcessId - Keeps only processes for which both events are present 3. **Processes each event type**: - CommandHistory: splits the multi-command history field (separator: ¶) and reassembles it with newlines into CommandHistoryClean - ProcessRollup2: builds an ExecutionChain string, e.g. `cmd.exe → powershell.exe (PID: 1234)` 4. **Groups results**: - One row per PowerShell process (aid + TargetProcessId) - Each row shows the host, the execution chain, and the full cleaned command history ## Required Telemetry Falcon sensor endpoint telemetry on Windows hosts. CommandHistory is emitted at process termination, so long-running PowerShell consoles only appear after they close. ## Interpreting Results The ExecutionChain shows what spawned the PowerShell session. Unusual parents (Office applications, browsers, java.exe, wscript.exe) combined with suspicious commands in the history are the main hunting signal. ## False Positives and Tuning Most results will be legitimate administrator and automation activity. To reduce noise: - Filter on suspicious parents, e.g. add `ParentBaseFileName=/^(winword|excel|outlook|mshta|wscript)\.exe$/i` to the ProcessRollup2 case - Search the history for keywords, e.g. append `| CommandHistoryClean=/downloadstring|invoke-webrequest|frombase64string/i` - Exclude known management hosts or service accounts as needed
Detection Query
#event_simpleName=/^(CommandHistory|ProcessRollup2)$/
event_platform=Win
| selfJoinFilter(
field=[aid, TargetProcessId],
where=[
{ #event_simpleName=ProcessRollup2 },
{ #event_simpleName=CommandHistory }
]
)
| case {
#event_simpleName=CommandHistory
| CommandHistory=*
| splitString(
field=CommandHistory,
by="¶",
as=CommandHistorySplit
)
| concatArray(
CommandHistorySplit,
separator="\n",
as=CommandHistoryClean
);
#event_simpleName=ProcessRollup2
| ImageFileName=/\\(?<ChildBaseFileName>[^\\]+)$/
| ExecutionChain := format(
format="%s → %s (PID: %s)",
field=[ParentBaseFileName, ChildBaseFileName, RawProcessId]
);
}
| groupBy([aid, ComputerName, TargetProcessId], function=[
selectLast(ExecutionChain),
selectLast(CommandHistoryClean)
], limit=max)
| CommandHistoryClean=*
Author
Kuribot
Data Sources
Platforms
Tags
Raw Content
# --- Query Metadata ---
# Human-readable name for the query. Will be displayed as the title.
name: "Command History with Process Tree"
# Description of what the query does and its purpose.
description: "Correlates CommandHistory events with ProcessRollup2 to show PowerShell command history together with the execution chain (parent \u2192 child process with PID). Provides context on how each PowerShell session was launched."
# The author or team that created the query.
author: "Kuribot"
# MITRE ATT&CK technique IDs covered by this query.
mitre_ids:
- T1059.001
# The required log sources to run this query successfully in Next-Gen SIEM.
log_sources:
- Endpoint
# Tags for filtering and categorization.
tags:
- Hunting
# --- Query Content ---
# The actual CrowdStrike Query Language (CQL) code.
cql: |
#event_simpleName=/^(CommandHistory|ProcessRollup2)$/
event_platform=Win
| selfJoinFilter(
field=[aid, TargetProcessId],
where=[
{ #event_simpleName=ProcessRollup2 },
{ #event_simpleName=CommandHistory }
]
)
| case {
#event_simpleName=CommandHistory
| CommandHistory=*
| splitString(
field=CommandHistory,
by="¶",
as=CommandHistorySplit
)
| concatArray(
CommandHistorySplit,
separator="\n",
as=CommandHistoryClean
);
#event_simpleName=ProcessRollup2
| ImageFileName=/\\(?<ChildBaseFileName>[^\\]+)$/
| ExecutionChain := format(
format="%s → %s (PID: %s)",
field=[ParentBaseFileName, ChildBaseFileName, RawProcessId]
);
}
| groupBy([aid, ComputerName, TargetProcessId], function=[
selectLast(ExecutionChain),
selectLast(CommandHistoryClean)
], limit=max)
| CommandHistoryClean=*
# Explanation of the query. Uses markdown for formatting on the webpage.
explanation: |
## How It Works
1. **Collects two event types** (Windows only):
- CommandHistory: the console history of a PowerShell process, written when the process exits
- ProcessRollup2: the process execution event, including parent process information
2. **Correlates via selfJoinFilter**:
- Joins on aid + TargetProcessId
- Keeps only processes for which both events are present
3. **Processes each event type**:
- CommandHistory: splits the multi-command history field (separator: ¶) and reassembles it with newlines into CommandHistoryClean
- ProcessRollup2: builds an ExecutionChain string, e.g. `cmd.exe → powershell.exe (PID: 1234)`
4. **Groups results**:
- One row per PowerShell process (aid + TargetProcessId)
- Each row shows the host, the execution chain, and the full cleaned command history
## Required Telemetry
Falcon sensor endpoint telemetry on Windows hosts. CommandHistory is emitted at process termination, so long-running PowerShell consoles only appear after they close.
## Interpreting Results
The ExecutionChain shows what spawned the PowerShell session. Unusual parents (Office applications, browsers, java.exe, wscript.exe) combined with suspicious commands in the history are the main hunting signal.
## False Positives and Tuning
Most results will be legitimate administrator and automation activity. To reduce noise:
- Filter on suspicious parents, e.g. add `ParentBaseFileName=/^(winword|excel|outlook|mshta|wscript)\.exe$/i` to the ProcessRollup2 case
- Search the history for keywords, e.g. append `| CommandHistoryClean=/downloadstring|invoke-webrequest|frombase64string/i`
- Exclude known management hosts or service accounts as needed