ROP is an exploit technique in which the attacker uses control of the stack to indirectly execute cherry-picked instructions or groups of machine instructions immediately prior to the return instruction in subroutines within the existing program code.
Because all the instructions that are executed are from executable memory areas within the original program, this avoids the need for direct code injection, and circumvents most measures that try to prevent the execution of instructions from user-controlled memory.
Thus, ROP reuses code in the existing program to perform exploitation, evading memory protection mechanisms. I’ll assume reader is aware of how basic stack overflow exploit works.
Consider the following program
This program is vulnerable to classic buffer overflow attack. In the
vul() we have buffer of 10 bytes while we are reading upto 100 bytes in the
read() since writing more data than what is allowed, it can lead to buffer overflow.
vuln() is called, stack might look somewhat like
When buffer is filled with just the right size it’s possible to modify saved return address allowing attacker to take control of EIP thus allowing him to execute any arbitrary code.
In modern systems this can be evaded by
- Stack Canaries
DEP stands for data execution prevention, this technique marks areas of memory as non executable. Usually the stack and heap are marked as non executable thus preventing attacker from executing code residing in these regions of memory.
ASLR stands for Address Space Layer Randomization. This technique randomizes address of memory where shared libraries , stack and heap are maapped at. This prevent attacker from predicting where to take EIP , since attacker does not knows address of his malicious payload.
In this technique compiler places a randomized guard value after stack frame’s local variables and before the saved return address. This guard is checked before function returns if it’s not same then program exits. It can be visualized as
If an attacker tries to modify return address, stack canary is also modified inevitably. So, before function returns this canary is checked thus preventing the exploitation.
ROP is a complex technique that allows us to bypass DEP and ALSR but unfortunately (or fortunately) this cannot bypass stack canary protection however if there’s an additional memory leak it may be possible to predict canary and exploit it.
ROP re-uses executable code portions within the binary or shared libraries. These code portions are often called as ‘ROP Gadgets’. We’ll have a look at special case of ROP called as Return2PLT . It should be noted that only libc base address is randomized, offset of a particular function from its base address always remains constant, If we can bypass shared library base address randomization, vulnerable programs can be successfully exploited even when ASLR is turned on.
Let’s consider this vulnerable code
Since we cannot bypass stack canaries using this method, this program should be compiled with flag telling compiler to turn off stack protector
reading the programs’s memory mapping we can we see that it’s stack is read/write only
and not executable.
Since scanf does not performs bound checking , we can control by EIP by overwriting return address of the function to point to some known location. I will try to point it to
grant(). we can obtain address of
grant using objdump
It should look like
Not let’s modify this function’s return address to
This should spawn a shell.
Well obviously most program will not call shell for you this easily, we need to modify program a little to be more realistic
using previous exploit we get
But how do we call
When disassembled our code looks like
let’s look briefly what each instruction does.
In exploitable we call grant() using
call instruction which does two things, it pushes next address that is
0x0804851b to the stack and changes
EIP to the address
0x080484cb which is where grant() is located.
This is function prolouge. It sets up stack frame for current function. It saves base pointer of stack of previous stack frame by pushing it and then changes current base pointer to stack pointer ($ebp = $esp). Now grant() can use it’s stack to store variables and whatnot.
After that it allocates space on stack for local variables by subtracting from esp (since stack grows down) and finally pushes address
on the stack before calling
system() which is pointer to string which will be passed as argument to
system() , It somewhat looks like
This is called as function calling convention in x86. After
system() returns stack is restored using leave which does opposite of function prolouge that is
RET pops the value from top of the stack into EIP which is saved return address of the function
We’ve seen how stack behaves when function is called , which means
- We can construct our own stack frames
- Control parameters to a function we jump to
- Decide where this function returns to
- If we control the stack between these two we can control return function’s parameters too
- Repeating this lets us chain multiple function
from objdump we see that address of “/bin/sh” is
we’ll return to
system() by modifying return address of
exploitable() and construct “fake” stack frame for our function
system(), the stack will look like
So , when exploitable() returns it goes to system() which will see return address as
41414141 and argument as “/bin/sh”, which will spawn a shell but when it returns
it will pop
41414141 to EIP but as expected will segfault but if it were a valid address we can chain them up as long as they do not need parameters. So , to exploit it