QEMU虚拟化逃逸学习之:WCTF2019 VirtualHole
字数 1321 2025-08-27 12:33:42

QEMU虚拟化逃逸漏洞分析:WCTF2019 VirtualHole详解

0. 前言

本文详细分析WCTF2019线下赛题目"VirtualHole",这是一个QEMU虚拟化逃逸漏洞的典型案例。通过这个案例,我们可以学习虚拟化逃逸的基本原理和利用方法。

1. 环境准备

1.1 QEMU编译与安装

  1. 从QEMU官网下载qemu-3.1.0-rc5版本
  2. 替换包含漏洞的megasas.c文件
  3. 安装依赖:
    sudo apt-get install -y zlib1g-dev libglib2.0-dev autoconf libtool libgtk2.0-dev
    sudo apt install qemu-kvm
    
  4. 编译配置(开启kvm和debug模式):
    ./configure --enable-kvm --target-list=x86_64-softmmu --enable-debug
    
  5. 安装:
    sudo make & make install
    

1.2 网络配置

修改宿主机网络配置(以Ubuntu 16.04为例):

  1. 编辑/etc/network/interfaces,添加br0配置:
    auto lo
    iface lo inet loopback
    
    auto br0
    iface br0 inet dhcp
    bridge_ports ens33
    bridge_stp off
    bridge_maxwait 0
    bridge_fd 0
    
  2. 重启宿主机网络
  3. 修改虚拟机静态IP与宿主机同网段
  4. 启动虚拟机命令:
    sudo qemu-system-x86_64 -m 2048 -hda Centos7-Guest.img --enable-kvm -device megasas -net tap -net nic
    

1.3 调试配置

  1. 查找QEMU进程:
    ps aux | grep qemu
    
  2. 附加调试:
    sudo gdb -p $PID
    

注意:Ubuntu 16.04上可能需要重新编译最新版gdb或编译时去掉PIE选项。

2. 漏洞分析

2.1 漏洞位置

漏洞位于megasas.c文件中的megasas_quick_read函数:

void megasas_quick_read(mainState *mega_main, uint32_t addr) {
    uint16_t offset;
    uint32_t buff_size, size;
    data_block *block;
    void *buff;
    
    struct {
        uint32_t offset;
        uint32_t size;
        uint32_t readback_addr;
        uint32_t block_id;
    } reader;
    
    pci_dma_read(mega_main->pci_dev, addr, &reader, sizeof(reader));
    offset = reader.offset;
    size = reader.size;
    block = &Blocks[reader.block_id];
    buff_size = (size + offset + 0x7) & 0xfff8;
    
    if(!buff_size || buff_size < offset || buff_size < size){
        return;
    }
    
    if(!block->buffer){
        return;
    }
    
    buff = calloc(buff_size, 1);
    
    if(size + offset >= block->size){
        memcpy(buff + offset, block->buffer, block->size);
    } else {
        memcpy(buff + offset, block->buffer, size);
    }
    
    pci_dma_write(mega_main->pci_dev, reader.readback_addr, buff + offset, size);
    free(buff);
}

2.2 漏洞原理

漏洞成因在于对size + offsetblock->size的判断不正确:

  1. size + offset >= block->size时,会复制block->size字节
  2. size + offset < block->size时,会复制size字节

这种判断逻辑会导致memcpy操作可能发生堆溢出,因为复制的数据量可能超过目标缓冲区的大小。

2.3 关键结构体

题目中定义的关键结构体frame_header

typedef struct _frame_header {
    uint32_t size;
    uint32_t offset;
    void *frame_buff;
    void (*get_flag)(void *dst);
    void (*write)(void *dst, void *src, uint32_t size);
    uint32_t reserved[56];
} frame_header;

3. 漏洞利用

3.1 利用思路

  1. 通过堆溢出覆盖frame_headersize字段
  2. 利用修改后的size读取frame_header中的敏感信息(如get_flag函数地址)
  3. 覆盖write函数指针劫持控制流

3.2 堆布局

  1. 连续分配大块内存进行占位(大小<0x80000)
  2. 释放其中一个Block
  3. 预留足够空间重新分配,为frame_headerframe_buff占位
  4. 释放特定大小的block让堆溢出的buff占位

注意:不同Linux发行版的堆分配策略可能不同,Ubuntu 16.04比18.04更容易实现预期布局。

