Partial Pointer Overwrite ASLR Bypass¶
Saklanan bir pointer'ın ya da return address'in yalnızca low byte'(lar)ını üzerine yazarak onu bilinen bir aralık içinde yönlendirmek — hiçbir information leak gerekmez, çünkü ASLR page offset'i asla randomize etmez.
Mechanism¶
Address Space Layout Randomization (ve ana binary için PIE), tüm memory bölgelerini
— stack, heap, library'ler, kod — her çalıştırmada rastgele bir base ile yeniden
konumlandırır. Kritik olarak, o base page granularity'sinde uygulanır: page'ler
4096 byte'tır, dolayısıyla randomizasyon 0x1000'in bir katıdır. Bir page'den daha
ince hiçbir şeye asla dokunulmaz.
Note
Yeniden konumlandırma page-aligned olduğundan, her adresin low 12 bit'i —
page offset'i — sabit ve çalıştırmalar arasında aynıdır. x86-64'te bu, herhangi
bir pointer'ın en düşük üç hex nibble'ının, ASLR/PIE tam etkin olsa bile sabit ve
bilinen olduğu anlamına gelir. Bir partial overwrite tam olarak bunu suistimal
eder: saklanan bir pointer'ın son byte'ını (ya da ikisini) yazabilirsen, yalnızca
low, randomize-edilmemiş kısmı değiştirirsin. High byte'lar — ASLR'nin gerçekten
randomize ettiği kısım — dokunulmadan kalır, böylece pointer doğru bölgenin içinde
kalır ve onları bilmek için hiçbir leak gerekmez. Erişilebilir target'lar,
kontrol edilebilir byte'ların ifade edebileceği aralıkla sınırlıdır: bir byte
(low 8 bit) sana bir page içinde 0x100 (256) farklı target'ı seçebilme
serbestliğini verir; iki byte aralığı genişletir ama en düşük randomize-edilen
nibble ile kısmen savaşır, bunu 16'da-1'lik bir brute force haline getirir.
guyinatuxedo'nun Nightmare'inin PIE için belirttiği gibi: "instruction adresinin son byte'ını üzerine yazabilirsin; bu, bir infoleak kullanmak ya da adresi brute force etmek zorunda kalmadan orijinal adresin etrafındaki belli bir aralık içinde jump etmene izin verir." Aynı mantık kaydedilmiş bir return address'e, bir GOT entry'sine, bir heap chunk pointer'ına ya da bir vtable pointer'ına da uygulanır.
Walkthrough¶
Klasik kurulum: saklanan bir return address (ya da function pointer) zaten target bölgesine işaret eder; onun aynı bölgedeki yakın bir gadget/fonksiyona işaret etmesini istersin. Yalnızca farkı kapatacak kadar low byte'ı üzerine yazman gerekir.
saved return address (in libc, ASLR on): 0x7f3a_c1b4_2e_47
\_______/ \___/ \/
randomized ??? fixed low byte(s)
target you want (same library): 0x7f3a_c1b4_2e_a3
differs only in the low byte 47 -> a3
Eğer orijinal ve target yalnızca son byte'ta farklıysa, tek-byte'lık bir overwrite yeter ve deterministik'tir — brute force yok. İkinci byte'ta da (üst nibble'ı randomize olan) farklıysalar, iki byte'ı üzerine yazar ve tek randomize-edilen nibble'ı brute force edersin (ortalama 16 deneme).
Tipik bir off-by-one / single-null overwrite tam olarak bir byte iletir:
char name[8];
read(0, name, 9); /* one byte past -> overwrites low byte of an
adjacent saved pointer */
Format string'lerle aynı şey hassas biçimde yapılır: %<n>c%<k>$hhn, k. argument
slot'una tek bir byte (hhn) yazar ve bir pointer'ın low byte'ını seçilen bir
değere set etmene izin verir.
Neden hiçbir leak gerekmez (somut aritmetik)
Page size = 0x1000 -> low 12 bits never randomized
Function A in libc = base + 0x4e147
Function B in libc = base + 0x4e1a3
Difference = 0xa3 - 0x47 = 0x5c (fits in one byte)
=> overwrite ONLY the last byte 0x47 -> 0xa3.
'base' (the ASLR-randomized part) cancels out: we never needed it.
base'i paylaştığından, aralarında yönlendirme,
ASLR'nin bozamadığı saf low-byte aritmetiğidir.
Warning
Teknik yalnızca orijinal pointer zaten doğru bölgeye düştüğünde ve istediğin target, kontrol ettiğin byte'ların delta'yı kapatacak kadar yeterince yakın olduğunda çalışır. Low byte'ın ötesine uzan ve en düşük randomize-edilen nibble ile (bit 12-15) çakışırsın; bu, deterministik bir write'ı her denemede ~1/16 başarılı bir brute-force'a dönüştürür — yalnızca process yeniden randomize etmeden respawn ediyorsa (forking server) ya da ucuza retry edebiliyorsan uygulanabilir. Endianness önemlidir: little-endian x86'da, ilk yazdığın byte pointer'ın low byte'ının ta kendisidir.
Detection¶
Aynı bölge içinde beklenen target'ından hafifçe sapan bir function pointer ya da return address imzadır. Stack canary'leri, canary'den kaçınan bir partial overwrite'ı durdurmaz; CFI / shadow stack'ler (CET) yönlendirilmiş return'ü tespit eder. Doğru modülde beklenmedik-ama-yakın bir adrese control transfer'i gösteren crash telemetry'si bir partial overwrite'ı düşündürür.
Mitigation¶
Full RELRO + PIE, korunmayan pointer'ların partial overwrite'larını engellemez, ama
CET shadow stack'leri ve forward-edge CFI yönlendirilmiş control flow'u yakalar. Her
fork'ta yeniden randomize etmek (exec-siz fork server'larından kaçınmak), iki-byte
overwrite'ların dayandığı bedava brute-force retry'larını reddeder. Nihayetinde,
attacker'ın pointer'ın low byte'ına hiç ulaşmasına izin veren öncül
out-of-bounds-write /
off-by-one-error / format-string-write'ı
engelle.