虚拟机逃逸漏洞(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虚拟系统实现的逃逸。

 

 

参考来源(原作者):https://zhuanlan.zhihu.com/p/27733895?utm_medium=social&utm_source=wechat_timeline&from=timeline&isappinstalled=1

posted @ 2022-01-08 22:52  sftsgly  阅读(13664)  评论(0编辑  收藏  举报