VBA Shellcode Runner
Next, let’s investigate a shellcode runner, a piece of code that executes
shellcode in memory. We’ll build this in VBA.
The typical approach is to use three Win32 APIs from Kernel32.dll:
VirtualAlloc, RtlMoveMemory, and CreateThread.
We’ll use VirtualAlloc to allocate unmanaged memory that is writable,
readable, and executable.
We’ll then copy the shellcode into the newly allocated memory with RtlMoveMemory,
and create a new execution thread in this process
through CreateThread to execute the shellcode.
Let’s inspect each of these Win32 APIs and reproduce them in VBA.
We’ll take one API at a time, starting with VirtualAlloc.
MSDN describes the following function prototype for VirtualAlloc:
This API accepts four arguments.
The first, lpAddress, is the memory allocation address.
If we leave this set to “0”, the API will choose the location.
The dwSize argument indicates the size of the allocation.
Finally, flAllocationType and flProtect indicate the allocation type
and the memory protections, which we’ll come back to.
Let’s declare these arguments in our first Declare statement
The first argument and the return value are memory pointers
that can be represented by LongPtr in VBA.
The remaining three arguments are integers
and can be translated to Long.
Now that we have our Declare statement, we need to figure out
some of the values we need.
Since we don’t yet know the size of our shellcode, let’s generate it first.
In order to generate the shellcode, we need to know the target architecture.
Obviously we are targeting a 64-bit Windows machine,
but Microsoft Word 2016 installs as 32-bit by default,
so we’ll generate 32-bit shellcode,
formatted as vbapplication, as the first stage of a Meterpreter shell.
Since we’ll be executing our shellcode inside the Word application,
we specify the Exit Function with a value of “thread” instead of the default
value of “process” to avoid closing Microsoft Word when the shellcode exits.
We’ll add this array to our VBA code.
Next, we’ll set the arguments for VirtualAlloc.
The MSDN documentation suggests that we should supply the value “0”
as the lpAddress, which will leave the memory allocation to the API.
For the second argument, dwSize, we could hardcode the size of our shellcode
based on the output from msfvenom,
but it’s better to set it dynamically.
This way, if we change our payload, we won’t have to change this value.
To do this, we’ll use the UBound function
to get the size of the array containing the shellcode.
For the third argument, we’ll use 0x3000, which equates
to the allocation type enums of MEM_COMMIT and MEM_RESERVE.
This will make the operating system allocate the desired
memory for us and make it available.
In VBA, this hex notation will be represented as &H3000.
We’ll set the last argument to &H40,
indicating that the memory is readable, writable, and executable.
Now that we’ve allocated memory with VirtualAlloc,
we must copy the shellcode bytes into this memory location.
This is done using the RtlMoveMemory function.
This function takes three variables.
The Destination pointer points to the newly allocated buffer,
which is already a memory pointer, so it may be passed as-is.
The Source buffer will be the address of an element from the shellcode array,
and must be passed by reference, while the Length is passed by value.
This function takes three variables.
The return value along with the first arguments may be translated to LongPtr,
the second uses Any, while the last argument may be translated to Long.
We’ll use this API to loop over each element of the shellcode array
and create a byte-by-byte copy of our payload.
The loop condition uses the LBound and UBound methods
to find the first and last element of the array.
This is where our knowledge of For loops helps.
In this code, we imported RtlMoveMemory,
declared two long variables, and copied our payload.
With the shellcode bytes copied into the executable buffer,
we are ready to execute it with CreateThread.
CreateThread is a fairly complicated API and works by instructing
the operating system to create a new execution thread in a process.
We’ll use it to create an execution thread using instructions
found at a specific memory address, which contains our shellcode.
While the number of arguments and the associated documentation
may seem daunting, most are not needed and we can set them to “0”.
First, as with the previous APIs, we must import the function
and translate its arguments to VBA data types.
The first two are used to specify non-default settings for the thread
and since we won’t need them,
we’ll set these values to zero and specify them as Long.
The third argument, lpStartAddress, is the start address for code execution
and must be the address of our shellcode buffer.
This is translated to LongPtr.
The fourth argument, lpParameter,
is a pointer to arguments for the code residing at the starting address.
Since our shellcode requires no arguments,
we can set this parameter type to LongPtr with a value of zero.
Having declared the function, we may now call it.
This line is pretty simple
with only one variable for the start address of our shellcode buffer.
Now we have pieced together the entire VBA Macro.
In summary, we begin by declaring functions for the three Win32 APIs.
Then we declare four variables, including a variable for our Meterpreter array,
and use VirtualAlloc to create some space for our shellcode.
Next, we use RtlMoveMemory to put our code in memory with the help of a For loop.
Finally, we use CreateThread to execute our shellcode.
Before executing it, we make sure to have a lister running,
With the EXIT Function set to thread.
When executed,
our shellcode runner calls back to the Meterpreter listener,
and opens the reverse shell as expected, entirely in memory.
This approach is rather low-profile. Our shellcode resides in memory
and there is no malicious executable on the victim’s machine.
However, the primary disadvantage is that when the victim closes Word,
our shell will die.
In the next section, we’ll once again turn to the strength of PowerShell
to overcome this disadvantage.