Return-Oriented Programming Attacks
Undefined behavior of computer software may be exploited by attackers to execute
arbitrary code, gain privileges, and commit other acts. An example of this is return-oriented
programming (ROP), where an attacker uses a bug in the software to cause buffer overflows of
data. The attacker then constructs a number of “gadgets” from existing code whose addresses
are chained together on the stack. When a return instruction is repeatedly called, the gadgets are
executed.
Computer security exploits generally involve an attacker taking advantage of a bug in a
computer system’s software that allows the attacker to write a malicious payload to the computer
system’s memory. For example, if a function (e.g., a subroutine of a computer program) does
not perform proper bounds checking before storing user-provided data into memory, the function
will accept more input data than it can store properly. This results in an overflow of the buffer
accepting the input data and in memory being overwritten with the user’s input data. In the case
where the overflowed buffer is the system stack, the attacker is able to overwrite the return
address stored on the stack (which points to the address of the caller of the function) with a new
return address pointing to the attacker’s desired location. In this way, the attacker is able to
hijack the control flow of the computer system’s software. In a code-injection type of attack, the
malicious code or payload is written onto the stack and the return address is made to point to the
location of the newly written code. Code-injection type attacks have been successfully defended
against by the implementation of mechanisms for marking locations in memory that are able to
be written to as non-executable, termed data execution prevention (DEP).
In order to circumvent DEP defenses, code-reuse attacks were developed in which the
attacker, rather than injecting malicious code onto the stack, uses instructions that are already
present in executable memory, referred to as a “gadget.” The attacker chains the gadgets to
perform a desired sequence of operations. Code-reuse attack techniques include return oriented
programming (ROP), jump oriented programming (JOP), call oriented programming (COP),
counterfeit object-oriented programming (COOP), and data oriented programming (DOP). In an
ROP type of attack, the attacker chains gadgets together by manipulating return addresses on the
stack to form a sequence of operations. Each gadget ends in a return instruction where the return
address points to the next gadget in the sequence. JOP is a variant of ROP that uses indirect
jumps rather than return instructions to chain the gadgets together. COP refers to ROP-derived
techniques that employ indirect calls to form the chain of gadgets. COOP is a code-reuse type of
attack that targets programs written in an object-oriented language such as C++. ROP, JOP,
COP, and COOP attacks all result in a disruption of the normal control flow of the program
being executed by the attacked computer system, and defenses have been developed that try to
ensure that the control flow remains legitimate. During program execution, memory can be
divided into a control plane and a data plane. Memory variables that are used directly in control
flow transfer instructions (e.g., returns, jumps, and indirect calls) are part of the control plane.
The aforementioned defenses against control flow attacks involve attempting to protect the
integrity of the control plane memory or checking the targets of control transfers. DOP attacks
circumvent these control flow attack defenses by targeting the data plane, meaning those
memory variables not directly used in control flow transfer instructions, to construct data-
oriented gadgets. DOP attacks exploit non-control data variables and cannot be dealt with by
control flow attack defenses since execution of data-oriented gadgets does not deviate from
a legitimate control flow path. Gadgets constructed using ROP, JOP, COP, COOP, and DOP
have all been shown to enable Turing-complete (e.g., arbitrary) malicious computation.
There are existing ROP gadget searching tools (e.g., ROPGadget, Ropper, and ROPME)
that perform static analysis on a given binary to find possible ROP gadgets. These tools facilitate
writing ROP exploits, but they may also be utilized by defense mechanisms. These tools,
however, perform syntactic searching and cannot return all suitable gadgets. They also suffer
from the weaknesses of static analysis to utilize runtime information in the context of detecting
all possible gadgets. Moreover, these tools return many irrelevant gadgets and are both
inefficient and time consuming. Another gadget finding tool, Opti-ROP, performs semantic
queries to find ROP gadgets. All of these tools, however, are specific to ROP gadgets and cannot
detect COP, JOP, DOP, or COOP gadgets.