Normal view

There are new articles available, click to refresh the page.
Before yesterdayMain Page

Example of Windows Warbird Encryption/Decryption

23 April 2023 at 07:00

Everything in this post was done on a Windows 10 22H2 machine. Kernel version was: 10.0.19041.2486

Introduction

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:

  • https://github.com/KiFilterFiberContext/warbird-obfuscator
  • https://github.com/KiFilterFiberContext/microsoft-warbird/

In addition, Alex Ionescu talked about Warbird in depth during this presentation.

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.

SystemControlFlowTransition

There is a semi-undocumented system information class for NtQuerySystemInformation called 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:

typedef struct _WB_OPERATION
{
	ULONG Operation;
	PVOID Buffer;
    ... (operation dependent data)
} WB_OPERATION, *PWB_OPERATION;

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.exe can 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 sppsvc.exe for calling WbProcessStartup looks like this:
SystemInfo[0] = 8;                      // Operation (WbProcessStartup)

SystemInfo[1] = &buffer;
NtQuerySystemInformation(0xB9, SystemInfo, 0x10, NULL);

Where buffer:

{
    ULONG: 0,
    ULONG: 0x64,
    ULONG64: 0,
    ULONG: 0
}

The name 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:

SystemInfo[0] = 1;                      // Operation

SystemInfo[1] = WarbirdPayload;         // At this point it is encrypted

SystemInfo[2] = PEBaseAddress;          // Base Address of the PE

SystemInfo[3] = 0x140000000;            // Image Base

SystemInfo[4] = UnknownLong64;          // Possibly something relating to encryption        

SystemInfo[5] = 0x2;                    // Unknown flags

result = NtQuerySystemInformation(0xB9, SystemInfo, 0x30u, NULL);

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.

Payload Format

For decryption (WbDecyptEncryptionSegment) the payload is in the format of WB_PAYLOAD structure.

typedef struct _WB_SEGMENT 
{
	ULONG Flags;
	ULONG RVA;
	ULONG Length;
} WB_SEGMENT, *PWB_SEGMENT;

typedef struct _FEISTEL_ROUND
{
	ULONG One;
	ULONG Two;
	ULONG Three;
	ULONG Four;
} FEISTEL_ROUND, *PFEISTEL_ROUND;

typedef struct _WB_PAYLOAD {
	BYTE Hash[0x20];        // SHA 256 hash of the payload sha256(payload size - 0x20)

	ULONG TotalSize;		// Total size (includes all segments)

	ULONG Reserved;			// Set to 0

	ULONG PayloadRVA;		// Offset between start of payload struct and actual start of the data passed (WarbirdPayload) in the NtQuerySystemInformation call

	ULONG SecondStageRVA;	// Offset between start of second stage struct and actual start of the data passed (WarbirdPayload) in the NtQuerySystemInformation call

	ULONG SecondStageSize;	// Size of the UnknownData in DWORDs

	ULONG UnknownLong;		// Looks like this is reserved. Must be 0?

	ULONG64 ImageBase;		// PE image base

	BYTE Unknown2[0x8];		// Looks like this is reserved. Must be 0?

	ULONG64 FeistelKey;
	FEISTEL_ROUND Rounds[10];
	ULONG SegmentCount;		// Number of segments

	WB_SEGMENT Segments[1]; // Segment struct(s)

} WB_PAYLOAD, * PWB_PAYLOAD;

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 PAGE_READONLY.

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.

The Mitigation

Note that 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.

The Bypass

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. WB Diagram

PoC Code

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.

What is NtPssCaptureVaSpaceBulk

14 May 2021 at 23:51

The API

Just a quick post about something I found quite interesting. In Windows 10 version 2004+ (according to this) there is a new system call which caught my attention: NtPssCaptureVaSpaceBulk. Specifically the caputing “bulk” referenced in the name made me think it could be useful. Looking at the API in the kernel, I quickly noticed several calls using the PsProcessType type as the first parameter, so I looked at the prototype of NtQueryVirtualMemory and went from there. My Google searching skills discovered that there is no offical prototype for this function, and so my final definition looks like this:

NTSTATUS NtPssCaptureVaSpaceBulk(HANDLE ProcessHandle, 
        PVOID BaseAddress, 
        PBULK_MEMORY_INFORMATION MemoryInfo, 
        SIZE_T Length, 
        PSIZE_T ReturnLength);


The structure of BULK_MEMORY_INFORMATION is:

