# dcquals 2019 babyheap

We see that we are given a libc file and a binary. Let's take a look at them:
```
$    file babyheap
babyheap: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/l, BuildID[sha1]=afa4d4d076786b1a690f1a49923d1e054027e8e7, for GNU/Linux 3.2.0, stripped
$    pwn checksec babyheap
[*] '/Hackery/pod/modules/tcache/dcquals19_babyheap/babyheap'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
    FORTIFY:  Enabled
$    ./babyheap
-----Yet Another Babyheap!-----
[M]alloc
[F]ree
[S]how
[E]xit
------------------------
Command:
> M
Size:
> 25
Content:
> 15935728
-----Yet Another Babyheap!-----
[M]alloc
[F]ree
[S]how
[E]xit
------------------------
Command:
> F
(Starting from 0) Index:
>
0
-----Yet Another Babyheap!-----
[M]alloc
[F]ree
[S]how
[E]xit
------------------------
Command:
> S
(Starting from 0) Index:
> 0
Show Error
```

## Reversing

So we can see that we are given a 64 bit binary with all of the standard binary mitigations. When we run it, we see that we are prompted with a menu. With this menu we can malloc memory, free it, and show it. To identify the version of libc, you should be able to just run the libc file (depending on your environment, this may not work). You can also use strings to ID it:

```
$    strings libc.so | grep libc-
libc-2.29.so
```

So we can see that it is running `libc-2.29.so`. With this version of libc, we will have to deal with the tcache mechanism. When we take a look at the binary in Ghidra, we don't see a `main` function labeled for us. However, looking through the functions (or checking xreferences) to strings we find this function which looks like the function which handles the menu:

```
/* WARNING: Could not reconcile some variable overlaps */

void FUN_0010151b(void)

{
  ulong uVar1;
  long in_FS_OFFSET;
  undefined8 local_108;
  undefined8 local_100;
  undefined8 local_f8;
  undefined8 local_f0;
  undefined2 local_e8;
  undefined local_e6;
  undefined8 local_e5;
  undefined8 local_dd;
  undefined8 local_d5;
  undefined8 local_cd;
  undefined2 local_c5;
  undefined local_c3;
  undefined8 local_c2;
  undefined8 local_ba;
  undefined8 local_b2;
  undefined8 local_aa;
  undefined2 local_a2;
  undefined local_a0;
  undefined8 local_9f;
  undefined8 local_97;
  undefined8 local_8f;
  undefined8 local_87;
  undefined2 local_7f;
  undefined local_7d;
  undefined8 local_7c;
  undefined8 local_74;
  undefined8 local_6c;
  undefined8 local_64;
  undefined2 local_5c;
  undefined local_5a;
  undefined8 local_59;
  undefined8 local_51;
  undefined8 local_49;
  undefined8 local_41;
  undefined2 local_39;
  undefined local_37;
  undefined2 menuOption;
  undefined8 local_30;
 
  local_30 = *(undefined8 *)(in_FS_OFFSET + 0x28);
  menuOption = 0;
  local_108 = 0x7465592d2d2d2d2d;
  local_100 = 0x726568746f6e4120;
  local_f8 = 0x[PII_REDACTED]614220;
  local_f0 = 0x2d2d2d2d2d2170;
  local_e8 = 0;
  local_e6 = 0;
  local_e5 = 0x636f6c6c615d4d5b;
  local_dd = 0x20;
  local_d5 = 0;
  local_cd = 0;
  local_c5 = 0;
  local_c3 = 0;
  local_c2 = 0x206565725d465b;
  local_ba = 0;
  local_b2 = 0;
  local_aa = 0;
  local_a2 = 0;
  local_a0 = 0;
  local_9f = 0x20776f685d535b;
  local_97 = 0;
  local_8f = 0;
  local_87 = 0;
  local_7f = 0;
  local_7d = 0;
  local_7c = 0x207469785d455b;
  local_74 = 0;
  local_6c = 0;
  local_64 = 0;
  local_5c = 0;
  local_5a = 0;
  local_59 = 0x2d2d2d2d2d2d2d2d;
  local_51 = 0x2d2d2d2d2d2d2d2d;
  local_49 = 0x2d2d2d2d2d2d2d2d;
  local_41 = 0;
  local_39 = 0;
  local_37 = 0;
  do {
    puts((char *)&local_108);
    puts((char *)&local_e5);
    puts((char *)&local_c2);
    puts((char *)&local_9f);
    puts((char *)&local_7c);
    puts((char *)&local_59);
    __printf_chk(1,"Command:\n> ");
    read(0,&menuOption,2);
    if ((char)menuOption == 'F') {
      uVar1 = freeMemory();
    }
    else {
      if ((char)menuOption < 'G') {
        if ((char)menuOption != 'E') {
          uVar1 = 0xfffffffe;
          break;
        }
        uVar1 = 0xffffffff;
      }
      else {
        if ((char)menuOption == 'M') {
          uVar1 = mallocSpace();
        }
        else {
          if ((char)menuOption != 'S') goto LAB_00101799;
          uVar1 = showSpace();
        }
      }
    }
  } while ((int)uVar1 == 0);
  do {
    errorPrint(uVar1 & 0xffffffff);
LAB_00101799:
    uVar1 = 0xfffffffe;
  } while( true );
}
```

