Reflection Shellcode Runner in PowerShell
With the power of the reflection technique in PowerShell,
we now have the ability to invoke Win32 APIs
from code that executes entirely in memory.
We must now translate our simple “Hello, World” proof-of-concept
into a full-fledged shellcode runner.
Since we are going to call three different Win32 APIs,
VirtualAlloc, CreateThread, and WaitForSingleObject,
we’ll rewrite the portion of code that creates the delegate type into a function
so we can easily call it multiple times.
We’ll also slim down the code, eliminating unneeded variables
to produce the smallest and most efficient code possible.
The resulting function is called getDelegateType and accepts two arguments:
the function arguments of the Win32 API given as an array and its return type.
Our previous code is built into three blocks.
The first block creates the custom assembly
and defines the module and type inside of it.
The second block of code sets up the constructor,
and the third sets up the invoke method.
Finally the constructor is invoked and the delegate type is returned to the caller.
Together with LookupFunc, we’ll resolve and call VirtualAlloc
using the same arguments as in the previous cases.
We’ll use LookupFunc to search Kernel32.dll
for the Win32 VirtualAlloc API.
The code uses our LookupFunc and getDelegateType functions
to allocate a memory buffer.
However, it is possible to optimize and condense this code
to remove unneeded variables.
This optimized version embeds the calls to LookupFunc and getDelegateType,
in the call to GetDelegateForFunctionPointer.
The next step is to generate the shellcode in ps1 format,
remembering to choose 32-bit architecture
due to PowerShell spawning as a 32-bit child process of Word.
With the shellcode generated, we can copy it using the .NET Copy method.
The shellcode and copy operation are identical to the version
for the Add-Type version of our shellcode runner.
Next, we can create a thread,
and call WaitForSingleObject to block PowerShell from terminating.
VirtualAlloc, CreateThread, and WaitForSingleObject are all resolved
and called in exactly the same manner with our condensed syntax.
Since the shellcode runner code is entirely located on the
Kali Linux Apache server, we do not need to update the Word macro
but will simply overwrite the run.ps1 file on the webserver,
make sure our listener is running,
and run the macro again.
The code is working and we have a reverse shell.
In addition, Process Monitor reveals that no .cs file
was written to the file system nor subsequently compiled.
Excellent. We’ve created a PowerShell shellcode runner
that executes entirely in-memory.
In addition, it can be triggered from VBA
without embedding any first stage shellcode inside the Macro.
In the next section, we’ll discuss network proxies,
which can create various issues when performing this type of attack.