ROP is a binary exploitation technique that allows attackers to execute custom code even when the NX bit is set (i.e., non-executable stack). Instead of writing and directly executing instructions on the stack (e.g., shellcode), ROP reuse instructions already present in the program to get around stack protection. There are mitigations against ROP such as CFI (control flow integrity), though non-ideal CFI configurations can still be bypassed (source).
To build a proper payload, the attacker looks for gadgets—sequences of useful instructions in the program usually ending with the ret
instruction—and chain them together; this process can be automated with tools like ropper
. A full ROP payload that the attacker write to stack is filled with addresses of these gadgets and data needed by pop
instructions and the like. As the payload gets executed, the first return address is consumed by ret
instruction of the exploited function, then subsequent addresses are consumed by the gadgets’ own ret
’s.
Note that ROP-based exploits may require constructing (ideally arbitrary) memory read and write primitives from simpler ROP gadgets. This process may be hard if the binary is too small and doesn’t contain many useful gadgets. Fortunately, we can use universal gadgets like ret2csu to solve this issue.