QEMU虚拟化逃逸学习之:WCTF2019 VirtualHole
字数 1321 2025-08-27 12:33:42
QEMU虚拟化逃逸漏洞分析:WCTF2019 VirtualHole详解
0. 前言
本文详细分析WCTF2019线下赛题目"VirtualHole",这是一个QEMU虚拟化逃逸漏洞的典型案例。通过这个案例,我们可以学习虚拟化逃逸的基本原理和利用方法。
1. 环境准备
1.1 QEMU编译与安装
- 从QEMU官网下载qemu-3.1.0-rc5版本
- 替换包含漏洞的megasas.c文件
- 安装依赖:
sudo apt-get install -y zlib1g-dev libglib2.0-dev autoconf libtool libgtk2.0-dev sudo apt install qemu-kvm - 编译配置(开启kvm和debug模式):
./configure --enable-kvm --target-list=x86_64-softmmu --enable-debug - 安装:
sudo make & make install
1.2 网络配置
修改宿主机网络配置(以Ubuntu 16.04为例):
- 编辑
/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 - 重启宿主机网络
- 修改虚拟机静态IP与宿主机同网段
- 启动虚拟机命令:
sudo qemu-system-x86_64 -m 2048 -hda Centos7-Guest.img --enable-kvm -device megasas -net tap -net nic
1.3 调试配置
- 查找QEMU进程:
ps aux | grep qemu - 附加调试:
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 + offset和block->size的判断不正确:
- 当
size + offset >= block->size时,会复制block->size字节 - 当
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 利用思路
- 通过堆溢出覆盖
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 信息泄露
构造特殊的数据结构:
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_header的size字段,从而读取到get_flag函数地址。
3.4 劫持控制流
- 释放原有的
frame_header和frame_buff - 重新分配进行占位
- 构造包含
frame_buff地址和get_flag地址的数据 - 调用被覆盖的
write函数指针
完整EXP可在GitHub上找到。
4. 总结
通过分析WCTF2019的VirtualHole题目,我们学习了:
- QEMU虚拟化逃逸的基本原理
- 如何通过堆溢出实现信息泄露和控制流劫持
- 虚拟化设备与客户机的交互方式
- 复杂的堆布局技巧
这个案例展示了虚拟化安全研究的基本方法,为进一步学习虚拟化安全打下了基础。