« Back to home

Revisiting PlaidCTF - bigpicture

During the PlaidCTF challenge, there were a couple of binaries which, whilst I had a general idea of how the vulnerability worked, I couldn't complete in time to grab a flag.

This has been bugging me since the end of the CTF, so I wanted to revisit one such challenge and see what I was missing. That challenge was "bigpicture".

To begin with, you are given a simple description:

Size matters! 
Running at bigpicture.chal.pwning.xxx:420

After reviewing some of the solutions to see where I went wrong, I wanted to take the opportunity to improve my binary exploit knowledge by attempting to exploit the vulnerability for myself.

Within the challenge download, you are provided a number of files, including:

  • bigpicture - An ELF file of the compiled challenge running on the CTF server.
  • bigpicture.c - The C source to the challenge.
  • libc-2.23.so - A compiled version of libc which is also running on the server.

Let's take a look at the source code to see where the vulnerability may be:

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

int width, height;
char *buf;

void plot(int x, int y, char c);
void draw();

int main() {
    ...
    puts("Let's draw a picture!");
    fputs("How big? ", stdout);
    scanf(" %d x %d", &width, &height);
    buf = calloc(width, height);
    ...
}

To begin with, we control the allocation size used by calloc, which will come in handy later. Once a size is provided, we are prompted to provide data to add to our image with the following code:

int main() {
...
  while(1) {
    fputs("> ", stdout);
    int x, y;
    if(scanf(" %d , %d , %c", &x, &y, &c) != 3)
        break;
    plot(x, y, c);
  }
...
}

char *get(size_t x, size_t y) {
    return &buf[x * height + y];
}

void plot(int x, int y, char c) {
    if(x >= width || y >= height) {
        puts("out of bounds!");
        return;
    }

    char *ptr = get(x, y);
    if(*ptr != 0)
        printf("overwriting %c!\n", *ptr);
    else
        *ptr = c;
    }
}

Immediately the part that stood out to me was the use of "int" data types, and the comparison that is completed on the inputted parameters.

This issue allows a negative offset to be provided by the user to the plot function. This negative value is then used to retrieve a pointer to the buf allocated memory via the get method. Using a negative offset will of course allow us to reference memory below the allocated heap.

Additionally, upon reviewing the plot function further, we see that we have the ability to leak memory when we attempt to overwrite a non-NULL byte:

char *ptr = get(x, y);
if(*ptr != 0)
    printf("overwriting %c!\n", *ptr);

Unfortunately during the CTF, this is kind of where things became stuck. The key part I was missing... __free_hook.

__free_hook is a symbol exported from libc, which allows a user defined function to be called when the free call is used.

Knowing this, let's go back and attempt to exploit this challenge.

First we want to know where __free_hook will be in memory when loaded by the bigpicture binary:

It appears that __free_hook is loaded from the heap of libc, shown as "mapped" in the above screenshot.

Next, we need to manipulate our application's heap to be above the libc heap, so we can overwrite the hook. After some trial and error, using a image size of 1 x 1000000 appears to place our "buf" pointer above the libc heap:

So in the above example, our buf allocation is at 0x7ffff7efe010. Knowing that __free_hook is found at 0x7ffff7dd57a8, we know that there is an offset of -0x128868 bytes between our buf pointer and the __free_hook function.

We can test that to see if we are right:

Let's draw a picture!
How big? 1 x 1000000
> 0,-1214568,A
> quit

Brilliant, we have our 'A' written to __free_hook and have control over the RIP register.

The next part of the puzzle is figuring out where to jump to with our hook overwrite. The obvious place is libc's system call, however as the process memory is likely different on the PlaidCTF host, we'll need to leak some values to defeat ASLR.

To do this, we find a number of pointers within the .bss section of libc which point to the .text section of libc. I finally settled on the pointer found at .bss+0x30:

Leaking this pointer, we know that libc's base can be calculated via 0x7ffff7a2f806-0x1F806.

Having the base address of libc, we can then find the address of system which we can overwrite __free_hook with.

Thankfully the PlaidCTF servers were still available at the time of writing this post, so I was able to test the exploit:

The final exploit code can be found below: