smallbin corruption¶
Bir small bin'in doubly-linked list'inin
fd/bkpointer'larını corrupt ederek glibc'nin unlink-on-allocation'ının attacker'ın seçtiği bir değeri yazmasını sağlamak.
Mechanism¶
glibc'nin ptmalloc'undaki small bin'ler, aynı boyutlu free edilmiş chunk'ları
(genellikle < 0x400) bir doubly-linked, FIFO list'te tutar. Her chunk bir
forward pointer fd ve bir backward pointer bk saklar. malloc() bir small bin'den
bir isteği servis ettiğinde, son chunk'ı (victim = last(bin)) alır ve onu komşularının
pointer'larını patch'leyerek unlink eder.
Small-bin allocation fast-path. Aşağıdaki check, small-bin'den
allocation sırasında çalışan glibc'ye özgü special-case koddur; ilerideki
general unlink macro'sundan ayrı bir code path'tir.
Small-bin allocation fast-path invariant
Invariant, doubly-linked-list tutarlılığıdır: kaldırılan chunk için
victim->bk->fd geriye victim'e işaret etmelidir. small-bin fast path'inde
glibc tam olarak bunu zorunlu kılar — bck = victim->bk; if (bck->fd != victim)
malloc_printerr("malloc(): smallbin double linked list corrupted"); — sonra
bin->bk = bck; bck->fd = bin; gerçekleştirir. O ikinci store, attacker'ın
etkilediği bir write'tır: victim->bk'yi (ona bck de) kontrol ediyorsan,
allocator bin pointer'ını (bir libc adresi) bck->fd'ye, yani
bck + offsetof(fd)'ye yazar. Corruption böylece bir heap write'ını, yalnızca
bck->fd == victim check'iyle kapılı olarak senin seçtiğin bir adrese bir libc
pointer'ı yazmaya çevirir.
General unlink macro (ayrı code path). Yukarıdaki small-bin fast-path
check'i (bck->fd != victim, tek bir store'u gate'ler) ile karıştırılmamalı:
klasik doubly-linked-list unlink macro'su — free-time consolidation'da
çalışır — bunu genelleştirir ve iki store'u koruyan bir guard kullanır. Bu
ayrım önemlidir çünkü ikisi farklı check string'leri (smallbin double linked
list corrupted vs. corrupted double-linked list) ve farklı tetikleme anları
(allocation-time vs. free-time) üretir. Guard'ı
if (FD->bk != P || BK->fd != P) malloc_printerr("corrupted double-linked list");'dir
ve unlink gövdesi FD->bk = BK; BK->fd = FD; çalıştırır — crafted bir fd/bk'nin
yönlendirdiği iki write (fd + offsetof(bk) ve bk + offsetof(fd)'ye).
Walkthrough¶
Note
Aşağıdaki craft, Mechanism'deki small-bin fast-path write'ından
(bck->fd = bin) farklı bir code path olan klasik unsafe-unlink
macro'sunu (general unlink) smallbin bağlamına uygular; bu tekniğin
canonical anlatımı için bkz. unsafe-unlink. Bu doc'a özgü
smallbin bağlamı, corrupt edilen chunk'ın unsorted'dan small bin'e taşınması
ve aynı-boyutlu bir sonraki malloc()'un allocation-time unlink'i
tetiklemesidir.
"safe unlink" bypass'ı, consistency check'ini sağlamak için chunk pointer'ının
kendisine bir pointer P kullanarak fd/bk'yi craft eder (örneğin free edilmiş
chunk'ı tutan bir global). 64-bit'te şöyle set et:
/* fake chunk fields, P = address of the pointer that holds this chunk */
fake->fd = (void *)((char *)&P - 0x18); /* &P - 3*8 */
fake->bk = (void *)((char *)&P - 0x10); /* &P - 2*8 */
Bunlar FD->bk == P ve BK->fd == P'yi sağlar çünkü *(&P - 0x18 + 0x18) == P
ve *(&P - 0x10 + 0x10) == P. Chunk unlink edildiğinde, FD->bk = BK,
&P - 0x10'u P'ye yazar — böylece global pointer artık gerçek chunk'ın biraz
öncesine işaret eder ve yakındaki data'ya overlapping bir write primitive verir.
how2heap'in unsafe_unlink.c PoC'si aslında yukarıdaki general unlink
macro'sunu (free-time consolidation) gösterir, bu doc'un ana small-bin
allocation-time path'ini değil; bkz. unsafe-unlink için o
PoC'nin tam anlatımı. Onu burada yalnızca aynı craft'ın tcache'i atlatma
tekniği için referans veriyoruz: modern PoC'ler tcache aralığı dışında boyutlar
kullanır (örneğin 0x420) veya eşleşen tcache bin'ini önceden doldurur ki free
edilmiş chunk tcache yerine unsorted/small bin path'ine düşsün.
gdb (pwndbg) altında smallbin check'ini tetikleme
$ gdb -q ./vuln
pwndbg> b *_int_malloc
pwndbg> run
# after corrupting victim->bk to a bad value, the next same-size malloc():
malloc(): smallbin double linked list corrupted
Program received signal SIGABRT, Aborted.
pwndbg> bins
smallbins
0x90: 0x... -> 0xdeadbeef (corrupted bk) <-- fails bck->fd == victim
bck->fd == victim geçerli olacak şekilde bk'yi forge eder ve write sessizce
devam eder.
Detection¶
glibc'nin kendi consistency check'leri, process'i malloc(): smallbin double linked
list corrupted veya corrupted double-linked list ile abort eder. Hardened
allocator'lar, guard page'leri ve AddressSanitizer, en başta fd/bk metadata'sını
corrupt eden out-of-bounds write'ı işaretler.
Mitigation¶
Doubly-linked check'lerinin kendisi birincil mitigation'dır ve naif unlink'i
güvenilmez kılar. Dikkat: safe-linking bu listeleri korumaz — yalnızca
singly-linked tcache/fastbin fd'sini guard eder. Öncül out-of-bounds write'ı önle
(bkz. heap-buffer-overflow ve
off-by-one-error) ve
FORTIFY_SOURCE ile derle.