Case file: the payload that hides in plain sight
PowerShell is a system administrator's best friend and an incident responder's recurring nightmare. In case after case, the initial access vector — a macro, a malicious LNK, a drive-by download — does one thing: it launches a PowerShell command line so mangled it looks like keyboard static. Strings reversed, variables named with random characters, the whole payload compressed and Base64-encoded three layers deep. That obfuscation exists for one reason: to slip past the Antimalware Scan Interface (AMSI), the layer that lets Defender inspect script content before it executes.
This hunt targets two linked behaviors. First, obfuscated PowerShell execution — command lines and script blocks that use encoding, string manipulation, or dynamic invocation to hide intent (T1027 Obfuscated Files or Information, T1059.001 PowerShell). Second, AMSI bypass attempts — explicit techniques like patching amsi.dll in memory, setting amsiInitFailed, or disabling script-block logging, mapped to T1562.001 Impair Defenses. Either one alone is worth a look; together, they're a strong signal of malicious intent.
The hypothesis
If an attacker is executing malicious PowerShell in our environment, then we will find PowerShell processes with command lines containing encoding flags (-enc/-EncodedCommand), reflection or dynamic-invocation primitives (FromBase64String, Invoke-Expression, IEX), or known AMSI-bypass strings — especially when launched by Office apps, browsers, or script hosts rather than by administrators.
Data you'll need
| Log source | Table / index | What it gives you |
|---|---|---|
| Microsoft Defender for Endpoint | DeviceProcessEvents, DeviceEvents | Full PowerShell command lines; AMSI-related detections and tamper events |
| PowerShell Script Block Logging | EventCode 4104 (index=wineventlog) | De-obfuscated script content — what actually ran |
| Sysmon | EventCode 1 | Parent-child chains for the launching process |
Hunting with KQL
This query scores PowerShell executions against a stack of obfuscation and AMSI-bypass indicators, so the most suspicious launches surface first.
// Hunt: obfuscated PowerShell + AMSI bypass indicators, scored
let ObfuscationMarkers = dynamic([
"-enc", "-EncodedCommand", "-e ", "FromBase64String",
"Invoke-Expression", "IEX", "Invoke-Mimikatz",
"-w hidden", "-windowstyle hidden", "-noni", "-NoProfile"]);
let AmsiBypassMarkers = dynamic([
"amsiInitFailed", "AmsiUtils", "amsi.dll",
"System.Management.Automation.AmsiUtils",
"amsiContext", "NonPublic,Static", "Set-MpPreference",
"DisableRealtimeMonitoring", "Reflection.Assembly"]);
DeviceProcessEvents
| where TimeGenerated > ago(14d)
| where FileName in~ ("powershell.exe", "pwsh.exe")
| extend HasObfuscation = ProcessCommandLine has_any (ObfuscationMarkers),
HasAmsiBypass = ProcessCommandLine has_any (AmsiBypassMarkers),
LaunchedBySuspiciousParent = InitiatingProcessFileName in~
("winword.exe", "excel.exe", "outlook.exe", "mshta.exe",
"wscript.exe", "cscript.exe", "rundll32.exe", "iexplore.exe", "chrome.exe")
| where HasObfuscation or HasAmsiBypass
| extend SuspicionScore = (iff(HasObfuscation, 1, 0)
+ iff(HasAmsiBypass, 3, 0) + iff(LaunchedBySuspiciousParent, 2, 0))
| project TimeGenerated, DeviceName, InitiatingProcessFileName,
InitiatingProcessCommandLine, ProcessCommandLine,
InitiatingProcessAccountName, HasObfuscation, HasAmsiBypass,
LaunchedBySuspiciousParent, SuspicionScore
| order by SuspicionScore desc, TimeGenerated desc
What this does: every PowerShell launch in the window is checked against two marker lists. AMSI-bypass strings score triple because they indicate deliberate defense evasion rather than mere obfuscation. A suspicious parent process (Office, browsers, script hosts) adds weight. The result: the query ranks itself, and your triage starts at the top.
Decode the payload. A -enc argument is Base64-encoded UTF-16LE. In your analysis environment — never on the production host — decode it to see the underlying intent:
// Decode a captured -EncodedCommand argument offline
$b64 = "<paste the base64 string here>"
[System.Text.Encoding]::Unicode.GetString(
[System.Convert]::FromBase64String($b64))
Example: what a true-positive result row looks like
| TimeGenerated | 2026-09-19 14:02:41 UTC |
| DeviceName | WS-SALES-0117 |
| InitiatingProcessFileName | winword.exe |
| ProcessCommandLine | powershell -nop -w hidden -enc SQBmACgAWwBJAG4AdABQAHQAcgBdADoAOgBTAGkAegBlACAAPQAgADQAKQAuAEMAbwBuAHQAYQBpAG4AcwAoACIAYQBtAHMAaQBJAG4AaQB0AEYAYQBpAGwAZQBkACIAKQA= |
| Decoded | If([IntPtr]::Size -eq 4){... .Contains("amsiInitFailed") ...} — reflects over AMSI internals to flip the init-failed flag |
| SuspicionScore | 6 (obfuscation + AMSI bypass + Office parent) |
Hunting with Splunk
In Splunk, Script Block Logging (EventCode 4104) is your best friend — it records the de-obfuscated script, so AMSI-bypass code is visible even when the command line was encoded:
index=wineventlog EventCode=4104 earliest=-14d | where match(ScriptBlockText, "(?i)(amsiInitFailed|AmsiUtils|amsi\.dll|amsiContext)") OR match(ScriptBlockText, "(?i)(FromBase64String|Invoke-Mimikatz|Invoke-Shellcode|DownloadString|DownloadFile)") | eval decoded_preview=substr(ScriptBlockText, 1, 300) | table _time, host, user, MessageNumber, decoded_preview, ScriptBlockText
What this does: it scans every logged script block for AMSI-tampering strings and common malicious .NET/download primitives, showing a 300-character preview so you can triage without pulling full script text. Complement it with a command-line sweep over Sysmon process creation:
index=sysmon EventCode=1 Image="*powershell.exe" earliest=-14d | where match(CommandLine, "(?i)(-enc|EncodedCommand|FromBase64String|\\bIEX\\b)") | stats count by host, ParentImage, CommandLine | sort -count
Example hit: EventCode 4104 on WS-SALES-0117 where ScriptBlockText contains [Ref].Assembly.GetType('System.Management.Automation.AmsiUtils') followed by reflection calls setting a private static field — the textbook in-memory AMSI bypass. The parent chain shows winword.exe → powershell.exe, and the document that started it is still sitting in the user's Downloads folder.
Validating the hit
- Decode and read the payload. Pull the full command line or script block, decode any Base64 layers, and read what it actually does. Look for the kill chain verbs: download, decode, inject, persist, exfiltrate.
- Check the parent and the lure. Identify the document, email, or web page that launched PowerShell. A macro-enabled attachment in the inbox minutes before the execution confirms the delivery vector.
- Look for what the bypass was protecting. AMSI bypasses exist to hide a second stage. Search the host for network connections, file writes, and child processes within ±15 minutes of the bypass — the real payload usually lands right after.
- Confirm defenses are intact. Check whether Defender real-time protection, Script Block Logging, or AMSI itself was disabled (
Get-MpPreference, registryDisableRealtimeMonitoring). If tampering succeeded, assume the host's own telemetry has gaps.
Tuning out false positives
- Legitimate encoded commands: SCCM, Intune, and admin tooling routinely use
-EncodedCommandto pass scripts safely. Baseline your management tooling's command-line patterns and exclude the service accounts that run them. - Security products themselves: EDR sensors and vulnerability scanners sometimes contain strings like
amsi.dllin their own script blocks. Correlate the host and user — scanner service accounts are not interactive logons. - Developer and IT automation: DevOps scripts love
Invoke-Expressionand Base64 for passing credentials. Scope the hunt to interactive user contexts and unusual parents before alerting.
What to do next
A confirmed AMSI bypass is an active intrusion signal — the attacker is investing effort to stay invisible on that box. Isolate the host immediately, kill the PowerShell process tree, and preserve the script block logs and any dropped files for forensics. Hunt the decoded payload's indicators (domains, hashes, file paths) across the fleet: obfuscated PowerShell is rarely a one-host event. If the bypass disabled Defender components, re-enable and verify them before returning the host to service — and consider a full reimage, because a host whose defenses were blinded can't fully vouch for itself.
Next in this series: ransomware precursors — hunting shadow copy deletion before the encryption starts.



