12558网页游戏私服论坛

 找回密码
 立即注册
游戏开服表 申请开服
游戏名称 游戏描述 开服状态 游戏福利 运营商 游戏链接
攻城掠地-仿官 全新玩法,觉醒武将,觉醒技能 每周新区 经典复古版本,长久稳定 进入游戏
巅峰新版攻 攻城掠地公益服 攻城掠地SF 新兵种、新武将(兵种) 进入游戏
攻城掠地公 散人玩家的天堂 新开 进入游戏
改版攻城掠 上线即可国战PK 稳定新区 全新改版,功能强大 进入游戏
少年江山 高福利高爆率 刚开一秒 江湖水落潜蛟龙 进入游戏
太古封魔录 开服送10亿钻石 福利多多 不用充钱也可升级 进入游戏
神魔之道 签到送元宝 稳定开新区 送豪华签到奖励 进入游戏
神奇三国 统帅三军,招揽名将 免费玩新区 激情国战,征战四方 进入游戏
龙符 三日豪礼领到爽 天天开新区 助你征战无双 进入游戏
王者之师 免费领豪华奖励 免费玩新区 6元送6888元宝 进入游戏
三国霸业 战车-珍宝-觉醒-攻城掠地SF-全新玩法 免费玩新区 攻城掠地私服 进入游戏
手游私服盒子 各类免费游戏 0.1折送海量资源 各类手游私服 进入游戏
皇家MU2 《奇迹 2:传奇》韩国网禅公司《奇迹》正统续作。 3D锁视角Mmrpg 暗黑3+传奇+流放之路+奇迹 进入游戏
查看: 1950|回复: 0

关于虚拟机的逃逸技术(转载)

[复制链接]
发表于 2019-8-10 19:06:23 | 显示全部楼层 |阅读模式
虚拟机让我们能够分享主机的资源并提供隔离。在理想的世界中,一个程序运行在虚拟机里,他应该无法影响其他虚拟机。不幸的是,由于技术的限制和虚拟化软件的一些bug,这种理想世界并不存在。在某些情况下,在虚拟机里运行的程序会绕过底层,从而利用宿主机,这种技术叫做虚拟机逃逸技术,由于宿主机的特权地位,其结果是整个安全模型完全崩溃。这也就是说,你在虚拟机上测试病毒、恶意软件,这些东西如果设计好的话,就会通过虚拟机进入你的系统!!!
从虚拟机中逃逸 虚拟环境的安全威胁已由理论变为现实

  从虚拟机中逃逸,一直以来被看作类似于一种黑色操作。你不断的能听到研究人员们研究一些恶意软件样本的传言,而这些恶意软件可以从虚拟客户机逃逸到主机里。与此同时,其他研究人员也在研究一些允许攻击者从虚拟机中逃逸的漏洞。
这些有形的攻击威胁到了虚拟化项目的神圣性,而虚拟化在许多公司里相当流行,因为在服务器整合和功耗方面,它们具有很大的优势。但漏洞利用工具的数量也正在日益高涨,每一个月都会增加不少。
在2009年7月下旬的美国黑帽大会上,一些研究机构对虚拟机的这一漏洞提出了最为清楚的阐释。Immunity是一家安全评估和渗透测试的公司,它向外界提供了一个被称为Cloudburst的工具软件的详细信息,该工具由高级安全研究员Kostya Kortchinsky开发。Cloudburst目前能提供给装有Immunity的CANVAS测试工具的用户使用,它利用的是VMware Workstation 6.5.1和更早期版本的显示功能bug,而这一bug同样出现在VMware Player、服务器、Fusion、ESXi和ESX [见CVE 2009-1244,以得到确切版本编号]。
Kortchinsky 在Cloudburst的开发中有一些创新的思维,他选择利用的是虚拟机和一些设备的依赖关系(如视频适配器、软盘控制器、IDE控制器、键盘控制器和网络适配器),从而获得对主机的访问。在黑帽大会上,他向外界做了一次报告,解释了他如何利用VMware模拟视频设备的漏洞来进行攻击,他还演示了如何利用主机泄漏到客户机的内存,以及如何从客户机向主机内存中的任何位置写入任意数据。
"视频适配器处理最复杂的数据,"他说到。"它有一个特别巨大的共享内存。"
Kortchinsky说,相同的代码模拟每个VMware产品上的设备。"如果有一个漏洞存在,那么每一个VMware的产品上都存在该漏洞,而且通过I / O端口或内存映射I / O端口可以从客户机上对其进行访问"。 Immunity表示,Cloudburst具有可以破坏(corrupt)内存的能力,这允许它以隧道方式在客户机帧缓冲区(frame buffer)之上建立起与主机的MOSDEF连接,从而与主机进行通信。MOSDEF是CANVAS工具集里的漏洞利用工具,它由Immunity公司创始人Dave Aitel开发。
在今年4月10日,VMware已经修补了这些版本的漏洞。4天之后,Cloudburst发布,并被加入到CANVAS工具集中。而正是这一点使得Cloudburst与众不同,它不再是一个漏洞验证性触发代码(proof of concept),这和大多数虚拟机恶意软件不同。
文章写到这里,该安全问题技术部分的内容已经结束了,但作为一个具有购买权力和负责决策的安全经理来说,它对于你意味着什么呢?在两年前经济还没有衰退的时候,这一问题给我们的启示会更多,你会争辩说企业的支出应更多的考虑安全因素,而不是经济因素。因为安全问题可能会影响企业的IT环境,而这种影响是可以切身感受到的。
虚拟化的威胁一直是抽象的,理论多于实践。当然,目前还存在着一些不易察觉的、虚拟的rootkit技术(如Blue Pill),但这要求黑客具有对技术的理解天赋,而把一些非常复杂的东西作为攻击的工具对黑客来说似乎是不可行的。专家同时警告说,虚拟环境里有形的威胁已经出现,但对于这些理论性的东西,你仍然不可能制定企业自身战略并购买相应的虚拟化产品。你很可能需要做的就是,跟上虚拟化的趋势,因为它能带来很大的好处,让用户垂涎欲滴。而它的安全性问题将来也会随之来临。
不过,是在未来。
  针对虚拟机的攻击已从理论慢慢的变成现实。目前,已经出现了关于虚拟机逃逸的五个CVE警报,在Kortchinsky、iDefense 公司的Greg McManus以及Core Security公司研究小组工作的基础上,研究人员和其他的攻击者会继续对这一问题进行分析、研究,因此以后出现更多安全漏洞几乎是必然的事情。网络不应该依赖传统的安全措施,因为它们不能抵御每个虚拟机的威胁。到目前为止,大多数组织都在反应有关虚拟环境的安全问题,以及不断涌现的新攻击、漏洞利用程序和漏洞验证性触发代码(proof of concept)。虚拟机的安全正处于风口浪尖的境地。
