You’re triaging an alert. Scheduled task, weird parent process, and a command line that looks like this:
powershell.exe -nop -w hidden -enc JABjACAAPQAg...
Enter fullscreen mode Exit fullscreen mode
You know the drill: grab the Base64 blob, decode it, read the script. So you paste it into a decoder and get back this:
$ c = " h t t p : / / ...
Enter fullscreen mode Exit fullscreen mode
Garbage. A space (or a null) between every single character. First instinct is that the payload is doubly-encoded or encrypted. It isn’t. This is the single most common gotcha with -EncodedCommand, and once you know it, it takes ten seconds to fix.
Why it looks garbled
powershell.exe -enc (short for -EncodedCommand) expects Base64 of UTF-16LE (little-endian Unicode) bytes — not UTF-8. That’s mandated by PowerShell itself, not a choice the attacker made.
In UTF-16LE, every ASCII character is stored as two bytes: the character followed by a 0x00 null byte. So the letter c isn’t 0x63, it’s 0x63 0x00. When you Base64-decode the blob and then read it as UTF-8, every one of those null bytes renders as a space or an invisible control character. Hence the h t t p spacing.
Text: c = "
UTF-16LE: 63 00 3D 00 22 00
UTF-8 view: c ␀ = ␀ " ␀ <- the null shows up as a "space"
Enter fullscreen mode Exit fullscreen mode
Decode it as UTF-16LE instead and the nulls disappear, because that’s what they were: the high byte of each 16-bit code unit.
Decode it correctly
In PowerShell itself — the encoding is literally called Unicode in .NET, which means UTF-16LE:
$enc = 'JABjACAAPQAg...'
[System.Text.Encoding]::Unicode.GetString([System.Convert]::FromBase64String($enc))
Enter fullscreen mode Exit fullscreen mode
In Python — decode the bytes, then read them as utf-16-le:
import base64
enc = "JABjACAAPQAg..."
print(base64.b64decode(enc).decode("utf-16-le"))
Enter fullscreen mode Exit fullscreen mode
In CyberChef — build the recipe From Base64 → Decode text (UTF-16LE). Or From Base64 then Remove null bytes for a quick-and-dirty look.
Any of these turns the spaced-out mess back into readable PowerShell.
The encode direction (for building test cases)
If you’re writing detections or a lab sample, this is how the blob is produced — same Unicode/UTF-16LE contract in reverse:
$cmd = 'Write-Host "hello from encoded command"'
[System.Convert]::ToBase64String([System.Text.Encoding]::Unicode.GetBytes($cmd))
Enter fullscreen mode Exit fullscreen mode
Feed that output back to powershell -enc and it runs. Legit automation uses this all the time — it’s a clean way to pass a multi-line script through a single argument without quoting nightmares. That’s exactly why malware hides in the same crowd.
The second layer: it’s often gzip too
Sometimes you decode correctly and still get binary garbage. That’s the next common trick: the real script is gzip-compressed, then Base64’d. The outer PowerShell you just decoded is a tiny loader whose whole job is to inflate the inner payload in memory:
# telltale pattern inside the decoded stub
$s = New-Object IO.MemoryStream(,[Convert]::FromBase64String("H4sIA..."))
IO.StreamReader(New-Object IO.Compression.GzipStream($s,[IO.Compression.CompressionMode]::Decompress)).ReadToEnd()
Enter fullscreen mode Exit fullscreen mode
The tell is the inner Base64 starting with H4sI — that’s the gzip magic number (1f 8b) in Base64. When you see it, decode Base64 again and gunzip to reach the actual script. Payloads can nest two or three layers deep this way.
Is it safe to decode a suspicious sample?
Yes. Decoding is not executing. Converting Base64 back to text and inflating gzip are pure data transformations — no powershell.exe runs, nothing touches the script block, nothing hits the network. The danger is only if you run the decoded command. So decode freely and read; just don’t pipe the result into a shell.
For fast triage there’s a free browser-based tool that decodes -enc / -EncodedCommand for you — it handles the UTF-16LE conversion and auto-inflates nested gzip layers, and it runs entirely client-side so it’s safe to paste suspicious samples into (nothing is uploaded): PowerShell Encoded Command Decoder.
Analyst checklist vs. the layers
What you see after decoding What it means Next step Space between every char You decoded as UTF-8 Re-decode as UTF-16LE Clean, readable PowerShell Done Read it, extract IOCs Binary garbage, startsH4sI
Inner gzip payload
Base64-decode again, then gunzip
Another -enc / FromBase64String
Nested loader
Repeat the whole process on the inner blob
TL;DR
-
powershell -enc <base64>is Base64 of UTF-16LE, not UTF-8 — that’s why naive decoding shows a space/null between every character. - Decode it right with
[System.Text.Encoding]::Unicode.GetString([Convert]::FromBase64String($enc)), or in Python withbase64.b64decode(enc).decode("utf-16-le"). - If the result is still binary and starts with
H4sI, it’s gzip inside Base64 — inflate it to reach the real script. - Decoding never executes the command, so it’s safe to analyze suspicious samples locally.
Seen a -enc payload with an interesting nesting trick? Share the pattern in the comments.
답글 남기기