Windows Kernel Pool Spraying

I) Introduction

When you are trying to exploit a kernel pool vulnerability, you’ll have to deal with chunks and pool metadata. There is severals checks made on chunks headers, and you’ll have to control everything if you want to avoid the BSOD.

Pool spraying is the art of making the allocations in the pool predictible. It means you’ll be able to know where a chunk will be allocated, and which chunks are around.

This is mandatory if you want to leak some precise informations, or overwrite specific data.

The purpose of this paper is not to explain in depth the pool internals, but only the basics you need to know in order to understand the spraying mechanics.

If you need exhaustive informations on the pool internals, you should read Tarjei Mandt papers [1] [2].

For the very same reasons, we will speak only of the x64 architectures.

Everything presented in this paper is made with Windows internals only, and can be applied on every versions from Windows 7 to Windows 10.

II) Some Pool Internals

The pool is the common place for every allocations in the Windows kernel. Since it’s very used, it’s more complex to control than a simple heap. It manages all type of data, from the simplest string to the biggest structures. Even if it’s not much different from a heap, the pool got its own allocator and structures.

The operating system kernel in Windows sets aside two pools of memory, one that remains in physical memory (NonPaged Pool ) and one that can be paged in and out of physical memory (Paged Pool). Note that Windows 8 introduced the NonPagedPoolNx ; it’s basically a NonPagedPool with DEP enabled.

There is several types of Pool, but the main structures are the same.

The Pool Descriptor keeps informations on the current state of the pool, it contains :

  •  A Deferred Free list (enabled by default) : a list of chunks which will be freed when the list is filled
  •  A ListHeads : A LIFO list of free chunks sorted by size
  •  A Lookaside List : A LIFO list of free chunks sorted by size. Very similar to the Lisheads list, but with a few limitations.
  •  Misceallenous informations on current allocations

The Lookaside list is a LIFO list of small free chunks, also sorted by size. It’s used as an alternative to the ListHeads for the chunks with a size inferior or equal to 0x200 (512) bytes, improving performances. Its internals will be described a bit later.

In plain terms, the pool is just a list of allocated pages. A page is 0x1000 bytes long, and is fragmented in chunks.

There are chunks that are bigger than 0x1000 bytes, and those represents a special case that we won’t study.

So we will focus on chunks smaller than 0xFF1 bytes.

Here is the structure of a kernel pool chunk :

  • PreviousSize : The blocksize of the previous chunk. This blocksize is stored as : actual_size >> 4 (divided by 16)
  • PoolIndex : index used to get the pool descriptor from this chunk in an array of pool descriptor of the corresponding pool type.
  • BlockSize : The blocksize of the chunk. This blocksize is stored as : actual_size >> 4 (divided by 16)
  • PoolType : a bitmask containing a few details on the chunk :
    • Its pool type (Nonpaged, paged…)
    • If it’s allocated or not
    • Quota bit : If the chunk is used in the management of a process’ quota. If this flag is raised, a pointer to the corresponding EPROCESS object is stored in ProcessBilled
    • Some other informations
  • PoolTag : 4 characters used to identify the chunk when debugging
  • ProcessBilled : If the Quota bit is set, a pointer to an EPROCESS object.

Allocations / Free in Kernel Pool

The pool has 3 differents ways to allocate a chunk :

  • If the chunk is a small chunk (≤ 0x200 bytes), the allocator will first try to use the lookaside lists. It will look for a chunk with the exact same size as requested. If there is no such chunk, the allocator will use the next way.
  • It will then use the ListHeads, and will also look for a chunk with the exact same size as requested. If there is no such chunk, the allocator will take a bigger chunk and split it in two parts ; one will be allocated, and the other one stored in the appropriate ListHeads.
  • If there is no corresponding chunks, it will allocate a new page, and it’s first chunk will be allocated at the top of the page. Every following chunk will be allocated at the bottom of the page.

In the same way, there is several mechanisms for freeing a chunk :

  • If the chunk is a small chunk (≤ 0x200 bytes), the allocator will first try to store it in the lookaside list corresponding to its type. However, a lookaside list can contain a maximum of 0xff (255) chunks of the same size, so it might be full.
  • If the DELAYED_FREES flag is raised (it is by default), the chunk will be stored in the DeferredFree list, until this list is full (maximum 0x20 chunks). Once it’s full, the list is used to free every chunk at the same time, improving performances.
  • Finally, the chunk is actually free ; the allocator checks if the surrounding chunks are free, and coalesce them if it ‘s the case, then store the new chunk in the appropriate ListHeads. If a whole page is actually freed, it will be simply desallocated.

III) Pool Spraying Basics

Now, let’s go right to the subject.

The basic of spraying is to allocate enough objects to make sure you control the future allocations. Windows provides us many tools permitting the allocation of objects in the differents types of pool.

For example, we can allocate ReservedObjects or Semaphores in the NonPagedPool (NonPagedPoolNx on Windows 8 and later).

It’s up to you to find the object that will match the type of pool you want to control.

The size of the object you chose will also matters, since it’s directly linked to the size of the gaps you’re going to create.

Once you chose your object, you’ll first derandomize the pool, by allocating massively this object.

The idea is to create pool pages like this :

When you will allocate those objects, Windows will obviously not provide you their address, since it’s some kernel-land addresses, but it will give you a handle on this object.

You can use this handle to free the object by calling CloseHandle().

Allocating this object massively assures us that we completly drained Lookaside and ListHead lists, and from now on, every allocations we make is using a new page.

If we keep a list of handles for all the objects we allocated, we might assume that there is a kind of correlation between the pool and our list of handle :

It allows us to easily create gaps with a semi-controlled size (since its the product of our object’s size), by calling CloseHandle() on chunks that are next to each other.

