Introduction to return oriented programming (ROP)

What is ROP?

Return Oriented Programming (ROP) is a powerful technique used to counter common exploit prevention strategies. In particular, ROP is useful for circumventing Address Space Layout Randomization (ASLR) and DEP. When using ROP, an attacker uses his/her control over the stack right before the return from a function to direct code execution to some other location in the program. Except on very hardened binaries, attackers can easily find a portion of code that is located in a fixed location (circumventing ASLR) and which is executable (circumventing DEP). Furthermore, it is relatively straightforward to chain several payloads to achieve (almost) arbitrary code execution.

Before we begin

If you are attempting to follow along with this tutorial, it might be helpful to have a Linux machine you can compile and run 32 bit code on. If you install the correct libraries, you can compile 32 bit code on a 64 bit machine with the -m32 flag via gcc -m32 hello_world.c . I will target this tutorial mostly at 32 bit programs because ROP on 64 bit follows the same principles, but is just slightly more technically challenging. For the purpose of this tutorial, I will assume that you are familiar with x86 C calling conventions and stack management. I will attempt to provide a brief explanation here, but you are encouraged to explore in more depth on your own. Lastly, you should be familiar with a unix command line interface.

My first ROP

The first thing we will do is use ROP to call a function in a very simple binary. In particular, we will be attempting to call not_called in the following program

void not_called() {
    printf("Enjoy your shell!\n");

void vulnerable_function(char* string) {
    char buffer[100];
    strcpy(buffer, string);

int main(int argc, char** argv) {
    return 0;

We disassemble the program to learn the information we will need in order to exploit it: the size of the buffer and the address of not_called

$ gdb -q a.out
Reading symbols from /home/ppp/a.out...(no debugging symbols found)...done.
(gdb) disas vulnerable_function 
Dump of assembler code for function vulnerable_function:
   0x08048464 <+0>:  push   %ebp
   0x08048465 <+1>:  mov    %esp,%ebp
   0x08048467 <+3>:  sub    $0x88,%esp
   0x0804846d <+9>:  mov    0x8(%ebp),%eax
   0x08048470 <+12>: mov    %eax,0x4(%esp)
   0x08048474 <+16>: lea    -0x6c(%ebp),%eax
   0x08048477 <+19>: mov    %eax,(%esp)
   0x0804847a <+22>: call   0x8048340 <strcpy@plt>
   0x0804847f <+27>: leave  
   0x08048480 <+28>: ret   
End of assembler dump.
(gdb) print not_called
$1 = {<text variable, no debug info>} 0x8048444 <not_called>