Return-Oriented Programming (ROP) is an exploitation technique used to execute arbitrary code on a target system. It is often used in environments with defense mechanisms like Write XOR Execute (W^X), Address Space Layout Randomization (ASLR), and stack canaries. ROP allows me to bypass these protections and gain control of the execution flow of a program, often leading to the execution of malicious code.
In this blog post, I will cover the basics of ROP, and how I use it.
🌸👋🏻 Join 10,000+ followers! Let’s take this to your inbox. You’ll receive occasional emails about whatever’s on my mind—offensive security, open source, boats, reversing, software freedom, you get the idea.
Gadgets
Gadgets are small snippets of code that end with a ret (return) instruction. These gadgets are in the existing code (e.g., in libraries or the executable itself). Each gadget typically performs a small, specific task, such as moving data between registers, adding numbers, or calling a function.
Example gadget
Here is an example of what a gadget might look like. This gadget could be found in a binary or a library:
pop eax: Pops the value from the top of the stack into theeaxregister.ret: Pops the next value from the stack into the instruction pointer (EIPorRIPon 64-bit systems), transferring control to the address specified by that value.
2. Chaining gadgets
In a ROP attack, I chain together multiple gadgets to perform operations—calling functions, manipulating registers, and executing system calls—without injecting new code. By controlling the stack (specifically the return addresses on the stack), I can make the program “return” to these gadgets in sequence. This sequence of gadgets is crafted to achieve a specific outcome, such as calling execve() to spawn a shell.
Example of chaining gadgets
Imagine I want to perform a simple operation like eax = ebx + ecx. I could achieve this using a chain of gadgets found in the binary:
1. Gadget 1: Load ebx into eax
This gadget loads a value into eax. Specifically, I would load the value of ebx into eax.
2. Gadget 2: Add ecx to eax
This gadget adds the value of ecx to eax.
3. Gadget 3: Return to caller or another gadget
This gadget returns, which could lead to another gadget or return to the original caller.
🌸👋🏻 Join 10,000+ followers! Let’s take this to your inbox. You’ll receive occasional emails about whatever’s on my mind—offensive security, open source, boats, reversing, software freedom, you get the idea.
Chained execution
When executed in sequence, these gadgets perform the following:
- Pop the value of
ebxintoeax.- The state after this gadget:
eax = ebx
- The state after this gadget:
- Add the value of
ecxtoeax.- The state after this gadget:
eax = eax + ecx(which is noweax = ebx + ecx)
- The state after this gadget:
- Return to the next instruction in the chain or complete the chain.
- The ret instruction in each gadget returns control to the next gadget in the chain, allowing the sequence to continue or complete the operation.
3. Bypassing security mechanisms
Now that we understand ROP, let’s explore how it can help us bypass security mechanisms.
Operating systems often employ multiple layers of security, making traditional buffer overflow, injection, and other exploitation techniques less effective. Some of these security mechanisms include W^X, ASLR, and stack canaries.
ROP, however, operates differently by chaining existing code rather than injecting new code, allowing me to circumvent these defenses. Bypassing or circumventing these mechanisms is needed for a ROP attack to succeed because these protections are designed to prevent or hinder exploitation techniques like ROP.
Write XOR Execute (W^X)
TLDR: W^X ensures that a memory page cannot be both writable and executable at the same time. ROP bypasses this by reusing existing executable code rather than injecting new code.
A page, memory page, or virtual page is a fixed-length contiguous block of virtual memory, described by a single entry in a page table. It is the smallest unit of data for memory management in an operating system that uses virtual memory. Similarly, a page frame is the smallest fixed-length contiguous block of physical memory into which memory pages are mapped by the operating system.
Wikipedia
Purpose
W^X is a security mechanism that enforces a rule where a memory page can be either writable or executable, but not both simultaneously. This protection prevents attackers from injecting and then executing malicious code in writable memory.
Bypassing
Since ROP does not rely on injecting new code by reusing existing executable code, W^X is not directly circumvented; instead, ROP renders it ineffective. In other words, I do not need to inject new code because I can reuse code that is already marked as executable, thereby bypassing the restrictions imposed by W^X.
🌸👋🏻 Join 10,000+ followers! Let’s take this to your inbox. You’ll receive occasional emails about whatever’s on my mind—offensive security, open source, boats, reversing, software freedom, you get the idea.
Address Space Layout Randomization (ASLR)
TLDR: ASLR randomizes the memory locations of code sections, making it difficult to predict where code will be in memory. However, if I can leak an address or brute-force the address space, I might be able to locate ROP gadgets.
Purpose
ASLR randomizes the memory addresses of a process, such as the stack, heap, and loaded modules (e.g., executables and shared libraries). This randomization makes it difficult for me to predict the location of gadgets, thus thwarting the construction of a reliable ROP chain.
Bypassing
To successfully perform a ROP attack, I need to know the exact memory locations of the gadgets I intend to use. If I can leak an address within the randomized space (e.g., via an information leak vulnerability), I can infer the positions of other gadgets.
Alternatively, brute-forcing ASLR can be attempted. However, this is usually impractical due to the large address space.
Stack canaries
TLDR: Stack canaries are values placed on the stack to detect buffer overflows. ROP can sometimes bypass canaries by avoiding overwriting them or by using non-stack-based attacks.
Purpose
Stack canaries are placed on the stack just before the return address. They are designed to detect and prevent buffer overflow attacks by ensuring that the canary value remains unchanged before the function returns. If a buffer overflow overwrites the canary, the program detects the anomaly and aborts execution, preventing the exploitation.
Bypassing
ROP can sometimes bypass stack canaries by avoiding overwriting them altogether.
Since ROP doesn’t always need to overflow the stack, I can bypass the canary protection if I control the stack pointer without changing the canary value. Additionally, non-stack-based attacks or methods that leak or predict the canary value can also allow me to neutralize this protection.
For example, a format string vulnerability is a non-stack-based attack that leaks the canary value, allowing me to bypass the protection. Here, I might use a format string vulnerability to read arbitrary memory, including the location where the stack canary is stored. By leaking the canary value through this method, I can bypass the stack protection mechanism during a buffer overflow attack, since I now know the correct canary value that needs to be preserved.
For these methods, it’s possible to bypass stack canary protection if I can control the stack pointer and redirect execution without overwriting the stack canary. This is because the canary’s purpose is to detect changes to specific stack values, typically in scenarios involving buffer overflows. If the canary is not altered, it won’t trigger a security alert, even if the stack pointer is manipulated to execute ROP gadgets.
🌸👋🏻 Join 10,000+ followers! Let’s take this to your inbox. You’ll receive occasional emails about whatever’s on my mind—offensive security, open source, boats, reversing, software freedom, you get the idea.
Search for gadgets
Now that we understand the importance of gadgets, how do we find them? Here’s an example Python script using the ropgadget tool to search for gadgets in a binary:
This script runs the ROPgadget tool against a specified binary and prints out potential gadgets, which can be used for ROP attacks.
Example use case
Let’s say I find the following gadgets in a binary:
0x08048430: pop eax; ret;0x08048432: add eax, ecx; ret;0x08048434: ret;
I could craft an exploit that places these addresses on the stack in the right order, manipulating the program to execute them sequentially, achieving the desired computation using existing code in the binary.
🌸👋🏻 Join 10,000+ followers! Let’s take this to your inbox. You’ll receive occasional emails about whatever’s on my mind—offensive security, open source, boats, reversing, software freedom, you get the idea.
Why use ROP?
As I stated previously, many operating systems employ multiple layers of security, and traditional buffer overflow techniques become less effective. ROP provides a way to exploit vulnerabilities even in the presence of these defenses by:
- Avoiding the need to inject new code (which W^X prevents).
- Working around ASLR by using techniques like information leaks to find gadget locations.
- Utilizing existing, legitimate code in a way that the original developers never intended.
Example scenario
Let’s look at our last example.
Suppose I find a buffer overflow vulnerability in a BSD-based application. Due to W^X, I cannot inject shellcode directly. Instead, I have to analyze the binary to identify gadgets, such as:
- A gadget to move data into a register.
- A gadget to set up a system call.
- A gadget to call
execve().
Then, I craft an exploit that overwrites the return address on the stack with the address of the first gadget. As the program executes, it “returns” to each gadget in my sequence, eventually leading to the execution of a system call that runs my shell.
After successfully chaining these gadgets together to achieve arbitrary code execution, the power of ROP is evident as an exploitation technique capable of bypassing advanced security mechanisms through the creative reuse of existing code. If you enjoyed this post, consider reading How Does OS Affect Binary Exploitation.

