top chunk extension¶
top chunk (wilderness)
sizefield'ını şişirilmiş bir değerle overwrite ederek allocator'ın sahip olduğundan çok daha fazla contiguous bellek sahibi olduğuna inanmasını sağlamak; böylece top pointer'ı arbitrary bir adrese doğru yürüten oversized allocation'lara olanak verir.
Mechanism¶
Note
glibc ptmalloc'ta top chunk (a.k.a. wilderness), arena'nın allocate edilmemiş
kuyruğudur, av->top ile takip edilir; ilk word'leri, size field'ı ne kadar free alan
kaldığını kaydeden normal bir chunk header'dır. Bir request bin/fastbin'lerden
karşılanamadığında, _int_malloc onu top'tan oyar: eğer request < top->size ise bir chunk
döndürür ve av->top = old_top + request yapar (wilderness'i ilerletir), aksi takdirde
heap'i büyütmek için sysmalloc/mmap'e geri döner.
Allocator'ın güvendiği invariant, top->size'ın contiguous region'ı dürüstçe sınırlamasıdır.
Top chunk extension bunu kırar: top->size'ı büyük bir değerle overwrite et (uç durum
-1 → 0xffffffffffffffff), böylece request < top->size bounds check'i her zaman
geçer. Bunun üzerine malloc asla mmap'e gitmez ve keyfi büyüklükte bir request'i basitçe
av->top'u ilerleterek karşılar. Bu, House of Force'un kalbindeki bozulmadır:
top->size forge edilmişken, hazırlanmış tek bir devasa request av->top'u bir hedef
adrese taşır ve sonraki normal malloc o hedefte bir chunk döndürür — bir arbitrary
allocation/write.
Walkthrough¶
how2heap'in house_of_force.c'sinde olduğu gibi, top chunk'ın size field'ını overwrite et
(top pointer'dan sizeof(long) offset ötesi size word'üdür):
/* clobber wilderness size with -1 so malloc never bounds-checks us out */
*(intptr_t *)((char *)ptr_top + sizeof(long)) = -1; /* top->size = 0xffffffffffffffff */
Sonra bir request'i öyle boyutlandır ki av->top'u ilerletmek tam olarak hedefe insin:
/* place the next top at `bss_var`; account for the two chunk headers how2heap subtracts */
unsigned long evil_size =
(unsigned long)bss_var - sizeof(long)*4 - (unsigned long)ptr_top;
malloc(evil_size); /* advances av->top to bss_var */
char *p = malloc(small); /* returns a chunk AT bss_var */
strcpy(p, payload); /* arbitrary write at the target */
Çıkarma teriminin nedeni
new_top = old_top + request ve user pointer chunk header'ın ötesinde oturur. how2heap,
devasa chunk'ın header'ı artı hedefte geri verilecek chunk'ın header'ı için telafi olarak
sizeof(long)*4 çıkarır; böylece döndürülen pointer — raw top değil — bss_var'a eşit
olur. Tam terim (2* mı yoksa 4*sizeof(long) mı) hedefteki alignment/header'lara bağlıdır;
bir sabite güvenmek yerine onu gözlemlenen top adresinden türet.
Warning
old_top'u bilmek ve evil_size'ı hesaplamak için bir heap leak'e ihtiyacın var.
Ayrıca dikkat et ki çıkarma unsigned long üzerindedir, dolayısıyla wrap eder: heap'in
altındaki hedeflere wrap-around ile ulaşılabilir; House of Force'un heap'ten daha düşük
.bss/GOT adreslerine vurabilmesinin yolu budur.
Detection¶
size'ıold_end'de page-aligned olmayan,MINSIZE'ın altında olan veyaprev_inuse'u clear olan bir top chunk bozuktur.
Mitigation¶
- glibc ≥ 2.29,
sysmalloc'ta wilderness size'ı mantıksız olduğunda abort eden bir top-chunk integrity assertion'ı ekledi:
assert ((old_top == initial_top (av) && old_size == 0) ||
((unsigned long) (old_size) >= MINSIZE &&
prev_inuse (old_top) &&
((unsigned long) old_end & (pagesize - 1)) == 0));
-1 olan forge edilmiş bir top->size bunu geçemez (page-aligned değil, size mantıksız),
dolayısıyla klasik House of Force modern glibc'de ölüdür — ama altta yatan primitive
(top->size'ı overwrite etmek), size'ın makul bir değere büyütüldüğü diğer heap hilelerini
hâlâ besler.