Introduction
Windows’ legacy graphics subsystem, GDI (Graphics Device Interface), has been a quiet powerhouse in kernel exploitation for over a decade. While Microsoft has layered in numerous mitigations like Kernel Control Flow Guard (KCFG), SMEP, and Win32k lockdown, the GDI attack surface persists — and still enables powerful primitives when paired with a suitable vulnerability.
In this deep-dive, we explore how to leverage GDI objects, particularly HBITMAP
, to gain arbitrary read/write access to kernel memory — a critical step in most local privilege escalation (LPE) chains. Whether exploiting a vulnerable driver or chaining from a userland bug, GDI can turn limited access into full SYSTEM-level code execution.
GDI Objects and the Kernel
GDI objects live in the win32k.sys
subsystem and are managed per-session via the GdiSharedHandleTable. Each handle (like HBITMAP
, HPALETTE
, HBRUSH
, etc.) corresponds to an entry in this table, which maps user-mode handles to kernel-mode structures.
Internals of a Bitmap
A simplified version of the SURFOBJ
structure (which underlies HBITMAP
) might look like:
typedef struct _SURFOBJ { DHSURF dhsurf; HSURF hsurf; DHPDEV private_dhpdev; HDEV private_hdev; DHPDEV dhpdev; HDEV hdev; SIZEL sizlBitmap; ULONG cjBits; PVOID pvBits; PVOID pvScan0; // Pointer to bitmap pixel data (kernel mode) LONG lDelta; ULONG iUniq; ULONG iBitmapFormat; USHORT iType; USHORT fjBitmap;} SURFOBJ;
The magic lies in the pvScan0
pointer. If we can corrupt this field to point to arbitrary kernel memory, then standard userland APIs like SetBitmapBits()
and GetBitmapBits()
let us write to and read from any kernel address.
Exploitation Plan
The exploitation steps are straightforward in concept, but require finesse in practice:
- Spray GDI objects (e.g., bitmaps) to create predictable structures.
- Leak the kernel address of a target bitmap.
- Overwrite its
pvScan0
field via a bug (e.g., arbitrary write). - Abuse GDI APIs to gain arbitrary kernel R/W.
- Elevate privileges by modifying kernel structures (e.g., process token).
Setup: Spraying and Locating Bitmaps
We allocate controlled bitmaps like so:
#define BITMAP_COUNT 512HBITMAP g_bitmaps[BITMAP_COUNT];
for (int i = 0; i < BITMAP_COUNT; i++) { g_bitmaps[i] = CreateBitmap(0x64, 0x64, 1, 32, NULL);}
Each HBITMAP
now maps to a SURFOBJ
in kernel memory. With a known leak or info disclosure, we can resolve the kernel address of a specific bitmap.
In older systems, a predictable layout or use of NtQuerySystemInformation
could assist in this step. On modern systems, this often requires a prior leak or bug to defeat Kernel Address Space Layout Randomization (KASLR).
Step 2: Overwriting pvScan0
Let’s assume a vulnerable driver gives us an arbitrary write primitive. We use it to overwrite the pvScan0
pointer of a selected bitmap object in kernel space:
// Overwrite target_bitmap->pvScan0 = &EPROCESS->Tokenarbitrary_write(target_bitmap_pvScan0_addr, (ULONG_PTR)target_token_addr);
After this corruption, any call to SetBitmapBits()
on the bitmap now writes directly to a sensitive kernel address — giving us full write access.
Step 3: Arbitrary Kernel R/W
Now the magic happens.
Kernel write
void KernelWrite(HBITMAP hBitmap, PVOID kernelAddress, BYTE* data, DWORD size) { // The bitmap's pvScan0 must already point to kernelAddress SetBitmapBits(hBitmap, size, data);}
Kernel read
void KernelRead(HBITMAP hBitmap, PVOID kernelAddress, BYTE* outBuffer, DWORD size) { GetBitmapBits(hBitmap, size, outBuffer);}
This effectively gives us arbitrary kernel read/write. From here, we can elevate privileges by swapping out our process token for SYSTEM
.
Token Stealing 101
Windows processes maintain a pointer to their token at:
EPROCESS + 0x4b8 // (Offset varies by version)
Once you identify:
- Your
EPROCESS
- The
SYSTEM
process’s token
You can copy the SYSTEM
token pointer over your own:
KernelWrite(hBitmap, my_eprocess + TOKEN_OFFSET, &system_token, sizeof(system_token));
Your process is now effectively running as NT AUTHORITY\SYSTEM
.
References
- https://learn.microsoft.com/en-us/windows/win32/gdi/bitmaps
- https://www.coresecurity.com/core-labs/articles/abusing-gdi-for-ring0-exploit-primitives
- https://www.vergiliusproject.com/kernels/x64/windows-11/21h2/_EPROCESS
- https://keramas.github.io/2020/06/21/Windows-10-2004-EPROCESS-Structure.html
- https://www.geoffchappell.com/studies/windows/km/ntoskrnl/inc/ntos/ps/eprocess/index.htm