Skip to content

large bin attack

Bir large bin chunk'ının bk_nextsize pointer'ını corrupt et ki glibc'nin _int_malloc large-bin insertion path'i kontrollü bir chunk adresini attacker-chosen bir konuma yazsın; bu da tek bir arbitrary write verir.

Mechanism

Suistimal edilen invariant

_int_malloc bir chunk'ı bir large bin'e insert ettiğinde, chunk'lar size-ordered bir fd_nextsize/bk_nextsize skip-list'inde tutulur. Yeni sort'lanmış victim bin'deki mevcut en küçük chunk'tan daha küçükse, glibc onu kuyruğa link'leyen branch'i alır. Smallest-chunk branch'i iki unguarded pointer write gerçekleştirir:

/* from _int_malloc, large bin insertion, glibc 2.35 */
if ((unsigned long) (size) < (unsigned long) chunksize_nomask (bck->bk)) {
    fwd = bck;
    bck = bck->bk;
    victim->fd_nextsize = fwd->fd;
    victim->bk_nextsize = fwd->fd->bk_nextsize;
    fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim;
}

victim->bk_nextsize->fd_nextsize = victim ifadesi, zaten bin'lenmiş bir chunk'tan kopyalanmış olan bk_nextsize'ı dereference eder. Attacker o zaten bin'lenmiş chunk'ın bk_nextsize'ını kontrol ediyorsa, X->fd_nextsize = victim victim'in adresini X + offsetof(malloc_chunk, fd_nextsize)'a yazar. 64-bit'te fd_nextsize chunk içinde offset 0x20'de durur, dolayısıyla target pointer &target - 0x20 olarak (yani target eksi 4 word) yerleştirilir. glibc 2.30'da eklenen iki integrity check (if (fwd->bk_nextsize->fd_nextsize != fwd) ve if (bck->fd != fwd)) insert-in-the-middle path'inde durur ve bu smallest-chunk branch'ini korumaz — technique'in modern glibc'de hayatta kalmasının nedeni budur.

İsimlendirme — \"House of Fun\"

Bu primitive'i kendi içinde bağımsız bir arbitrary-write building-block'u olarak çerçeveleyen yazımlar ona House of Fun der; altta yatan bug ve smaller-insert front-link code path'i bu nottakiyle aynıdır — ayrı bir variant değildir. Yazılan değer attacker-chosen bir değer değil, doğrudan seçmediğin bir heap adresidir (victim'in chunk'ı); sadece nereye indiğini seçersin, dolayısıyla o heap adresindeki sahte yapıyı önceden planlaman gerekir.

Walkthrough

how2heap PoC'si (glibc_2.35/large_bin_attack.c) global bir target'ı bir chunk'ın adresiyle overwrite eder:

size_t target = 0;
size_t *p1 = malloc(0x428);   // large chunk
malloc(0x18);                  // guard, blocks top consolidation
size_t *p2 = malloc(0x418);   // smaller chunk, same large bin
malloc(0x18);                  // guard

free(p1);
malloc(0x438);                 // sort p1 into the large bin

free(p2);                      // p2 now sits in the unsorted bin

// corrupt the already-binned chunk's bk_nextsize:
p1[3] = (size_t)(&target - 4); // p1[3] == p1->bk_nextsize

malloc(0x438);                 // sorts p2; smaller-than-p1 branch fires
// -> target == (size_t)p2
Beklenen output
The target value is 0
Now p2 is a chunk in unsortedbin, and p1 is a chunk in largebin.
We overwrite the largebin chunk p1's bk_nextsize with &target-4 ...
The target value is now 0x... (== address of p2)

Footgun'lar

  • İki chunk aynı large bin'e düşmeli ve p2 < p1 olmalı; p2 > p1 ise allocator bunun yerine kontrol edilen middle-insert branch'ini alır.
  • Large chunk'lar arasındaki guard allocation'ları zorunludur: onlar olmadan komşu free'ler consolidate olur ve layout çöker.
  • Write arbitrary bir değer değil, bir heap pointer (victim) bırakır. Tamamen kontrollü bir write'a pivot etmek için target'ı corrupt edilebilir başka bir yapıya yönelt (örn. _IO_list_all, bir tcache/fastbin head'i ya da __free_hook'a komşu bir qword) ki oraya bir heap pointer yerleştirmek daha güçlü bir primitive'i bootstrap'lesin (House of Storm bunu bir unsorted-bin attack ile, house-of-husk ise bir printf dispatch-table ile zincirler).

Detection

Heap consistency assertion'ları (MALLOC_CHECK_, glibc malloc hardened build'leri) ve her insertion'da fd_nextsize/bk_nextsize karşılıklılığını doğrulayan allocator instrumentation'ı, skip-list invariant'ı ihlal edildiğinde takılır. AddressSanitizer / bir guard-page allocator'ı en başta bk_nextsize'ı corrupt eden altta yatan out-of-bounds write'ı tespit eder.

Mitigation

glibc 2.30 yukarıdaki iki reciprocity check'ini ekledi; bunlar middle-insert varyantını öldürür ama smallest-chunk branch'ini değil. nextsize link'lerini XOR-encode eden ya da tamamen revalidate eden veya bin layout'unu randomize eden hardened allocator'lar çıtayı yükseltir. Kök neden her zaman bir in-bin chunk'ı corrupt eden ayrı bir write primitive'idir (overflow / UAF); onu ortadan kaldırmak saldırıyı kaldırır.

References