Decoding a PowerShell -EncodedCommand
A powershell -enc <base64> blob is Base64 of UTF-16LE bytes, not UTF-8 — that is why a naive decoder returns garbled, space-separated text with a null between every character. Decode it as UTF-16LE and the mess turns back into readable PowerShell. It takes ten seconds once you know the trick.
The scenario
You are triaging an alert. A scheduled task, a weird parent process, and a command line that looks like this:
powershell.exe -nop -w hidden -enc JABjACAAPQAg...
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 : / / ...
Garbage — a space (or a null) between every single character. The 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, the fix is trivial.
Why it looks garbled
powershell.exe -enc (short for -EncodedCommand) expects Base64 of UTF-16LE (little-endian Unicode) bytes — not UTF-8. That is 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"
Decode it as UTF-16LE instead and the nulls disappear, because that is exactly 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))
In Python — decode the bytes, then read them as utf-16-le:
import base64
enc = "JABjACAAPQAg..."
print(base64.b64decode(enc).decode("utf-16-le"))
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. If you prefer not to leave the browser, the PowerShell Encoded Command Decoder handles the UTF-16LE conversion for you.
The encode direction (for building test cases)
If you are writing detections or a lab sample, this is how the blob is produced — the same Unicode/UTF-16LE contract in reverse:
$cmd = 'Write-Host "hello from encoded command"' [System.Convert]::ToBase64String([System.Text.Encoding]::Unicode.GetBytes($cmd))
Feed that output back to powershell -enc and it runs. Legitimate automation uses this all the time — it is a clean way to pass a multi-line script through a single argument without quoting nightmares. That is exactly why malware hides in the same crowd. For the encode side and the UTF-16 trap in general, see Base64 in PowerShell.
The second layer: it's often gzip too
Sometimes you decode correctly and still get binary garbage. That is 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()
The tell is the inner Base64 starting with H4sI — that is 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. The Base64 gzip decoder inflates these in one step.
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, the PowerShell Encoded Command Decoder handles the UTF-16LE conversion and auto-inflates nested gzip layers. It runs entirely client-side, so nothing is uploaded — safe to paste suspicious samples into. When you don't yet know how a blob is encoded, Magic Decode detects the layers for you.
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, starts H4sI | 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 is 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 is gzip inside Base64 — inflate it to reach the real script. - Decoding never executes the command, so it is safe to analyze suspicious samples locally.
Frequently Asked Questions
Why does my decoded PowerShell -EncodedCommand have a space between every character?
Because you decoded it as UTF-8. A powershell -enc / -EncodedCommand blob is Base64 of UTF-16LE bytes, where every ASCII character is stored as the character followed by a 0x00 null byte. When you read those bytes as UTF-8, each null renders as a space or an invisible control character. Decode it as UTF-16LE instead and the nulls disappear.
How do I decode a PowerShell -EncodedCommand correctly?
Base64-decode the blob, then read the bytes as UTF-16LE. In PowerShell: [System.Text.Encoding]::Unicode.GetString([System.Convert]::FromBase64String($enc)). In Python: base64.b64decode(enc).decode("utf-16-le"). In CyberChef: From Base64 then Decode text (UTF-16LE). The .NET encoding named Unicode means UTF-16LE.
Is it safe to decode a suspicious -EncodedCommand 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, and nothing hits the network. The danger only exists if you run the decoded command, so decode freely and read it; just don't pipe the result into a shell.
The decoded command is still binary garbage. Why?
The real script is often gzip-compressed and then Base64-encoded, with the outer PowerShell acting as a tiny loader that inflates it in memory. The tell is an inner Base64 string starting with H4sI, which is the gzip magic number (1f 8b) in Base64. When you see it, Base64-decode again and gunzip to reach the actual script. Payloads can nest two or three layers deep.
What does the -enc flag mean in PowerShell?
-enc is short for -EncodedCommand. It lets you pass a whole script to powershell.exe as a single Base64 argument, avoiding quoting problems with multi-line commands. PowerShell requires that Base64 to be of UTF-16LE bytes. Legitimate automation uses it constantly, which is exactly why malware hides in the same crowd.
Decode a -EncodedCommand safely in your browser
Paste a powershell -enc blob and get readable PowerShell back — UTF-16LE handled, nested gzip auto-inflated, nothing uploaded.