Table of contents

  1. return oriented programming
return oriented programming return oriented programming

return oriented programming

During a recent CTF, I revisited a technique called Return Oriented Programming that I find very fascinating.

The target file was called market, a pwn challenge during HackGT 8. It was a 64-bit ELF running with ASLR and a non-executable stack.

I’d previously solved basic buffer overflow challenges like this easy buffer overflow, but never something with multiple defenses in place like this, so I took it as a challenge. Here’s a link to the final exploit with a more detailed writeup.

ASLR means that each time the executable runs, it’s loaded into a slightly different memory address. So if we’re trying to inject code that jumps into something like libc, we can’t jump to a fixed address offset since the memory layout is randomized. DEP means that the stack itself is not executable, so if we just inject code onto the stack, it’s not even posssible to execute it. So how does ROP bypass this?

Well, we do control one thing when we can do a stack buffer overflow— the return address. If we overwrite that with something, when the ret instruction at the end of the current stack frame executes, it’ll return to the saved return pointer on the stack. There are pieces of code already present in the binary and its libraries—short instruction sequences that end in a ret—called gadgets in ROP. With ROP, instead of trying to inject our own code, we chain these gadgets together by carefully writing their addresses (and any arguments they need) onto the stack. When the function returns, it jumps to the first gadget; that gadget runs and ends with another ret, which pops the next address off the stack and jumps there, and so on.

By stringing together the right gadgets in the right order, we can get the program to perform complex actions—essentially programming using the code that’s already present in memory. Since we never execute code we’ve injected, DEP/NX doesn’t stop us. And if we can figure out the randomized addresses (for example, with an infoleak or a partial overwrite), we can defeat ASLR too. A creatively titled 2007 paper introduced this concept, and it makes the argument that in all x86 programs of sufficient complexity, there exists enough variety in gadgets to be Turing complete. Despite something seemingly foolproof like the W xor X principle—there’s always another clever exploit. ROP uses the program’s own code against itself. All an attacker has to do was inject a few bytes in the right place, and they can redirect control flow. Kind of like biological viruses—the original hackers.

Life, uh, finds a way, right?

Reading:


← Back to blog