Looking at this function, it looks like a pretty standard menu function for ctf challenges. When we take a look at the `mallocSpace` function, we see this:

```
/* WARNING: Globals starting with '_' overlap smaller symbols at the same address */

undefined8 mallocSpace(void)

{
  long lVar1;
  long *plVar2;
  ulong size;
  void *largePtr;
  void *smallPtr;
  undefined8 result;
  ulong i;
  uint uVar3;
  uint sizeCpy;
  long in_FS_OFFSET;
  bool check;
  char inputChar;
 
  lVar1 = *(long *)(in_FS_OFFSET + 0x28);
  if (_pointers == 0) {
    uVar3 = 0;
  
  else {
    uVar3 = 1;
    plVar2 = &DAT_00104070;
    while (*plVar2 != 0) {
      uVar3 = uVar3 + 1;
      plVar2 = plVar2 + 2;
    }
    if (9 < uVar3) {
      result = 0xfffffffd;
      goto LAB_001013ae;
    }
  
  __printf_chk(1,"Size:\n> ");
  size = getLong();
  if ((int)size - 1U < 0x178) {
    sizeCpy = (uint)(size & 0xffffffff);
    if (sizeCpy < 0xf9) {
      smallPtr = malloc(0xf8);
      *(void **)(&pointers + (ulong)uVar3 * 0x10) = smallPtr;
    }
    else {
      largePtr = malloc(0x178);
      *(void **)(&pointers + (ulong)uVar3 * 0x10) = largePtr;
    }
    if (*(long *)(&pointers + (ulong)uVar3 * 0x10) == 0) {
      result = 0xfffffffd;
    }
    else {
      *(uint *)(&sizes + (ulong)uVar3 * 0x10) = sizeCpy;
      __printf_chk(1,"Content:\n> ");
      read(0,&inputChar,1);
      i = 0;
      do {
        if ((inputChar == '\n') || (inputChar == '\0')) {
          result = 0;
          goto LAB_001013ae;
        }
        *(char *)(*(long *)(&pointers + (ulong)uVar3 * 0x10) + i) = inputChar;
        read(0,&inputChar,1);
        check = (size & 0xffffffff) != i;
        i = i + 1;
      } while (check);
      result = 0;
    }
  
  else {
    result = 0xfffffffd;
  
LAB_001013ae:
  if (lVar1 != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  
  return result;
}
```

So in this function, we see that it prompts us for a size. We can see that we can only allocate two size chunks, `0xf8` and `0x178`. After that it allows us to scan in content into the chunk equal to `size` number of bytes. Thing is, this gives us a single byte overflow. Since arrays are zero index, if we get to scan in data to index `0xf8` that gives us `0xf9` bytes worth of data to scan into a `0xf8` byte chunk (it should scan in `size - 1` bytes to prevent the bug). In addition to that it saves a pointer to the chunk in the bss in `pointers` (`0x104060`), and the size o