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:
Xgerç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).Xyanlış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" —
.textaralığı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 ise0x7f'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
execveyap; bu hem canary'yi hem ASLR'yi yeniden atar. "BROP, bir crash'ten sonra rerandomize eden (örn. execve) PIE server'ları hedefleyemez." (MySQLmysqld_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.