Invoke-Decoder – A PowerShell script to decode/deobfuscate malware samples

I have been spending a lot of time reviewing PowerShell based attacks and malware over the last few months and I wanted to take some time to really understand how some of the common obfuscation techniques really work under the hood. The best way for me to learn more about something like this is to turn it into a tool, so in this post I am releasing a POC script called Invoke-Decoder.

You can find the script on my GitHub here:

Invoke-Decoder is a menu driven script that can decode the following types of payloads at this time:
– base64
– base64 and compressed
– base64 and gzip compressed
– base64 and xor

There is also an additional option to strip out non-printable and extended ASCII characters in attempt to make decoded binaries a little more human readable.

PS> PowerShell.exe -Exec Bypass .\Invoke-Decoder.ps1

To demo how the tool works, let’s take a look at a PowerShell based “Grunt” payload from Ryan Cobb’s amazing Covenant C2 framework.

The encoded version of the grunt payload that is generated looks something like this:


When this command is executed on the victim or target machine, the host will then connect to the Covenant C2 server and allow the operator to control the host.

To start decoding this sample, load Invoke-Decoder.ps1 into a PowerShell session by using the following command:
PS> PowerShell.exe -Exec Bypass .\Invoke-Decoder.ps1

It should look something like this:

All samples and output will be saved to the running directory \Invoke-Decoder:

Use the L option to Load a sample, then the S option since this will be a string, and paste in the base64 encoded portion of the sample:

If the string is very large (8190+) characters, it will need to be saved directly into a file as PowerShell will truncate the input at the console. The script will detect when this condition exists and automatically create the file then open it in notepad. In this example the sample is over 19,000 characters:

A timestamp is created at the time the sample is loaded and used in the filenames for any files created in relation to the sample.

Since this payload appears to be just base64 encoded, option 1 can be used to decode it and the new decoded sample will be opened in notepad:

Now we see what appears to be another PowerShell command and looking at the area highlighted in red, this payload appears to be compressed and then base64 encoded. If this was executed on a system and PowerShell logs were available, the script block log (Event ID 4104) would automatically decode the payload to this point. But if the PowerShell logs automatically decode PowerShell, why isn’t this new encoded portion also automatically decoded? That’s because the encoded portion isn’t PowerShell, it is a .Net Assembly that will be reflected directly into memory.

Copy out base64 portion and load up the new sample by using the L and then S again:

As mentioned previously, the payload appears to be compressed and base64 encoded, so use option 2:

Now we have what appears to be a Windows Portable Executable file as indicated by the MZ header and we can see some human readable language as well but there is also a bunch off non readable stuff as well. Let’s see if we can clean it up just a little bit more.

Use the R option to remove all of the non-printable and extended ASCII characters. Note: Since this option is being ran directly against the entire sample that was just decoded, there is no need to reload the sample as it is already loaded in memory:

Now to let’s try opening up the cleaned up sample in Notepad++ to see if it’s any more readable:

Notice the IP of the Covenant/C2 server ( as well as a few indicators that this is likely a Covenant implant or “Grunt”.

This has been a fun little project and I plan to continue to expand Invoke-Decoder as I experiment with additional techniques.

Comments are closed.