QEMU-Pwn-XNUCA-2019-vexx

 

描述

官方给了下载链接,然后一段描述给了用户名和密码:

user: root
pass: goodluck
Try to escape the QEMU world!

压缩包下载下来,看目录:

$ ll
-rw-r--r-- 1 raycp raycp 4.2M Aug  6 01:42 bzImage
-rwxr-xr-x 1 raycp raycp  228 Aug 23 19:04 launch.sh
drwxr-xr-x 6 raycp raycp 4.0K Aug  6 01:42 pc-bios
-rwxr-xr-x 1 raycp raycp  58M Aug  6 01:42 qemu-system-x86_64
-rw-r--r-- 1 raycp raycp  60M Aug  6 01:42 rootfs.ext2

launch.sh内容:

#!/bin/sh
./qemu-system-x86_64 -hda rootfs.ext2 -kernel bzImage -m 64M -append "console=ttyS0 root=/dev/sda oops=panic panic=1" -L ./pc-bios -netdev user,id=mynet0 -device rtl8139,netdev=mynet0 -nographic -device vexx -snapshot

根据参数-device vexx,多半是要去找vexx里面的漏洞。

 

分析

sudo ./launch.sh把虚拟机跑起来,然后将qemu-system-x86_64拖进IDA里面。

在函数里面搜索vexx,查看相关函数:

查看vexx_class_init函数,知道了vendor_id0x11E91234realize函数是pci_vexx_realize

# lspci
00:01.0 Class 0601: 8086:7000
00:04.0 Class 00ff: 1234:11e9
...
# cat /sys/devices/pci0000:00/0000:00:04.0/resource
0x00000000febd6000 0x00000000febd6fff 0x0000000000040200
0x00000000febd0000 0x00000000febd3fff 0x0000000000140204
...
# cat /proc/iomem
...
  febd0000-febd3fff : 0000:00:04.0
...
  febd6000-febd6fff : 0000:00:04.0
...

通过命令知道了该设备有两个MMIO地址空间,一个地址为0xfebd0000,大小为0x4000;另一个则地址为0xfebd6000,大小为0x1000

去看pci_vexx_realize函数:

