Skip to content

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 >> 12 hesaplayarak — ve geçerli, şifrelenmiş bir fd pointer'ı 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:

key = heap_leak >> 12        # PAGE_SHIFT == 12

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)
a = malloc(0x40); b = malloc(0x40)
free(b); free(a)                       # tcache: a -> b
key = heap_leak >> 12                  # recover position key
# overwrite a's fd so the 2nd malloc returns &target
edit(a, p64(protect(a_addr, target)))  # encrypted fd
malloc(0x40)                           # returns a
win = malloc(0x40)                     # returns target  <-- arbitrary ptr

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 >> 12 page number'ını gizli tutmak için heap layout hardening'iyle (guard page'leri, randomize chunk yerleşimi) birleştir.

References