Skip to content

smallbin corruption

Bir small bin'in doubly-linked list'inin fd/bk pointer'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
Abort, integrity check'inin ateşlendiğini doğrular; başarılı bir saldırı ise 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.

References