Skip to content

EVTX Log Forensics & High-Speed Parsing

Techniques for extracting and analyzing Windows Event Logs (.evtx) with maximum performance using Get-WinEvent and structured hashtable filters.


⚡ Performance Rule: Always Use -FilterHashtable

Section titled “⚡ Performance Rule: Always Use -FilterHashtable”

[!WARNING] Never pipe unfiltered logs to Where-Object (e.g. Get-WinEvent -LogName Security | Where {$_.Id -eq 4624}). This forces PowerShell to instantiate millions of .NET event objects in memory. Always push the filter directly to the Windows Event Log API via -FilterHashtable.


Event IDLogDescriptionKey Forensic Value
4688SecurityA new process has been createdProcess name, PID, PPID, ProcessCommandLine
4624SecuritySuccessful account logonLogonType (2=Interactive, 3=Network, 10=RDP), Source IP
4625SecurityFailed account logonFailureReason, SubStatus, Source IP, Target Account
4672SecuritySpecial privileges assigned to new logonAdministrator privilege assignment (SeDebugPrivilege)
4697 / 7045Sec / SysA service was installedServiceName, ImagePath (Persistence indicator)
4720SecurityA user account was createdRogue user creation, Creator user
4728 / 4732SecurityMember added to security groupPrivilege escalation to local Administrators group
4104PS-OpScript block executionFull decrypted script text (Script Block Logging)
1102 / 104Sec / SysThe audit log was clearedAnti-forensics / log tampering evidence

1. Hunting Process Creation (Event 4688) with Command Lines

Section titled “1. Hunting Process Creation (Event 4688) with Command Lines”
  • 🎯 What it looks for: Windows Security Log Event ID 4688 records containing suspicious reconnaissance, download, or privilege escalation commands (powershell, cmd, net, whoami, vssadmin, certutil, bitsadmin).
  • 💡 Why it matters: When Command Line Process Auditing is enabled, Event 4688 captures the complete argument string passed to any newly spawned process, including the launching parent process and executing user context. This reconstructs the adversary’s exact interactive terminal commands even if shell histories are cleared.
Terminal window
# Extract Event 4688 in the last 2 hours matching suspicious commands
$filter = @{
LogName = 'Security'
Id = 4688
StartTime = (Get-Date).AddHours(-2)
}
Get-WinEvent -FilterHashtable $filter | ForEach-Object {
$xml = [xml]$_.ToXml()
$eventData = $xml.Event.EventData.Data
$procName = ($eventData | Where-Object { $_.Name -eq 'NewProcessName' }).'#text'
$cmdLine = ($eventData | Where-Object { $_.Name -eq 'CommandLine' }).'#text'
$parent = ($eventData | Where-Object { $_.Name -eq 'ParentProcessName' }).'#text'
$user = ($eventData | Where-Object { $_.Name -eq 'TargetUserName' }).'#text'
[PSCustomObject]@{
TimeCreated = $_.TimeCreated
User = $user
Process = $procName
Parent = $parent
CommandLine = $cmdLine
}
} | Where-Object { $_.CommandLine -match '(powershell|cmd|net|whoami|vssadmin|certutil|bitsadmin)' } |
Format-Table -AutoSize

2. RDP and Remote Network Logons (Event 4624, Logon Types 3 & 10)

Section titled “2. RDP and Remote Network Logons (Event 4624, Logon Types 3 & 10)”
  • 🎯 What it looks for: Successful authentication events (Event 4624) categorized as Network logons (Logon Type 3) or Remote Interactive / Remote Desktop logons (Logon Type 10), filtering out local system loopback addresses.
  • 💡 Why it matters: Lateral movement across a cloud-managed network manifests as inbound remote connections. Correlating the source IP address with the target user account reveals which internal device or external VPN/bastion address was used to pivot onto the machine.
Terminal window
# Filter for Network (Type 3) and Remote Desktop (Type 10) logons
$filter = @{
LogName = 'Security'
Id = 4624
StartTime = (Get-Date).AddDays(-1)
}
Get-WinEvent -FilterHashtable $filter | ForEach-Object {
$xml = [xml]$_.ToXml()
$data = $xml.Event.EventData.Data
$logonType = ($data | Where-Object { $_.Name -eq 'LogonType' }).'#text'
$targetUser = ($data | Where-Object { $_.Name -eq 'TargetUserName' }).'#text'
$ipAddress = ($data | Where-Object { $_.Name -eq 'IpAddress' }).'#text'
if ($logonType -in @('3', '10') -and $ipAddress -ne '-' -and $ipAddress -ne '127.0.0.1') {
[PSCustomObject]@{
TimeCreated = $_.TimeCreated
User = $targetUser
LogonType = if ($logonType -eq '10') { 'RDP (10)' } else { 'Network (3)' }
SourceIP = $ipAddress
}
}
} | Format-Table -AutoSize

3. PowerShell Script Block Deobfuscation (Event 4104)

Section titled “3. PowerShell Script Block Deobfuscation (Event 4104)”
  • 🎯 What it looks for: PowerShell Operational Log Event 4104 entries containing in-memory decompilation and execution patterns (FromBase64String, DownloadString, WebClient, GzipStream, Invoke-Expression, IEX).
  • 💡 Why it matters: Script Block Logging records the complete content of script blocks executed by PowerShell, regardless of whether the code was executed from a file, invoked interactively, or piped through multiple layers of Base64/XOR obfuscation. PowerShell de-obfuscates the code immediately prior to execution, and Event 4104 captures this final decrypted payload.
Terminal window
# Extract suspicious PowerShell script block executions
$filter = @{
LogName = 'Microsoft-Windows-PowerShell/Operational'
Id = 4104
StartTime = (Get-Date).AddDays(-3)
}
Get-WinEvent -FilterHashtable $filter | ForEach-Object {
$xml = [xml]$_.ToXml()
$scriptText = $xml.Event.UserData.ExecuteCommandHierarchy.ScriptBlockText
if ($scriptText -match '(FromBase64String|DownloadString|WebClient|GzipStream|Invoke-Expression|IEX)') {
[PSCustomObject]@{
TimeCreated = $_.TimeCreated
ScriptText = $scriptText.Substring(0, [Math]::Min(300, $scriptText.Length)) # Preview first 300 chars
}
}
} | Format-List

4. Direct Offline .evtx File Analysis (XPath Filtering)

Section titled “4. Direct Offline .evtx File Analysis (XPath Filtering)”
  • 🎯 What it looks for: Queries raw forensic .evtx files exported from a disk image or live response collection, using high-speed XPath queries to extract log clearing attempts (Event 1102), rogue user account creation (Event 4720), and new service installations (Event 7045).
  • 💡 Why it matters: Forensic analysts frequently triage triage packages or disk images on an analytical workstation without mounting the live operating system. XPath filtering directly queries the offline XML structure, returning critical indicators of compromise in seconds without needing third-party GUI viewers.
Terminal window
# Parse a carved or collected offline EVTX file from a forensic image
$evtxPath = "C:\Forensics\Triage\Security.evtx"
Get-WinEvent -Path $evtxPath -FilterXPath "*[System[(EventID=1102 or EventID=4720 or EventID=7045)]]" |
Select-Object TimeCreated, Id, Message |
Format-Table -Wrap