Kristal's Notebook

RE Writeups

View on GitHub

CVE-2021-40449 Introduction

The vulnerability in question is a Use After Free in a kernel function in win32kfull.sys.
It depends on a unique mechanism that the graphics system uses which is called Usermode Callbacks - a way for the kernel to call usermode functions.

In this case, during a NtGdiResetDC syscall, it calls several Usermode functions.
If during the first Usermode callback you call NtGdiResetDC again, it frees the object that the original call operates on.
To abuse this UAF we can reclaim the memory of the freed object during the Usermode callback using a spray primitive.

The call stack during the free basically looks like that:

poc.exe -> NtGdiResetDC (uses ObjectA) -> Usermode callback to DrvResetPDEV (hooked) -> NtGdiResetDC (frees ObjectA)

I took this vulnerability as a random case study of recent win32k vulnerability and tried to exploit it only based on the original Kaspersky report, so I can learn the subject better and then learn even more by comparing my way with the published POCs.
To exploit the vulnerability we have to do several things:

There are several ways to implement each step and I’ll detail some of them.
For following along the post, my POC is found here.

Hooking the Usermode callback

Driver’s function table hook (public POCs method)

The Usermode callback functions are not exported but published by each Usermode printer driver as a function table.
To hook it is just to query the function table, VirtualProtect it to make it Writeable and then overwrite the wanted callback with our own function.

Inline hooking

After finding the Usermode DrvEnablePDEV function, either through the callback table (previous option) or through pattern searching, it’s possible to just inline hook it.
I did the pattern search + inline hook because I didn’t know about the function tables before reading the public POCs.

Gdi Callback Table Hook

There are a lot of Usermode callbacks that win32k uses and they’re maintained in an undocumented struct that’s called KernelCallbackTable and pointed by the PEB.
Several public POCs (of different CVEs and targets) use this method.

Reclaiming the freed space

The object that’s freed is allocated in the Paged Session Pool that win32k normally uses. There are known primitives that use Windows, Bitmaps, and Palettes. The most recent one is the palettes.

In more modern Windows 10 versions, apparently from RS3 and up there’s a mitigation in win32k that’s called TypeIsolation.
In essence, it changes the way that objects are allocated in win32k. Instead of being allocated with ExAllocatePoolWithTag, it has a different (and pretty simple) allocation mechanism.
Each object type (palettes, devobjs, bitmaps, etc) is now allocated in its own kind of pool. This makes abusing the UAF with a type-confusion harder and maybe even not possible.

In the TypeIsolation array, the PDEV object uses gpTypeAllocation[6] pool. The Palette object for example uses the gpTypeAllocation[1] pool.

Palette spray

Let’s take the palette primitive for an example, without the TypeIsolation mitigation.
A palette is compromised from a header (LOGPALETTE) and palette entries (PALETTEENTRY). Palette entry is defined like this:

typedef struct tagPALETTEENTRY {
    BYTE        peRed;
    BYTE        peGreen;
    BYTE        peBlue;
    BYTE        peFlags;
} PALETTEENTRY, *PPALETTEENTRY, FAR *LPPALETTEENTRY;

The palette entries are located in memory adjacent to the palette header, and their content is controlled by us.
The palette structure enables us to control each byte’s value in the kernel memory, besides the palette header.

Example:
In Windows 10 build 1709, the size of PDEVOBJ is 0xe30. Palette header size in the kernel is 0xa0.
Therefore to precisely reclaim the PDEVOBJ memory using palettes (with each the value of 0x01020304) we need to use palettes with (0xe30 - 0xa0)/4 == 0x364 entries:

pal = (LOGPALETTE*)malloc(sizeof(LOGPALETTE) + 0x364 * sizeof(PALETTEENTRY));
pal->palVersion = 0x300;
pal->palNumEntries = 0x364;
PALETTEENTRY * parr = pal->palPalEntry;
for (int i = 0; i < 0x364; i++) {
    parr[i].peRed = 0x01;
    parr[i].peGreen = 0x02;
    parr[i].peBlue = 0x03;
    parr[i].peFlags = 0x04;
}
for (int i = 0; i < SPRAY_SIZE; i++) {
    hPalArr[i] = CreatePalette(pal);
}

During testing I found that there’s a limit on the amount of objects like palettes that one can create: 10000 (that’s why) .
That’s a limit that Microsoft devs determined to be enough for normal cases. The amount that each process uses is viewable in Process Explorer (add the “USER Objects” column).

Exploiting arbitrary function call

The vulnerability allows us to call an arbitrary kernel function (not protected by kCFG: __guard_dispatch_icall_fptr->_guard_dispatch_icall_nop).
Also in this specific case we have the ability to control the first parameter to the function (in rcx).

The public POCs all use the RtlSetAllBits method which is available from RS1 upwards:

My POC uses a different, more generic method, therefore also applicable to Windows versions below RS1.
I call it “more generic” because it doesn’t require having control of any parameters, but practically it depends on offsets that are specific to each windows version.

Therefore to adapt my POC to a different Windows version, the following things need to be fixed:

Even though it looks like a lot, it took me about 10 minutes of adapting it for a different version. It can even be done dynamically in the exploit using pattern searching, or automated beforehand with a python script.
More info about the shellcode is in my previous post.

Note: For some reason the poc process crashes after the exploit. I didn’t bother debugging it as it doesn’t really matter (besides the Windows Error Reporting popup that can be disabled beforehand) as I’m elevating the parent process’s token.


https://github.com/Kristal-g/CVE-2021-40449_poc

Conclusion

I think I learned more effectively by ignoring the public POCs and first trying on my own.
After creating my exploit and learning it practically, reading the public POCs methods was again more informative and effective for me than usual.

But eventually, it’s my first time exploiting a win32k vulnerability, so if you see I did something really stupid in the exploit I’d really love to hear about it!

Contact me

Feel free to reach out at my Twitter or email!