Skip to content

Non-paged pool overflow exploitation (POOL_HEADER + object spray)

NonPagedPoolNx allocation'ında lineer bir out-of-bounds write, komşu pool chunk'ına taşar; onun _POOL_HEADER'ını ve groom'lanmış bir victim object'i corrupt eder, ardından bu arbitrary read/write ve SYSTEM'e çevrilir.

Mechanism

Note

Her Windows kernel pool chunk'ının başında bir _POOL_HEADER bulunur. Lineer bir overflow'un uyması gereken invariant şudur: bir chunk free edildiğinde _POOL_HEADER'ı hâlâ valid olmalıdır — "when a _POOL_HEADER structure is freed and it isn't a valid header, a system crash will occur." Yani overflow önce bir sonraki chunk'ın header'ına, sonra data'sına iner; attacker header'ı hâlâ validate olan bir değerle yeniden yazmak zorundadır.

_POOL_HEADER (x64, 0x10 byte, kLFH bucket'ını seçen allocation size'a dâhil olur) PreviousSize/PoolIndex/BlockSize/PoolType, bir PoolTag (örn. "Hack") ve offset 0x8'de bir union paketler — ProcessBilled (quota allocation yapan _EPROCESS) ile AllocatorBackTraceIndex/PoolTagHash üst üste biner.

Win10 19H1+ üzerinde segment heap / kLFH sabit boyutlu bucket'lara hizmet verir; bu allocator'a karşı genel strateji (allocator metadata'sını corrupt etmek yerine bitişik bir victim object'i corrupt etmek) Bayet & Fariello'nun "Scoop the Windows 10 Pool!" çalışmasında formüle edilmiştir. İki özellik overflow'u pratik kılar:

  • kLFH chunk'ları unencoded bir _POOL_HEADER tutar (Variable-Size segment chunk'larının _HEAP_VS_CHUNK_HEADER'ının aksine XOR cookie yoktur), böylece attacker header'ı birebir hardcode edip tekrar üretebilir (örn. 16 byte'lık 0x6b63614802020000 değeri).
  • kLFH coalesce yapmaz, dolayısıyla layout öngörülebilirdir — ama bucket önce activate edilmelidir (~16–17 ardışık aynı boyutta request).

Bir overflow sadece aynı bucket içinde tam olarak aynı boyutta bir chunk'a ulaşabildiği için grooming VULN | VICTIM | VULN | VICTIM şeklinde bir layout'a zorlar.

Walkthrough

İki tane public, reproduce edilebilir recipe (HEVD tabanlı):

Connor McGarr — ARW helper object
  1. ~5.000 helper object spray et; aynı boyutta gap açmak için her ikincisini free et (örn. defrag filler olarak CreateEventA object'leri).
  2. Komşu chunk'ın _POOL_HEADER'ını aynı valid hardcoded değerle overflow et ki sonraki free crash etmesin.
  3. Komşu 16 byte'lık helper object'in Name pointer'ını corrupt et; iki object'lik bir indirection arbitrary read/write, ardından SYSTEM verir.
insideyourkernel — NP_DATA_QUEUE_ENTRY (Win11 x64)
  1. ~10.000 named-pipe queue entry groom et; gap delmek için son 5.000 içinde ~her 64'üncüsünü free et.
  2. Overflow, komşu bir NP_DATA_QUEUE_ENTRY'nin DataSize'ını 512 yapar ve Flink'ini kontrollü bir SystemBuffer'a sahip userland IRP'ye referans veren bir userland queue entry'ye yönlendirir → arbitrary read.

Data-only / header-only yol: komşu header'ı PoolType, PoolQuota flag'ini taşıyacak şekilde corrupt et ve ProcessBilled'i overwrite et; free sırasında kernel, attacker'ın kontrol ettiği pointer üzerinden dereference/decrement yapar — explicit bir victim object olmadan privilege escalation için EPROCESS.Token'ı decrement etmekte kullanılabilir.

Warning

Overflow sadece aynı bucket içindeki aynı boyutta chunk'lara ulaşır, dolayısıyla reliability corruption'ın kendisinde değil grooming'de yaşar ya da ölür (bucket activation + defrag filler'lar + gap punching).

Detection

  • Special Pool / Verifier, OOB write'ları ve free sırasında invalid _POOL_HEADER'ı yakalar.
  • BAD_POOL_HEADER (0x19) / BAD_POOL_CALLER (0xC2) bugcheck'leri çoğu zaman başarısız bir overflow'un ardından gelir.

Mitigation

  • Segment-heap metadata korumaları: Variable-Size chunk'larındaki encoded _HEAP_VS_CHUNK_HEADER cookie'leri çıtayı yükseltir (kLFH header'ları unencoded kalır, bu yüzden odak kLFH boyutlu overflow'lardadır).
  • kCFG / kernel CFI, naif function-pointer hijack'lerini engeller ve attacker'ları data-only ProcessBilled/token tekniklerine doğru iter.

References