Monday, April 8, 2013

ASLR and mapped libraries

I learned something new recently.

While working on an exploit for a challenge ('postit-hardened' at http://treasure.pwnies.dk/), I had to find out how libc was mapped in an ASLR environment and I were pleasantly surprised (from an attackers viewpoint).

The service is running in a 32 bit environment, so I started by gathering statistics on how well libc was randomized. I did that with the following C program:

#include <stdio.h>
#include <string.h>

long libc_mapping() {
    long address = 0;
    char buffer[1024] = {0};
    FILE * f = fopen("/proc/self/maps", "r");
    if (f) {
        while (fgets(buffer, sizeof(buffer) - 1, f)) {
            if (strstr(buffer, "libc") && strstr(buffer, "r-xp")) {
                sscanf(buffer, "%lx", &address);
                break;
            }
        }
        fclose(f);
    }
    return address;
}

int main(int argc, char const *argv[]) {
    printf("%ld", libc_mapping());
    return 0;
}
It simply looks up the address where libc is loaded for itself by looking into '/proc/self/maps'. We are interested in the executable mapping.

Together with this shell script we can get some statistics:
#!/bin/bash

comp_addr=0
aslr_bitmap=0

while true; do
    addr=$(./libc_addr)
    if [ "${comp_addr}" == "0" ]; then
        comp_addr=${addr}
    else
        diff=$((comp_addr^addr))
        aslr_bitmap=$((aslr_bitmap|diff))
        printf "%x\n" $aslr_bitmap
    fi
done

It runs the program repeatedly and finds a bitwise difference between the current mapping and the first mapping. Then it writes out all changed bits.

Runing this for a short while we get this mask on my Ubuntu machine: 3ff000

This means that only 10 bits are randomized giving us 1024 possible locations for libc. That is very brute forcable!

Just for fun I tried the same with 64 bits and got this mask: 1fffffff000
That is 29 bits which is more than 500 million possible locations. Much better (from a defender viewpoint).

BUT! Another thing...the service used fork to spawn a new process to handle our connection. How is libc randomized when the process is forked?

Lets see.

I modified the program to fork out and write the address of libc mapped into the child:

#include <stdio.h>
#include <string.h>
#include <unistd.h>

long libc_mapping() {
    long address = 0;
    char buffer[1024] = {0};
    FILE * f = fopen("/proc/self/maps", "r");
    if (f) {
        while (fgets(buffer, sizeof(buffer) - 1, f)) {
            if (strstr(buffer, "libc") && strstr(buffer, "r-xp")) {
                sscanf(buffer, "%lx", &address);
                break;
            }
        }
        fclose(f);
    }
    return address;
}

int main(int argc, char const *argv[]) {
    pid_t pid;
    int i, status;

    printf("Parent: %lx\n", libc_mapping());

    for (i = 1; i <= 10; i++) {
        pid = fork();
        if (pid) {
            waitpid(pid, &status, 0);
        } else {
            printf("Child %d: %lx\n", i, libc_mapping());
            return;
        }
    }

    return 0;
}


This is a sample run:
$ ./forked_libc_addr
Parent: f758a000
Child 1: f758a000
Child 2: f758a000
Child 3: f758a000
Child 4: f758a000
Child 5: f758a000
Child 6: f758a000
Child 7: f758a000
Child 8: f758a000
Child 9: f758a000
Child 10: f758a000

Again I tried the same on a 64 bit machine with the same result:
$ ./forked_libc_addr
Parent: 7f1953193000
Child 1: 7f1953193000
Child 2: 7f1953193000
Child 3: 7f1953193000
Child 4: 7f1953193000
Child 5: 7f1953193000
Child 6: 7f1953193000
Child 7: 7f1953193000
Child 8: 7f1953193000
Child 9: 7f1953193000
Child 10: 7f1953193000
So...when forking libc (and all other mappings) stay.

This makes perfect sense when you think about it. The process is cloned, all memory must be intact. How should the operating system know, if your program have pointers into libc which needs updating?

It cannot, so libraries must stay. This is very interesting especially for my exploit since the program has an info leak vulnerability (as well as some others) but when I have received the info the connection is cut. So I have to make a new connection.

But since the info I want is the location of libc this apparently is not a problem because the info is still valid.

WIN!!!