1、UAF漏洞,控制RIP(大意为每条指令的位置);2、out-of-bound read 漏洞。3、out-of-bound write 漏洞。和第二个漏洞合力起到泄露关键信息的作用。
    利用这三个漏洞,可以突破虚拟机的限制,拿到系统权限和同一宿主“治下”所有其他虚拟机的数据。
很多商业银行、政府,包括美国的一些大型公司,都在使用 VMware 的产品。而这些产品和我攻击的 Workstation 内部原理基本一致。也就是说,利用这种攻击手法,同样可以攻击这些重要的机构。安全公司CrowdStrike的Jason Geffner,已经发布了一系列QEMU漏洞,通过影响虚拟机软盘驱动代 码实现让一个攻击者从虚拟机逃逸[1]到宿主机中。虽然这个漏洞已经在网络社区中引起了广泛关注——也许因为它有一个专有名称(VENOM)——但这已经不是第一次出现这种漏洞了。2011年,Nelson Elhage[2]发布并成功利用了一个在QEMU虚拟机热插拔PCI设备的漏洞。 利用过程可参见[3]。最近,奇虎360的刘旭和汪圣平在HITB 2016黑客大会上展示了KVM/QEMU的成功利用。他们在两种不同的网卡设备仿真模型RTL8139 和PCNET上,演示了两个漏洞(CVE-2015-5165 和CVE-2015-7504)的利用。在展示中,他们列举了在宿主机上运行代码的主要步骤,但没有提供任何漏洞利用或技术细节来再现逃逸过程。在本文中,我们对CVE-2015-5165(一个内存泄露漏洞)和CVE-2015-7504(一个堆溢出漏洞)进行了深度分析和利用。同时利用这两个漏洞可以实现虚拟机逃逸并在目标宿主机上运行代码。我们讨论了在QEMU的网卡设备仿真上利用这两个漏洞的技术细节,并提供了继续利用QEMU未来出现的漏洞的通用技术。比如一个用来共享内存区域和共享代码的交互式bindshell。
.KVM/QEMU 概述KVM(Kernal-based Virtual Machine)是一个为用户空间程序提供整个虚拟化基础架构的内核模型。它允许运行多个Linux或Windows映像的虚拟机。KVM的用户空间部分包含于处理主要设备仿真的主线QEMU(高速虚拟机)。 工作区环境为了方便读者使用本文中示例代码,我们在这一节给出了搭建开发环境的主要步骤。因为我们使用的漏洞已经被修补了,所以需要找到QEMU仓库的源并恢复到漏洞被修补之前。然后,我们配置QEM只支持x86_64并激活漏洞:在我们的测试环境中,使用4.9.2版本的Gcc搭建QEMU。剩下的,我们假设读者已经有一个Linux x86_64映像并可以执行以下命令:我们分配2GB内存空间并创建两个网卡: RTL8139和PCNET。我们在3.16单核x_86_64架构的Debian 7上运行QEMU。2.2 QEMU内存配置为用户分配的物理内存实际上是QEMU虚拟地址空间中的一个mmap映射的私有区域。需要指出的是,为用户分配物理内存时PROT_EXEC参数是不可用的。下图说明了用户内存和主机内存是如何共存的。
此外,QEMU为BIOS和ROM分配了一个内存区域。这些映射保存在QEMU的映射文件中:
地址转换QEMU中有两个地址转换层:
1.从用户虚拟地址转换到用户物理地址。实验中,我们需要配置DMA(Direct Memory Access,直接内存存取)方式的网卡。例如,我们需要为传送缓冲区和接收缓冲区提供物理地址来正确配置网卡。
2.从用户物理地址转换到QEMU虚拟地址空间。实验中,我们需要添加虚拟结构并得到它们在QEMU虚拟地址空间中的准确地址。在x64系统中,一个虚拟地址由一个页地址(0-11位)和一个页号构成。在linux系统中,pagemap文件允许有CAP_SYS_ADMIN特权的用户空间进程发现每个虚拟页映射到哪个物理框架。为了便于在内核中记录,pagemap文件包含了每个虚拟页的一个64位的值。参考文章[5]:为了将虚拟地址转换成物理地址,我们使用Nelson Elhage的代码[3]。下面的程序分配了一段缓冲区,用字符串“Where am I?”填充并打印其物理地址:
如果在用户模式下运行以上代码并连接gdb到QEMU进程,可以看到我们的缓冲区位于分配给用户的物理地址空间上。更确切的说,输出的地址实际上就是基于用户物理内存基地址的偏移地址。
VMware反应非常迅速,他们很快发布了一个安全补丁修复这些漏洞,同时公布了一份安全公告。根据我们在闭源软件安全问题方面的一贯做法,我们研究了一下这份公告。公告里面提到这样一段话:“VMware Workstation和Fusion的拖放(drag-and-drop,DnD)功能中存在一个越界内存访问漏洞。在运行Workstation或Fusion的操作系统上,攻击者可以利用这个漏洞实现客户机逃逸,在宿主机上执行代码。在Workstation Pro和Fusion上,如果拖放功能和复制粘贴(copy-and-paste,C&P)功能都被禁用,那么这个漏洞就无法利用”。这个漏洞存在于拖放和复制粘贴功能中。这两个功能都用到了VMware远程过程调用(remote procedure call,RPC)机制。VMware的RPC机制一直以来都是一个非常容易被攻破的点,容易实现客户机到宿主机的逃逸。在我们深入分析VMSA-2016-0019(CVE-2016-7461)的补丁之前,我们必须先对VMware Workstation如何处理客户机到宿主机或者宿主机到客户机之间的复制粘贴操作有所了解。
下图从类的层次化角度描述了VMware的拖放和复制粘贴(DnDCP)模式(来源:VM Tools开源代码):
为了无缝实现宿主机与客户机之间的拷贝粘贴操作,客户机操作系统需要安装VMware tools。VMware tools负责处理客户机到宿主机或者宿主机到客户机之间的通信。在我们研究过程中,我们使用的环境为Windows客户机以及Windows宿主机。在Windows客户机中,tools的主进程为vmtoolsd.exe。宿主机与客户机之间相互通信的一种方法是借助RPC接口。VMware使用了一种名为后门(Backdoor)的RPC接口。客户机的RPC机制让我们好好研究一下客户机与宿主机系统彼此如何通过RPC接口进行通信。为了理解客户机的RPC机制,我们参考了VMware tools的开源组件,即open-vm-tools,这个组件使用如下函数来处理客户机的RPC调用:
从理论上讲,任何用到RpcChannel_Send()或者RpcOut_send()函数的报文都可以使用rpctools.exe工具来发送,这个工具是VMWare Workstation内置的一个命令行工具。
RpcOut_Send()调用了Message_Send(),后者会调用Backdoor()函数。
Backdoor函数负责通过VMware专用的I/O端口来发送消息。
当调用Backdoor函数从客户机往宿主机发送消息时,通常情况下我们可以看到如下指令集:
安装完VMware tools后,我们可以在vmtools.dll中找到这个函数。如下图所示,我们可以看到Backdoor()正在调用sub_10050190函数:
深入研究后,我们发现这个函数会执行特权指令“in.”。让我们回到漏洞上来。我们之所以对DnDCP RPC消息感兴趣,原因是根据安全公告,该漏洞位于DnDCP RPC中。在VM Tools源码中,我们可以找到DnDCP RPC消息的具体结构:
从源码中,我们可知该结构体的第一个成员为RPC命令。如果我们研究客户机中vmtoolsd.exe的vmtools!RpcOut_send(x,x,x,)函数,我们也会看到相同的信息。[table][tr][td]1[/td][td]Bool RpcOut_send(RpcOut *out, charconst*request, size_treqLen,charconst**reply, size_t*repLen);RpcOut_Send()函数的第二个参数是request-RPC报文。如果我们从客户机操作系统的vm-tools进程中导出相关报文,在数据报文中我们首先可以看到一个RPC命令(也就是DnDCPMsgHrV4结构中的第一个成员),我们也可以看到一个复制粘贴请求报文,这个报文与我们放在客户机桌面上的debasish.txt测试文件有关。
客户机中RPC报文的处理过程我们来看看宿主机操作系统如何处理RPC请求。在宿主机中,正在运行的每个虚拟机都有一个独立的进程,进程名为vmware-vmx.exe。当客户机发起RPC请求时,vmware-vmx.exe内部的代码会搜索客户机的RPC处理表,找到与请求对应的处理程序。如果我们使用IDA Pro反汇编vmware-vmx.exe,从中查找原始字符串信息,我们可以找到大部分处理程序。
漏洞分析根据以上信息,我们可知vmware-vmx.exe是宿主机上的主要组件,负责处理存在漏洞的复制粘贴RPC组件。接下来,我们从二进制角度对比了已打补丁和未打补丁的程序。VMware Workstation从12.5.2版本起就打上了漏洞补丁,因此我们从二进制角度对比了12.5.2版和12.5.1版的vmware-vmx.exe之间的区别。我们发现补丁修改了vmware-vmx.exe中的几个函数,如下图所示。
其中比较有趣的一个函数是vmware_vmx!sub_140621520。
这个函数之所以会引起我们的注意,原因在于该函数内部调用了memcpy()函数,这个地方是触发越界漏洞的完美场景。经过一番调试及逆向分析后,我们确认vmware_vmx!sub_140621520函数负责处理RPC报文,并且我们在客户机系统中可以控制该函数的某个参数。该参数为指向某个结构体的一个指针,因此我们得以控制传入的结构体的具体内容。具体情况如下图所示。左图表示的是客户虚拟机,右图表示我们已将windbg附加到vmware_vmx.exe进程上。
如上图所示,我们在vmtoolsd.exe进程运行期间修改了一个RPC报文,随后该报文被vmware-vmx.exe进程中的vmware_vmx!sub_140621520函数接收。让我们反编译已打上补丁的函数,看看函数添加了哪些代码来解决这个问题。
为了能够发送一个有效的RPC报文,我们参考了VM Tools的源码,源码中定义了RPC报文的具体结构。RPC报文头部结构如下图所示,我们可知该报文头部大小为0x38。
binarySize以及payloadSize字段对应的是反汇编代码中的v6及v5变量。我们可以控制这两个字段的值,触发越界访问漏洞。为了能从客户机往宿主机发送任意RPC报文,我们开发了一个独立工具,该工具利用Backdoor函数,可以实现从客户机往宿主机发送RPC报文。经过完整的逆向分析后,我们发现利用存在漏洞的这个函数,可以实现vmware-vmx.exe进程中的越界读取及写入目标。越界读取根据前文分析,我们可以控制payloadSize字段。在发送报文时,如果我们使用了一个以非常大的payloadSize,同时又没有分配payload缓冲区,那么当程序执行到memcpy()时,就会越界读取其他一些内存数据
如上图所示,程序会拷贝从0x36E4D96到0x378A0D0之间0x500个字节长度的数据。然而,我们自己的数据在0x36E4DB7处的0x4C那就已经结束了,0x36E4DB7之后的数据会导致越界读取漏洞。越界写入如果RPC消息中包含多个报文,那么sub_1406215F0就会被调用。
为了在这个函数中实现越界写入,我们需要在一个会话内发送多个RPC报文,然后vmware-vmx会创建一个新的缓冲区,将所有报文的载荷结合在一起。经过全面的逆向分析实验后,我们最终发现,我们可以从客户机往宿主机发送包含如下特征的RPC报文,实现越界写入目标。首先,我们需要发送带有如下特征的一个拖放RPC报文:
1、packet->binarySize为0x10000。2、packet->payloadOffset为0x0。3、packet->payloadSize为0x500。
通过这些选项,我们就能通过前面所有的检查条件,这些条件包括:
1、packetSize大于DND_CP_MSG_HEADERSIZE_V4。2、packet->payloadSize小于0xFF64。3、packet->binarySize小于0x400000。4、packet->payloadOffset + packet->payloadSize < packet->binarySize。
程序将会创建一个新的缓冲区,将我们所有的报文载荷复制到缓冲区中。接下来,我们需要发送另一个报文,该报文使用相同的会话ID,具体特征为:
1、packet->binarySize为0x10100。2、packet->payloadOffset为0x500。3、packet->payloadSize为0xFC00。这些选项依然满足过滤条件,这些条件包括:1、packetSize大于DND_CP_MSG_HEADERSIZE_V4。2、packet->payloadSize小于0xFF64。3、packet->binarySize小于0x400000。4、packet->payloadOffset + packet->payloadSize < packet->binarySize。
由于这个报文与第一个报文的会话ID相同,并且程序已经分配了一个新的缓冲区,因此程序会继续将载荷数据复制到当前缓冲区中(因为0x500 + 0xFC00的值等于0x10100,不等于0x10000)。这样就会导致程序越界写入0x100字节大小的数据。
上图显示了vmware-vmx.exe进程在越界写入之前的内存状态。
上图显示了vmware-vmx.exe进程在越界写入之后的内存状态,其中0x40E3070处的内存为新的缓冲区尾部(0x10000)之后的内存。我们发送完第二个报文后,成功实现将0x100字节大小的数据覆盖了位于0x40E3070处的内存数据。总结本文简要介绍了VMware Workstation中的RPC机制,也分析了如何利用RPC中的漏洞实现从客户机系统到宿主机系统的虚拟机逃逸。在之后的一系列文章中,我们将详细分析漏洞利用的每个步骤,并向大家演示如何组合这些漏洞,在VMware中实现完整的虚拟机逃逸。