3.3 信息泄露

构造特殊的数据结构:

struct {
    uint32_t offset;
    uint32_t size;
    uint32_t readback_addr;
    uint32_t block_id;
    uint64_t heapheader[2];
    uint32_t hsize;
    uint32_t hoffset;
} *reader = kzalloc(0x1000, GFP_KERNEL);

reader->offset = 0x100 - 0x40 + 0x18;
reader->size = 0x200 + 0x40 - 0x18;
reader->heapheader[0] = 0;
reader->heapheader[1] = 0x115;
reader->hsize = 0x200 + 0x310 + 0x10 + 0x20;

通过精心构造的偏移和大小,可以覆盖frame_headersize字段,从而读取到get_flag函数地址。

3.4 劫持控制流

  1. 释放原有的frame_headerframe_buff
  2. 重新分配进行占位
  3. 构造包含frame_buff地址和get_flag地址的数据
  4. 调用被覆盖的write函数指针

完整EXP可在GitHub上找到。

4. 总结

通过分析WCTF2019的VirtualHole题目,我们学习了:

  1. QEMU虚拟化逃逸的基本原理
  2. 如何通过堆溢出实现信息泄露和控制流劫持
  3. 虚拟化设备与客户机的交互方式
  4. 复杂的堆布局技巧

这个案例展示了虚拟化安全研究的基本方法,为进一步学习虚拟化安全打下了基础。

QEMU虚拟化逃逸漏洞分析:WCTF2019 VirtualHole详解 0. 前言 本文详细分析WCTF2019线下赛题目"VirtualHole",这是一个QEMU虚拟化逃逸漏洞的典型案例。通过这个案例,我们可以学习虚拟化逃逸的基本原理和利用方法。 1. 环境准备 1.1 QEMU编译与安装 从QEMU官网下载qemu-3.1.0-rc5版本 替换包含漏洞的megasas.c文件 安装依赖: 编译配置(开启kvm和debug模式): 安装: 1.2 网络配置 修改宿主机网络配置(以Ubuntu 16.04为例): 编辑 /etc/network/interfaces ,添加br0配置: 重启宿主机网络 修改虚拟机静态IP与宿主机同网段 启动虚拟机命令: 1.3 调试配置 查找QEMU进程: 附加调试: 注意:Ubuntu 16.04上可能需要重新编译最新版gdb或编译时去掉PIE选项。 2. 漏洞分析 2.1 漏洞位置 漏洞位于 megasas.c 文件中的 megasas_quick_read 函数: 2.2 漏洞原理 漏洞成因在于对 size + offset 和 block->size 的判断不正确: 当 size + offset >= block->size 时,会复制 block->size 字节 当 size + offset < block->size 时,会复制 size 字节 这种判断逻辑会导致 memcpy 操作可能发生堆溢出,因为复制的数据量可能超过目标缓冲区的大小。 2.3 关键结构体 题目中定义的关键结构体 frame_header : 3. 漏洞利用 3.1 利用思路 通过堆溢出覆盖 frame_header 的 size 字段 利用修改后的 size 读取 frame_header 中的敏感信息(如 get_flag 函数地址) 覆盖 write 函数指针劫持控制流 3.2 堆布局 连续分配大块内存进行占位(大小 <0x80000) 释放其中一个Block 预留足够空间重新分配,为 frame_header 和 frame_buff 占位 释放特定大小的block让堆溢出的buff占位 注意:不同Linux发行版的堆分配策略可能不同,Ubuntu 16.04比18.04更容易实现预期布局。 3.3 信息泄露 构造特殊的数据结构: 通过精心构造的偏移和大小,可以覆盖 frame_header 的 size 字段,从而读取到 get_flag 函数地址。 3.4 劫持控制流 释放原有的 frame_header 和 frame_buff 重新分配进行占位 构造包含 frame_buff 地址和 get_flag 地址的数据 调用被覆盖的 write 函数指针 完整EXP可在GitHub上找到。 4. 总结 通过分析WCTF2019的VirtualHole题目,我们学习了: QEMU虚拟化逃逸的基本原理 如何通过堆溢出实现信息泄露和控制流劫持 虚拟化设备与客户机的交互方式 复杂的堆布局技巧 这个案例展示了虚拟化安全研究的基本方法,为进一步学习虚拟化安全打下了基础。