Skip to content

House of Storm

Bir unsorted-bin attack ile bir large-bin attack'i birleştir; böylece allocator kontrol edilen bir adreste sahte bir chunk size forge eder ve sonra o keyfi adresi malloc/calloc'tan geri verir.

Mechanism

Invariant: unsorted-bin unlink bk'ya güvenir, large-bin insert bk_nextsize üzerinden yazar

House of Storm, tek bir free edilmiş chunk'ın bir _int_malloc taraması boyunca hem unsorted bin hem de large bin code path'lerinde yaşadığında pre-2.29 glibc'nin açtığı iki write primitive'i zincirler:

  1. Unsorted-bin attack (bir chunk'ın nereye döndürüleceğini kontrol eder). _int_malloc unsorted bin'i gezerken victim'i bck = victim->bk; unsorted_chunks->bk = bck; bck->fd = unsorted_chunks yazarak unlink eder. unsorted_bin->bk'yı fake_chunk = target - 0x10 ile overwrite ederek attacker target'ı çekilecek bir sonraki chunk olacak şekilde hazırlar — ama yalnızca size field'ı isteğe eşleşirse. Tek başına bu, libc pointer'ın klasik unsorted-bin write'ıdır; burada bir "bu adresi döndür" primitive'i olarak yeniden amaçlanmıştır.

  2. Large-bin attack (target'taki size'ı forge eder). Aynı taramada daha büyük chunk bir large bin'e eklenir. Insertion kodu fwd->bk_nextsize->fd_nextsize = victim (ve benzeri) yapar; bu da bir heap pointer'ın seçilen bir adrese attacker-kontrollü write'ıdır. large_bin->bk_nextsizefake_chunk - 0x18 - shift'e işaret ettirerek yazılan heap pointer misaligned iner; böylece yüksek byte'ları target'ın size offset'inde belirir ve geçerli bir size sentezler (ör. 0x000056... makul bir chunk size olarak okunur).

  3. Yakınsama (convergence). Large-bin write target'a aklı başında bir size yerleştirdiğinde, unsorted-bin taraması bozulmuş bk üzerinden erişilebilen chunk'ı yeniden inceler, eşleşen bir size görür ve target'ı caller'a döndürür. Sonuç, bilinen writable bir adreste arbitrary chunk allocation'dır.

Attack, target size için tcache'in dolu olmasını gerektirir (böylece tcache stashing'i unsorted/large-bin mantığını kısa devre yaptırmaz), target size IS_MMAPPED/NON_MAIN_ARENA bitlerini set etmekten kaçınmalı ve glibc 2.23–2.28'de çalışır: unsorted-bin unlink'in 2.29 hardening'i (bck->fd != victim reciprocal kontrolü) onu öldürür.

Walkthrough

Maxwell "Strikeout" Dulin'in shellphish/how2heap house_of_storm.c'sinden uyarlandı. Unsorted-bin chunk'ı large-bin chunk'ından daha büyük olmalı ve allocation size heap base'inden türetilir, böylece misaligned write geçerli bir size üretir.

char target[0x60];                      // the address we want malloc to return
char *unsorted_bin, *large_bin, *ptr;

// 1) An unsorted-bin chunk (bigger) and a large-bin chunk (smaller).
unsorted_bin = malloc(0x4e8); malloc(0x18);   // guard against top consolidation
// ... compute alloc_size from (unsorted_bin >> shift) so the forged size matches
large_bin = malloc(0x4d8); malloc(0x18);

// 2) Sort them into their bins.
free(large_bin);  free(unsorted_bin);
unsorted_bin = malloc(0x4e8);                  // pushes large_bin into large bin
free(unsorted_bin);                            // unsorted_bin back to unsorted bin

char *fake_chunk = target - 0x10;

// 3) Unsorted-bin attack: bk -> target's fake chunk header.
((size_t *)unsorted_bin)[1] = (size_t)fake_chunk;        // unsorted->bk

// 4) Large-bin attack: fd and bk_nextsize craft the size write.
((size_t *)large_bin)[1] = (size_t)fake_chunk + 8;       // large->bk
((size_t *)large_bin)[3] = (size_t)fake_chunk - 0x18 - shift_amount; // bk_nextsize

// 5) Trigger. calloc bypasses tcache; the scan writes the size, then returns target.
ptr = calloc(alloc_size, 1);
strncpy(ptr, "ABCDEFG", 0x58 - 1);             // ptr aliases target[]
// printf("%s\n", target) now shows "ABCDEFG"  -> arbitrary chunk obtained

Footgun'lar

  • alloc_size türetmesi load-bearing'dir. target'taki forge edilmiş size, misaligned yazılan heap adresinin üst byte'larından gelir. Request size'ı o sentezlenmiş değere eşit seçmelisin, yoksa unsorted tarama chunk'ı reddeder ("malloc(): memory corruption").
  • PIE vs non-PIE offset. bk_nextsize offset'i farklıdır (misalignment shift'inde genellikle 5'e karşı 2), çünkü target'a inen heap-pointer genişliği değişir.
  • Tcache stashing. Target size bir tcache size (0x20–0x410) ise ve bin dolu değilse, glibc unsorted/small bin'den tcache'e stash eder ve yakınsama hiç gerçekleşmez — önce 7 slotun hepsini doldur.
  • malloc değil calloc kullan, son allocation'da tcache fast path'ini atlamak için.
gdb view of the two corrupted bins

pwndbg> bins
unsortedbin
all: 0x...->bk = 0x601060 (&target-0x10)   # unsorted-bin attack target
largebins
0x4d0: 0x...  bk_nextsize -> 0x601048       # large-bin write target
calloc'tan sonra target forge edilmiş bir size tutar (ör. 0x55...) ve döndürülen pointer target'a eşittir.

Detection

  • Heap-dışı writable memory'ye (bss/global/stack) işaret eden bir unsorted-bin bk'sı ya da large-bin bk_nextsize/fd_nextsize'ı imzadır.
  • Tek bir taramada hem unsorted hem large-bin insertion ile aynı anda tutarlı görünen bir chunk anormaldir; heap-integrity araçları (ör. glibc tcache/malloc tutarlılık abort'ları) çoğu zaman ortaya çıkan size uyuşmazlığını yakalar.

Mitigation

  • glibc ≥ 2.29 unsorted-bin reciprocal kontrolü bck->fd != victim'i ve large-bin insertion sanity kontrollerini (fwd->bk_nextsize->fd_nextsize guard'ı) ekler; bunlar yakınsamayı bozar — teknik upstream'de fiilen ölüdür.
  • Bin pointer'lar üzerindeki attacker kontrolünü sınırlamak/doldurmak (ör. guarded metadata ile) attack'in dayandığı unlink/insert write'larını engeller.

References