Everything in this post was done on a Windows 10 22H2 machine. Kernel version was: 10.0.19041.2486
Microsoft Warbird is an undocumented encryption technology generally used for things relating to software licensing (DRM) and security mechanisms. There has been some, but not much, previous open source research. Some links which provide further insight:
The Warbird technology is appears to be designed to be integrated at compile time, and could function either as an obfuscation approach on the existing code, or as some type of “enclave” block encryptor. This second approach is what this post will dive into.
There is a semi-undocumented system information class for
SystemControlFlowTransition (0xB9) which when called ends up in the
WbDispatchOperation function. Placing a breakpoint on this function will show that the
sppsvc.exe process periodically calls this. More on this later.
WbDispatchOperation will branch into several different functions depending on the
operation value passed when calling
NtQuerySystemInformation. The struct looks something like this:
These are the operations:
- 1 = WbDecryptEncryptionSegment
- 2 = WbReEncryptEncryptionSegment
- 3 = WbHeapExecuteCall
- 4 = non symbol name function
- 5 = non symbol name function.
- 6 = same as case 5
- 7 = WbRemoveWarbirdProcess
- 8 = WbProcessStartup
- 9 = WbProcessModuleUnload
Each one of these operations has some type of unique operation dependent data attached to the initial struct. Reversing the
sppsvc.execan give us hints on how these structures should be formatted and how they are called. The decrypt and re-encrypt steps can occur multiple times. The rough pseudocode based on
WbProcessStartuplooks like this:
WbProcessStartup seems to suggest that sometype this call does some form of initialization which is required before decrypting/reencrypting data. However, this does not appear to be the case, and the calls to decrypt/reencrypt seem to work without.
The rough pseudocode based on
sppsvc.exe for calling
WbDecyptEncryptionSegment looks like this:
It’s important to note that the
WarbirdPayload is actually embedded in the
sppsvc.exe binary in a section named
?g_Encry. There are multiple of these sections.
For decryption (
WbDecyptEncryptionSegment) the payload is in the format of
The most important field is the
Segments, an array of
WB_SEGMENT structures. These point (using RVA) to the encrypted blocks of code to be decrypted. The flags field in the
WB_SEGMENT specify what protection the segment should be decrypted as. If any value is present, it is a
PAGE_EXECUTE_READ else it is
How to Encrypt
As you may have noticed in the supported operations values, and from the description of the
sppsvc.exe usage, there is no encrypt. This is most likely because this API is intended to be used only after a binary is compiled with the Warbird encrypted chunks. To get around this, you can use the
WbReEncryptEncryptionSegment functionality to first decrypt some random data, replace that data with the bytes we want to encrypt. Then, reencrypt this same memory. If you then save this strucutre (the segment bytes as well as the payload structure) you can then have memory that when restored, can simply be decrypted.
sppsvc.exe is a Windows signed binary. This brings us to a problem. In Alex Ionescu’s talk he explained that part of the patch Microsoft made to fix the bug he found was only allow decryption of payloads that were signed by the Windows team at Microsoft. The kernel does this by calling
ZwQueryVirtualMemory with the
MemoryImageInformation class on the memory passed as the payload. Process Hacker’s NT headers have the structure for this undocumented memory class. The
ImageFlags is then compared to ensure the memory was backed with the appropriate signature.
This however, is not a perfect mitigation as at runtime memory which has been backed by a PE with specific signatures can be modified simply by changing the existing virtual memory protections (RX to RW or RWX).
Putting it all Together
Here is a simplified view of how this whole process will work. The “code” resides within the address space of a signed image.
This PoC will simply follow the steps above. In summary, this will load a signed DLL as the scratch space, then decrypting, writing code, reencrypting, and finally decrypting again.