typedef struct _BULK_MEMORY_INFORMATION
{
	ULONG QueryFlags;
	ULONG NumberOfEntries;
	PVOID MaxUserAddress;
	BYTE Reserved[0x18];
	PVOID LowestAddressFound;
	BYTE Reserved2[0x10];
    MEMORY_BASIC_INFORMATION MemoryInfo[1];
} BULK_MEMORY_INFORMATION, *PBULK_MEMORY_INFORMATION;


As a side note: When creating this structure definition I remembered seeing similar Windows structs which has arrays as the last element but had it defined as a 1-length based, looking into this I found this post by Raymond Chen explaining why this is the case.

The QueryFlags member does not seem to be used except for being bitwise anded with 0xFFFFFFFC so only 1, 2, and 3 are valid. Again these are currently not used to control anything, but needs to be set or you’ll get STATUS_NOT_SUPPORTED. The main logic then follows which involves attaching to the provided process handle, re-calling itself in with the Zw variant of PssCaptureVaSpaceBulk, and then preforming a loop of NtQueryVirtualMemory. In the past, looping VirtualQuery(Ex) is essentially what developers had to do to query the memory of a process. That code would look something like this:

MEMORY_BASIC_INFORMATION memInfo;
for (PVOID pMem = NULL; VirtualQueryEx(GetCurrentProcess(), pMem, &memInfo, sizeof(memInfo)) == sizeof(memInfo); (BYTE*)pMem += memInfo.RegionSize)
{
	printf("Region: %p\n", memInfo.BaseAddress);
}


But now you can do something like this:

SIZE_T nReturn;
SIZE_T nHeap = 0x1000;
PBULK_MEMORY_INFORMATION pBulk = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, nHeap);
pBulk->QueryFlags = 0x1;
while (NtPssCaptureVaSpaceBulk(GetCurrentProcess(), 0, pBulk, nHeap, &nReturn))
{
	HeapFree(GetProcessHeap(), 0, pBulk);
	nHeap += 0x1000;
	pBulk = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, nHeap);
	pBulk->QueryFlags = 0x1;
}
for (size_t i = 0; i < pBulk->NumberOfEntries; i++)
{
	printf("Mem: %p\n", pBulk->MemoryInfo[i].BaseAddress);
}


Is it more complicated? Yes. Why use it? I’m not sure, but maybe in the future the query flags will be able to gather different information. For now it is simply appears to be a redundent way to capture the entire virtual address space of a process.

The Win32k Import Mystery

12 November 2020 at 23:51

The Crash

This all began when I was playing around with the PE file format and ended up tinkering with the loading process of drivers. Upon loading a particularly simple driver the OS crashed with the following error:

IMG1

The stack clearly shows the bugcheck occuring within the driver loading process.

IMG1

I instantly suspected that the issue was with the imports as the driver I loaded only imported functions from Win32k.

IMG1

Analysis

Peering through the callstack functions I found that MiResolveImageReferences appeared to be the one with the logic I was interested in. To summerize the function:

1. Uses RtlImageDirectoryEntryToData to find the import directory (IMAGE_DIRECTORY_ENTRY_IMPORT) of the loading driver PE

2. Finds first module to resolve addresses from. In this case it is Win32k.sys

3. Walks the PsLoadedModuleList looking for the module using RtlEqualUnicodeString

4. Once found, MiSessionReferenceImage is called with the the module (Win32k) address as its only argument

5. MiSessionReferenceImage immediately calls MiSessionLookupImage and passes the address

6. Inside MiSessionLookupImage the current process’ EPROCESS structure is derferenced, followed by the Session memeber

7. In this case, the Session member is null

8. There is no check. The Session member is derefereced (+0x58) and the system crashes

While testing other drivers, I found that the Session member always appears to be null as the loading context takes place in the System process. However, while other drivers do trigger MiResolveImageReferences, they never reach MiSessionReferenceImage. That is until I looked at drivers loaded at boot time. The inital loaded of the Win32k* drivers do reach MiSessionReferenceImage, however they have a valid Session member in thier EPROCESS. This appears to be due to the fact that they are loaded by csrss.exe and not the actual system (PID 4) process.

Further Information