void __fastcall pci_vexx_realize(VexxState *pdev, Error_0 **errp)
{
  ...
  if ( !msi_init(&pdev->pdev, 0, 1u, 1, 0, errp) )
  {
    timer_init_full(&v2->vexxdma.dma_timer, 0LL, QEMU_CLOCK_VIRTUAL, 1000000, 0, (QEMUTimerCB *)vexx_dma_timer, v2); //注册 vexx_dma_timer
    ...
    memory_region_init_io(&v2->mmio, &v2->pdev.qdev.parent_obj, &vexx_mmio_ops, v2, "vexx-mmio", 0x1000uLL);  //注册大小为0x1000的mmio
    memory_region_init_io(&v2->cmb, &v2->pdev.qdev.parent_obj, &vexx_cmb_ops, v2, "vexx-cmb", 0x4000uLL);        //注册大小为0x4000的mmio
    portio_list_init(&v2->port_list, &v2->pdev.qdev.parent_obj, vexx_port_list, v2, "vexx"); 
    v3 = pci_address_space_io(&pdev->pdev);
    portio_list_add(&v2->port_list, v3, 0x230u); //添加pmio,端口为0x230,信息在vexx_port_list结构体中
    pci_register_bar(&pdev->pdev, 0, 0, &v2->mmio);
    pci_register_bar(&pdev->pdev, 1, 4u, &v2->cmb);
  }

可以看到相应的存在两个mmio空间一个pmio空间,接下来具体去分析几个io函数。

先看vexx_mmio_ops中的vexx_mmio_read以及vexx_mmio_write。这个结构对应的mmio地址是0xfebd6000,空间大小为0x1000。这两个函数没啥作用,基本上就是对dma进行的操作。需要知道的是在vexx_mmio_write里面addr为0x98可以触发dma_timer。漏洞不在这里,想对dma有进一步了解,可以去看之前写的htib2017的babyqemu的writeup

然后是vexx_cmb_ops中的vexx_cmb_read以及vexx_cmb_writevexx_cmb_read关键代码如下:

uint64_t __fastcall vexx_cmb_read(VexxState *opaque, hwaddr addr, unsigned int size)
{
  uint32_t memorymode; // eax
  uint64_t result; // rax

  memorymode = opaque->memorymode;
  if ( memorymode & 1 )
  {
    result = 0xFFLL;
    if ( addr > 0x100 )
      return result;
    LODWORD(addr) = opaque->req.offset + addr;
    goto LABEL_4;
  ...
LABEL_4:
    result = *(_QWORD *)&opaque->req.req_buf[(unsigned int)addr];
  }
  return result;

req.req_buf的定义为如下:

00000000 VexxRequest     struc ; (sizeof=0x108, align=0x4, copyof_4574)
00000000                                         ; XREF: VexxState/r
00000000 state           dd ?
00000004 offset          dd ?
00000008 req_buf         db 256 dup(?)
00000108 VexxRequest     ends

可以看到当opaque->memorymode为1的时候,如果我们可以控制req.offset就可以实现对req.req_buf的越界读。

再看vexx_cmb_write函数关键代码:

void __fastcall vexx_cmb_write(VexxState *opaque, hwaddr addr, uint64_t val, unsigned int size)
{
  uint32_t memorymode; // eax
  hwaddr v5; // rax

  memorymode = opaque->memorymode;
  if ( memorymode & 1 )
  {
    if ( addr > 0x100 )
      return;
    LODWORD(addr) = opaque->req.offset + addr;
    goto LABEL_4;
  }
  ...
LABEL_4:
    *(_QWORD *)&opaque->req.req_buf[(unsigned int)addr] = val;
}

同理我们可以控制req.offset就可以实现对req.req_buf的越界写。

如果可以控制req.offset的话,我们可以越界读写什么:

00000000 VexxState       struc ; (sizeof=0x1CF0, align=0x10, copyof_4575)
00000000 pdev            PCIDevice_0 ?
000008E0 mmio            MemoryRegion_0 ?
000009D0 cmb             MemoryRegion_0 ?
00000AC0 port_list       PortioList_0 ?
00000B00 thread          QemuThread_0 ?
00000B08 thr_mutex       QemuMutex_0 ?
00000B38 thr_cond        QemuCond_0 ?
00000B70 stopping        db ?
00000B71                 db ? ; undefined
00000B72                 db ? ; undefined
00000B73                 db ? ; undefined
00000B74 addr4           dd ?
00000B78 fact            dd ?
00000B7C status          dd ?
00000B80 irq_status      dd ?
00000B84 memorymode      dd ?
00000B88 req             VexxRequest ?   //req结构体
00000C90 vexxdma         VexxDma ?      // Vexxdma结构体
00001CF0 VexxState       ends
00000000 ; ---------------------------------------------------------------------------
00000000
00000000 VexxDma         struc ; (sizeof=0x1060, align=0x8, copyof_4573)
00000000                                         ; XREF: VexxState/r
00000000 state           dd ?
00000004                 db ? ; undefined
00000005                 db ? ; undefined
00000006                 db ? ; undefined
00000007                 db ? ; undefined
00000008 dma             dma_state ?
00000028 dma_timer       QEMUTimer_0 ?
00000058 dma_buf         db 4096 dup(?)
00001058 dma_mask        dq ?
00001060 VexxDma         ends
00001060
00000000 ; ---------------------------------------------------------------------------
00000000
00000000 dma_state       struc ; (sizeof=0x20, align=0x8, copyof_4571)
00000000                                         ; XREF: VexxDma/r
00000000 src             dq ?
00000008 dst             dq ?
00000010 cnt             dq ?
00000018 cmd             dq ?
00000020 dma_state       ends
00000020
00000000 ; ---------------------------------------------------------------------------
00000000
00000000 QEMUTimer_0     struc ; (sizeof=0x30, align=0x8, copyof_1099)
00000000                                         ; XREF: VexxDma/r
00000000 expire_time     dq ?
00000008 timer_list      dq ?                    ; offset
00000010 cb              dq ?                    ; offset
00000018 opaque          dq ?                    ; offset
00000020 next            dq ?                    ; offset
00000028 attributes      dd ?
0000002C scale           dd ?
00000030 QEMUTimer_0     ends

可以看到req结构体后面紧跟的是VexxDma结构体,看到该结构体中存在QEMUTimer结构体,因为qwb 2018 final里面也出现过痛过覆盖QEMUTimer来实现逃逸的题,所以瞬间看到了希望。

接下来要搞定的就是看下req.offset是否可控以及能否将opaque->memorymode设置为1。

最后还剩下vexx_port_list结构体中的vexx_ioport_readvexx_ioport_write没有分析,相关线索应该也会在它们中。

vexx_ioport_read函数会返回req.offset等参数。关键的是vexx_ioport_write函数:

void __fastcall vexx_ioport_write(VexxState *opaque, uint32_t addr, uint32_t val)
{
  if ( addr - 0x230 <= 0x20 )
  {
    switch ( addr )
    {
      case 0x240u:
        opaque->req.offset = val; //设置req.offset
        break;
      case 0x250u:
        opaque->req.state = val;
        break;
      case 0x230u:
        opaque->memorymode = val; //设置opaque->memorymode
        break;
    }
  }
}

可以看到该函数正好满足了我们的需求,当访问的端口是0x240的时候可以设置req.offset;当端口addr0x230的时候可以设置opaque->memorymode

至此漏洞就比较明显了,利用vexx_ioport_write设置req.offset以及opaque->memorymode。然后利用vexx_cmb_readvexx_cmb_writereq.req_buf进行越界读写,通过QEMUTimer来实现泄漏与利用。

 

利用

整个利用包含三个部分。

第一部分是为了能够触发漏洞代码,需要设置opaque->memorymode以及req.offset,这一步可以通过PMIO调用vexx_ioport_write函数实现。

第二部分是泄露。由于程序开了PIE,所以需要泄露地址。可以通过越界读取req.req_buf后面QEMUTimer结构体中的opaque指针来泄露堆地址(opaque指针刚好也是VexxState对应的那个指针),可以通过读写QEMUTimer结构体中的cb指针来泄露程序基址(cb指针对应的是vexx_dma_timer函数的地址),指针如下图所示。

$ checksec qemu-system-x86_64
[*] '/home/raycp/work/vm_escape/release/qemu-system-x86_64'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
    FORTIFY:  Enabled

第三部分就是控制程序执行流。在vexx_mmio_write触发timer的代码流程中,存在一个函数调用链:timer_mod->timer_mod_ns->timerlist_notify->notify_cb(notify_opaque),可以控制执行流程。即将timer结构体中的cb覆盖为system plt的地址;将cat ./flag写入到req_buf中,利用堆偏移计算出req_buf的地址,再将该地址覆盖到timer结构体的opaque处。在最后控制执行流的时候实现system("cat ./flag")的调用。

最终执行前结构体被覆盖内容如下:

真正在写exp的时候有个坑点:

  1. 不知道为啥在访问PMIO的时候,不能用outl指令,只能用outwoutb指令,而且用outw指令也会变成一个字节一个字节写,可能是和这个设备有关系,对pci设备还是不太了解,需要进一步学习。

 

小结

还是要有系统的概念会更好一些。

相关文件和脚本链接

(完)