Windows Kernel Exploitation Tutorial Part 4: Pool Feng-Shui –> Pool Overflow


We discussed about Write-What-Where vulnerability in the previous part. This part will deal with another vulnerability, Pool Overflow, which in simpler terms, is just an Out-of-Bounds write on the pool buffer. This part could be intimidating and goes really in-depth on how to groom the pool in a way to control the flow of the application reliably everytime to our shellcode, so take your time with this, and try to understand the concepts used before actually trying to exploit the vulnerability.

Again, huge thanks to @hacksysteam for the driver.

Pool Feng-Shui

Before we dig deep into Pool Overflow, we need to understand the basics of pool, how to manipulate it to our needs. A really good read on this topic is available here by Tarjei Mandt. I highly suggest to go through it before continuing further in this post. You need to have a solid understading on the pool concepts before continuing further.

Kernel Pool is very similar to Windows Heap, as it’s used to serve dynamic memory allocations. Just like the Heap Spray to groom the heap for normal applications, in kernel land, we need to find a way to groom our pool in such a way, so that we can predictably call our shellcode from the memory location. It’s very important to understand the concepts for Pool Allocator, and how to influence the pool allocation and deallocation mechanism.

For our HEVD driver, the vulnerable user buffer is allocated in the Non-Paged pool, so we need to find a technique to groom the Non-Paged pool. Windows provides an Event object, which is stored in Non-Paged pool, and can be created using the CreateEvent API:

Here, we would need to create two large enough arrays of Event objects with this API, and then, create holes in that allocated pool chunk by freeing some of the Event objects in one of the arrays by using the CloseHandle API, which after coalescing, would combine into larger free chunks:

In these free chunks, we’d need to insert our vulnerable user buffer in such a way, that it reliably overwrites the correct memory location everytime, as we’d be “corrupting” an adjacent header of the event object, to divert the flow of our execution to our shellcode. A very rough diagram of what we are going to do here should make this a bit more clear (Yeah, I’m a 1337 in paint):

After this, we’d be carefully placing the pointer to our shellcode in such a way, that it could be called by manipulating our corrupted pool header. We’d be faking a OBJECT_TYPE header, carefully overwriting the pointer to one of the procedures in OBJECT_TYPE_INITIALIZER.


To analyze the vulnerability, let’s look into the PoolOverflow.c file:

This would seem a little more compllicated, but we can clearly see the vulnerability here, as in the last line, the developer is directly passing the value without any validation of the size. This leads to a Vanilla Pool Overflow vulnerability.

We’ll find the IOCTL for this vulnerability as described in the previous post:

This gives us IOCTL of 0x22200f.

We’ll just analyze the function TriggerPoolOverflow in IDA to see what we can find:

We see a tag of “Hack” as our vulnerable buffer tag, and having a length of 0x1f8 (504). As we have sufficient information about the vulnerability now, let’s jump to the fun part, exploiting it.


Let’s start with our skeleton script, with the IOCTL of 0x22200f.

We are triggering the Pool Overflow IOCTL. We can see the tag ‘kcaH’ and the size of 0x1f8 (504). Let’s try giving 0x1f8 as the UserBuffer Size.

Cool, we shouldn’t be corrupting any adjacent memory right now, as we are just at the border of the given size. Let’s analyze the pool:

We see that our user buffer is perfectly allocated, and just ends adjacent to the next pool chunk’s header:

Overflowing this would be disastrous, and would result in a BSOD/Crash, corrupting the adjacent pool header.

One interesting thing to note here is how we are actually able to control the adjacent header with our overflow. This is the vulnerability that we’d be exploiting by grooming the pool in a predictable manner, derandomising our pool. For this, our previously discusssed CreateEvent API is perfect, as it has a size of 0x40, which could easily be matched to our Pool size 0x200.

We’ll spray a huge number of Event objects, store their handles in arrays, and see how it affects our pool:

Our Event objects are sprayed in the non-paged pool. Now we need to create holes, and re-allocate our vulnerable buffer Hack into the created holes. After reallocating our vulnerable buffer, we’d need to “corrupt” the adjacent pool header in such a way, that it leads to our shellcode. The size of the Event object would be 0x40 (0x38 + 0x8), including the Pool Header.

Let’s analyze the headers:

As we are reliably spraying our Non-Paged pool with Event objects, we can just append these values at the end of our vulnerable buffer and be done with it. But, it won’t work, as these headers have a deeper meaning and needs a minute modification. Let’s dig deep into the headers to see what needs to be modified:

The thing we are interested in this is the TypeIndex, which is actually an offset (0xc) in an array of pointers, which defines OBJECT_TYPE of each object supported by Windows. Let’s analyze that:

This all might seem a little complicated at first, but I have highlighted the important parts:

  • The first pointer is 00000000, very important as we are right now in Windows 7 (explained below).
  • The next highlighted pointer is 85f05418, which is at the offset of the 0xc from the start
  • Analyzing this, we see that this is the Event object type
  • Now, the most interesting thing here is the TypeInfo member, at an offset of 0x28.
    • Towards the end of this member, there are some procedures called, one can use a suitable procedure from the provided ones. I’d be using the CloseProcedure, located at 0x038.
    • The offset for CloseProcedure becomes 0x28 + 0x38 = 0x60
    • This 0x60 is the pointer that we’d be overwriting with pointer to our shellcode, and then call the CloseProcedure method, thus ultimately executing our shellcode.

Our goal is to change theĀ TypeIndex offset from 0xc to 0x0, as the first pointer is the null pointer, and in Windows 7, there’s a **flaw** where it’s possible to map NULL pages using the NtAllocateVirtualMemory call:

And then writing pointer to our shellcode onto the desired location (0x60) using the WriteProcessMemory call:

Adding all the things discussed above together, our rough script would look like:

Our Vulnerable buffer now sits flush between our Event objects, in the hole that we created.

The TypeIndex is modified from 0xc to 0x0
Bingo, our shellcode address resides in the desired address.

Now, we just need to call the CloseProcedure, load our shellcode in VirtualAlloc memory, and our shellcode should run perfectly fine. The script below is the final exploit:

And we get our usual nt authority\system shell:

4 thoughts on “Windows Kernel Exploitation Tutorial Part 4: Pool Feng-Shui –> Pool Overflow

  1. Hi, am following your article to understand and write kernel pool overflow exploits in python, I understand till mapping null and even it works for changing value “0xc” to “0x0”. After that, it shows question mark(?) while typing dd 0x00000000 ( Don’t understand this part) Can you please help me in this regard

    Thank you in Advance

    1. Question mark would suggest that null page isn’t getting mapped correctly. Are you setting up the breakpoint before the program crashes and analyzing it?

  2. Hello, Thanks for your outstanding work. I’m not good at english…

    I think I have understand the post:
    [+] pool overflow to control pool header( )
    [+] make 0xc to 0 to make the fakedEvent(zero memory is in control, we could put balabala which we want)
    [+] replace the CloseProcedure –> shellcode
    [+] pwn

    However, your exp don’t work on my virtual machine(win7-sp1-x86), I debug it and found that the “event” spary failed… (after “*Hack not “Even””)could give me some tips…

    Thank you very much.

Leave a Reply

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