# 0ctf 2016 - Zerostorage

## Static Analysis

First, we will understand how the binary functions and see what sort of constraints we will have to face. To begin, let's see what type of type of file this is and what mitigations it holds.

```
➜  zerostorage file zerostorage
zerostorage: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/l, for GNU/Linux 2.6.24, BuildID[sha1]=93c36d63b011f873b2ba65c8562c972ffbea10d9, stripped
➜  zerostorage checksec zerostorage
[*] '/home/vagrant/pwning/0ctf16/zerostorage/zerostorage'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
    FORTIFY:  Enabled
```

As can be seen, this is a 64-bit ELF and there are all mitigation techniques. RELRO is full so the global offset table will not be exploitable, and PIE is enabled so we should look for a leak before exploiting.

Now let's look at this binary in Ghidra. The main function is located at address 100c40, and this contains a menu of options. 

```
  puts("== Zero Storage ==");
  puts("1. Insert");
  puts("2. Update");
  puts("3. Merge");
  puts("4. Delete");
  puts("5. View");
  puts("6. List");
  puts("7. Exit");
  puts("==================");
  __printf_chk(1,"Your choice: ");
```

This menu function shows that we have a few standard options for a heap exploitation challenge. We can create, delete, edit, and view chunks. Also, we can merge chunks, which I have never seen before. Additionally we can list chunks and exit, so let's explore a few important functions to see what happens.

Before this menu is printed, a function at 00100f20 is called. This function sets buffering for stdin and stdout, and it also sets an alarm (common practice for CTF challenges). Additionally, /dev/urandom is opened and 8 bytes are read into a global variable. For now we can call this variable GLOBAL_KEY, and we will see how this is used later.

```
  setvbuf(stdin,(char *)0x0,2,0);
  setvbuf(stdout,(char *)0x0,2,0);
  alarm(0x3c);
  __stream = fopen("/dev/urandom","rb");
  if (__stream != (FILE *)0x0) {
    bytes_read = fread(&GLOBAL_KEY,1,8,__stream);
    if (bytes_read == 8) {
      fclose(__stream);
      return;
    }
  }
```

### Insert

The insert function is located at 00100fd0. Here, we can see that the size of our chunk must be between 0x80 and 0x1000. With the metadata, that means we can only make heaps of sizes at least 0x90. This means that we cannot use fastbins, so we will have to find another exploitation type. Also, we can see that the pointers are xored with a global key before they are stored in the binary, so it may be hard to exploit these. Luckily, we will be able to use the unsorted bin, so we will look into that attack after the static analysis.

```
      if (0 < entry_length) {
        intermediate_len = 0x1000;
        if (entry_length < 0x1001) {
          intermediate_len = entry_length;
        }
        final_len = 0x80;
        if (0x7f < intermediate_len) {
          final_len = intermediate_len;
        }
        chunk_ptr = calloc((long)final_len,1);
```

After this, the pointer to the chunk and size are saved into some special global variables. I labelled these arrays as follows:

```
          enc_ptr = (ulong)chunk_ptr ^ GLOBAL_KEY;
          (&IN_USE)[lVar2 * 6] = 1;
          (&CHUNK_SIZES)[lVar2 * 3] = (long)intermediate_len;
          (&ENC_PTR)[lVar2 * 3] = enc_ptr;
          NUM_CHUNKS = NUM_CHUNKS + 1;
```

### Delete

The delete function is located at 00101530. This delete function free's the heap pointer at the index of choice, and it also zeros out this pointer before returning. This should be effective for preventing a use after free, so this function does not appear to be exploitable. Additionally, it makes sure that the chunk is in use before freeing it, so this prevents double frees.

```
  if ((uint)heap_index < 0x20) {
    index = (long)(int)(uint)heap_index;
    if ((&IN_USE)[index * 6] == 1) {
      enc_ptr = (&ENC_PTR)[index * 3];
      (&IN_USE)[index * 6] = 0;
      (&CHUNK_SIZES)[index * 3] = 0;
      NUM_CHUNKS = NUM_CHUNKS + -1;
      free((void *)(enc_ptr ^ GLOBAL_KEY));
      (&ENC_PTR)[index * 3] = 0;
      __printf_chk(1,"Entry %d is successfully deleted.\n",heap_index & 0xffffffff);
      return;
    }
  }
```

### View

The view function (00101600) show's the size of bytes from the heap pointer. This function could possibly be used to get an info leak later on, but we still have not found an exploitable bug that could let us use a free heap.

```
  entry_num = get_choice();
  if ((entry_num < 0x20) && (this_entry = (long)(int)entry_num, (&IN_USE)[this_entry * 6] == 1)) {
    __printf_chk(1,"Entry No.%d:\n",(ulong)entry_num);
    print_buffer((&ENC_PTR)[this_entry * 3] ^ GLOBAL_KEY,(&CHUNK_SIZES)[this_entry * 3]);
    puts("");
    return;
  }
```

### Merge

The merge function (located at 001012c0) has two different possible code paths. If the combined size of the two chunks is the same as either chunk or less than 0x80, a new index is created but the merge to poinnter is used. However, if they are different, a realloc is performed to create a new chunk. Then, the merge from chunk is freed. This is a problem because if the two indeces are the same, the chunk will be freed and the newly created chunk will point to this freed region. This let's us use and view a free chunk, which is perfect! 

```
              if (total_size_final != to_size_final) {
                to_ptr = realloc(to_ptr,total_size_final);
                if (to_ptr == (void *)0x0) {
                  fwrite("Memory Error.\n",1,0xe,stderr);
                    /* WARNING: Subroutine does not return */
                  exit(-1);
                }
                from_size = (&CHUNK_SIZES)[from_index * 3];
                to_size = (&CHUNK_SIZES)[to_index * 3];
              }
              memcpy((void *)((long)to_ptr + to_size),
                     (void *)(GLOBAL_KEY ^ (&ENC_PTR)[from_index * 3]),from_size);
              key = GLOBAL_KEY;
              new_index = (long)(int)this_index;
              (&ENC_PTR)[new_index * 3] = (ulong)to_ptr ^ GLOBAL_KEY;
              from_ptr = (&ENC_PTR)[from_index * 3];
              (&IN_USE)[new_index * 6] = 1;
              (&CHUNK_SIZES)[new_index * 3] = total_size;
              (&IN_USE)[from_index * 6] = 0;
              (&CHUNK_SIZES)[from_index * 3] = 0;
              free((void *)(key ^ from_ptr));
```

In order to satisfy this condition, the combination of to and from sizes should be less than 0x80 large. This should be easy to create because we could create two chunks of size 0x20 to merge together. While they would be stored as a size 0x80, the saved size in the global array would be 0x20. Also, we could even merge a chunk with itself to make this more simple, because there is no check that the indeces are different.

### Update

The update function, located at 00101120, lets you create a new