Skip to content

Pool lookaside list confusion (BlockSize corruption)

Free edilmiş bir chunk'ın POOL_HEADER yapısındaki BlockSize alanını corrupt ederek kernel'in onu yanlış lookaside / free list üzerine dosyalamasını (veya oradan servis etmesini) sağlamak; böylece küçük bir allocation'a sonradan daha büyük bir bölge dönen — ya da tam tersi — bir size confusion ortaya çıkar.

Mechanism

Note

Windows kernel pool, küçük ve sık kullanılan free chunk'ları, chunk'ın yuvarlanmış boyutuna göre indekslenen lookaside list'ler (per-pool-descriptor ve per-processor) üzerinde cache'ler. Boyut, POOL_HEADER.BlockSize alanında block unit cinsinden (x64'te 16 byte) tutulur. Free sırasında allocator BlockSize'ı okur, lookaside index'ini hesaplar ve chunk'ı o boyuta ait singly-linked list'e link'ler; aynı boyutta sonraki bir allocation'da chunk o list'ten unlink edilip caller'a döndürülür.

Dolayısıyla list seçimi BlockSize'a mutlak doğru gibi güvenir. Free edilmek üzere olan bir chunk'ın BlockSize'ı komşu bir overflow ya da off-by-one ile corrupt edilirse, chunk forge edilmiş boyutun list'ine dosyalanır. Bu forge edilmiş boyuta uyan sonraki bir allocation isteği chunk'ı unlink edip geri verir — ama hem caller hem de sistemin o belleğe dair geri kalan görüşü onu hâlâ gerçek boyutuyla ele alır. Sonuç bir size confusion'dır: A boyutu için yapılan istek, başka kodun B boyutunda sandığı bir chunk ile karşılanır, allocation'lar üst üste biner veya attacker'ın kontrol ettiği bir slack bırakılır. Bu, userland heap'teki "wrong-bin" reallocation'ın pool karşılığıdır.

x64'te POOL_HEADER (alttan üste) PreviousSize, PoolIndex, BlockSize (her biri block unit cinsinden küçük bir bitfield), ardından PoolType ve 4 byte'lık bir PoolTag paketler. BlockSize ile PreviousSize bitişik ve dar alanlar olduğundan, bir sonraki header'a taşan tek byte'lık/QWORD bir overflow görünen size class'ı değiştirmeye yeter — ki bu da yapıyı yaygın linear-overflow bug'larından erişilebilir kılan şeydir.

Warning

Alanların tam genişlikleri, lookaside-list'in maksimum block size'ı ve hangi path'lerin lookaside'ı, hangilerinin segment/LFH free list'lerini kullandığı Windows 7 → Windows 10/11 boyunca esaslı şekilde değişti (segment pool, kLFH). Aşağıdaki mekanizma kavramsal olarak anlatılıyor; güncel offset'lere güvenmeden önce bunlar hedef build'in symbol'lerine karşı mutlaka doğrulanmalı.

Walkthrough

Yapı seviyesinde akıl yürütme (Mandt'ın pool internals'ına dayanarak; temsili, build'e bağlı):

Kavramsal POOL_HEADER (x64).

// Representative layout — confirm field widths against target symbols (dt nt!_POOL_HEADER)
typedef struct _POOL_HEADER {
    USHORT PreviousSize : 8;   // size of previous chunk, in 16-byte blocks
    USHORT PoolIndex    : 8;   // pool descriptor index
    USHORT BlockSize    : 8;   // THIS chunk size, in 16-byte blocks  <-- corruption target
    USHORT PoolType     : 8;   // allocation type bits
    ULONG  PoolTag;            // 4-char tag, e.g. 'enoN'
} POOL_HEADER;

Corruption'dan confusion'a giden dizilim.

1. Groom: place [ overflow chunk ][ victim chunk V ] adjacent (see pool-feng-shui).
2. Overflow writes into V's POOL_HEADER, changing V.BlockSize from 0x4 (0x40 bytes)
   to 0x10 (0x100 bytes).   <-- forged larger size class
3. Free V. The allocator reads BlockSize=0x10 and links V onto the 0x100 lookaside/free list.
4. Allocate size 0x100. The allocator unlinks V (it is on that list) and returns it,
   but only 0x40 bytes were ever V's real footprint.
5. The 0x100 "allocation" overlaps whatever followed V in memory -> overlapping objects.
Size confusion sonucu (örnekleme amaçlı)
before free:   V @ X  real size 0x40   (BlockSize forged to 0x10 -> 0x100)
after realloc: caller holds X..X+0x100, but X+0x40..X+0x100 still belongs to
               the next live object -> attacker writes through one alias,
               reads/executes through the other (type/size confusion).

Exploit değeri overlap'ten gelir: over-sized alias üzerinden yazmak, komşu canlı object'in header'ını ya da pointer'larını corrupt eder; bu da kontrollü bir callback'e veya arbitrary write'a kadar takip edilir.

Detection

  • Free/alloc sırasında BlockSize'ı chunk'ın gerçek boyutuna ve bir sonraki chunk'ın PreviousSize değerine karşı validate et (safe-unlinking / header consistency check'leri).
  • Checked build'lerde lookaside/free-list integrity assert'leri; eşleşmeyen size alanlarında BAD_POOL_HEADER / BAD_POOL_CALLER ile etiketlenen bugcheck'ler.

Mitigation

  • Pool header cookie'leri / integrity check'leri (Windows 8+), chunk relink edilmeden önce tahrif edilmiş POOL_HEADER alanlarını tespit eder.
  • kLFH / segment pool randomization'ı ve out-of-line metadata, corrupt eden overflow için erişilebilir ve öngörülebilir adjacency'yi azaltır.
  • Safe unlinking, list pointer'larını validate eder ve aksi halde confusion'ı izleyecek unlink primitive'ini bloklar.

See also: pool-feng-shui, non-paged-pool-overflow-exploitation, kernel-pool-overflow-from-restrictive-chunk-size, hevd-pool-buffer-overflow-scenario.

References