unsafe unlink¶
fd/bk'si bilinen bir global pointer'ın yakınını gösteren sahte bir free chunk forge et, böylecefree()consolidate edipunlink()macro'sunu çalıştırdığında safe-unlinking check'i geçer ve macro o pointer'ı near-arbitrary bir write ile overwrite eder.
Mechanism¶
glibc'nin free()'si bir chunk'ı free bir komşuyla birleştirdiğinde, komşuyu doubly linked
bin'inden çıkarmak için unlink() macro'sunu çağırır. Klasik unlink (ve modern olanın naive
bir anlayışı), bir chunk P için şunu çalıştırır:
FD = P->fd;
BK = P->bk;
FD->bk = BK; // *(FD + offsetof(bk)) = BK
BK->fd = FD; // *(BK + offsetof(fd)) = FD
Bu iki store, attacker'ın etkilediği fd/bk değerleri üzerinden yapılan write'lardır. Modern
glibc bunları safe-unlinking check'i ile korur:
Note
Invariant şudur: "gerçek bir doubly linked list'te P->fd->bk == P ve P->bk->fd == P."
Saldırı bu check'i kırmak yerine karşılar. Saldırıya uğrayan chunk'ın adresini tutan bir
global pointer chunk0_ptr varsa, sahte chunk'ın fd = &chunk0_ptr - 3*8 ve
bk = &chunk0_ptr - 2*8 (64-bit'te 8 = sizeof(uint64_t)) olarak ayarla. O zaman FD->bk
ve BK->fd'nin ikisi de chunk0_ptr'ın kendisini alias'lar, ki o da zaten P'ye eşittir —
böylece check geçer. İki macro store'u sonra chunk0_ptr'a &chunk0_ptr - 0x18 yazar (ve
tersi), chunk0_ptr'ı kendinden 0x18 byte önceyi gösterir bırakır. chunk0_ptr[...]
üzerinden sonraki bir write artık pointer'ın kendi storage'ına (ve komşu global'lere/GOT'a)
ulaşır ve bozulmayı near-arbitrary bir write'a yükseltir.
Walkthrough¶
Kanonik referans shellphish how2heap unsafe_unlink.c'dir. Sahte fd/bk, safe-unlinking
check'i her iki tarafta da chunk0_ptr'ı görecek şekilde hazırlanır:
// craft a fake free chunk in-place at chunk0_ptr so FD->bk == P and BK->fd == P
chunk0_ptr[2] = (uint64_t) &chunk0_ptr - (sizeof(uint64_t)*3); // fake fd
chunk0_ptr[3] = (uint64_t) &chunk0_ptr - (sizeof(uint64_t)*2); // fake bk
// make the next chunk's header point back to our fake chunk and clear PREV_INUSE
chunk1_hdr[0] = malloc_size; // prev_size: distance to the fake chunk
chunk1_hdr[1] &= ~1; // clear prev_inuse -> "previous chunk is free"
free(chunk1_ptr); // backward consolidation runs unlink() on the fake chunk
free() sonrası chunk0_ptr artık user data'yı göstermez — &chunk0_ptr - 0x18'i gösterir.
Exploit sonra arbitrary belleğe vurmak için onun üzerinden yazar:
chunk0_ptr[3] = (uint64_t) victim_string; // overwrite a pointer near chunk0_ptr
chunk0_ptr[0] = 0x4141414142424242LL; // write through it -> arbitrary write
$ git clone https://github.com/shellphish/how2heap
$ cd how2heap/glibc_2.23 && make unsafe_unlink && ./unsafe_unlink
...
The new value of the global chunk0_ptr is &chunk0_ptr-0x18
We can now overwrite that pointer and write where we want
Now the next write to chunk0_ptr[...] modifies victim_string
Warning
Teknik, self-referential fd/bk'nin hesaplanabilmesi için chunk'a bilinen bir adreste
(chunk0_ptr global'i) yazılabilir bir pointer gerektirir — onsuz safe-unlinking check'i
karşılanamaz. Ayrıca PREV_INUSE'u temizlemek için sonraki chunk'ın prev_size/size
header'ına bir heap overflow gerektirir. Modern glibc'de ek corrupted size vs. prev_size
check'ine de uyulmalıdır; how2heap'in eşleşmesi için prev_size = malloc_size ayarlamasının
nedeni budur. Kullanışlı bir pointer olmadığında, ilgili off-by-one / consolidation
saldırıları (off-by-null-poison-null-byte, house-of-einherjar) yerine kullanılır.
Detection¶
- Forge edilmiş metadata tutarsız olduğunda glibc
malloc(): corrupted double-linked list(safe-unlinking check'i) veyacorrupted size vs. prev_sizeile abort eder. - ASan, sonraki chunk header'ına yapılan öncü
heap-buffer-overflow'u işaretler. - Bir overflow'un ulaşabileceği bir chunk'a global/heap pointer'ı, komşu chunk'ın bir
free()'siyle birlikte denetle.
Mitigation¶
- Safe-unlinking check'in kendisi (
FD->bk == P && BK->fd == P, artıcorrupted size vs. prev_size) allocator içi mitigation'dır; glibc'yi güncel tut. - Chunk metadata'sına yapılan kök heap overflow'u ortadan kaldır (bounds-checked kopyalar,
_FORTIFY_SOURCE, testte ASan). - Bir overflow'un etraflarında
fd/bkforge edebileceği heap chunk'larının adresini saklayan uzun ömürlü global'ler tutmaktan kaçın.