safe-linking bypass¶
Konuma bağlı XOR key'ini kurtararak glibc'nin Safe-Linking pointer mangling'ini yen — boş bir bin'deki ilk chunk'tan kolayca ya da herhangi bir heap leak'inden
addr >> 12hesaplayarak — ve geçerli, şifrelenmiş birfdpointer'ı forge et.
Mechanism¶
Note
glibc 2.32'den beri singly-linked free list'ler (tcache ve fastbin'ler) fd
pointer'larını mangle edilmiş olarak saklar; böylece naif bir heap overwrite
onu arbitrary bir adrese yönlendiremez. Mangling (Check Point'in Safe-Linking'i)
şudur:
#define PROTECT_PTR(pos, ptr) \
((__typeof (ptr)) ((((size_t) pos) >> 12) ^ ((size_t) ptr)))
#define REVEAL_PTR(ptr) PROTECT_PTR (&ptr, ptr)
burada pos, pointer'ı tutan slot'un adresidir ve ptr next-chunk pointer'ıdır.
Saklanan değer (pos >> 12) ^ ptr'dir. XOR involutive olduğundan, açığa çıkarma
aynı işlemi kullanır: (stored) ^ (pos >> 12) = ptr. Savunucunun güvendiği
invariant, pos >> 12'nin heap page number'ı olmasıdır ve bunu ASLR randomize
eder — yani heap base'ini bilmeyen bir attacker, hem seçilen bir hedefe decrypt
olan hem de eklenen 16-byte alignment check'i geçen bir değer üretemez (açığa
çıkarılan pointer'ın alt 4 bit'i 0 olmalı, bu da blind forgery'lerin ~15/16'sını
engeller).
Bu yüzden bypass, pos >> 12 key'ini öğrenmeye indirgenir, yani heap page
number'a. Safe-Linking yalnızca çıtayı "fd'yi overwrite et"ten "heap'i leak et,
sonra fd'yi overwrite et"e yükseltti.
Walkthrough¶
Key recovery 1 — boş-bin bedavası. Boş bir bin'e free edilen ilk chunk'ın
fd = NULL'dur, dolayısıyla saklanan değeri (pos >> 12) ^ 0 = pos >> 12'dir
— key'in kendisi. O free edilmiş chunk'ın ilk 8 byte'ını okuyabiliyorsan (örneğin
bir UAF veya out-of-bounds read), key'i doğrudan okursun:
# free one chunk into an empty tcache, then read its fd field
key = u64(leak_first_qword(freed_chunk)) # == heap_base >> 12
Key recovery 2 — herhangi bir heap leak'inden. Leak'lenen herhangi bir heap pointer'ı page number'ı verir:
Poison'lı bir pointer forge et. Allocator'ın target'ı geri vermesini sağlamak
için, onu tutacak slot'un adresini pos olarak kullanarak şifrelenmiş formu sakla:
def protect(pos, ptr):
return (pos >> 12) ^ ptr # mirrors PROTECT_PTR
forged = protect(chunk_addr, target) # chunk_addr = where the fd lives
overwrite_fd(victim_chunk, forged)
Safe-Linking altında uçtan uca tcache poisoning (glibc >= 2.32)
Warning
Forge edilen target 16-byte aligned olmalı, aksi halde
malloc(): unaligned tcache chunk detected check'i (ve fastbin karşılığı)
process'i abort eder. Write hedefini 0x10'a hizala. Ayrıca dikkat: pos,
fd'yi tutan chunk'ın adresidir, hedef değil — yanlış pos kullanmak yanlış
decrypt olan bir değer üretir.
Bu, tcache-poisoning ve fastbin-dup ile
aynı akıştır; Safe-Linking sadece protect() adımını araya ekler ve bir heap
address-leak gerektirir.
Mitigation¶
- Safe-Linking'in kendisi mitigation'dır; yalnızca bir heap leak verildiğinde yenilir, dolayısıyla info-leak primitive'lerini ortadan kaldırmak (ve heap'i tam randomize etmek) gerçek savunmadır. Bkz. glibc Safe-Linking.
- Decrypt edilen pointer'lardaki 16-byte alignment check'i, key'i kurtarmayan blind/kısmi overwrite'ları engeller.
pos >> 12page number'ını gizli tutmak için heap layout hardening'iyle (guard page'leri, randomize chunk yerleşimi) birleştir.