This blog post is a reshare of a personal note I wrote 15 years ago, and I’m sharing it here for nostalgic reasons. fast forward to 2024, and we now have KASAN integrated into the Windows kernel.

Original content:

In recent days, Windows kernel vulnerabilities have become more valuable due to the limitations imposed by sandboxes. Every RCE exploit now requires an additional phase to bypass these restrictions in order to achieve full system access.

Many local privilege escalation vulnerabilities are based on flaws in Win32k, particularly how it handles or uses objects. In most cases, Win32k uses freed memory, leading to use-after-free vulnerabilities.

Win32k uses HMAllocObject to allocate memory for its objects, and the function uses different memory management subsystems based on the object type, either from the heap or from the kernel pool.

int __stdcall HMAllocObject(int a1, PVOID Object, char a3, ULONG Size)
{
	....
	....

  if ( v5 & 0x10 && Object )
  {
    v7 = DesktopAlloc((int)Object, Size, ((unsigned __int8)a3 << 16) | 5);
    if ( !v7 )
    {
LABEL_28:
      UserSetLastError(8);
      return 0;
    }
    LockObjectAssignment(v7 + 12, Object);
    *(_DWORD *)(v7 + 16) = v7;
  }
  else
  {
    if ( v5 & 0x40 )
    {
      v8 = SharedAlloc(Size);
    }
    else
    {
      v9 = !Object && v5 & 0x20;
      if ( !(v5 & 8) || v9 )
        v8 = Win32AllocPoolWithTagZInit(Size, dword_BF9F191C[v4]);
      else
        v8 = Win32AllocPoolWithQuotaTagZInit(Size, dword_BF9F191C[v4]);
    }
    v7 = v8;
	....
	....
	....
	....
  }
}

These memory management functions include:

  • DesktopAlloc function uses heap,
  • SharedAlloc function uses heap.
  • Win32AllocPoolWithQuotaTagZInit, Win32AllocPoolWithTagZInit functions use pool.

For example, a Menu object uses DesktopAlloc, while an Accelerator object uses Kernel Pool.

For objects that use heap memory, when the object’s life ends, the OS calls RtlFreeHeap to free the used memory. However, after RtlFreeHeap returns, the freed memory still contains the old/valid contents. If another part of win32k.sys uses the freed memory, nothing will happen because it uses memory with old contents (no BSOD occurs), and the bug will be missed.

Until now, researchers have typically discovered these types of bugs through reverse engineering. but if someone is fuzzing Win32k, they will miss vulnerabilities, due to having valid content even after free, They need to allocate a large amount of memory to ensure that the freed memory is reallocated with different content, which can trigger a crash. But how can one know when the OS will use the freed memory? and allocating memory in each iteration has runtime overhead.

In user-mode code, we can use GFlags to enable PageHeap system-wide. This doesn’t affect the heap implementation in the kernel. There is also a special pool that can be enabled with the verifier, but it doesn’t help us with heap-based objects.

gflags.exe /i iexplore.exe +hpa +ust to to enable the page Heap (HPA)

So, my idea is to patch RtlFreeHeap and fill the freed memory with invalid content, such as 0x0c0c0c0c.

With the help of the RtlSizeHeap function (thanks to @ponez ), we can find the size of heap object and fill it with 0x0c0c0c0c. in alomst all cases the object contains a pointer to another object, so an pointer needs to be dereferenced, it means OS will dereference 0x0c0c0c0c address and we will get a kernel BSOD when it uses freed memory.

__declspec(naked) my_function_detour_RtlFreeHeap()
{
	//PVOID  func=RtlSizeHeap;;
	__asm
	{		
		// exec missing instructions
		mov     edi,edi
		push    ebp
		mov     ebp,esp
		push    ebx
		mov     ebx,dword ptr [ebp+10h]
		int 3;
		/*
		BOOLEAN	RtlFreeHeap
		( 
		IN PVOID  HeapHandle,
		IN ULONG  Flags,
		IN PVOID  HeapBase
		); 
		mov     ebx,dword ptr [ebp+10h] get HeapBase  
		*/
		PUSHAD
		PUSH dword ptr [ebp+10h]
		PUSH dword ptr [ebp+0Ch]
		PUSH dword ptr [ebp+08h]
		call RtlSizeHeap;
		sub  ecx,ecx;
		mov ecx, eax; // size from RtlSizeHeap
		mov eax, 0x0c
		mov edi, ebx; // address of heap chunk
		rep stos byte ptr es:[edi]
		POPAD
}
}

I tested my Win32k heap sanitizer detector with some old UAF vulnerabilities in Win32k and it worked like a charm.