large bin attack¶
Bir large bin chunk'ının
bk_nextsizepointer'ını corrupt et ki glibc'nin_int_malloclarge-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
Footgun'lar
- İki chunk aynı large bin'e düşmeli ve
p2 < p1olmalı;p2 > p1ise 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çintarget'ı corrupt edilebilir başka bir yapıya yönelt (örn._IO_list_all, birtcache/fastbinhead'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.