h3xduck@blog:~#

Return Oriented Programming

by h3xduck

25 May 2021

The Blue Team vs. Read Team point of view

A few days ago we analyzed how a typical buffer overflow looks like and what are the underlying mechanics which make possible such a vulnerability. If you haven’t yet read those posts I recommend checking out Part 1 and Part 2 on the matter. As we mentioned there, exploitation involves submitting a payload (usually via the program input data) containing malicious shellcode which is later executed by overwritting the value of the saved EIP ret in the stack.

Let’s now imagine that we are on the defenders’ side of this type of attack. Clearly our main priority is to remove all possible buffer overflows, but in real life mistakes happen and sometimes projects are too large to rely on finding all vulnerabilities, at least during the development process. Therefore, we need some kind of second line of defense so that, in the eventuality of a buffer overflow in the stack, we can prevent execution of code, or at least make it far more difficult.

This is where the concept of NX(No-Execute) a.k.a. DEP(Data Execution Prevention) comes into place. In essence, these technologies will mark your stack (and other data sections) as not executable, so that even if you can inject shellcode, you will not be able to execute it just because you lack the permissions to do it. In the end, there shouldn’t be any reason why an user will try to execute an instruction pushed in the program stack.

Back to the attackers’ point of view, the implementation of these technologies may seem to completely mitigate any exploitation, but actually it does not. As with many other vulnerabilities, more advanced attacks have been invented to bypass what in the end is just a patch. In this post, we will be explaining one of these advanced modern techniques called Return Oriented Programming (ROP), from a low-level and visual perspective.

Note that I’m not saying ROP was the direct innovation against NX/DEP, other techniques such as the use of GLibc functions are simpler to use and date from before.

Bypassing NX/DEP with ROP gadgets

Okay, we do not have permission to execute our code if it’s in the stack, but there is a lot of already present code - from libraries for example - which is executable and accessible in the system. Of course that code would not be malicious as our injected shellcode because we do not have control over what is placed in a library, but what if we could, somehow, get small pieces of code from multiple places, and join them together so that, when executed sequentially, we composed the code we wanted to execute in the first place? Given that there are a lot of instructions of many types already in the system, in theory we could reconstruct, as when creating a collage with image pieces, any code we wanted.

The above point is the fundamental idea on which ROP is based. We will be joining together groups of small pieces of code called ROP gadgets, which are formed of some instructions followed by a ret (function return) instruction. These gadgets would take their data from the stack, from which we have full control given that it was overwritten after a buffer overflow. Apart from this, the ESP register will be used as “an instruction pointer” in the stack.

This was quite theoretical and abstract, but let’s see with a simple example how could we execute a sequence of assembly instructions which could be part of our shellcode using ROP. Imagine that part of the shellcode we want to execute consists of the following:

mov %edx, 10
mov %eax, [%esp]      

Let’s see what we need to inject into the stack of the vulnerable program in order to execute those instructions using the ROP technique:

If this is the first time you hear about ROP, I recommend you take pen and pencil and try to reproduce the following steps by yourself.

The above image illustrates the moment right after we have completed the buffer overflow, and the subroutine has ended. The address in ret has been overwritten, and therefore the new EIP now points to an address of our choice, which is the address of the first gadget, that is, some other code already existing on the machine which we previously found and selected. Also, ESP has been readjusted because of the function returning, now it points to the next element after where ret used to be.


This is what happens right after the first gadget instruction has been executed. As it can be seen, we have popped the data from the stack into the EDX register. That data has been put specifically at that position of the stack during the buffer overflow stage so that it resembles the operation we want to do. Also note that ESP now points to the next gadget address… as if it was a program counter, but of gadgets!


This diagram shows what happens after the return address has been executed. Remember that when returning, we pop the last position of the stack and put that value into EIP. Thus, we now have EIP pointing at the gadget 2 address.


Finally, we can see how the cycle is repeated. Now the data was used for the instruction and ESP points to the next gadget location, which after the return address will be put into EIP again. We have achieved code execution and bypassed NX/DEP!

As a final note, one can argue that ROP may not work sometimes because of the difficulty of finding the adequate ROP gadgets at libraries’ code, but in reality it is perfectly feasible if there is a large enough codebase, and many tools automate the search for the instructions we want followed by a return to construct the gadgets. We may cover some of these tools in the future.

Have fun!

h3xduck

tags: stack - overflow - buffer - exploitation - shellcode - assembly - IA-32 - Intelx86 - memory - registers - code - execution - nx - bypass - DEP - return - oriented - programming - ROP - exploit

Creative Commons License
This work is licensed under a Creative Commons Attribution 4.0 International License.