Dipping into windows kernel exploitation with HEVD

After playing around with HEVD by following the excellent blog post here and managing to have some first success. I wanted to look into some of the other types of vulnerabilities and try to exploit them. To force myself to actually understand what I’m doing I’ll try to do all of those on Windows 7 SP 1 amd64, since most writeups seem to be for x86.

My Setup

If you want to follow along, you can follow the blog post above to get an essentially identical setup to mine, aside from a few changes listed below:

As mentioned above I’m doing all of this on 64 bit.

I’d highly recommend creating the following registry key
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Debug Print Filter with the DWORD value IHVDRIVER set to 0xf.
That way you should get the debug print from only IHVDRIVER type drivers as opposed to what the blog post advises.

I’m running on a windows host, so windbg is not running in a VM but rather on my host.

I’m developing the exploit on my host machine and have the output directory mapped into the VM as a shared folder.

What Type of Vulnerability to Pick?

I’m pretty inexperienced when it comes to the windows kernel space. Because of that I’ll try to avoid vulnerabilities that require a lot of understanding for the internals like Pool Overflows or Race Conditions, at least for now. So what are the options? Well, here’s the list from the HEVD github with my main candidates highlighted.

  • Write NULL
  • Double Fetch
  • Buffer Overflow
    • Stack
    • Stack GS
    • NonPagedPool
    • NonPagedPoolNx
    • PagedPoolSession
  • Use After Free
    • NonPagedPool
    • NonPagedPoolNx
  • Type Confusion
  • Integer Overflow
    • Arithmetic Overflow
  • Memory Disclosure
    • NonPagedPool
    • NonPagedPoolNx
  • Arbitrary Overwrite
  • Null Pointer Dereference
  • Uninitialized Memory
    • Stack
    • NonPagedPool
  • Insecure Kernel Resource Access

Since I’ve already done the stack overflow I’ll try the UAF in a NonPagedPool next. Mainly because I think it’ll be similiar to user mode user after free vulns and at the same time will (potentially) introduce me to more data structures inside the Windows kernel.

Understanding the Use After Free Vulnerability

Lucky for me the HEVD github repo is quite neatly organized, so identifying the relevant parts of code is very easy. The relevant header and c file are easy to identify.

However, since in the wild we don’t often have the luxury of seeing the code, I’m attempting to work things out in Ghidra only without referring to the code.
For an introduction to that, I really enjoyed Matt Hands recent blog post about his methodology.

After identifying the function responsible for handling IOCTL in Ghidra (essentially the way the blogpost suggests), the debug output in the driver gives away which IOCTL function each branch performs.

Some of the IOCTL handlers for the UAF code path.

I always find it useful to name the things I see as soon as I encounter them.
If I’m not 100% sure what the function does I will suffix it with a qualifier like _maybe or_probably.

After going through the ioctl handler we’ve identified the following functions (all named acoording to the debug print/IOCTL macro name) that seem relevant:

  • HEVD_IOCTL_ALLOCATE_UAF_OBJECT_NON_PAGED_POOL()
  • HEVD_IOCTL_USE_UAF_OBJECT_NON_PAGED_POOL()
  • HEVD_IOCTL_FREE_UAF_OBJECT_NON_PAGED_POOL()
  • HEVD_IOCTL_ALLOCATE_FAKE_OBJECT_NON_PAGED_POOL(param1, buf)

A few things things to note here right away.
Only the fake object takes parameters, looking into it a bit more the second one can be identified as the input struct, so we can most likely influence what the fake objects memory looks like.
Secondly, these functions perform all of the actions I’d associate with a UAF vulnerability. We can trigger the allocation, freeing and usage of the UAF object and we have another way to allocate that can be influenced by our user input.
The names suggest that all of those are allocated from the same pool.

So let’s go through those functions one by one and work out what they do. I’ll drop the HEVD_IOCTL prefix and NON_PAGED_POOL suffix for the rest of this.

ALLOCATE_FAKE_OBJECT

The function essentially just does a sanity check on the input buffer then passes the input buffer on to a second function. I’m going to assume that that function does the actual allocation and will name it UAF_allocate_fake_object_probably accordingly.

Ghidra disassembly of fake object function

Taking a look at the next function is a little intimidating at first.
But it also has some things going for it that make things easier.

We get debug output which immediately makes it easier to understand which components are what.

