House of Spirit¶
Writable memory'de (stack/bss/global) forge ettiğin sahte bir fastbin-boyutlu chunk'ı free et; böylece allocator bir sonraki
malloc/calloc'ta o kontrol ettiğin bölgeyi geri verir.
Mechanism¶
Invariant: free() chunk header'a güvenir, kökenine değil
House of Spirit, free()'in bir pointer'ın gerçekten heap'ten gelip
gelmediğini hiç kontrol etmemesini suistimal eder. fastbin-boyutlu bir
istek için _int_free (ve ondan önceki tcache path'i) chunk'ı neredeyse
tamamen, attacker'ın yerinde (in-place) sağladığı metadata'dan doğrular:
-
Size class kontrolü. Chunk'ın
sizefield'ı (victim - 8'de okunur) bir fastbin/tcache boyutuna yuvarlanmalı. x86-64'te0x40'lık birsizemalloc(0x30..0x38)'i karşılar.PREV_INUSE(bit 0) fastbin-boyutlu free'ler için yok sayılır, amaIS_MMAPPED(bit 1) veNON_MAIN_ARENA(bit 2) clear olmalı, aksi haldefreemunmap ya daheap_for_ptrpath'ine girer ve crash eder. -
Next-chunk size tutarlılığı. Modern glibc (
_int_freeve fastbin path'i) bir sonraki chunk'ın size'ınıvictim + (size & ~0x7) - 8'de okur ve2*SIZE_SZ < next_size < av->system_mem'i sağlamasını ister — yani> 0x10ve< av->system_mem(main arena için varsayılansystem_mem=0x20000, yani 128 KiB).0x1234gibi bir değer geçer. Bu forge edilmiş "next size" olmadan glibc "free(): invalid next size (fast)" ile abort eder. -
Alignment. Sahte chunk'ın user pointer'ı, gerçek bir chunk gibi 16-byte aligned olmalı, yoksa
malloctutarlılık kontrolleri ve allocator'ın alignment varsayımları bozulur.
fastbin-boyutlu bir free'ye list-membership ya da arena-ownership kontrolü
uygulanmadığı için, (a) bilinen bir target adresini ve (b) target'taki
(size field) ve target + 0x40'taki (next size) byte'ları kontrol eden bir
attacker, fastbin/tcache freelist'ine keyfi bir pointer enjekte edebilir.
Eşleşen boyuttaki bir sonraki istek o pointer'ı döndürür: forge edilen bölge
üzerinde neredeyse-arbitrary bir write primitive.
Walkthrough¶
Kanonik shellphish/how2heap demosu bir stack array içinde yan yana iki sahte chunk forge eder ve ilkini free eder. Önce tcache'i doldururuz (7 free), böylece target size klasik kontrollerin yaşadığı fastbin'e yönlenir.
// gcc -no-pie spirit.c -o spirit (glibc 2.39 demo)
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
int main(void) {
setbuf(stdout, NULL);
// Step 1: fill the 0x40 tcache bin so the next same-size free hits fastbin.
void *chunks[7];
for (int i = 0; i < 7; i++) chunks[i] = malloc(0x30);
for (int i = 0; i < 7; i++) free(chunks[i]);
// Step 2: forge the fake chunk in 16-byte-aligned writable memory.
long fake_chunks[10] __attribute__((aligned(0x10)));
fake_chunks[1] = 0x40; // chunk->size : fastbin class, mmap/non-main bits clear
fake_chunks[9] = 0x1234; // next chunk->size : 0x10 < x < system_mem
// Step 3: free the user pointer (&fake_chunks[2] == chunk header + 0x10).
void *victim = &fake_chunks[2];
free(victim); // injects the chunk header (&fake_chunks[0]) into the 0x40 fastbin
// Step 4: reclaim it. calloc skips tcache, so it pulls straight from fastbin.
void *allocated = calloc(1, 0x30);
printf("calloc(0x30): %p, fake chunk: %p\n", allocated, victim);
assert(allocated == victim); // we own the stack region
return 0;
}
Footgun'lar
- size field'da off-by-one.
malloc(0x30)içinsize == 0x40gerekir (request + header, yuvarlanmış). Size'ı literal request boyutuna değil, bir sonraki request'in yuvarlanmış class'ına göre ayarla. IS_MMAPPED/NON_MAIN_ARENA'yı unutmak.sizeiçindePREV_INUSEdışındaki herhangi bir başıboş yüksek ya da düşük bitfree'yi saptırır ve abort ettirir.- tcache. glibc 2.26'dan beri tcache free'yi önce yakalar ve "next size"
kontrolünü yapmaz, ama 7 entry'de tıkanır ve (2.29'dan beri) double-free
için
key/e->key'i kontrol eder. tcache'i doldurmak, orijinal teknikte kullanılan klasik fastbin path'ini zorlar.
Expected output / gdb view
fastbin head'i artık bir stack adresi tutuyor — heap-dışı pointer'ın kabul edildiğinin kanıtı.Detection¶
- Bilinen herhangi bir heap arena'sının dışına (stack, bss ya da mapped libc)
işaret eden bir fastbin/tcache
fd'si güçlü bir göstergedir. Hardened allocator'lar (ör. glibc'nintcachekey'i ya da Scudo/PartitionAlloc) heap-dışı pointer'ların free'sini reddeder. MALLOC_CHECK_=3ve glibc'nin__libc_freenon-canonical-pointer kontrolleri bazı misaligned ya da wild free'leri runtime'da yüzeye çıkarabilir.
Mitigation¶
- glibc'nin "free(): invalid next size (fast)" mesajı ve
IS_MMAPPED/arena bit kontrolleri naif varyantları zaten bloklar; bunlar yalnızca attacker her iki size field'ını da tamamen kontrol ettiğinde başarısız olur. - Daha yeni glibc'lerdeki tcache
e->keydouble-free tespiti ve free edilen tcache entry'lerinin geçerli bir key taşıması zorunluluğu tcache varyantı için çıtayı yükseltir. - Metadata'yı out-of-line (user data'dan ayrı) saklayan allocator'lar in-place header forge etmeyi tamamen etkisizleştirir.