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 ctfpalA 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
- `-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 agrep. - `FromBase64String`, usually feeding
[Text.Encoding]::UTF8.GetString. - `[char]0x41` and
[char]65, joined into a string one letter at a time. - 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. - Backtick escapes - `
Invoke-ExpressionisInvoke-Expression`, and defeats every signature looking for the name while changing nothing about what runs. - 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.