Off-by-NULL / poison null byte¶
Bir sonraki chunk'ın size'ına tek bir NULL-byte overflow'u onun low byte'ını (ve prev_inuse bit'ini) temizler ve glibc'i canlı bir chunk üzerinden backward-consolidate etmeye kandırarak overlap oluşturur.
Mechanism¶
Birçok string routine'i bir byte fazla yazar — sonlandıran '\0'. O byte bir sonraki
heap chunk'ının size field'ının en düşük anlamlı byte'ına düştüğünde, size'ı küçültür
ve kritik olarak PREV_INUSE (low) bit'ini temizler. Allocator artık önündeki
chunk'ın free olduğuna inanır. Sonraki bir free()'de glibc, chunk'ın önündeki
prev_size field'ını kullanarak backward-consolidate etmeye çalışır — attacker'ın
kontrol ettiği bir field — böylece hâlâ canlı bir allocation ile overlap eden bir
bölgeyi unlink edip merge eder.
Note
Tek byte'lık write yalnızca bir bit kadar metadata'yı çevirir, ama bu glibc'in
"kullanımdaki bir chunk komşularının inuse bit'leriyle sınırlanır" invariant'ını
kırmaya yeter. how2heap'in poison_null_byte'ında off-by-one
((char *)victim2)[-8] = '\0'; yazar ve victim'in size byte'ını temizler. prev
içine sahte bir 0x501 size'ı yerleştirilir, böylece poison'dan sonra chunk'ın
kayıtlı size'ı ile bir sonraki chunk'ın prev_size'ı (0x500'e set edilmiş)
uyuşur — glibc'in consistency check'ini geçer. Free etmek sonra backward
consolidate eder ve overlapping chunk'lar üretir; bu da hâlâ allocate edilmiş
prev2 üzerinde arbitrary read/write verir. Bu, house-of-einherjar'ın
yalnızca-metadata kuzenidir (o, sadece bir NULL byte değil, tam bir pointer ister).
Walkthrough¶
Kanonik referans how2heap'in poison_null_byte.c'sidir:
// off-by-one NULL overflow: clear the low byte of victim's size field
((char *)victim2)[-8] = '\0'; // size 0x511 -> 0x500, PREV_INUSE cleared
// a fake size (0x501) was pre-placed and the next chunk's prev_size set to 0x500
free(victim); // backward consolidation over a live chunk
Build and run how2heap
$ git clone https://github.com/shellphish/how2heap
$ cd how2heap/glibc_2.31
$ make poison_null_byte
$ ./poison_null_byte
...
Now we malloc a chunk... it overlaps with prev2 (overlapping chunks)
Overlapping succeeded: we can now edit prev2 through a different pointer
Tam "fake size" sabitleri hedeflenen glibc sürümüne bağlıdır; how2heap'teki
glibc_2.31 ve sonraki varyantlar bunları o sürümün prev_size-vs-size ve
unlink check'lerini sağlamak için ayarlar.
Warning
Off-by-one, size field'ının low byte'ına isabet etmeli, dolayısıyla heap
layout/alignment victim2'yi overflow eden buffer'dan tam 0x10 sonra
yerleştirmeli. Yeni glibc, tekniğin etrafında ayarlanması gereken check'ler
(corrupted size vs. prev_size, tcache key ve unlink integrity) ekledi — bir
glibc sürümünden bayatlamış bir how2heap sabiti, başka bir sürümde basitçe
malloc_printerr ile abort eder.
Detection¶
- glibc'in kendi
malloc_printerrcheck'leri ("corrupted size vs. prev_size", "free(): invalid next size") birçok naif denemeyi abort eder. - ASan, orijinal off-by-one'ı size 1'lik bir
heap-buffer-overflowolarak raporlar. - String işleme kodunu
lenboyutlu buffer'laralen + 1write'ları ve rezerve NUL byte'ı olmayanstrcpy/strcatiçin denetle.
Mitigation¶
- Buffer'ları terminator'ı içerecek şekilde boyutlandır; doğru sınırlarla
strlcpy/snprintftercih et. Altta yatan off-by-one'ı tedavi etmek kök düzeltmedir. - 1-byte'lık write'ı yakalamak için test sırasında
_FORTIFY_SOURCE=2/3ve ASan ile build et. - Güncel glibc, poison'ı overlapping chunk'lara dönüştürme çıtasını yükselten
prev_size/sizeconsistency veunlinkcheck'leri ekler.