6. Bypassing NX with Ret2Libc

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:

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.

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

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:

Calculating the difference.

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

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.

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:

Running the binary:

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

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.

Last updated

Was this helpful?