In the end, I could’ve learned all this from a StackOverflow anwser which explains the results I was seeing. In addition, the csrss process loads the Win32k* drivers in the current session. The system process is not part of that session. The API called is not NtLoadDriver, but rather NtSetSystemInformation using a special infromation class seen here. With this information, it appears that drivers, at least those loaded via NtLoadDriver, were never designed to import (in PE loader terms) functions from Win32k. Perhaps in past Windows versions it was possible. Another mystery solved?

When Downloads Lead to LPE

25 March 2020 at 23:51

Observations

In the world of privlige escalation, 3rd party elevated service programs are espically interesting to look at. One such program is Sophos’ HitmanPro.Alert (HMPA). This program is designed to work with an additional AV product to provide “advanced” detection and prevention capabilities against malware and exploits.

However, this HMPA became interesting when I observed this message in my kernel debugger:

IMG1

Just from this message there are a couple of issues I had to research. Are downloads really over HTTP? What does an update look like (full exe or just dll plugin)? Is the directory really a low privileged controlled directory? Using Wireshark I was able to identify that the downloads were really HTTP (they use URLDownloadToFile). Secondly, using a proxy I was able to get a file to appear in user’s temp directory. The “update” is just the full hmpalert.exe (yes, the one that is downloaded when you first install). Depending on which flag was used in the command line /service for the service, /tray for the tray icon and user interactive version, etc… But they are all the same executable.

After fiddling with the proxy server, I discovered the service will execute whatever file you give it, as long as it is digitally signed. It’s important to note that ANY signer is accepted. This means that anyone with a couple hundered bucks and a “valid” company someone could spoof updates. The only issue is that update checks only happen on startup and ~60 minutes. While the possiblity of RCE is interesting, I was looking for LPE and discovered something else. The “Scan” feature of HMPA actually donwloads HitmanPro (not Alert) from http://get.hitmanpro.com which could also be spoofed. This however, can be triggerd at any time because the /tray instance of HMPA listens for window messages.

IMG1

The actual communication with the /service instance takes place over named pipes, but simply broadcasting to all windows with SendMessage with the proper Msg and Param allows for any user to trigger a download and run (under SYSTEM user) of the scanner. Because the program is downloaded to the current user’s temp directory (like the update) I figured DLL hijacking would work. However, the scanner program by Sophos was very careful to only load libraries from System32. Everything then changed when I realized that the downloading of the scanner took place in the /tray instance which meant that a local proxy could work to spoof the downloads. Using InternetSetOption with approprite flags, a user can set the global proxy which applies to all processes running as that user (so not SYSTEM processes).

Results and Fix

In the end, the PoC I sent to Sophos did the following:

1. Clears download cache

2. Starts server and sets global proxy

3. Broadcast’s scan message to all windows

4. Feeds cmd.exe (or any signed binary) to client

The client /tray then notifys the /service which executes as SYSTEM the signed binary. Because any signed binary is allowed, a DLL hijacking, or simply getting a certificate could led to LPE for custom code. I worked with Sohpos’ PSIRT to fix the issue. They published an advisory which can be found here. In addition, I’ve uploaded the PoC code which can be downloaded here. To my knowledge Sophos fixed the problem by only executing images signed by their certificate.

Creating Fake Drivers

24 January 2020 at 23:51

Research

Drivers commanly use IOCTLs for communication from user and kernel mode, but what really goes on behind the scene. DeviceIoControl is the API responsible for sending an IRP_MJ_DEVICE_CONTROL request (along with an IOCTL and buffers of data) to a driver. The driver specifies the address of the funciton that then handles these requests. This is done through the array DRIVER_OBJECT.MajorFunction[]. Continuing to trace back, the DRIVER_OBJECT comes from the DriverEntry which is the entry point on a WDM driver. It’s clear that the DRIVER_OBJECT needed for IOCTL communication comes from the kerenl, but where? Following at NtLoadDriver eventually leads to IopLoadDriver which calls ObCreateObject with IoDriverObjectType as the OBJECT_TYPE. It’s worth noting that driver objects can be viewed in the NT object manager under the “Driver” subdirectory with tools such as WinObjEx.

ObCreateObject with IoDriverObjectType allows us to create our own driver object. To ensure the object survives and is not deleted you need to reference the object. For example you can use ObReferenceObjectByPointer or in the object attributes pass OBJ_PERMANENT which will start the reference count at 1. It is then necessary to call ObInsertObject in order to add the object to the system. ObInsertObject returns a handle, which can be closed as it does not affect whether the object will be deleted or not. Next, there are several members of the driver that we need to modify, the first being the MajorFunction member which points to the function(s) that will handle IOCTLs. Secondly, the Flags member needs to be specifically set with the type of IO transfers that the driver object will use. Finally, the FastIoDispatch member must be set to NULL (or could be a valid handler) so the kernel won’t try and direct functions to it. All other members are optional. An example of a newly created driver object:

IMG1

Purpose

With the ability to create a fake driver and device, a executable kernel pool could communicate with and a user mode process using IOCTLs. In addition, I belive it’s good to understand that not every driver is really tied to a physical file. Another aspect is that a valid driver can create multiple device objects, or that a device object could actually use the driver object of another driver. This could lead to a “hijacking” of driver objects. In any case, I’ve created a PoC for the method above which can be downloaded here.

Qualcomm Service Vulnerability

16 October 2019 at 23:51

Discovery

Qualcomm has a serivce program named AdminService.exe which runs on Windows machines with the Qualcomm Atheros QCA61x4 Bluethooth device. Other devices possibly have this service. You can download the entire service and driver package here. In a real world situation, the package would be installed by Windows.

So, what is wrong with this AdminService? Well, services can receive control codes from other applications. This is done with the ControlService API. Looking at the MSDN documentation the control codes after 128 (up to 255) are used by 3rd party services for their own purposes. This is what originally piqued my interest. I figured that custom control codes might preform privileged operations and these could possibly be exploited. By chance, AdminService.exe (service name AtherosSvc) handles a bunch of custom control codes. I went down each control code case until I found code 133 (0x85). Like many of the other codes, 133 posts a message to another thread. The message code for 133 is 24162 (0x5E62). This case in the thread’s GetMessage loop calls OutputDebugString with “Enter case CUSTOM_THREAD_EVENT_REG_MODIFY”.

IMG1

The next call to KeyParsingAndOperations is where the vulnerabiltiy lies. Inside, AdminService.exe looks for a file at C:\ProgramData\Atheros\AtherosServiceConfig.ini. This file is not present after a default installation. If the file is present, then there is a series of messy parsing compares and calls to GetPrivateProfileStringW execute. If you create a file and carefully debug the service you can see what the service is looking for in the configuration data. Something like the image below demonstrates how the file can be used.

IMG2

For regOpType:

1 = Delete a key

2 = Open a key, no writing, and handle never closed

3 = Create a new key

For regType:

1 = REG_SZ

2 = REG_EXPAND_SZ

3 = REG_BINARY

4 = REG_DWORD

The other values in the config file have pretty obvious meanings.

Impact

Now that there is a way to create any key, with any data, anywhere, exploiting for elevating privileges is easy. An example would be something like the MSI Server at HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\msiserver. The ImagePath key could be replaced with something malicious, and the installer can be triggered by running msiexec.exe /q /i 'random.msi'. The only problem is you won’t see GUI elements because of Session 0 Isolation If you need GUI interaction I’d suggest looking at maybe injecting code into a different SYSTEM process but in session 1 (winlogon.exe for example).

Report and Response

I reported this issue Qualcomm on April 16th of 2019. It was publicly disclosed on October 7th in a Qualcomm Security Bulletin. It was assigned CVE-2019-10617. The PoC code is available on my github here.

Additional Information

A few days ago I was looking at my twitter feed when I saw a write-up by @monoxgas on this exact vulnerability. Turns out, he had reported the issue last month, and now that the disclosure was public, published his work in an excellent post which can be found here.

Vulnerabilities In Similar Software

19 March 2019 at 23:51

SoftEther VPN is a VPN program by the university of Tsukba in Japan. Users can use the client program to connect to servers, or use theserver program to host servers. The Server Manager for Windows used a driver named See.sys for low level operations. This driver (See.sys) was actually part of SoftEther’s github repository which can be found here.

The driver is based off the old WinPcap driver which had an issue (CVE-2007-3681) with malfourmed IRP Parameters. Here are More Details. When a user program sends an IOCTL to a driver it is packaged, along with buffers, sizes, and other information inside of an IRP. IRPs have a field that named UserBuffer. This is the output buffer in a DeviceIoControl call. When a device is created with DO_DIRECT_IO (as this one does) there is no Irp->AssociatedIrp.SystemBuffer, so output data is usually copied to the UserBuffer. The issue is that no checks are preformed on the UserBuffer by the IO Manager. Obviously, a ProbeForWrite could be used on IRP.UserBuffer, but the programmers of WinPcap didn’t. The WinPcap driver had this issue fixed quite a while ago, but the See.sys driver, based on the WinPcap driver, was forgotten.

In See.sys, there is an IOCTL (0x1CF7) which calls memmove with a destination on IRP.UserBuffer (rdi+70H) The source ends up being the the wide string “SEEXXXXXXXXXX” where the X’s are digits associated with the event name (according to the source) The size is a constant 0x1A. IMG1

While the data isn’t fully controllable, the destination is. This is the critical issue in this driver. Because you can write this string anywhere in kernel memory, it is possible to overwrite some bit fields and gain elevated priviliges. In my PoC, the call writes the string to the K_USER_SHARED_DATA memory region at offset +0x80. IMG2

This issue was reported and is fixed in the new versions (4.3+). SoftEther Notice. Special thanks to Daiyuu Nobori of the SoftEther team. It was assigned CVE-2019-11868. The PoC code is available on my github here.

The Search For Drivers

18 December 2018 at 23:51

Lately I’ve been digging into driver exploitation in Windows. One of the hardest parts is actually finding the drivers to decompile, debug, and experiment with. This led me to look for easy to find drivers. I found Driver Easy.

Driver Easy seems to have a large database of drivers, and I thought I could access them for experimentation purposes. I discovered that the files are retrieved by HTTP requests but the data is encrypted. Coincidentally the program is a .NET and therefore extremely easy to open up and peek at the inner workings.

The encryption is a combination of: gzip compression, base64, and xor flipping. That is the encryption order, so naturally the decryption is the opposite. For the xor step, they would of course use a long randomly generated key, right? (They are a Gold partner with Microsoft for application development. Link)

How about: 39096799Easy

Yes, that is the xor key used for the traffic encryption. With the traffic decrypted, I was able to see that communication was done using XML. Encrypting and sending an XML file like this.

MachineId can be anything. After you decrypt the response, it will look like this.

As you can see, you need to know the Hardware Ids for the drivers you are looking for. Unfortunately brute-forcing, or even educated guessing would be extremely ineffective as vendors can name them, and guidelines are very bare.

In conclusion, my attempts to gain a large amount of possibly vulnerable drivers failed. However, I did practice my Python skills. Hopefully in the future I’ll look into more of these driver assistant programs that the internet seems to be full of.

Note: In Easware.Driver.Core, a module loaded by the DriverEasy.exe assembly there is a Class of functions called DriverUpload Sadly this function isn’t used in the application, and the upload server, found in the program, always returns 403 – Forbidden. However, at one time, Driver Easy may have been uploading users drivers to build their own database, imagine the security problems. This is only a theory though. Scripts and repository

Spoofing the Windows 10's Task Manager

3 March 2018 at 23:51

Observant Windows 10 users will notice that some of the new metro apps can only run as a single instance. Examples include Settings and Task Manager. Attempting to open another instance of these processes will result in the already opened one being focused.

I focused on Task Manager. Initially, I began looking for calls to either mutexes or semaphores, which can (if a name argument is passed) be stored in the Windows’s object manager. Calls to create a mutex or semaphore with a name that already exists will return ERROR_ALREADY_EXISTS. In one such instance CreateMutexW used the wide string: Local\\TM.750ce7b0-e5fd-454f-9fad-2f66513dfa1bTerminal Having another process create a mutex with this same name will result in ERROR_ALREADY_EXISTS being returned whenever the second process calls CreateMutexW. The second process, in this case, would be Task Manager

This doesn’t successfully spoof Task Manager, however, as it has more startup checks. After receiving ERROR_ALREADY_EXISTS, Task Manager checks FindWindow for a class “Task Manager” In order to spoof this you need to call CreateWindowEx and pass lpClassName a string of: “TaskManagerWindow” and pass lpWindowName a string of: “Task Manager”

Finally, after Task Manager finds the duplicate Window, it will call SendMessageTimeoutW with msg parameter set to: 0x4D3. The result of SendMessageTimeoutW is then compared to 0x4D3. In order for this compare to evaluate to true, you need to specify a place in your WNDPROC (From WNDCLASSEX in CreateWindowEx) for a call to ReplyMessage if wmsg, in your WNDPROC, is equal to 0x4D3. ReplyMessage needs to be called with the same 0x4D3. If Task Manager receives 0x4D3 as a reply, it will go down a series of procedures and terminate itself. Check out the Github PoC Code

GIF1

❌
❌