虚拟机逃逸漏洞(CVE-2017-4901)的研究以及复现
前言:
这个项目是我在2019年和黎同学申请的南京邮电大学STITP(大学生创新训练计划)项目。
在将近一年的紧凑时间内,我和黎同学在老师以及学长的帮助下,从立项开始一点点地开始学习如何复现这个当时还十分火热的CVE。
由于当时还没有系统学习网安专业知识,研究时间也是在课余时间里挤出来的,因此会有一些仓促,但好在有了老师和学长的指导,最终成功进行了复现并提出了一些改进方法。
由于已经难以找到当时的报告以及部分研究调试数据,为此我根据最终答辩时的PPT制作了这个核心思路总结概述。
总结的过程有一些简陋,如果有所缺漏请指出,谢谢。
正文:
1、 后门(BACKDOOR)接口
这个接口使得在用户态就可以通过这个接口发送命令和host通信函数中含有一个”inl”指令会导致用户态程序产生权限错误,该错误会被host的hypervisor捕捉从而实现通信,类似于一个错误中断。
void Backdoor_InOut(Backdoor_proto *myBp) // IN/OUT { uint64 dummy; __asm__ __volatile__( #ifdef __APPLE__ /* * Save %rbx on the stack because the Mac OS GCC doesn't want us to * clobber it - it erroneously thinks %rbx is the PIC register. * (Radar bug 7304232) */ "pushq %%rbx" "\n\t" #endif "pushq %%rax" "\n\t" "movq 40(%%rax), %%rdi" "\n\t" "movq 32(%%rax), %%rsi" "\n\t" "movq 24(%%rax), %%rdx" "\n\t" "movq 16(%%rax), %%rcx" "\n\t" "movq 8(%%rax), %%rbx" "\n\t" "movq (%%rax), %%rax" "\n\t" "inl %%dx, %%eax" "\n\t" /* NB: There is no inq instruction */ "xchgq %%rax, (%%rsp)" "\n\t" "movq %%rdi, 40(%%rax)" "\n\t" "movq %%rsi, 32(%%rax)" "\n\t" "movq %%rdx, 24(%%rax)" "\n\t" "movq %%rcx, 16(%%rax)" "\n\t" "movq %%rbx, 8(%%rax)" "\n\t" "popq (%%rax)" "\n\t" #ifdef __APPLE__ "popq %%rbx" "\n\t" #endif : "=a" (dummy) : "0" (myBp) /* * vmware can modify the whole VM state without the compiler knowing * it. So far it does not modify EFLAGS. --hpreg */ : #ifndef __APPLE__ /* %rbx is unchanged at the end of the function on Mac OS. */ "rbx", #endif "rcx", "rdx", "rsi", "rdi", "memory" ); }
2、 RPCI(Remote Procedure Call Interface)
基于Backdoor机制实现,通过这个机制,guest可以向host发送请求完成某些操作,例如拖放(Drag n Drop)和复制黏贴(Copy Paste),RPCI依据info-get guestinfo.ip获取guest的ip地址,这是通过套接字实现的。
3、 漏洞
类似于过去在Version4中所出现的漏洞,在Version3中也同样存在,当guest发送DnD/CP数据包时,host会重组guest发送的DnD/CP消息。关键问题在于,函数只检查了包头的buffer长度,对后续的数据包检查无效,因此可以在后续的包中指定更大的binarySize来满足检查,触发溢出。
4、 漏洞利用执行方法
我们需要在堆上覆盖一个函数指针,或者破坏C++的虚表指针
(1)首先将DnD/CP协议设置为version 3,RPCI在检查到DnD/CP协议版本修改时会创建一个对应版本的C++对象,对于version3,两个C++对象会被创建,一个用于DnD,一个用于CP
tools.capability.dnd_version 3 tools.capability.copypaste_version 3 vmx.capability.dnd_version vmx.capability.copypaste_version
(2)分配一个内存块,让它分配在C++对象前,利用堆溢出改写C++对象的vtable指针(虚表),让其指向可控内存,执行SHELL
5、 绕过ASLR
在实现上述过程首先要绕过保护机制ASLR(地址空间布局随机化),关键在于找到能够破坏的,带有长度或数据指针的对象,并且可以被guest读取。于是找到了“info-set”和”info-get”
info-set guestinfo.KEY VALUE info-get guestinfo.KEY
我们可以通过溢出来覆盖结尾的null字节,让字符串连接上相邻的内存块。如果我们能够在发生溢出的内存块和DnD或CP对象之间分配一个字符串,那么我们就能泄露对象的vtable地址,从而我们就可以知道vmware-vmx的地址。
问题在于,这种覆盖可能导致关键函数被覆盖从而崩溃(卡到母系统有时候都一卡一卡的,每次运行都是一种折磨,虚拟机甚至有时候都要重装)
具体实现策略:
1.首先分配一些填满“A”的字符串,
2.然后通过溢出写入一些“B”,
3.接下来读取所有分配的字符串,其中含有“B”的就是被溢出的字符串。
4.这样我们就找到了一个字符串可以被用来读取泄露的数据,然后以bucket的内存块大小0xA8的粒度继续溢出,每次溢出后都检查泄露的数据。
5.由于DnD和CP对象的vtable距离vmware-vmx基地址的偏移是固定的,每次溢出后只需要检查最低一些数据位,就能够判断溢出是否到达了目标对象。
这个是原作者的图,我觉得十分形象所以拿过来解释一下
执行流程图
6、 两种情形,两种方法
(1)DND
不能只覆盖vtable指针,需要伪造一个vtable来躲避访问
(2)CP
覆盖虚指针,让它指向我们可控的其他数据,这些数据可以用作对象虚表,只要我们找到一个指向可控数据的指针就可以解决
7、 如何解决LFH堆问题
LFH堆一旦开启便无法关闭,但是使用一些全局标志可以导致无法启用LFH(gflags调试)
按照微软百科的描述,当gflags中某些调试项出现时会导致LFH堆无法正常开启
我们当时是找到了一些和pageheap有关的调试选项可以导致上述现象的发生,但是具体参数我们来不及找出来了(这还是我们后期找出来的)
但我们并不确定这些“理想条件”是否能直接利用,因为毕竟它是在调试工具中,并不在代码中实现,时至今日我们也只是有一些皮毛理解。
所以我们当初给出的是一个解决方案,而不是一个具体的代码实现。
8、 例图
由于我们之前执行获得的结果截图没有保存,因此这里使用的是原作者的例图。
我们使用的是WINXP虚拟系统实现的逃逸。