Skip to content
All tools
ForensicsRuns locallyNo account

PowerShell deobfuscator

Peel -EncodedCommand, FromBase64String, [char] codes, format operators and backtick escapes off an obfuscated PowerShell command, and see what it actually does.

Open in ctfpal

A malicious document's macro is usually four lines long. Its only job is to launch PowerShell with a string, and the string is where everything is - which is why deobfuscating the macro and then stopping leaves you exactly one step short.

The layers, in the order they stack

  1. `-EncodedCommand` (also -enc, -e). Base64 of UTF-16LE, not UTF-8. This is the outermost layer on most samples and the one that hides everything else from a grep.
  2. `FromBase64String`, usually feeding [Text.Encoding]::UTF8.GetString.
  3. `[char]0x41` and [char]65, joined into a string one letter at a time.
  4. The `-f` format operator - "{1}{0}" -f "E","I" is "IE". The commonest obfuscation after base64, because it scatters the string across the argument list in an order nobody can read.
  5. Backtick escapes - ` Invoke-Expression is Invoke-Expression`, and defeats every signature looking for the name while changing nothing about what runs.
  6. Concatenation, `-replace`, and reversed literals.

Why UTF-16 matters

Decoding an -EncodedCommand payload as UTF-8 gives a string with a NUL between every character. All the letters are there, so it reads as a success - and then nothing downstream matches, because every pattern you search for has a null byte in the middle of it. If a decode looks right but nothing else works, this is why.

What it deliberately leaves alone

IEX, Invoke-Expression, DownloadString, Start-Process and the rest stay in the output. They are the answer, not noise - a deobfuscator that helpfully removed them would have destroyed the thing you came to find. Nothing is executed at any point: every layer is a string rewrite, which is what makes it safe to paste a live sample in.

Common questions

Is it safe to paste a live sample in?
Yes. Every layer is a string rewrite - there is no evaluation anywhere in the module, and it runs entirely in your browser with no network. What it cannot do, by the same token, is resolve a layer that needs execution; it stops there and says what it resolved.
It stopped without fully decoding. Why?
Either it met a layer it does not know, or it hit its pass limit while still making progress - which it says explicitly rather than quietly returning a partial result as if it were final. Deeply nested samples are usually the second case; run the output back through it.