[+] BOP and ROP - Code Reuse Attacks
/ 5 min read
Table of Contents
How I Learned ROP and BOP
When I first heard about Return-Oriented Programming, I thought it was magic. The idea that you could build a working exploit using code that already existed in the binary - without injecting anything - blew my mind. Then I tried to actually build one and it humbled me fast.
Here is what I have learned about ROP and BOP through practice.
Why I Needed to Learn This
Modern binaries have protections that block the classic approach:
- DEP/NX - stack is not executable, so I cannot just inject shellcode
- ASLR - addresses are randomized, so hardcoded addresses break
- Stack canaries - detect overwrites before ret executes
ROP bypasses DEP completely because I am not executing new code. I am reusing existing instructions. That clicked for me and I dove in.
How ROP Works (How I Understand It)
I overwrite the return address on the stack. But instead of pointing it at my shellcode, I point it at a small snippet of existing code - a gadget - that ends with ret. When that gadget finishes and hits ret, it pops the next address off the stack. Which points to my next gadget. And so on.
I chain these tiny pieces together to do whatever I want.
A Simple Gadget
pop rdiretThis sets RDI (the first argument register on x86-64) to whatever value I put on the stack. Then it returns to the next gadget.
Another useful one:
xor eax, eaxretSets EAX to zero. Simple but needed for syscall setup.
What I Can Build With Gadgets
By chaining enough of these, I can:
- Set up registers with specific values
- Call system functions like
execve("/bin/sh") - Spawn a shell, read files, whatever I need
Even though each gadget does almost nothing individually, the chain becomes Turing-complete.
My First ROP Chain
I was working on a CTF binary with a buffer overflow and NX enabled. Classic setup. I used ROPgadget to find what I needed:
ROPgadget --binary vuln | grep "pop rdi"Found pop rdi; ret at some address. Then I needed the address of /bin/sh in libc and the address of system(). My chain looked like:
from pwn import *
elf = ELF('./vuln')libc = ELF('./libc.so.6')
pop_rdi = 0x401234 # pop rdi; retbin_sh = libc.address + next(libc.search(b'/bin/sh'))system = libc.symbols['system']
payload = b'A' * 72 # overflow to saved RIPpayload += p64(pop_rdi)payload += p64(bin_sh)payload += p64(system)It did not work the first time. Took me hours to figure out why.
The Stack Alignment Problem
My exploit worked locally but crashed on the remote server. I was losing my mind until I learned about stack alignment.
The x86-64 ABI guarantees 16-byte alignment when a call instruction executes. libc’s system() uses SSE instructions (like movaps) that require this alignment. If RSP is not a multiple of 16, it crashes.
The fix is stupid simple - add an extra ret gadget before the call to system:
ret = elf.address + 0x401016 # just a ret instruction
payload = b'A' * 72payload += p64(pop_rdi)payload += p64(bin_sh)payload += p64(ret) # align the stackpayload += p64(system)That extra ret pops RSP forward by 8 bytes, fixing the alignment. Once I understood this, I never forgot it.
What I Learned About BOP
After getting comfortable with ROP, I read about Basic Operation Programming (BOP). It extends the ROP concept by not limiting itself to gadgets that end in ret.
Instead of ret-based gadgets, BOP uses basic blocks - any sequence of instructions that ends in a branch (conditional jump, indirect call, whatever). This gives the attacker way more code fragments to work with.
Why BOP Matters
ROP has a weakness: some defenses specifically look for ret instructions or monitor return-based control flow. Control Flow Integrity (CFI) can restrict which addresses a ret can legally jump to.
BOP works around this by using transitions that CFI considers valid:
- Virtual function table entries in C++ binaries
- Indirect jumps through attacker-controlled values
- Exception handlers
- Computed branches
ROP vs BOP (How I See It)
| Aspect | ROP | BOP |
|---|---|---|
| Gadget type | Ends in ret | Any basic block |
| Stack dependent | Yes | Not necessarily |
| Bypasses CFI | Sometimes | Better at it |
| Complexity | Lower | Higher |
| Gadget availability | Large | Even larger |
I have not built a full BOP chain yet. It requires deep understanding of the target’s control-flow graph. But knowing it exists helps me understand where exploit development is headed.
Tools I Use for This
- ROPgadget and Ropper - finding gadgets in binaries
- pwntools - my main exploitation framework, makes chain building easy
- Ghidra - understanding the binary and finding useful code paths
- radare2 - quick gadget search and binary analysis
- GDB + pwndbg - debugging my chains when they crash
- Angr - symbolic execution when I need to find gadgets automatically
What Clicked for Me
- ROP is just data on the stack. I am not writing code, I am writing addresses. The binary executes its own code in the order I choose.
- Stack alignment is real. Always check if your target libc needs 16-byte alignment.
- More gadgets = more power. Larger binaries and linked libraries give me more to work with.
- BOP is the evolution. When defenses catch up to ROP, BOP-style techniques will dominate.
- Practice on CTFs. I learned more from 10 CTF pwn challenges than from reading papers.
Where I Am Going Next
I want to:
- Build a working BOP chain against a CFI-protected binary
- Learn SROP (Sigreturn-Oriented Programming) for cases where gadgets are limited
- Study JOP (Jump-Oriented Programming) as another ret-less technique
- Get better at defeating ASLR through information leaks
Code reuse attacks taught me that attackers do not need to write new code. They take what already exists and creatively repurpose it. That is both terrifying and fascinating.