Skip to content

Stack Reading (BROP byte-by-byte canary/return leak)

Forking bir server'a karşı bir crash oracle kullanıp canary'yi, saved RBP'yi ve return address'i bir seferde bir byte overwrite et — 2^64'lük bir aramayı byte başına en fazla 256 denemeye indirir ve canary'ler + 64-bit ASLR/PIE'yi yener.

Mechanism

Note

Stack reading, "Hacking Blind"den (Bittau, Belay, Mashtizadeh, Mazières, Boneh — IEEE S&P 2014, DOI 10.1109/SP.2014.22) gelen Generalized stack reading primitive'idir; Blind ROP'un (BROP) temelidir.

Threat model'i, bir crash'ten sonra re-randomize etmeden restart eden bir forking server'dır — yani yalnızca fork(), execve() yok. Fork edilen bir child, parent'ın address space'ini miras alır, dolayısıyla stack canary ve tüm adresler fork'lar ve crash'ler arasında sabittir. Saldırganın binary'nin ya da source'un bir kopyasına ihtiyacı yoktur; yalnızca bir remote stack overflow ve bağlantının hayatta kalıp kalmadığını gözlemleme yeteneği yeter.

Temel hile bir crash oracle'dır. Buffer'ı hassas bir değere (canary, saved frame pointer ya da saved return address) kadar overflow et ve tam olarak bir byte'ı bir X tahminiyle overwrite et:

  • X gerçek byte ile eşleşirse, değer değişmez → server crash etmez ve request tamamlanır (socket açık kalır / response gelir).
  • X yanlışsa, corrupt edilmiş değer __stack_chk_fail'ı ya da sonraki bir fault'u tetikler → process crash eder (socket response olmadan kapanır).

X ∈ 0..255 üzerinde tekrarlamak byte'ı ortalama 128 denemede, en kötü 256 denemede bulur. Byte byte ilerlemek tam 8-byte'lık (64-bit) canary'yi ~1024 ortalama / 2048 worst-case request'te kurtarır — blind bir tahmin için 2^64'e karşı.

Genelleme: canary'den sonra saved frame pointer ve saved return address gelir, dolayısıyla "üç word okunmalıdır." Saved return address'i leak etmek geçerli bir .text address'i verir ve 64-bit'te ASLR/PIE'yi yener — daha önce infeasible sanılırdı, çünkü tam 28-bit-entropy'li word bir kerede brute-force edilemez.

Makalenin ölçülen maliyeti (Table I, ortalama request'ler):

Target Blind brute force Stack Reading
32-bit Linux (16-bit entropy) 2^15 512
64-bit Linux (28-bit entropy) 2^27 640
64-bit Mac OS X (16-bit entropy) 2^15 640

Bir pointer-reading optimizasyonu, x86-64'te zero/0x7f/0x00 olduğu bilinen byte'ları atlar ve address başına ~3 byte (~384 request) tasarruf eder. Gerçek nginx saldırısında, stack reading 846 request'ti (run'ın ~%35'i); bir shell'e giden tam blind path 4.000 request'in / ~20 dakikanın altındaydı.

Walkthrough

Overflow'un içinden yürüdüğü stack layout'u: buffer → canary → saved frame pointer (RBP) → saved return address (RIP).

Her byte'ı brute et, crash etmeyen değeri kilitle:

def leak_word(offset, known):           # known = bytes already recovered
    word = b""
    for _ in range(8):                  # 8 bytes per 64-bit word
        for guess in range(256):
            payload = b"A" * offset + known + word + bytes([guess])
            if not crashes(payload):    # oracle: connection stayed open / request done
                word += bytes([guess])
                break                   # byte locked, advance
    return word

def crashes(payload):
    # send payload; return True if the socket closed with no response (wrong byte),
    # False if the request completed / connection survived (correct byte)
    ...

canary    = leak_word(offset, b"")                       # ~1024 avg / 2048 max reqs
saved_rbp = leak_word(offset, canary)
saved_rip = leak_word(offset, canary + saved_rbp)        # valid .text addr -> beats ASLR/PIE

Makaleden pratik notlar:

  • Gerçek bir Linux canary'si "her zaman sıfırla başlar" (low byte'ı 0x00'dır); kurtarılan bir değer üzerinde yararlı bir sanity check.
  • Return address için, stack reading "zorunlu olarak tam saved instruction pointer'ı döndürmez" — .text aralığındaki crash etmeyen herhangi bir değer kabul edilebilir. Saved-frame-pointer word'üne sıfır yerleştirmek, read'leri worker process'ler arasında robust yapar.
  • Non-PIE main-binary return address'lerinin upper byte'ı 0x40'tır; library/stack address'lerinin ise 0x7f'tir.

saved_rip'i kurtardıktan sonra saldırgan .text'in nereye map'lendiğini bilir ve BROP gadget scan'e geçer — saved return address'i aday code address'leriyle overwrite eder ve yine crash-vs-hang'i oracle olarak kullanır.

Detection

  • Forking bir servise karşı tek bir client'ın uzun bir single-byte-incrementing crash serisi üretmesi imzadır: tek bir trailing byte ile farklılaşan yüzlerce segfault.
  • Aynı socket/peer'dan gelen per-process crash log'ları, özellikle *** stack smashing detected *** ile birlikte, devam eden bir canary brute force'a işaret eder.

Mitigation

  • Her crash'ten sonra re-randomize et. Makalenin birincil savunması: crash/spawn'da fork ve execve yap; bu hem canary'yi hem ASLR'yi yeniden atar. "BROP, bir crash'ten sonra rerandomize eden (örn. execve) PIE server'ları hedefleyemez." (MySQL mysqld_safe üzerinden re-exec yapar, dolayısıyla canary'si değişir ve stack reading başarısız olur.)
  • Tüm segment'lere PIE uygula ki leak'lenen address'ler sabit bir base'i ifşa etmesin.
  • Crash throttling: NetBSD segvguard / grsecurity bir segfault'tan sonra bir gecikme (örn. 30 s) dayatır ve binlerce prob'u pratikte imkânsız derecede yavaşlatır.
  • Bu primitive, byte-by-byte-canary-brute-force ve aslr-brute-force'in ardındaki motordur ve generalized-brop-stop-found-gadget-scan'i etkinleştirir; disclose edilmiş bir canary, stack-canary-leak'teki gibi replay de edilebilir. Not: block-oriented-programming ise ayrı, bağımsız bir data-only tekniktir (CFI-uyumlu gadget'lar) — stack reading'e bağımlı değildir.

References