Reconstructing PowerShell scripts from multiple Windows event logs
Although not a strict pre-requisite, please check out the article below which explains about various types of artifacts left or generated by PowerShell script/module execution.
PowerShell Command History Forensics — Blog — Sophos Labs — Sophos Community
Sometimes while going through Microsoft-Windows-PowerShell/Operational Windows Event Logs, you may encounter the execution of suspicious PowerShell code logged Event ID 4104. The code could be simple commands or part of a bigger-badder .PS1 file.
C:\Windows\System32\winevt\Logs\Microsoft-Windows-PowerShell%4Operational.evtx
The PowerShell script (.ps1) might be deleted by the threat actor but Windows Script Block logging to the rescue!
But there’s just one challenge,
I tried to use various open-source tools such as,
- DeepBlueCLI
- Countercept/chainsaw
- EVTXecmd
but I couldn’t find a point-and-shoot way to extract the complete PowerShell script from within the Event Logs.
The answer lay hidden here: Script Tracing and Logging — PowerShell | Microsoft Docs
$created = Get-WinEvent -FilterHashtable @{ ProviderName="Microsoft-Windows-PowerShell"; Id = 4104 } |
Where-Object { $_.<...> }
$sortedScripts = $created | sort { $_.Properties[0].Value }
$mergedScript = -join ($sortedScripts | % { $_.Properties[2].Value })
That’s it — Microsoft expects the on-looker to grasp the concept just like that. Well, that’s not the case for some audiences so I thought of writing this blog to hand-hold you through the entire process.
Say Hello to PowerShell ISE
The WHAT
Once you discover the Script which is spread across 121 Event Log entries, take a note of the ScriptBlock ID.
Open PowerShell ISE and execute the command after replacing the location of your Event Log (EVTX) & ScriptBlock ID:
$StoreArrayHere = Get-WinEvent -FilterHashtable @{ Path="C:\EVTXSamples\Microsoft-Windows-PowerShell%4Operational.evtx"; ProviderName=“Microsoft-Windows-PowerShell”; Id = 4104 } | Where-Object { $_.Message -like '*51baf005-40a5-4878-ab90-5ecc51cab9af*' }$SortIt = $StoreArrayHere | sort { $_.Properties[0].Value }$SortIt | select ActivityId,Message
That does not seem right. It looks like we only have a partial script recorded in the Event Logs i.e. 97 to 121. We seem to be missing 1–96
That’s OK. I wanted to show you that due to log rotation, you might miss a snippet or two, but for an Incident Responder, it’s important to extract and look for cues in whatever data you have.
Options:
- Remove the Where-Object filter and find out which script blocks have been completely recorded in the Event Logs.
$StoreArrayHere = Get-WinEvent -FilterHashtable @{ Path="C:\EVTXSamples\Microsoft-Windows-PowerShell%4Operational.evtx"; ProviderName=“Microsoft-Windows-PowerShell”; Id = 4104 }$SortIt = $StoreArrayHere | sort { $_.Properties[0].Value }$SortIt | select ActivityId,Message
2. Using Event Log Explorer or Windows Event Viewer, find out another ScriptBlock ID of interest.
Turns out, we were able to capture a few scripts. We filtered using one of the ScriptBlock ID entries from the list,
$StoreArrayHere = Get-WinEvent -FilterHashtable @{ Path="Microsoft-Windows-PowerShell%4Operational.evtx"; ProviderName=“Microsoft-Windows-PowerShell”; Id = 4104 } | Where-Object { $_.Message -like '*97b04021-6c0b-4fd2-8f57-39ada2599db8*' }$SortIt = $StoreArrayHere | sort { $_.Properties[0].Value }$SortIt | select ActivityId,Message
The HOW
To restore this script to disk,
$StoreArrayHere = Get-WinEvent -FilterHashtable @{ Path="Microsoft-Windows-PowerShell%4Operational.evtx"; ProviderName=“Microsoft-Windows-PowerShell”; Id = 4104 } | Where-Object { $_.Message -like '*97b04021-6c0b-4fd2-8f57-39ada2599db8*' }$SortIt = $StoreArrayHere | sort { $_.Properties[0].Value }$MergedScript = -join ($SortIt | % { $_.Properties[2].Value }) | Out-File SomeBadScript.ps1
Update
Due to the amazing response I got on this, I wrote (and tested on real-world EVTX files) a PowerShell script which you can use to simply dump all the PS1/Commands contained in an offline EVTX:
Once again, thank you!