zoukankan      html  css  js  c++  java
  • vmware漏洞之二——简评:实战VMware虚拟机逃逸漏洞

    下文取自360,是vmware exploit作者自己撰写的。本文从实验角度对作者的文章进行解释,有助于学习和理解。文章虚线内或红色括号内为本人撰写。

    ----------------------------------------------------------------

    转:http://bobao.360.cn/learning/detail/4143.html

    作者:skyer

    0x00 前言


    最近长亭把Pwn2Own中遗憾的在比赛前一天被补上的漏洞利用发了出来,Amat大佬的博客有这篇文章,同时在长亭知乎专栏有杨博士发的中文版。 但是并没有公开的exp,如何真正实现呢?自己花了十几天才写出exp,其中踩坑无数,本着分享精神,于是就有了本文。

    0x01 Backdoor


    backdoor是vmware实现的一套Guest和Host通信的机制,我们不需要去深入研究这种机制如何实现的,只需要大概了解一下这个机制的实现。 先看通信的代码,这部分代码在open-vm-tools的github上也有,链接在此。由于需要在VS中编译,所以需要先转换成为intel的asm格式。

    http://p1.qhimg.com/t01a8f3046a1c9e56b9.png

    在正常操作系统中直接执行in指令会导致出错,因为这是特权指令。但是在Guest中这个错误会被vmware捕获,然后传给vmware-vmx.exe进程内部进行通信。 

    而后面我们需要操作的message,全部通过backdoor通信方式来通信。 关于message的操作,open-vm-tools里面也有相关实现,链接在此。直接拿过来用就行了。 有了Message_Send和Message_Recv这些函数,我们就可以直接在Guest里面与Host进程进行通信。 需要注意的是Backdoor通信在Guest内部不需要管理员权限,所以此bug可在普通用户触发。

    0x02 Drag and Drop RPCI


    RPCI是基于backdoor实现的通信方式。open-vm-tools相关实现在此。可以直接使用这个发送RPC的函数。 这个漏洞存在在DnD操作的v3版本代码中,对应bug代码在此。

    ida中更加明显: 

    http://p0.qhimg.com/t01c75c211912d49386.png

    ----------------------------------------------------------------------------------

    个人分析情况:

    char __fastcall my_TransportBufAppendPacket_4F0540(__int64 a1, struct_v5 *a2, unsigned __int64 a3)
    {
    struct_v3 *v3; // rbx@1
    __int64 v4; // rcx@1
    struct_v5 *v5; // rdi@1 v5为发送的包。//IDA直接分析时没有结构体,需要右键自动创建
    int v6; // eax@3
    unsigned int v7; // edx@3
    __int64 v8; // rax@9
    __int64 v9; // rcx@10
    char result; // al@11

    v3 = (struct_v3 *)a1;
    v4 = a2->payload_size;
    v5 = a2;
    if ( a3 != v4 + 20 )
      goto failed;
    if ( a3 > 0xFF9C )
      goto failed;
    v6 = a2->offset;
    v7 = a2->totalsize;
    if ( v6 + (signed int)v4 > v7 || v7 > 0x3FFFF3 )
      goto failed;
    if ( v3->seq != v5->seq )
      sub_4F0430(v3);
    if ( !v3->buffer ) // 第一次分配。
    {
      if ( v5->offset )
        goto failed;
      v3->buffer = my_malloc_3D4A90(v5->totalsize);// 以totalsize为准
      v3->totalsize = v5->totalsize;
      v8 = v5->seq;
      v3->offset = 0i64;
      v3->seq = v8;
    }
    v9 = v3->offset;
    if ( v9 == v5->offset )
    {
      memcpy((char *)v3->buffer + v9, &v5->payload, v5->payload_size);// 用户更改binarysize为很大。
      result = 1;
      v3->offset += v5->payload_size;
      return result;
    }
    failed:
      free(v3->buffer);
      v3->buffer = 0i64;
      v3->seq = 0i64;
      v3->totalsize = 0i64;
      v3->offset = 0i64;
      v3->qword20 = 0i64;
      return 0;
    }

    ----------------------------------------------

    由于没有realloc或者totalsize的判断,导致第二个包的totalsize可以改成一个大值,payloadsize因此也可以变大导致一个堆溢出。

    顺带一提,发送DnD操作的命令在dndCPTransportGuestRpc.hpp中。 通过阅读open-vm-tools的代码,可以得出RPC的发送对应路径: 

    rpcv3util::SendMsg->DnDCPTransportGuestRpc::SendPacket->RpcChannel_Send->Message_Send->backdoor

    0x03逆向分析


    看完相关的open-vm-tools的代码之后,开始逆向vmware-vmx.exe,我的版本是12.5.2.13578,workstation是12.5.2-build4638234版本。 

    首先很容易通过字符串“tools.capability.dnd_version”的xref找到对应的处理函数。

    my_rpcicmd_func_map_8A140()

     http://p3.qhimg.com/t012527b12791d74463.png

    bindfun只是把对应的参数值写入了全局变量,其实是一个表。bindfun参数4就是对应rpc命令的处理函数,而rpc命令函数的参数3和参数4分别是我们发送的RPC原始request和RPCrequest的长度。参数5和参数6是我们得到的 reply的地址和reply的长度。 

    http://p4.qhimg.com/t0156d1580181144a54.png

    可以看出这个命令有一个参数,也就是版本号。

    其他的RPC命令类似,在发送“vmx.capability.dnd_version”命令的时候,对应的处理函数中如果发现当前版本和设置的版本不一致,就会调用函数创建新的 object,把原来的版本的object销毁。

    my_vmx_capability_dnd_83EE0

     http://p0.qhimg.com/t01910513930f85086e.png

     http://p5.qhimg.com/t0195e752837dc0ee40.png

    DnD和CP的Object的size都是一样的,都是0xa8大小。vmware_vmx+9C470

     http://p0.qhimg.com/t01a4992c613558749b.png

    0x04 漏洞利用


    Amat大佬的文章中推荐用info-set和info-get来操作堆,其中info-set对应的handle函数内部很复杂,通过windbg动态调试,可以发现我们发送“info-set guestinfo.test1 “+’a'*0xa7可以创建一个0xa8大小的buffer。实际测试我在malloc和free下断点,整个info-set过程大概有10-13次malloc(size=0xa8),也有 接近10次的free操作,最终剩下一个buffer也就是说整个info-set过程干扰很大

     info-get可以读取刚刚set的值,这就没什么好说。 关于windows的LFH的风水,由于info-set中有多次malloc 0xa8操作,所以比较困难。我没有什么好的办法,目前我exp成功率还是比较低。 

    思路大概就是把内存变成这个样子:

    http://p0.qhimg.com/t01befb96211e1c4980.png

    如果一旦没有布局成功。。vmware-vmx就会崩溃。。。 

    如果你正好挂了windbg调试器。那么整个host就会其卡无比(暂时无法解决。Ollydbg,immunity只适合于32位。vmware12只有64位。不过linux环境下用gdb调试居然不卡!)。未知bug。只能缓慢的对windbg调试器按q退出调试。 

    推荐安装windbg的pykd插件,大爱python。 我写了个小脚本用来辅助调试:(其实就是打印rax)--(也可以直接用windbg打印)

    1
    2
    3
    4
    5
    6
    from pykd import *
    import sys
    s=''
    if len(sys.argv)>1:
    s=sys.argv[1]+' '
    print s+'Object at '+hex(reg('rax'))

    所以就可以在attach上vmx进程的时候这么输入:

    1
    2
    3
    bp 7FF7E394C4D8 "!py dumprax DnD;gc;";bp 7FF7E394BF68 "!py dumprax 
    CP;gc;";bp 7FF7E3DA05AB "!py dumprax vuln;gc;";bp 7FF7E3DA05DB;bp 
    7ff7e38c1b2d;bp 7ff7`e38f1dc2;g

    第一个地址是DnD Object malloc完毕后的下一条指令,第二个地址是CP Object的,第三个是vuln的,第四个地址是memcpy触发的地方,后面两个是gadget地址。

    因为windows中进程重启后基地址还是不会变,所以只要你不重启电脑,可以一直用。

    通过一些布局(运气)变成了如上的内存之后,就可以开始leak了。

    主要是通过覆盖info-set的value buffer,修改value buffer内部的值,如果此时info-get读取的valuebuffer值不同,那就说明被覆盖了。

    而如果溢出到了Object头部,从info-get读取的信息就会包含vtable的地址,从而泄露出程序基地址。

    当然这个过程中有可能触发RtlHeapFree等堆函数然。。因为堆chunk头被覆盖,理所当然崩溃。。。

    --------------------------------------------------------------------------

    目前来看成功率越为30%左右。不过跟环境可能有关系,一次将虚拟机大小设为2G,双核,命中率极低;将大小改为512MB,单核,命中率达到30%。

    在ASLR完成后,可以通过"s -a 0 L8000000 "exploit test"来定位dnd/cp对象。

    -------------------------------------------------------------------------- 

    0x05 DnD Object 覆盖


    如果覆盖的是DnD Object,那么在DnD_TransportBufAppendPacket(vmware_vmx+4F0540函数结束之后的上层函数(vmware_vmx+4FE960)会立刻发生调用。

    http://p4.qhimg.com/t015a03b1d4a70c6a2d.png

    所以在这之前,需要先在一块内存布局好vtable,原文推荐使用“unity.window.contents.chunk” 命令,这个RPC命令会把我们的参数复制进去data段上一个堆指针内部。

    这个全局变量指针由命令“unity.window.contents.start” 创建。

    这两个unity的命令。有反序列化操作而且没有官方文档可以看,只能自己慢慢debug,摸索出对应的结构。。具体的结构请看文章末尾的Github代码。

    call之后,首先需要一个stack pivot到堆上,然后就是愉快的ROP。

    需要说明的是,vmware中的data段居然是rwx的。。直接复制shellcode上去就能执行了。(用!address查看地址熟悉,内存布局

    http://p9.qhimg.com/t01d1109d08dff01e3a.png

    具体的ROP见文章末尾的Github代码。

    ----------------------------------------------------------

    断点参考:由于无法单步跟踪,只有通过r,kb指令打印信息。

    ba r8 vmware_vmx+0xb87100 ".echo chunk_addr;r;kb;g"//chunk地址,全局固定偏移
    bp vmware_vmx+0x11b2d ".echo stack_piovt;kb;g"//栈迁移
    bp vmware_vmx+0x69220 ".printf "msg:%ma\n",@r8;dc r8 L10;.echo;g"//vmware_vmx接收的rpci命令
    bp vmware_vmx!opus_repacketizer_get_nb_frames+0x44fb40 ".echo appendbuf;kb;g"//appendbuf:my_DnD_TransportBufAppendPacket_4F0540,包拼接
    bp vmware_vmx!opus_repacketizer_get_nb_frames+0x44fbdb ".echo datacopy;r r8;dc rdx;dc rcx;kb;g"//call appendbuf->memcpy;+4F05DB,包拷贝情况
    ba r8 04d2c670 ".echo datacopynow;kb;gu;dq 04d2c670;g"//开始数据拷贝,04d2c670是dnd对象。
    bp vmware_vmx!opus_repacketizer_get_nb_frames+0x45e00b ".echo call0;r rbx;kb;g"//分析调用rop前,vtable如何跳转
    bp vmware_vmx!opus_repacketizer_get_nb_frames+0x45e01c ".echo call1;r;kb;g"//vtable调用

    调用的函数过程(数据拷贝)

    1.vmware_vmx接收rpci命令

    2.my_rpci_command_check_69220对rpci进行检查,是否是支持的命令

    3.sub_9D5F0?

    4.my_finish_DnD_TransportBufAppendPacket_4FE960//函数5结束后,调用

    .text:00000004FEA0B mov rcx, [rbx+8] ; r rbx
    .text:00000004FEA0F mov r8, [rsp+28h+Memory]
    .text:00000004FEA14 mov r9, rax
    .text:00000004FEA17 mov r10, [rcx] ; r rcx
    .text:00000004FEA1A xor edx, edx
    .text:00000004FEA1C call qword ptr [r10+10h] ; 这里发生虚函数调用。调用伪造的虚表。

       dnd+0x38存储chunk的地址,全局变量:base+0xb87118;->chunk内容:0460f1f0;->Stackpiovt_Gadget:base+0x11b2d(poi(0460f1f0+10))

    5.my_DnD_TransportBufAppendPacket_4F0540

    6.memcpy//dnd.transport传输的包,拷贝。

    ----------------------------------------------------------

    0x06 CopyPaste Object 覆盖


    如果覆盖的是CP Object,那么覆盖掉vtable之后,vmx进程不会崩溃,原文推荐使用cp命令触发vtable调用,而我用了这个Object的destructor。也就是再把版本设 置回4的话,程序会调用vtable中对应的destructor. 

    通过上面提到的”unity.window.contents.start“命令可以设置一个qword大小的gadget在程序的数据段上,而之前已经通过leak得到了程序的基地址,所 以可以得到这个gadget的指针的地址。

    这个点不是特别好用,寄存器的值不是很方便,但最终依然找到了合适的gadget来利用。详细ROP见文章末尾Github 代码。

    -------------------

    个人调试附图:cp destructor调用过程

    需要设置的断点

    ba r8 03c4cf50 ".echo access_cp;r;kb;g"//在访问cp对象时触发。cp地址可以用s -a 0 L8000000 "exploit test"后确定。
    bp vmware_vmx+0x33700 ".echo getchunk_stackpiovt;dq 0399b090 l40;r;kb;g"//栈迁移命令。已经调用cp虚表,进入rop.
    ba r8 vmware_vmx+0xb87100 ".echo accesschunkaddr;"//访问chunkaddr,固定的全局地址。
    bp vmware_vmx!opus_repacketizer_get_nb_frames+0x437120 ".echo ropnow;r;kb;g"//rop地址
    bp vmware_vmx+0x69220 ".printf "msg:%ma\n",@r8;dc r8;g"//vmware_vmx接收的所有rpci消息。
    bp vmware_vmx!opus_repacketizer_get_nb_frames+0x44fbdb ".echo datacopy;dc rdx;dc rcx;r;kb;g"//my_TransportBufAppendPacket_4F0540,进行数据拷贝,发生溢出的地方。

    -------------------

    0x07 最后说两句


    这个漏洞能不能稳定利用,关键在于堆布局做的怎么样,这个方面我研究不多。。以后还得继续看。长亭在这种情况能达到60-80%的成功率,太厉害了。 

    该漏洞在VMware Workstation 12.5.5之后被修补。

    如果文章中有任何错误请在评论指出,谢谢各位表哥。

    完整EXP:点我

  • 相关阅读:
    1094. Car Pooling
    121. Best Time to Buy and Sell Stock
    58. Length of Last Word
    510. Inorder Successor in BST II
    198. House Robber
    57. Insert Interval
    15. 3Sum java solutions
    79. Word Search java solutions
    80. Remove Duplicates from Sorted Array II java solutions
    34. Search for a Range java solutions
  • 原文地址:https://www.cnblogs.com/studyskill/p/7279956.html
Copyright © 2011-2022 走看看