Byte-by-Byte Canary Brute Force (forking servers)¶
Forking bir server'a karşı bir stack canary'yi byte byte recover et: child'lar parent'ın canary'sini paylaşır, dolayısıyla tek bir byte'ı overwrite ettikten sonra crash-vs-survive, memory leak etmeden onu açığa çıkarır.
Mechanism¶
Neden çalışır
Stack canary, process startup'ta bir kez seçilir ve korunan her ret'ten
önce check edilir. Yanlış bir canary process'i abort eder; doğru olan sessizce
geçer. Normalde 8 byte'ın tamamını tahmin edemezsin (≈2^56 kullanışlı
entropy).
Ama "fork() çağrıları etkin olarak parent process'in bir kopyasını oluşturur," dolayısıyla forking bir server'ın her child'ı aynı canary değerini tutar. Bir crash'te yalnızca child ölür — parent, özdeş canary ile servis etmeye devam eder. Bu, her child'ı bir single-byte oracle yapar:
- Tam olarak bir canary byte'ına kadar ve dahil olmak üzere, bir tahmin edilen değerle overflow yap.
- Tahmin yanlışsa, canary check başarısız olur ve child crash eder.
- Tahmin doğruysa, check (şimdilik) geçer ve request normal şekilde devam eder — ayırt edilebilir bir "survive" sinyali.
O byte için 0x00–0xFF'i iterate et; crash etmeyen değer doğrudur, sonra sonraki byte'a geç. Canary'nin dayandığı invariant — secret'ın hepsi-bir-anda tahmin edilmesi gerekir — partial correctness gözlemlenebilir olduğundan lineer bir aramaya çöker.
Walkthrough¶
64-bit bir canary için maliyet: least-significant byte Linux'ta sabit 0x00'dır,
geriye ~7 bilinmeyen byte bırakır → en fazla 256 × 7 ≈ 1.792 deneme (2^56
blind'e karşı).
canary = b"\x00" # LSB is the NUL terminator byte on Linux
for pos in range(1, 8): # recover bytes 1..7
for guess in range(0x100):
payload = b"A"*OFFSET # fill up to the canary
payload += canary + bytes([guess]) # known bytes + this guess
child = connect(target)
send(child, payload)
if survived(child): # no crash -> this byte is correct
canary += bytes([guess])
break
print("canary =", canary.hex()) # ~1792 connections worst case
Beklenen davranış: her byte pozisyonu için, 256 denemenin 255'i forked child'ı crash eder (connection reset / normal yanıt yok) ve tam olarak biri hayatta kalır — o byte recover edilir. 7 round sonra tüm canary bilinir ve gerçek overflow'da replay edilebilir.
Koşullar: fork, NUL-append yok, gözlemlenebilir survival
Yalnızca şu durumlarda çalışır: (a) server fork edip aynı canary'yi tutar,
(b) input path binary-safe'tir (örn. read/recv, 0x00 byte'ında truncate
edecek NUL-terminating bir string copy değil) ve (c) crash vs. başarı ayırt
edilebilirdir. Request başına execve, canary'yi re-randomize eder ve bunu
çürütür.
Detection¶
ASLR brute force gibi, her byte bulunmadan önce bir child
crash seli üretir — __stack_chk_fail abort'ları, *** stack smashing detected ***
log satırları ve core dump'lar — ki bunları crash-rate monitoring işaretleyebilir.
Mitigation¶
(Neyin durdurduğu.) Canary'yi her connection'da re-randomize et (yalnızca fork değil, re-exec), client başına anormal crash rate'lerini detect/throttle et ve canary'leri tek bir secret'a bağlı olmayan mitigation'larla (shadow stack'ler) eşleştir. Aynı fork-inheritance zayıflığı stack reading'in de temelindedir.
References¶
- bananamafia. Brute-Forcing x86 Stack Canaries. — https://bananamafia.dev/post/binary-canary-bruteforce/
- CTF101. Stack Canaries. — https://ctf101.org/binary-exploitation/stack-canaries/