案例一(利用一个堆溢出漏洞实现VMware虚拟机逃逸)

1. 介绍
2017年3月,长亭安全研究实验室(Chaitin Security Research Lab)参加了Pwn2Own黑客大赛,我作为团队的一员,一直专注于VMware Workstation Pro的破解,并成功在赛前完成了一个虚拟机逃逸的漏洞利用。(很不)幸运的是,就在Pwn2Own比赛的前一天(3月14日),VMware发布了一个新的版本,其中修复了我们所利用的漏洞。在本文中,我会介绍我们从发现漏洞到完成利用的整个过程。感谢@kelwin在实现漏洞利用过程中给予的帮助,也感谢ZDI的朋友,他们近期也发布了一篇相关博客,正是这篇博文促使我们完成本篇writeup。本文主要由三部分组成:首先我们会简要介绍VMware中的RPCI机制,其次我们会描述本文使用的漏洞,最后讲解我们是如何利用这一个漏洞来绕过ASLR并实现代码执行的。
2. VMware RPCI机制VMware实现了多种虚拟机(下文称为guest)与宿主机(下文称文host)之间的通信方式。其中一种方式是通过一个叫做Backdoor的接口,这种方式的设计很有趣,guest只需在用户态就可以通过该接口发送命令。VMware Tools也部分使用了这种接口来和host通信。我们来看部分相关代码(摘自open-vm-tools中的lib/backdoor/backdoorGcc64.c):
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)         */        &quot;pushq %%rbx&quot;           &quot;\n\t&quot;#endif        &quot;pushq %%rax&quot;           &quot;\n\t&quot;        &quot;movq 40(%%rax), %%rdi&quot; &quot;\n\t&quot;        &quot;movq 32(%%rax), %%rsi&quot; &quot;\n\t&quot;        &quot;movq 24(%%rax), %%rdx&quot; &quot;\n\t&quot;        &quot;movq 16(%%rax), %%rcx&quot; &quot;\n\t&quot;        &quot;movq  8(%%rax), %%rbx&quot; &quot;\n\t&quot;        &quot;movq   (%%rax), %%rax&quot; &quot;\n\t&quot;        &quot;inl %%dx, %%eax&quot;       &quot;\n\t&quot;  /* NB: There is no inq instruction */        &quot;xchgq %%rax, (%%rsp)&quot;  &quot;\n\t&quot;        &quot;movq %%rdi, 40(%%rax)&quot; &quot;\n\t&quot;        &quot;movq %%rsi, 32(%%rax)&quot; &quot;\n\t&quot;        &quot;movq %%rdx, 24(%%rax)&quot; &quot;\n\t&quot;        &quot;movq %%rcx, 16(%%rax)&quot; &quot;\n\t&quot;        &quot;movq %%rbx,  8(%%rax)&quot; &quot;\n\t&quot;        &quot;popq          (%%rax)&quot; &quot;\n\t&quot;#ifdef __APPLE__        &quot;popq %%rbx&quot;            &quot;\n\t&quot;#endif      : &quot;=a&quot; (dummy)      : &quot;0&quot; (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. */      &quot;rbx&quot;,#endif      &quot;rcx&quot;, &quot;rdx&quot;, &quot;rsi&quot;, &quot;rdi&quot;, &quot;memory&quot;   );}上面的代码中出现了一个很奇怪的指令inl。在通常环境下(例如Linux下默认的I/O权限设置),用户态程序是无法执行I/O指令的,因为这条指令只会让用户态程序出错并产生崩溃。而此处这条指令产生的权限错误会被host上的hypervisor捕捉,从而实现通信。Backdoor所引入的这种从guest上的用户态程序直接和host通信的能力,带来了一个有趣的攻击面,这个攻击面正好满足Pwn2Own的要求:“在这个类型(指虚拟机逃逸这一类挑战)中,攻击必须从guest的非管理员帐号发起,并实现在host操作系统中执行任意代码”。guest将0x564D5868存入$eax,I/O端口号0x5658或0x5659存储在$dx中,分别对应低带宽和高带宽通信。其它寄存器被用于传递参数,例如$ecx的低16位被用来存储命令号。对于RPCI通信,命令号会被设为BDOOR_CMD_MESSAGE(=30)。文件lib/include/backdoor_def.h中包含了一些支持的backdoor命令列表。host捕捉到错误后,会读取命令号并分发至相应的处理函数。此处我省略了很多细节,如果你有兴趣可以阅读相关源码。
2.1 RPCI远程过程调用接口RPCI(Remote Procedure Call Interface)是基于前面提到的Backdoor机制实现的。依赖这个机制,guest能够向host发送请求来完成某些操作,例如,拖放(Drag n Drop)/复制粘贴(Copy Paste)操作、发送或获取信息等等。RPCI请求的格式非常简单: 。例如RPCI请求info-get guestinfo.ip可以用来获取guest的IP地址。对于每个RPCI命令,在vmware-vmx进程中都有相关注册和处理操作。需要注意的是有些RPCI命令是基于VMCI套接字实现的,但此内容已超出本文讨论的范畴。
3. 漏洞花了一些时间逆向各种不同的RPCI处理函数之后,我决定专注于分析拖放(Drag n Drop,下面简称为DnD)和复制粘贴(Copy Paste,下面简称为CP)功能。这部分可能是最复杂的RPCI命令,也是最可能找到漏洞的地方。在深入理解的DnD/CP内部工作机理后,可以很容易发现,在没有用户交互的情况下,这些处理函数中的许多功能是无法调用的。DnD/CP的核心功能维护了一个状态机,在无用户交互(例如拖动鼠标从host到guest中)情况下,许多状态是无法达到的。我决定看一看Pwnfest 2016上被利用的漏洞,该漏洞在这个VMware安全公告中有所提及。此时我的idb已经标上了很多符号,所以很容易就通过bindiff找到了补丁的位置。下面的代码是修补之前存在漏洞的函数(可以看出services/plugins/dndcp/dnddndCPMsgV4.c中有对应源码,漏洞依然存在于open-vm-tools的git仓库的master分支当中):
static Bool  DnDCPMsgV4IsPacketValid(const uint8 *packet,                          size_t packetSize){   DnDCPMsgHdrV4 *msgHdr = NULL;   ASSERT(packet);   if (packetSize < DND_CP_MSG_HEADERSIZE_V4) {      return FALSE;   }   msgHdr = (DnDCPMsgHdrV4 *)packet;   /* Payload size is not valid. */   if (msgHdr->payloadSize > DND_CP_PACKET_MAX_PAYLOAD_SIZE_V4) {      return FALSE;   }   /* Binary size is not valid. */   if (msgHdr->binarySize > DND_CP_MSG_MAX_BINARY_SIZE_V4) {      return FALSE;   }   /* Payload size is more than binary size. */   if (msgHdr->payloadOffset + msgHdr->payloadSize > msgHdr->binarySize) { // [1]每个包的binarySize可以手动设置,但是程序默认为不修改。      return FALSE;   }   return TRUE;}Bool  DnDCPMsgV4_UnserializeMultiple(DnDCPMsgV4 *msg,                                 const uint8 *packet,                               size_t packetSize){   DnDCPMsgHdrV4 *msgHdr = NULL;   ASSERT(msg);   ASSERT(packet);   if (!DnDCPMsgV4IsPacketValid(packet, packetSize)) {//检查长度      return FALSE;   }   msgHdr = (DnDCPMsgHdrV4 *)packet;   /*    * For each session, there is at most 1 big message. If the received    * sessionId is different with buffered one, the received packet is for    * another another new message. Destroy old buffered message.    */   if (msg->binary &&       msg->hdr.sessionId != msgHdr->sessionId) {      DnDCPMsgV4_Destroy(msg);   }   /* Offset should be 0 for new message. */   if (NULL == msg->binary && msgHdr->payloadOffset != 0) {      return FALSE;   }   /* For existing buffered message, the payload offset should match. */   if (msg->binary &&       msg->hdr.sessionId == msgHdr->sessionId &&       msg->hdr.payloadOffset != msgHdr->payloadOffset) {      return FALSE;   }   if (NULL == msg->binary) {      memcpy(msg, msgHdr, DND_CP_MSG_HEADERSIZE_V4);      msg->binary = Util_SafeMalloc(msg->hdr.binarySize);//以第一次设置的长度分配空间   }   /* msg->hdr.payloadOffset is used as received binary size. */   memcpy(msg->binary + msg->hdr.payloadOffset,          packet + DND_CP_MSG_HEADERSIZE_V4,          msgHdr->payloadSize); // [2].void *memcpy(void *dest, const void *src, size_t n);   msg->hdr.payloadOffset += msgHdr->payloadSize;   return TRUE;}对于Version 4的DnD/CP功能,当guest发送分片DnD/CP命令数据包时,host会调用上面的函数来重组guest发送的DnD/CP消息。接收的第一个包必须满足payloadOffset为0binarySize代表堆上分配的buffer长度。[1]处的检查比较了包头中的binarySize,用来确保payloadOffset和payloadSize不会越界。在[2]处,数据会被拷入分配的buffer中。但是[1]处的检查存在问题,它只对接收的第一个包有效,对于后续的数据包,这个检查是无效的,因为代码预期包头中的binarySize和分片流中的第一个包相同,但实际上你可以在后续的包中指定更大的binarySize来满足检查,并触发堆溢出。所以,该漏洞可以通过发送下面的两个分片来触发:
packet 1{   ... binarySize = 0x100 payloadOffset = 0 payloadSize = 0x50 sessionId = 0x41414141 ... #...0x50 bytes...#}packet 2{   ... binarySize = 0x1000 payloadOffset = 0x50 payloadSize = 0x100 sessionId = 0x41414141 ... #...0x100 bytes...#}有了以上的知识,我决定看看Version 3中的DnD/CP功能中是不是也存在类似的问题。令人惊讶的是,几乎相同的漏洞存在于Version 3的代码中(这个漏洞最初通过逆向分析来发现,但是我们后来意识到v3的代码也在open-vm-tools的git仓库中):
Bool  DnD_TransportBufAppendPacket(DnDTransportBuffer *buf,          // IN/OUT                               DnDTransportPacketHeader *packet, // IN                             size_t packetSize)                // IN{   ASSERT(buf);   ASSERT(packetSize == (packet->payloadSize + DND_TRANSPORT_PACKET_HEADER_SIZE) &&          packetSize payloadSize + packet->offset) totalSize &&          packet->totalSize payloadSize + DND_TRANSPORT_PACKET_HEADER_SIZE) ||       packetSize > DND_MAX_TRANSPORT_PACKET_SIZE ||       (packet->payloadSize + packet->offset) > packet->totalSize || //[1]       packet->totalSize > DNDMSG_MAX_ARGSZ) {      goto error;   }   /*    * If seqNum does not match, it means either this is the first packet, or there    * is a timeout in another side. Reset the buffer in all cases.    */   if (buf->seqNum != packet->seqNum) {      DnD_TransportBufReset(buf);   }   if (!buf->buffer) {      ASSERT(!packet->offset);      if (packet->offset) {         goto error;      }      buf->buffer = Util_SafeMalloc(packet->totalSize);      buf->totalSize = packet->totalSize;      buf->seqNum = packet->seqNum;      buf->offset = 0;   }   if (buf->offset != packet->offset) {      goto error;   }   memcpy(buf->buffer + buf->offset,          packet->payload,          packet->payloadSize);   buf->offset += packet->payloadSize;   return TRUE;error:     DnD_TransportBufReset(buf);   return FALSE;}Version 3的DnD/CP在分片重组时,上面的函数会被调用。此处我们可以在[1]处看到与之前相同的情形,代码依然假设后续分片中的totalSize会和第一个分片一致。因此这个漏洞可以用和之前相同的方法触发:
packet 1{   ... totalSize = 0x100 payloadOffset = 0 payloadSize = 0x50 seqNum = 0x41414141 ... #...0x50 bytes...#}packet 2{   ... totalSize = 0x1000 payloadOffset = 0x50 payloadSize = 0x100 seqNum = 0x41414141 ... #...0x100 bytes...#}在Pwn2Own这样的比赛中,这个漏洞是很弱的,因为它只是受到之前漏洞的启发,而且甚至可以说是同一个。因此,这样的漏洞在赛前被修补并不惊讶(好吧,也许我们并不希望这个漏洞在比赛前一天被修复)。对应的VMware安全公告在这里。受到这个漏洞影响的VMWare Workstation Pro最新版本是12.5.3。接下来,让我们看一看这个漏洞是如何被用来完成从guest到host的逃逸的!
4. 漏洞利用为了实现代码执行,我们需要在堆上覆盖一个函数指针,或者破坏C++对象的虚表指针。首先让我们看一看如何将DnD/CP协议的设置为version 3,依次发送下列RPCI命令即可:
tools.capability.dnd_version 3  tools.capability.copypaste_version 3  vmx.capability.dnd_version  vmx.capability.copypaste_version  前两行消息分别设置了DnD和Copy/Paste的版本,后续两行用来查询版本,这是必须的,因为只有查询版本才会真正触发版本切换。RPCI命令vmx.capability.dnd_version会检查DnD/CP协议的版本是否已被修改,如果是,就会创建一个对应版本的C++对象。对于version 3,2个大小为0xA8的C++对象会被创建,一个用于DnD命令,另一个用于Copy/Paste命令。这个漏洞不仅可以让我们控制分配的大小和溢出的大小,而且能够让我们进行多次越界写。理想的话,我们可以用它分配大小为0xA8的内存块,并让它分配在C++对象之前,然后利用堆溢出改写C++对象的vtable指针,使其指向可控内存,从而实现代码执行。这并非易事,在此之前我们必须解决一些其他问题。首先我们需要找到一个方法来绕过ASLR,同时处理好Windows Low Fragmented Heap
4.1 绕过ASLR一般来说,我们需要找到一个对象,通过溢出来影响它,然后实现信息泄露。例如破坏一个带有长度或者数据指针的对象,并且可以从guest读取,然而我们没有找到这种对象。于是我们逆向了更多的RPCI命令处理函数,来寻找可用的东西。那些成对的命令特别引人关注,例如你能用一个命令来设置一些数据,同时又能用相关命令来取回数据,最终我们找到的是一对命令info-set和info-get:
info-set guestinfo.KEY VALUE  info-get guestinfo.KEY  VALUE是一个字符串,字符串的长度可以控制堆上buffer的分配长度,而且我们可以分配任意多的字符串。但是如何用这些字符串来泄露数据呢?我们可以通过溢出来覆盖结尾的null字节,让字符串连接上相邻的内存块。如果我们能够在发生溢出的内存块和DnD或CP对象之间分配一个字符串,那么我们就能泄露对象的vtable地址,从而我们就可以知道vmware-vmx的地址。尽管Windows的LFH堆分配存在随机化,但我们能够分配任意多的字符串,因此可以增加实现上述堆布局的可能性,但是我们仍然无法控制溢出buffer后面分配的是DnD还是CP对象。经过我们的测试,通过调整一些参数,例如分配和释放不同数量的字符串,我们可以实现60%到80%的成功率。下图总结了我们构建的堆布局情况(Ov代表溢出内存块,S代表String,T代表目标对象)。
我们的策略是:首先分配一些填满“A”的字符串,然后通过溢出写入一些“B”,接下来读取所有分配的字符串,其中含有“B”的就是被溢出的字符串。这样我们就找到了一个字符串可以被用来读取泄露的数据,然后以bucket的内存块大小0xA8的粒度继续溢出(为什么刚刚好能够达到dnd/cp对象?),每次溢出后都检查泄露的数据。由于DnD和CP对象的vtable距离vmware-vmx基地址的偏移是固定的(检查是否包含7f),每次溢出后只需要检查最低一些数据位,就能够判断溢出是否到达了目标对象
4.2 获取代码执行现在我们实现了信息泄露,也能知道溢出的是哪个C++对象,接下来要实现代码执行。我们需要处理两种情形:溢出CopyPaste和DnD。需要指出的是能利用的代码路径有很多,我们只是选择了其中一个。
4.2.1 覆盖CopyPaste对象对于CopyPaste对象,我们可以覆盖虚表指针,让它指向我们可控的其他数据。我们需要找到一个指针,指针指向的数据是可控并被用做对象的虚表。为此我们使用了另一个RPCI命令unity.window.contents.start。这个命令主要用于Unity模式下,在host上绘制一些图像。这个操作可以让我们往相对vmware-vmx偏移已知的位置写入一些数据。该命令接收的参数是图像的宽度和高度,二者都是32位,合并起来我们就在已知位置获得了一个64位的数据。我们用它来作为虚表中的一个指针,通过发送一个CopyPast命令即可触发该虚函数调用,步骤如下:

  • 发送unity.window.contents.start命令,通过指定参数宽度和高度,往全局变量处写入一个64位的栈迁移gadget地址
  • 覆盖对象虚表指针,指向伪造的虚表(调整虚表地址偏移)
  • 发送CopyPaste命令,触发虚函数调用ROP(存在于cp对象后面固定偏移处。在覆盖cp对象时拷贝过去)
4.2.2 覆盖DnD对象对于DnD对象,我们不能只覆盖vtable指针,因为在发生溢出之后vtable会立马被访问(覆盖完后的上一个函数会马上调用call dnd+0x38),另一个虚函数会被调用,而目前我们只能通过unity图像的宽度和高度控制一个qword,所以无法控制更大的虚表。让我们看一看DnD和CP对象的结构,总结如下(一些类似的结构可以在open-vm-tools中找到,但是在vmware-vmx中会略有区别):
DnD_CopyPaste_RpcV3{      void * vtable;    ...    uint64_t ifacetype;    RpcUtil{        void * vtable;        RpcBase * mRpc;        DnDTransportBuffer{            uint64_t seqNum;            uint8_t * buffer;            uint64_t totalSize;            uint64_t offset;            ...        }        ...    }}RpcBase{      void * vtable;    ...}
我们在此省略了结构中很多与本文无关的属性。对象中有个指针指向另一个C++对象RpcBase,如果我们能用一个可控数据的指针的指针覆盖mRpc这个域,那我们就控制了RpcBase的vtable。对此我们可以继续使用unity.window.contents.start命令来来控制mRpc,该命令的另一个参数是imgsize,这个参数代表分配的图像buffer的大小。这个buffer分配出来后,它的地址会存在vmware-vmx的固定偏移处。我们可以使用命令unity.window.contents.chunk(与start不同的时,其会在start+10?偏移存储chunk的地址。这样就会有指针的指针的调用。)来填充buffer的内容。步骤如下:

  • 发送unity.window.contents.start命令来分配一个buffer,后续我们用它来存储一个伪造的vtable。
  • 发送unity.window.contents.chunk命令来填充伪造的vtable,其中填入一个栈迁移的gadget
  • 通过溢出覆盖DnD对象的mRpc域,让它指向存储buffer地址的地方(某全局变量处),即写入一个指针的指针。
  • 通过发送DnD命令来触发mRpc域的虚函数调用(不需要发送,其上层函数会自动调用)ROP
P.S:vmware-vmx进程中有一个可读可写可执行的内存页(至少在版本12.5.3中存在,!address命令可以查看)。
4.3 稳定性讨论正如前面提及的,因为Windows LFH堆的随机化,当前的exploit无法做到100%成功率。不过可以尝试下列方法来提高成功率:

  • 观察0xA8大小的内存分配,考虑是否可以通过一些malloc和free的调用来实现确定性的LFH分配,参考这里这里
  • 寻找堆上的其他C++对象,尤其是那些可以在堆上喷射的
  • 寻找堆上其他带有函数指针的对象,尤其是那些可以在堆上喷射的
  • 找到一个独立的信息泄漏漏洞打开更多脑洞



来源:http://www.12558.net
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
楼主热帖
回复

使用道具 举报

*滑块验证:
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|Archiver|手机版|小黑屋|12558网页游戏私服论坛 |网站地图

GMT+8, 2024-11-24 10:24 , Processed in 0.078125 second(s), 31 queries .

Powered by Discuz! X3.4

© 2001-2017 Comsenz Inc.

快速回复 返回顶部 返回列表