Return-Oriented Programming (ROP)
Overview of return-oriented programming (ROP).
Introduction
Return-oriented programming (ROP) is a binary exploitation technique that is used to bypass defenses including executable space protection and code signing. It is achieved by gaining control of the stack and hijacking the program’s control flow to execute instructions called gadgets that already exist in memory. Gadgets are extracted from an existing program’s subroutines and / or shared library code. The gadgets will typically terminate with a return instruction which allows multiple gadgets to be chained together into what is called a ROP chain. Therefore, an attacker is able to piece together an exploit without introducing any shellcode.
There are differences when crafting ROP chains in various architectures due to the differing calling conventions. For instance, x86 pushes all arguments onto the stack while x64 loads the first six arguments into registers in the following order: (rdi, rsi, rdx, rcx, r8, r9), and then pushes any subsequent arguments onto the stack. Therefore, ROP chains crafted with x64 require specific registers to be popped from the top of the stack while x86 has more freedom when choosing gadgets. An example can be seen below.
x86
from pwn import *
PPR = 0x8000f000
payload = [
p32(0xdeadbeef),
p32(0xfacecafe),
p32(PPR),
]
x64
from pwn import *
PR_RDI = 0x8000f000
PR_RSI = 0x8000f010
payload = [
p64(PR_RDI),
p64(0xdeadbeef),
p64(PR_RSI),
p64(0xfacecafe),
]
Example Exploit
A full demonstration of exploiting a binary vulnerable to a ROP exploit. The C source code and compilation instructions are provided below.
# Insert source
Compile via the CLI with some security protections disabled. Ensure to leave executable space protection enabled.
gcc
First, check the security protections enabled.
checksec ./vuln
# TODO: Insert
Next, overflow the stack using a cyclic pattern and determine the offset required to overflow the instruction pointer register.
gdb ./vuln
(gdb) r <<< $(cyclic 500)