Kernel Pool Overflow Exploitation in Real World: Windows 10

Blog articles

1) Introduction

This article is the sequel of the article Kernel Pool Overflow Exploitation In Real World - Windows 7. We will exploit the very same vulnerability on Windows 10, which is pretty challenging since Microsoft did a great job at mitigating kernel pool attacks since Windows 8. So make sure to read the first part of the article before, because we're going deep in the pool!

1.1) Windows 8 Mitigations

Windows 8 came with a bunch of security improvements in the pool. I won't give an exhaustive list of those, but we can note:
  • Real safe linking / unlinking
  • PoolIndex Validation: The pool index overwrite attack is not a thing anymore
  • NonPagedPoolNx: There is a new type of pool which is basically NonPagedPool with NX activated. Windows uses it by default instead of NonPagedPool.
  • SMEP: Supervisor Mode Execution Prevention
  • MIN_MAP_ADDR: the 0x1000 first bytes of the memory are reserved and can't be allocated. This prevent the exploitation of Null Dereference vulnerabilities. This mitigation has been reverted on Windows 7 and Vista on x64 architectures
  • The NtQuerySystemInformation() leak can't be used anymore in low integrity level (usually browser sandboxes)

About the attack we used, the Quota Process Pointer Overwrite:
  • The process pointer is now encoded with a cookie:
    • When the chunk is allocated, the process pointer is encoded: ExpPoolQuotaCookie XOR ChunkAddress XOR ProcessPointer
    • When the chunk is free, the process pointer is used as a canary and encoded: ExpPoolQuotaCookie XOR ChunkAddress
  • The process pointer, after being decoded, has to points in kernel land, or it will trigger a bugcheck.

If you want an exhaustive list of Windows 8 mitigations, you should read this [1]. Every attack presented by Tarjei Mandt in his paper [2] has been mitigated. There is actually some exploits on Windows 8 that aims the data to take the control of RIP, but it has been mitigated in Windows 10 by using another cookie in the _OBJECT_HEADER. So, if we wanted to use the very same attack, a Quota Process Pointer Overwrite, we would need:
  • The PoolCookie: Because we need to properly encode the pointer
  • The address of the chunk we overflow: Because we need to properly encode the pointer
  • Some arbitrary data in kernel land at a known address: Even if we properly encode the pointer, it needs to point in kernel land but also on a fake structure we crafted.

It's worth to give it a try!

2) The address of the chunk we overflow

This will be very fast. If you remember, in the Windows 7 version of the exploit, we used basic Pool Spraying. Welp, it's time to use the advanced Pool Spraying, as described in this article [3]. Using the methods in this article, we can predict any future allocation, and it's pretty easy to know the address of the SystemBuffer allocated by the I/O Manager when calling the vulnerable IOCTL. Since the SystemBuffer is the overflowing buffer, the chunk we overflow is just behind the SystemBuffer, so we know its address.


Note: As I said several times before, the NtQuerySystemInformation leak is not available in low Integrity Level. So we can't have this address by being in low IL. We will remain in medium IL!

3) Some arbitrary data in kernel land at a known address

There is actually a few ways to achieve this. For a long time, I used a pool spraying combined with some random IOCTL to put some data in kernel land and use them after free, but it was not really reliable. Since then, I found a much more trustworthy method. The CreatePrivateNamespace function allocates a directory object in the PagedPool. Here is its prototype:

HANDLE WINAPI CreatePrivateNamespace(
  _In_opt_ LPSECURITY_ATTRIBUTES lpPrivateNamespaceAttributes,
  _In_     LPVOID                lpBoundaryDescriptor,
  _In_     LPCTSTR               lpAliasPrefix

A few things are interesting:
  • It returns a handle. Pretty normal, since it's an object. But it means we can leak the directory object's address in the PagedPool.
  • The second argument is a Boundary descriptor. It has to be unique, so you can create it with the function CreateBoundaryDescriptor:
    • HANDLE WINAPI CreateBoundaryDescriptor(
        _In_ LPCTSTR Name,
        _In_ ULONG   Flags
    • takes a name

Here is the thing: the name of the boundary descriptor is directly stored in the object in the PagedPool:

So this code:


gives this chunk in the PagedPool:


The name "Hello World !" is stored at object_address + 0x1A8And it seems to have no limits on the name:


Here, the chunk size is two times bigger, just to contains the name of the Boundary Descriptor! By the way, since the size of this object is controllable, it becomes a great tool to spray the PagedPool... Anyway, here we are, we can put some arbitrary data in kernel land, and we can have its address using the NtQuerySystemInformation leak!

4) The Pool Cookie

This is getting intense. The ExpPoolQuotaCookie is generated at boot and has the size of a pointer: 8 bytes on x64 architectures. Its entropy is good, so there is no way to guess or compute it. At first look, the only way to leak the pool cookie is to have an arbitrary read, which is a pretty big and rare vulnerability. So I studied where the ExpPoolQuotaCookie is used:

When a pool chunk is used in the quota management of a process, the Quota Bit is set in its PoolType, and there is a pointer encoded in the last 8 bytes of its pool header (x64 version):


But this is the case when the chunk is allocated ! When this chunk is freed, the chunk is a bit different:


Here, the Process Billed value is only the Pool Cookie xored with the address of the chunk! This value is used as a canary, to detect overflow and attacks on the pool. But this is interesting for us; using an advanced pool spraying, we can easily know the address of a chunk. Thus, if we manage to read the Process Billed value, we might be able to get the Pool Cookie! So I imagine the following attack:
  1. Using a pool spraying, we allocate some chunk we control ; we know their address and we're able to free them at anytime.
  2. We will first free one of the chunk...
  3. Then free the chunk just before
  4. Just after, we will reallocate a chunk at the exact same place of the two previous chunk. We can use an IOCTL to do this, and make sure the SystemBuffer is allocated here !
  5. Even with the free and reallocation of the chunks, the former pool header is not overwritten ! It means the value "Pool Cookie XOR ChunkAddress" is still in the data of our chunk !

Kernel Pool

Here, we can imagine an IOCTL that will return a little bit more data than it writes: an Out-Of-Bounds read! With the tiniest OOB read, we could manage to leak the Pool Cookie. So we  went from an arbitrary read to a OOB read to leak the Pool Cookie, which is way more common. And I'm saying this for a reason ; I found an OOB read in the very same vulnerable driver!

4.1) About CVE-2017-7441

Here is the pseudo code of the vulnerable IOCTL with code 0x22E1C0:


So the driver calls the function RtlLookupElementGenericTableAvl with our input in the SystemBuffer. If the function succeed, it will copy the return of the function in the SystemBuffer using memcpy. But this time, the size is checked before the copy, so the memcpy is not a problem. Though there is an error in the calculation of how many bytes the driver is written, and it returns 2 excess bytes. If I want to reach the vulnerable code, I need the RtlLookupElementGenericTableAvl function to success and be able to control at least the length of the value it returns. The only way if found to do this is by writing the current process id in the SystemBuffer; The RtlLookupElementGenericTableAvl function works fine and returns the path to the executable of the current process as value. I can more or less control the length of the path to my executable; the maximum length of a path on Windows is 255 bytes. We just have to trigger the vulnerability 4 times by creating 4 different process with 4 different executables (with various path size) in order to leak the 8 bytes of the Pool Cookie!
5) Conclusion

We have everything we needed to convert our Windows 7 exploit into a Windows 10 exploit :

Exploiting a pool overflow in Windows 10 is one minor leak away from Windows 7!

The Pool Spraying and the NtQuerySystemInformation leak gives to an attacker too much knowledge on the kernel state, making attacks on the pool still reliable.

You can find the source of the exploit on my github 4].

6) References

[1] Windows 8 Heap internals

[2] Kernel Pool Exploitation on Windows 7

[3] Pool Spraying article

[4] Source code of the exploit

Research, exploit and article by BAYET Corentin.

Related Contents

This website uses cookies to ensure you get the best experience on our website. Learn more.