The common windows API calls are named, so it’s immediately clear that some memory (0x58 bytes) is being allocated and if the allocation succeeds some action is performed involving the input buffer. That action repeats a lot so I’ve snipped out the middle of it.

Shortened decompilation of actual allocation function

Let’s see if we can’t understand a bit better what is going on.

First some memory is being allocated, so what do the parameters mean? Helpfully there’s existing documentation on functions like ExAllocatePoolWithTag.
First parameter is PoolType (NonPagedPool)
Second parameter is number of bytes (0x58)
Third parameter is the Tag. (kcaH in ASCII)

If memory allocation fails the function opts out and we’re done, that much is clear.
If it succeeds, some action is performed, but what is that action?

Let’s first note that at the very end a 0 byte is written into the allocated memory. That smells of strings to me.
Secondly starting from the top, uVar1 is assigned the value of input_buffer[1] and then assigned to our newly allocated buffer at offset 4.
This pattern repeats all the way through.
So what this effectively does is copying our input buffer into the newly allocated buffer 4 bytes at a time. This is probably an inlined and loop unrolled memcpy or strncpy.

So in short, this method allocates some memory and if it succeeds copies our input buffer into it.

ALLOCATE_UAF_OBJECT

This method takes no input parameter from us, so it’ll generally parform more or less the same action. Here’s the decompiled output from Ghidra, with some things renamed.

Decompilation of allocate_uaf_object function

First thing we notice is that it allocates memory again. The pool type and tag are the same, but the size is 0x60 not 0x58 as with the fake object.

Next it calls memset (I’ll skip how I worked that out, but it’s similiar to how the memcpy/strcpy was worked out just before) on the allocated buffer and fills everything except the first 8 bytes. It assigns a constant value to those first 8 bytes instead.

It then assigns the pointer to the new buffer to a global variable.

So what’s in the first 8 bytes? Well, the debug output has a pretty clear hint for us.
DbgPrintEx(0x4d,3,”[+] UseAfterFree->Callback: 0x%p\n”,*puVar1);
That seems pretty straight forward, so if that’s true we’d expect the assigned constant to actually point to a function. And sure enough there it is:

Callback function pointed to by 0x140087c58

So in short, this method allocates a buffer, creates an object with a callback function in it and assigns this new object to a global variable.

USE_UAF_OBJECT

This function again takes no parameters and should be pretty straight forward.
Since there really isn’t anything to analyze here, I’ll just show you the decompilation and write a short summary.

Decompilation of use_uaf_object

The function first checks if the global variable we’ve noticed in the object creation has been set. If it has it performs some debug output and then calls the callback function.

FREE_UAF_OBJECT – The Actual Vulnerability

This function is pretty simple again, but it’s behaviour has some interesting implications.

Decompiled free_uaf_object method

As in the use method just before the method first checks if the global variable has been set. If it has, it will then proceed to free up that memory.

But it does not clear the global variable

This may seem like a small oversight, but as we’ve seen before other methods rely on this global variable being set to work out wether the object exists or not.

For that reason it’s possible for the object to be used after being freed. Which is exactly what constitutes a UAF vulnerability.

The Plan of Attack

So here I’d like to summarize again what those 4 functions do and then proceed to formulate a plan for how to exploit this vulnerability.

ALLOCATE_FAKE_OBJECT
This method allocates some memory and if it succeeds copies our input buffer into it.

ALLOCATE_UAF_OBJECT
This method allocates a buffer, creates an object with a callback function in it and assigns this new object to a global variable.

USE_UAF_OBJECT
The function first checks if the global variable is set. If it is it calls the callback function.

FREE_UAF_OBJECT
This function checks if the global variable is set, if it is it frees the memory of that object. But doesn’t reset the global variable

Seeing those 4 methods and their summaries all together it’s easy to formulate a plan of attack.

  1. Allocate UAF object
  2. Free UAF object, this will allow the memory to be used again, without unsetting the global variable
  3. Allocate fake object and copy some input that when interpreted as a UAF object will contain a callback function we control.
  4. Hope that the allocated fake object gets the same memory that the UAF object just freed.
  5. Use the UAF object, if we’ve succeeded our overwritten callback function should be called.

So much for the theory.
I’ll move the building of the actual exploit over into the next post, since this post has already gotten a bit long.

Leave a Reply

Your email address will not be published. Required fields are marked *