Kernel Pool Overflow Exploitation In Real World – Windows 10

1) Introduction

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 use 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 mitigations has been reverted on Windows 7 and Vista on x64 architectures

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 + 0x1A8

And 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 enthropy 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 easyly 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:

Using a pool spraying, we allocate some chunk we control ; we know their address and we’re able to free them at anytime.

We will first free one of the chunk…

Then free the chunk just before

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 !

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 !

Here, we can imagine an IOCTL that will return a little bit more data than it writes: an Out-Of-Bounds read ! With the tyniest 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 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 differents process with 4 differents 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 :