CrowdStrike Falcon uses LogScale — formerly Humio — as its underlying query and search engine. The query language is fast and powerful but has a different structure from Splunk SPL or Sentinel KQL, and the documentation assumes you already know what you're doing. This guide covers the language from the ground up, the way you'd need to know it to build real detections in a production SOC environment.
How LogScale Queries Work
LogScale queries are pipeline-based. Each step in the pipeline takes the output of the previous step and transforms it. The pipe character `|` separates steps. This is similar to Splunk's pipe syntax, but LogScale's filter functions work differently and the field naming conventions are specific to CrowdStrike's event schema.
The basic structure is: filter events → extract or transform fields → aggregate or format output.
// Step 1: filter by event type #event_simpleName=ProcessRollup2 // Step 2: filter by field value FileName="powershell.exe" // Step 3: transform — extract fields, rename, compute | rename(field=ComputerName, as=Hostname) // Step 4: aggregate | groupBy([Hostname, UserName], function=count()) // Step 5: sort output | sort(_count, order=desc)
The CrowdStrike Event Schema
CrowdStrike events in LogScale follow a consistent naming convention. Understanding this is the first step to writing effective queries — if you use the wrong field name, the query returns nothing without an error, which makes debugging frustrating.
Core fields present on every event
| Field | Description | Example |
|---|---|---|
| #event_simpleName | The event type — most important filter field | ProcessRollup2, DnsRequest, NetworkConnectIP4 |
| @timestamp | Event timestamp in milliseconds | Used with | timechart or time range filters |
| ComputerName | Hostname of the endpoint | WORKSTATION-001 |
| UserName | Username associated with the event | DOMAIN\jsmith |
| aid | Agent ID — unique per sensor install | Used to correlate events across types |
| cid | Customer ID — your tenant identifier | Same across all your events |
Process event fields (ProcessRollup2)
| Field | Description |
|---|---|
| FileName | Name of the process binary (e.g. powershell.exe) |
| ImageFileName | Full path to the process binary |
| CommandLine | Full command line including arguments |
| ParentBaseFileName | Name of the parent process binary |
| ParentProcessId | PID of the parent process |
| ProcessId | PID of this process |
| SHA256HashData | SHA256 hash of the binary |
| UserSid | SID of the user running the process |
CrowdStrike field names are case-sensitive. `FileName` works. `filename` returns nothing. `FILENAME` returns nothing. This catches analysts coming from Splunk or KQL where field matching is often case-insensitive. Always check the exact field name in a sample event before building a detection.
Filter Syntax
Exact match
// String exact match — case sensitive by default FileName="powershell.exe" // Case-insensitive match using regex flag FileName=/powershell\.exe/i // Numeric match ProcessId=1234 // NOT match FileName!="explorer.exe"
IN list matching
// Match any of a list of values FileName IN ["powershell.exe", "pwsh.exe", "cmd.exe"] // Case-insensitive IN ParentBaseFileName IN~ ["winword.exe", "excel.exe", "outlook.exe"] // NOT IN — exclude a list FileName NOT IN ["svchost.exe", "lsass.exe", "csrss.exe"]
Wildcard and regex matching
// Wildcard — * matches any characters ImageFileName=*\powershell.exe // Regex — wrap in forward slashes CommandLine=/(-enc|-encodedcommand)/i // Regex with anchors ImageFileName=/\/powershell\.exe$/i // Contains substring (use regex for this) CommandLine=/http/i
Field existence and null checks
// Field exists and has a value CommandLine=* // Field does not exist or is empty CommandLine!=* // Using test() for complex conditions | test(ProcessId > 1000)
Aggregation Functions
Aggregations summarize event data across a time window or group. These are what turn raw event streams into detection signals.
// Count events, grouped by fields | groupBy([ComputerName, UserName], function=count()) // Count with custom output field name | groupBy([ComputerName], function=count(as=EventCount)) // Count distinct values | groupBy([UserName], function=count(field=ComputerName, distinct=true, as=UniqueHosts)) // Collect values into an array | groupBy([UserName], function=collect([ComputerName, FileName])) // Multiple aggregations in one groupBy | groupBy([ComputerName], function=[ count(as=TotalEvents), count(field=UserName, distinct=true, as=UniqueUsers), max(field=@timestamp, as=LastSeen) ])
Time Range and Time Series
// Time chart — count events per time bucket | timechart(span=1h, function=count()) // Filter to specific time window in query | where(@timestamp > now() - 1h) // Time since event (in milliseconds) | eval(age = now() - @timestamp) | eval(ageMinutes = age / 60000)
Regex Extraction
Regex extraction pulls structured data out of unstructured fields like command lines. This is essential for parsing encoded commands, URLs, file paths, and other variable-length strings.
// Extract encoded command from PowerShell command line | regex( "(?i)-(?:enc|encodedcommand)\s+(?P<EncodedPayload>[A-Za-z0-9+/=]{20,})", field=CommandLine ) // Extract domain from DNS request | regex( "(?P<TLD>[^.]+\.[^.]+)$", field=DomainName ) // Extract username from UPN format | regex( "(?P<Username>[^@]+)@", field=UserPrincipalName ) // Named capture groups become new fields automatically
Joins and Lookups
// Find processes that also made network connections #event_simpleName=ProcessRollup2 FileName="powershell.exe" | join( query={ #event_simpleName=NetworkConnectIP4 | groupBy([ContextProcessId], function=collect([RemoteAddressIP4, RemotePort])) }, field=ProcessId, include=[RemoteAddressIP4, RemotePort] )
Production Detection Patterns
Suspicious process spawning
#event_simpleName=ProcessRollup2 ParentBaseFileName IN~ [ "winword.exe", "excel.exe", "outlook.exe", "powerpnt.exe" ] FileName IN~ [ "powershell.exe", "cmd.exe", "wscript.exe", "cscript.exe", "mshta.exe", "rundll32.exe" ] | table([@timestamp, ComputerName, UserName, ParentBaseFileName, FileName, CommandLine]) | sort(@timestamp, order=desc)
Lateral movement — NTLM authentication sweep
#event_simpleName=UserLogon LogonType=3 AuthenticationPackage="NTLM" // Exclude machine accounts UserName!=*$ | groupBy([UserName, RemoteIP], function=[ count(as=AuthCount), count(field=ComputerName, distinct=true, as=TargetHosts), collect([ComputerName]) ]) | where(TargetHosts >= 3) | sort(TargetHosts, order=desc)
Data exfiltration — large DNS query volume
#event_simpleName=DnsRequest // Filter out common benign domains DomainName!=*.microsoft.com DomainName!=*.windows.com DomainName!=*.windowsupdate.com | groupBy([ComputerName, DomainName], function=count(as=QueryCount)) | where(QueryCount > 100) // High query volume to single domain = possible exfil via DNS | sort(QueryCount, order=desc)
Common Mistakes in LogScale Queries
- Using wrong event type — always start with `#event_simpleName` filter. Without it the query scans all events and returns mixed results that are hard to interpret
- Case-sensitive field names — `FileName` not `filename`. Check a raw event to confirm exact field names before writing a detection
- Forgetting to exclude machine accounts — most user-focused detections need `UserName!=*$` to remove computer accounts that end with a dollar sign
- Using `=` for substring matching — `FileName="power"` only matches exactly the string "power", not "powershell.exe". Use regex `/power/i` for substring matching
- Not specifying time range — without a time range LogScale defaults to a short recent window. For detections, always set explicit time ranges or use the scheduled search time window
Every issue that covers CrowdStrike includes a complete LogScale query — field names verified, false positive suppression tuned, investigation steps included, MITRE mapping attached. Rotating across CrowdStrike, Sentinel, and Splunk weekly.
Join — $14.99/mo See a Sample Issue