5 Minute Forensics: Decoding PowerShell Payloads

TL;DR Here's how to decode some PowerShell commands so you can find IPs and other IOCs.

Background

Through consulting with several of our clients during IR engagements, we have discovered that several clients are taking steps to restrict and log PowerShell in their environment. However, in several engagements we have discovered that many of the analysts are unable to get the correct information from these logs to begin containment or investigate further. Understanding these commands that are wreaking havoc in your environment is critical to the incident response process.

Understanding Common PowerShell Exploits

Let's take a look at a common PowerShell exploit structure and break it down to better understand what's going on. Fin7, and copy cats, like to use this method (A LOT) so it's a good place to start:

powershell.exe -NoE -Nop -NonI -ExecutionPolicy Bypass -C "sal a New-Object;iex(a IO.StreamReader((a IO.Compression.DeflateStream([IO.MemoryStream][Convert]::FromBase64String('redacted-base64-encoded-string'),[IO.Compression.CompressionMode]::Decompress)),[Text.Encoding]::ASCII)).ReadToEnd()"

In a nutshell: the payload calls PowerShell, gives it some flags to make sure it executes, sets a command alias 'a', and provides the true payload to be decoded and executed.
The first section of the command calls PowerShell and some flags:

powershell.exe -NoE -Nop -NonI -ExecutionPolicy Bypass -C 

These options are:

  • NoE - NoExit
  • Doesn't exit after running the command, i.e. creates a process and stays running as powershell.exe
  • NoP - NoProfile
  • Doesn't load the PowerShell profile
  • NonI – NonInteractive
  • Doesn't create an interactive prompt, i.e. it runs the command without the PowerShell window popping up a persistent terminal on the user's screen
  • ExecutionPolicy Bypass
  • Bypasses the execution policy if it is set (self-explanatory)
  • C - Command
  • What to run (again, pretty self-explanatory)

This section allows PowerShell to execute a command that will be invisible to the user and create a process that stays running after the command executes.

The second portion of the command is where things get interesting, and this is where the actual forensics begins:

"sal a New-Object;iex(a IO.StreamReader((a IO.Compression.DeflateStream([IO.MemoryStream][Convert]::FromBase64String('redacted-base64-encoded-string'),[IO.Compression.CompressionMode]::Decompress)),[Text.Encoding]::ASCII)).ReadToEnd()"

The Set-Alias cmdlet 'sal' creates a shortcut 'a' for New-Object. The subsequent section uses the Invoke-Expression cmdlet 'iex' to execute the payload, which consists of the alias 'a' and some classes to convert a base64 encoded string to a memory stream. So, the command executed is actually this:

powershell.exe -NoE -Nop -NonI -ExecutionPolicy Bypass -C "iex(New-Object IO.StreamReader((New-Object IO.Compression.DeflateStream([IO.MemoryStream][Convert]::FromBase64String('redacted-base64-encoded-string'),[IO.Compression.CompressionMode]::Decompress)),[Text.Encoding]::ASCII)).ReadToEnd()"

Honestly, it sounds more complicated than it really is. It just reads a string in to memory and executes it. There's not a ton going on in this command, but once you understand it, you can modify it to make it output the real information you want.

Modifying the Payload to Identify its Contents

We know the payload is reading something in to memory and executing it so, here's an idea, let's just read it and NOT execute it. So, what's causing the execution? In the section above, we identified the call to PowerShell with the flags, and 'IEX' cmdlet. Here's the magic: ALL YOU NEED TO DO IS REMOVE 'IEX', THE DOUBLE QUOTES, AND THE FLAGS!!!!

Once you remove the execution commands (powershell.exe -NoE -Nop -NonI -ExecutionPolicy Bypass -C, "", and iex) from the payload you're left with:

sal a New-Object;(a IO.StreamReader((a IO.Compression.DeflateStream([IO.MemoryStream][Convert]::FromBase64String('redacted-base64-encoded-string'),[IO.Compression.CompressionMode]::Decompress)),[Text.Encoding]::ASCII)).ReadToEnd()

This payload is now safe to run in PowerShell. All it does now is set the alias 'a', and read a string in to memory. Here's a look at an actual payload from Fin7, and how we used this same method to identify one of their C2 servers:

Once you remove the "powershell.exe -NoE -Nop -NonI -ExecutionPolicy Bypass -C", "iex", and wrapping double-quotes, the payload will decode the string and print it to the screen. It really is that simple. Just remove the "execution" from the command, and it becomes a benign payload.

Caveats

So maybe it's not as simple as just finding 'IEX'… There are a few ways in PowerShell to call for execution (Invoke-Expression, IEX, Invoke-Command, ICM, .invoke(), setting an alias, etc.), and then there's even more obfuscated ways, as Daniel Bohannon points out: https://www.slideshare.net/DanielBohannon2/invokeobfuscation-derbycon-2016

What you need to focus on, is identifying the execution within a command. Once you find all the executable calls, you can delete them or replace them with non-executable functions, such as writing out to text files. The above example is simple because the IEX command is in plain sight, and the attackers make no effort to further obfuscate their payloads.

Be diligent in examining the command before running anything. We recommend taking it one step further and using a VM with no network access, when attempting to run these payloads as demonstrated. You don't want to gift an attacker a shell with admin privileges while you're investigating!

Further Investigation

The command from our example spawns a Meterpreter shell, as seen in this snippet from the decoded payload:

if ($threadHandle -ne [IntPtr]::Zero)
{
Write-Output "Successfully created thread!"
Write-Output "Meterpreter session created!"
}
else
{
Write-Error "Thread creation was failed, exit..."
exit -2
}

While there are plenty of other C2 frameworks, attacker methodologies are similar and almost always include the following: establish C2, establish persistence, dump creds, and pivot. It's fair to assume that this is just the beginning, and after this payload is run, there will be a healthy dose of Mimikatz, pass-the-hash, wmi-exec, etc.

Some tips for following up after discovering a payload like this would be:

  • Don't block the IP just yet. You want to identify all affected systems and determine if multiple C2's are being used. Attackers like Fin7 have been known to use between 3-5 different C2's for a single attack.
  • Do a quick investigation on any other affected hosts, so that you can give them the ban-hammer all at once. Otherwise you could end up playing whack-a-mole for a while.
  • Review logs for all devices that connect back to the attacker's identified IP
  • Review all internal connections to and from the affected host (especially SMB traffic)
  • Review all external connections to and from the affected host (including DNS traffic)
  • Multiple threat actors use DNS traffic for C2 and to avoid detection.
  • Review the affected hosts for artifacts and persistence mechanisms (reg keys, scheduled tasks, emails, word docs, etc.)
  • Review anomalous or suspicious activity from these hosts, such as connecting to dropbox, google docs, etc., to determine if data was exfiltrated.
  • Don't start recovery until it's certain the threat has been removed. The last thing you want to do spend a ton of hours recovering (resetting passwords, reimaging, etc.) and then have to do it all over again for the same incident!

References

Richard De La Cruz

Read more posts by this author.