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)1 and DEP2. 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 program3:

Since we want our payload to overwrite the return address, we provide 0x6c bytes to fill the buffer, 4 bytes to replace the old %ebp, and the target address (in this case, the address of not_called). Our payload looks like:

Return to libc

So far, we've only been looking at contrived binaries that contain the pieces we need for our exploit. Fortunately, ROP is still fairly straightforward without this handicap. The trick is to realize that programs that use functions from a shared library, like printf from libc, will link the entire library into their address space at run time. This means that even if they never call system, the code for system (and every other function in libc) is accessible at runtime. We can see this fairly easy in gdb:

This example illustrates several important tricks. First, the use of ulimit -s unlimited which will disable library randomization on 32-bit programs. Next, we must run the program and break at main, after libraries are loaded, to print values in shared libraries (but after we do so, then even functions unused by the program are available to us). Last, the libc library actually contains the string /bin/sh, which we can find with gdb5 use for exploits!

It is fairly straightforward to plug both of these addresses into our previous exploit:

Chaining gadgets

With ROP, it is possible to do far more powerful things than calling a single function. In fact, we can use it to run arbitrary code6 rather than just calling functions we have available to us. We do this by returning to gadgets, which are short sequences of instructions ending in a ret. For example, the following pair of gadgets can be used to write an arbitrary value to an arbitrary location:

pop%ecxpop%eaxret

mov%eax,(%ecx)ret

These work by poping values from the stack (which we control) into registers and then executing code that uses them. To use, we set up the stack like so:

You'll see that the first gadget returns to the second gadget, continuing the chain of attacker controlled code execution (this next gadget can continue).

Other useful gadgets include xchg %eax, %esp and add $0x1c,%esp, which can be used to modify the stack pointer and pivot it to a attacker controlled buffer. This is useful if the original vulnerability only gave control over %eip (like in a format string vulnerability) or if the attacker does not control very much of the stack (as would be the case for a short buffer overflow).

Chaining functions

We can also use ROP to chain function calls: rather than a dummy return address, we use a pop; ret gadget to move the stack above the arguments to the first function. Since we are just using the pop; ret gadget to adjust the stack, we don't care what register it pops into (the value will be ignored anyways). As an example, we'll exploit the following binary3:

We can see that the goal is to call add_bin, then add_sh, then exec_string. When we call add_bin, the stack must look like:

| <argument> |
| <return address> |

In our case, we want the argument to be 0xdeadbeef we want the return address to be a pop; ret gadget. This will remove 0xdeadbeef from the stack and return to the next gadget on the stack. We thus have a gadget to call add_bin(0xdeadbeef) that looks like:

| 0xdeadbeef |
| <address of pop; ret> |
| <address of add_bin> |

Since add_sh(0xcafebabe, 0x0badf00d) use two arguments, we need a pop; pop; ret:

This time we will use a python wrapper (which will also show off the use of the very useful struct python module).

#!/usr/bin/pythonimportosimportstruct# These values were found with `objdump -d a.out`.pop_ret=0x8048474pop_pop_ret=0x8048473exec_string=0x08048414add_bin=0x08048428add_sh=0x08048476# First, the buffer overflow.payload="A"*0x6cpayload+="BBBB"# The add_bin(0xdeadbeef) gadget.payload+=struct.pack("I",add_bin)payload+=struct.pack("I",pop_ret)payload+=struct.pack("I",0xdeadbeef)# The add_sh(0xcafebabe, 0x0badf00d) gadget.payload+=struct.pack("I",add_sh)payload+=struct.pack("I",pop_pop_ret)payload+=struct.pack("I",0xcafebabe)payload+=struct.pack("I",0xbadf00d)# Our final destination.payload+=struct.pack("I",exec_string)os.system("./a.out \"%s\""%payload)

Some useful tricks

One common protection you will see on modern systems is for bash to drop privileges if it is executed with a higher effective user id than saved user id. This is a little bit annoying for attackers, because /bin/sh frequently is a symlink to bash. Since system internally executes /bin/sh -c, this means that commands run from system will have privileges dropped!

In order to circumvent this, we will instead use execlp to execute a python script we control in our local directory. We will demonstrate this and a few other tricks while exploiting the following simple program:

The general strategy will be to execute a python script via execlp, which searches the PATH environment variable for an executable of the correct name.

Unix filenames

We know how to find the address of execlp using gdb, but what file do we execute? The trick is to realize that Unix filenames can have (almost) arbitrary characters in them. We then just have to find a string that functions as a valid filename somewhere in memory. Fortunately, those are are all over the text segment of program. In gdb, we can get all the information we need:

We will execute the file U\211\345\203\344\360\350\317\377\377\377\270. We first create this file in some temporary directory and make sure it is executable7 and in our PATH. We want a bash shell, so for now the file will simply ensure bash will not drop privileges:

Keeping stdin open

Before we can exploit this, we have to be aware of one last trick. We want to avoid closing stdin when we exec our shell. If we just naively piped output to our program through python, we would see bash execute and then quit immediately. What we do instead is we use a special bash sub shell and cat to keep stdin open8. The following command concatenates the output of the python command with standard in, thus keeping it open for bash:

cat <(python -c 'print "my_payload"') - | ./a.out

Now that we know all the tricks we need, we can exploit the program. First, we plan what we want the stack to look like:

To recap, this exploit required us to use the following tricks in addition to ROP:

Executing python since bash drops privileges

Controlling the PATH and executing a file in a directory we control with execlp.

Choosing a filename that was a "string" of bytes from the code segment.

Keeping stdin open using bash sub shells and cat.

Debugging

gdb

When you exploit doesn't work the first time, there are some tricks you can use to debug and figure out what is going on. The first thing you should do is run the exploit in gdb with your payload. You should break on the return address of the function you are overflowing and print the stack to make sure it is what you expect. In the following example, I forgot to do ulimit -s unlimited before calculating libc addresses so the address of execlp is wrong:

ASLR is the technique where portions of the program, such as the stack or the heap, are placed at a random location in memory when the program is first run. This causes the address of stack buffers, allocated objects, etc to be randomized between runs of the program and prevents the attacker. ↩

DEP is the technique where memory can be either writable or executable, but not both. This prevents an attacker from filling a buffer with shellcode and executing it. While this usually requires hardware support, it is quite commonly used on modern programs. ↩

To make life easier for us, we compile with gcc -m32 -fno-stack-protector easy_rop.c. ↩↩↩

You'll note that we use print the exploit string in a python subshell. This is so we can print escape characters and use arbitrary bytes in our payload. We also surround the subshell in double quotes in case the payload had whitespace in it. ↩

These can be found in the libc library itself: ldd a.out tells us that the library can be found at /lib/i386-linux-gnu/libc.so.6. We can use objdump, nm, strings, etc. on this library to directly find any information we need. These addresses will all be offset from the base of libc in memory and can be used to compute the actual addresses by adding the offset of libc in memory. ↩