Hunting Falcon
Last updated
Last updated
event_platform=win event_simpleName=ProcessRollup2 ImageSubsystem_decimal=3
event_simpleName=PeVersionInfo event_platform=win NOT LanguageId_decimal IN (1033, 0)
| rex field=FilePath "(\\\\Device\\\\HarddiskVolume\d+)?(?<trimmedFilePath>.*)"
| search "Users"
| regex trimmedFilePath!=".*\\\(Google|boot\\efi)\\\.*"
| regex FileName!=".*\.LocalizedResources\..*"
| stats count(aid) as uniqueEndpoints, values(FileName) as fileNames, values(OriginalFilename) as originalFileNames, values(trimmedFilePath) as filePaths by SHA256HashData, LanguageId_decimal
| sort -occurrences
| lookup local=true LanguageId.csv LanguageId_decimal OUTPUT lcid_lang, lcid_string
| table SHA256HashData, fileNames, originalFileNames, filePaths, uniqueEndpoints, LanguageId_decimal, lcid_lang, lcid_string
| rename SHA256HashData as SHA256, fileNames as "File Names", originalFileNames as "Original FileNames", filePaths as "File Paths", uniqueEndpoints as "Endpoints", LanguageId_decimal as "Language ID", lcid_lang as "LCID Code", lcid_string as "LCID String"
Explanation:
🔸When a Portable Executable (PE) file is written to disk or loaded, the sensor will generate the PeVersionInfo
event. There is quite a bit of useful information contained within: FileVersion
, OriginalFileName
, etc. The field that is usually overlooked, that we’ll zoom-in on today, is LanguageId_decimal
.
🔸The field LanguageId_decimal
is mapped to the Windows Language Code Identifier (LCID) value as specified by Microsoft. The Microsoft article requires the download of a PDF or DOCX file to view it, but you can see an extrapolated table at this website.
🔸So the general hypothesis is: if I see a low prevalence PE file being written or loaded that has an unexpected LCID value for my environment, that might be a point of interest to start a hunt and/or investigation.
(event_simpleName=ProcessRollup2 OR event_simpleName=SyntheticProcessRollup2) AND FileName=WerFault.exe AND ParentBaseFileName=prunsrv-amd64.exe
| rename TargetProcessId_decimal AS ContextProcessId_decimal, FileName as crashProcessor, ParentBaseFileName as crashingProgram, RawProcessId_decimal as osPID
| join aid, ContextProcessId_decimal
[search event_simpleName=DmpFileWritten]
| table timestamp aid ComputerName UserName crashProcessor crashingProgram TargetFileName ContextProcessId_decimal, osPID
| sort + timestamp
| eval timestamp=timestamp/1000
| convert ctime(timestamp)
| rename ComputerName as endpointName, UserName as userName, TargetFileName as dmpFile, ContextTimeStamp_decimal, as crashTime, ContextProcessId_decimal as falconPID
Explanation: Problematic programs. Software wonkiness. LSASS pilfering. Dump files on Windows are rarely good news. This week, we're going to do some statistical analysis on problematic programs that are creating a large numbers of dump files, locate those dump files, and upload them to the Falcon cloud for triage. What we are absolutely NOT going to do is make jokes about dump files, log purges, flushing the cache, etc. That is in no way appropriate and we would never think of using cheap toilet humor like that for a laugh.
Step 1 - The Event
When a Windows process crashes, for any reason, it typically goes through a standard two step process. In the first step, the crashing program spawns werfault.exe
. In the second step, werfault.exe
writes a dump file (usually with a .dmp
extension, but not always) to disk.
In the first part of our journey, since we're concerned about things spawning werfault.exe
, we'll use the ProcessRollup2
event. You can view all those events (there are a lot of them!) with the following query:
event_platform=win (event_simpleName=ProcessRollup2 OR event_simpleName=SyntheticProcessRollup2)
NOTE: Falcon emits an event called SyntheticProcessRollup2
when a process on a system starts before the sensor is there. Example: Let's say you install Falcon for the first time, right this very second, on the computer you're currently using. Unlike some other endpoint solutions (you know who you are!), you do not need to restart the system in order for prevention to work and for EDR data to be collected and correlated. But Falcon just arrived on your system, and your system is running, so there are some programs that are in flight already. Falcon takes a good, hard look at the system and emits SyntheticProcessRollup2
events for these processes so lineage can be properly recorded, the Falcon Situational Model can be built on the endpoint, and preventions enforced.
Step 2 - FileName and ParentBaseFileName Pivot
What we need to do now is to refine our query a bit as, at present, we're just looking at every Windows process execution. We'll want to key in on two things: (1) when is WerFault.exe
running (2) what is invoking it. For this we can use the fields FileName
and ParentBaseFileName
. Let's get all the WerFault.exe
executions first. To do that, we'll just add one argument to our query:
event_platform=win (event_simpleName=ProcessRollup2 OR event_simpleName=SyntheticProcessRollup2) AND FileName=werfault.exe
Now we should be looking at all executions of WerFault.exe
.
Fun fact: the "wer" in the program name stands for "Windows Error Reporting."
Step 3 - Statistical Analysis of What's Crashing
What we want to do now is either: (1) figure out what programs seems to be crashing a lot (operational use case) or (2) figure out what programs aren't really crashing that much and what are the dump files (hunting use case).
With the query above we have all the data we need, it just needs to be organized using stats
. Here we go...
event_platform=win (event_simpleName=ProcessRollup2 OR event_simpleName=SyntheticProcessRollup2) AND FileName=werfault.exe
| stats dc(aid) as endpointCount count(aid) as crashCount by ParentBaseFileName
| sort - crashCount
| rename ParentBaseFileName as crashingProgram
Here's what we're doing:
by ParentBaseFileName
: if the ParentBaseFileName
(this is the thing invoking WerFault) is the same, treat the events as a dataset and perform the following stats
commands.
| stats dc(aid) as endpointCount count(aid) as crashCount
: perform a distinct count on the field aid
and name the output endpointCount
. Perform a raw count on the field aid
and name the output crashCount
.
| sort - crashCount
: sort the values in the column crashCount
from highest to lowest.
| rename ParentBaseFileName as crashingProgram
: unnecessarily rename ParentBaseFileName
to crashingProgram
so it matches the rest of the output and Andrew-CS's eye doesn't start twitching.
A few quick notes...
You can change the sort if you would like to see the field crashCount
organized lowest to highest. Just change the -
to a +
like this (or click on that column in the UI):
| sort + crashCount
I personally like using stats
, but you can cheat and use common
and rare
when evaluating things like we are.
Examples:
event_platform=win (event_simpleName=ProcessRollup2 OR event_simpleName=SyntheticProcessRollup2) AND FileName=werfault.exe
Or...
event_platform=win (event_simpleName=ProcessRollup2 OR event_simpleName=SyntheticProcessRollup2) AND FileName=werfault.exe | common ParentBaseFileName limit=25
You can change the limit
value to whatever you desire (5, 10, 500, etc.).
Okay, back to our original query using stats. As a sanity check, it should look something like this: https://imgur.com/a/2Spsqup
Step 4 - Isolate a Dump File
In my example, I see prunsrv-amd64.exe
crashing one time on a single system. So what we're going to do, in my example, is: isolate that process, locate it's dump file, and upload it to Falcon via Real-Time Response (RTR).
What we need to do now is link two events together, the process execution event for WerFault and the dump file event for whatever it created (DmpFileWritten
).
This is the query:
(event_simpleName=ProcessRollup2 OR event_simpleName=SyntheticProcessRollup2) AND FileName=WerFault.exe AND ParentBaseFileName=prunsrv-amd64.exe
| rename TargetProcessId_decimal AS ContextProcessId_decimal, FileName as crashProcessor, ParentBaseFileName as crashingProgram, RawProcessId_decimal as osPID
| join aid, ContextProcessId_decimal [search event_simpleName=DmpFileWritten]
As you can see, we've added AND ParentBaseFileName=prunsrv-amd64.exe
to the first line of the query to isolate that program. Here's what the rest is doing:
| rename TargetProcessId_decimal AS ContextProcessId_decimal, FileName as crashProcessor, ParentBaseFileName as crashingProgram, RawProcessId_decimal as osPID
: this is a bunch of field renaming. The very important one, is renaming TargetProcessId_decimal
to ContextProcessId_decimal
since the event DmpFileWritten
is a context event. This is how we'll be linking these two together.
| join aid, ContextProcessId_decimal
: here is the join
statement. We're saying, "take the values of aid
and ContextProcessId_decimal
, then search for the matching corresponding values in the event below and combine them.
[search event_simpleName=DmpFileWritten]
: this is the sub-search and the event we're looking to combine with our process execution event. Note sub-searches always have to be in braces.
We'll add some quick formatting so the output is prettier:
(event_simpleName=ProcessRollup2 OR event_simpleName=SyntheticProcessRollup2) AND FileName=WerFault.exe AND ParentBaseFileName=prunsrv-amd64.exe
| rename TargetProcessId_decimal AS ContextProcessId_decimal, FileName as crashProcessor, ParentBaseFileName as crashingProgram, RawProcessId_decimal as osPID
| join aid, ContextProcessId_decimal [search event_simpleName=DmpFileWritten]
| table timestamp aid ComputerName UserName crashProcessor crashingProgram TargetFileName ContextProcessId_decimal, osPID
| sort + timestamp | eval timestamp=timestamp/1000 | convert ctime(timestamp)
| rename ComputerName as endpointName, UserName as userName, TargetFileName as dmpFile, ContextTimeStamp_decimal, as crashTime, ContextProcessId_decimal as falconPID
Don't forget to substitute out prunsrv-amd64.exe
in the first line to whatever you want to isolate.
Just as a sanity check, you should have some output that looks like this: https://imgur.com/a/r4fneBo
Step 5 - Dump File Acquisition
If you look in the above screen shot, you'll see we have the complete file path of the .dmp file. Now, we can use RTR to grab that file for offline examination. Just initiate an RTR with the system in question (or use PSFalcon!) and run: get C:\Windows\System32\config\systemprofile\AppData\Local\CrashDumps\prunsrv-amd64.exe.1820.dmp Application In The Wild
This week's use-case is operational with some hunting adjacencies. You can quickly see (using steps 1-3) which programs in your environment are crashing most frequently or least frequently and, if desired, acquire the dump files (using steps 4-5). You can (obviously) hunt more broadly over the DmpFileWritten
event and look for unexpected dumps.
Bonus: when a system blue screens for any reason (the dreaded BSOD!) Falcon emits an event called CrashNotification
... if you want to go hunting for those as well!
event_simpleName="NetworkConnectIP4"
| rename ContextProcessId_decimal as TargetProcessId_decimal
| join TargetProcessId_decimal
[ search event_simpleName=ProcessRollup2 ]
| table RemoteIP RemotePort_decimal ImageFileName UserName UserSid_readabl
event_simpleName="DnsRequest"
| rename ContextProcessId as TargetProcessId
| join TargetProcessId
[ search (event_simpleName="ProcessRollup2" OR event_simpleName="SyntheticProcessRollup2") AND FileName="explorer.exe"
| rename TargetProcessId_decimal as ParentProcessId_decimal
| join ParentProcessId_decimal
[ search event_simpleName="ProcessRollup2" FileName="powershell.exe" ]]
| table ComputerName timestamp ImageFileName DomainName CommandLine