Some details remains and might cause you trouble :

  • If the size of the object you chose is ≤ 0x200 bytes, which is more than likely, the corresponding chunk will be freed in the lookaside list, avoiding the coalescing of the chunks.To avert this, you have to fill the lookaside list, by freeing enough objects just before freeing the chunks that will compose the gap.
  • Your freed chunks might then fall in the DeferredFree list, and won’t coalesce immediatly. So you have to free enough objects to fill this list just after freeing the chunks that will compose the gap.
  • Finally, you’re allocating in the pool, and it’s common to the whole kernel. It means the gap you just created might be allocated at any moment by something you don’t control. You’ll have to be fast !

Short abstract of the steps :

  1. Chose the chunks to be freed using their handles
  2. Free enough chunks to fill the lookaside list
  3. Free the chosen chunks
  4. Free enough chunks to fill the DeferredFree list
  5. Use your gap as fast as possible !

IV) Linking with the leaks

I said just before that Windows won’t give you the address of your objects since it’s kernel addresses . Well, I lied to you. Yes.

On Windows, there is some well known leaks using the function NtQuerySystemInformation(). This function is a bit magical, and allows many leaks of kernel addresses.

We are mainly interested in its capacity to provide a list of every objects currently allocated, by giving us those structures :

typedef struct _SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX
{
 PVOID Object;
 ULONG_PTR UniqueProcessId;
 HANDLE HandleValue;
 ULONG GrantedAccess;
 USHORT CreatorBackTraceIndex;
 USHORT ObjectTypeIndex;
 ULONG HandleAttributes;
 ULONG Reserved;
} SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX, *PSYSTEM_HANDLE_TABLE_ENTRY_INFO_EX;
typedef struct _SYSTEM_EXTENDED_HANDLE_INFORMATION
{
 ULONG_PTR NumberOfHandles;
 ULONG_PTR Reserved;
 SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX Handles[1];
} SYSTEM_EXTENDED_HANDLE_INFORMATION, *PSYSTEM_EXTENDED_HANDLE_INFORMATION;

Calling NtQuerySystemInformation with the SystemExtendedHandleInformation argument provides us the _SYSTEM_EXTENDED_HANDLE_INFORMATION structure.

We can use the Handles field to list every object allocated on the system.

Every object is described by the _SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX structure, which contains :

  • The HandleValue field that matchs the handle we got when we allocated the object.
  • The Object field that is the address of the object in the kernel pool memory.

Voilà ! By using this list, we can get any object kernel address using its handle !

Edit: It’s important to note that, since Windows 8, this leak isn’t available anymore in low integrity level. So if you’re in low IL, you have to stick with basic spraying !

We can use this to:

Improve our spraying and make it 100 % reliable

Our current method to create gaps in the pool is not that much trustworthy ; even if there is a lot of chances that two objects allocated one just after the other are two chunks next to each other in pool memory, it’s still not sure. They might have been allocated in two different pages, or a unknown chunk could have been allocated between them, who knows.

With those address leaks, we are able to easily make sure the gaps we create are valid :

  • Chose a handle in our list and leak its kernel address
  • Take the following handle and leak its address ; it should be equal to the previous address added to the size of the chunk. If not, then the chunks aren’t next to each other, and the gap won’t be valid

Check this for every chunks composing the gap, but also for those surrounding the gap.

Using this method, we are 100 % sure that our gap is valid.

V) Conclusion

Nowadays, pool spraying is so powerful that exploiting a vulnerability in the kernel pool without it seems impossible.

However, pool spraying remains limited on a few points .

First, we can’t create gaps of arbitrary size, since it will always depends of the size of the object chosen for spraying.

Of course, we might imagine a spray mixing several objects in order to create gaps with more varied sizes, but so far we never needed to use such methods.

Though It remains entirely possible, since the address leaks will always allows us to assure the validity of our gaps.

Second, it also seems complicated to predict the allocation of a chunk with a size inferior or equal to 0x200 bytes, since this allocator will use the lookaside list. The only way to achieve this is to use an object with the exact same size of the chunk you want to control.

I wrote a library that use the methods explained here, and provides an easy API to spray the pool. Check it out [5] !

It should be time for Windows to completely (since it not available anymore in low IL) remediate the NtQuerySystemInformation() leaks, because it’s the only way in my opinion to finally mitigate every attacks on kernel pool.

Until then, enjoy your kernel pool parties !

VI) References

[1] http://www.mista.nu/research/MANDT-kernelpool-PAPER.pdf  – Tarjei Mandt’s paper on Windows 7 Kernel Exploitations

[2] http://illmatics.com/Windows%208%20Heap%20Internals.pdf – Windows 8 Heap internals

[3] http://blog.ptsecurity.com/2013/03/stars-aligners-how-to-kernel-pool.html – Great article on pool spraying and exploitation

[4] https://github.com/fishstiqz/poolinfo – This extension is great for investigating the pool state

[5] https://github.com/cbayet/PoolSprayer – My library to spray the pool !

 

Research and article by BAYET Corentin

2 thoughts on “Windows Kernel Pool Spraying

  1. Me Bar dit :

    Nice article!

    BTE it’s worth noting that the NtQuerySystemInformation leak isn’t possible when Integrity Leval is low, which most remote attack surfaces is. this leak is only applied in limited cases where you’re running as medium IL.

    1. Philippe dit :

      Hey, thanks for your comment.

      You’re right, since Windows 8 the NtQuerySystemInformation leak is not possible in low integrity level.
      I should have mention it and I will edit the article !

      But in my opinion, the medium integrity level is not that hard to get, since a remote attack often comes with a sandbox escape, or you don’t expect to do much on the machine.

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

cinq × un =