尋找VMware Workstation渲染器中的漏洞
背景
一月中旬,ZDI宣布了2017年比賽的規(guī)則,其中包括了攻破VMware,完成虛擬機(jī)逃逸的隊(duì)伍會(huì)獲得相當(dāng)高額的獎(jiǎng)金。VMware已經(jīng)不是一個(gè)新目標(biāo)了。在2016年,VMware就被確定為攻擊目標(biāo)。
作為攻擊目標(biāo),VMware已經(jīng)經(jīng)歷過(guò)各種各樣的攻擊,攻擊點(diǎn)很多。
有趣的是,早在2006-2009年間,就有針對(duì)D&D和C&P的漏洞而完成虛擬機(jī)逃逸了。然而在2015年Kostya Kortchinsky和lokihardt又在D&D和C&P中發(fā)現(xiàn)了類(lèi)似的漏洞。從此,研究員們開(kāi)始對(duì)這些代碼更加深入的研究。
從我們旁觀(guān)者的角度,這一現(xiàn)象是令人深思的。我們?cè)谙?,VMware的漏洞一共有多少?其中又有哪些能被我們發(fā)現(xiàn)?
雖然一系列的漏洞被曝光,但是在2016年的Pwn2Own上,沒(méi)有一支隊(duì)伍能夠成功完成虛擬機(jī)逃逸。雖然像VMware這樣的傳統(tǒng)桌面軟件不是我們的研究領(lǐng)域。但我們還是對(duì)尋找VMware中的漏洞非常感興趣。
我們決定面對(duì)這個(gè)挑戰(zhàn),看看挖掘VMware中的漏洞到底有多困難。我們定了一個(gè)計(jì)劃,用一個(gè)月的業(yè)余時(shí)間來(lái)尋找漏洞。雖然我們沒(méi)能在Pwn2Own前完成,但我們確實(shí)發(fā)現(xiàn)了一些高危漏洞,并且嘗試通過(guò)這些漏洞找到VMware中可利用的攻擊點(diǎn)。
攻擊面
之前并不了解VMware的細(xì)節(jié),我們開(kāi)始不清楚實(shí)施攻擊應(yīng)該從何處著手。關(guān)注指令模擬的內(nèi)部細(xì)節(jié)會(huì)有幫助么?有些CPU支持VT,又有多少指令是被模擬的?為了避免與他人撞洞,除了打印和像D&D或C&P的主機(jī)客戶(hù)機(jī)交互外,還剩下什么呢?
下文是我們的研究成果,正如Pwn2Own規(guī)定的,所有的漏洞都要能被虛擬機(jī)里的普通用戶(hù)所利用。
VMWare模塊
在VMware的各種模塊中,GUI是最不受關(guān)注的部分。VMware在主機(jī)和虛擬機(jī)端都有內(nèi)核模塊(至少有vmnet/VMCI),thnuclnt(負(fù)責(zé)虛擬打印),vmnet-dhcpd,vmnet-natd,vmnet-netifup,vmware-authdlaucher,vmnet-bridge,vmware-usbarbitrator,vmware-hostd,還有虛擬機(jī)端最重要的vmware-tools。
幾乎所有的這些模塊都是作為特權(quán)進(jìn)程運(yùn)行,這使得他們成為被研究者分析的對(duì)象。虛擬打印已經(jīng)被攻擊多次了。
vmnet-dhcpd吸引了我們的注意,因?yàn)樗詒oot模式運(yùn)行并且是從ISC-DHCPD演變而來(lái)。更令人感興趣的是,vmware-dhcpd基于isc-dhcp2。我們開(kāi)始把它作為攻擊目標(biāo)。
然而,當(dāng)我們發(fā)現(xiàn)公開(kāi)的漏洞后(CVE-2011-2749,CVE-2011-2748)我們就放棄了這一想法。VMware為了防止漏洞,已經(jīng)在最新的isc-dhcp中修補(bǔ)了漏洞。
于是我們決定在QEMU和AFL對(duì)vmware-dhcpd的一些小補(bǔ)丁進(jìn)行fuzz測(cè)試。一個(gè)月的fuzzing并沒(méi)有顯示出任何漏洞。vmware-hostd也是一個(gè)令人感興趣的進(jìn)程,它作為一個(gè)web服務(wù)器,用于虛擬機(jī)共享,而且可以從虛擬機(jī)內(nèi)部訪(fǎng)問(wèn)到。然而,我們還是決定把精力投入研究VMware的核心組件。
vmware-vmx是最主要的虛擬機(jī)監(jiān)管模塊,在主機(jī)上作為root/系統(tǒng)進(jìn)程運(yùn)行,擁有一些令人感興趣的特性。事實(shí)上,它有兩個(gè)版本,vmware-vmx和vmware-vmx-debug。
如果VMware的設(shè)置中調(diào)試選項(xiàng)被啟用,那么使用的就是后者。這一點(diǎn)很重要,因?yàn)楫?dāng)我們進(jìn)行逆向工程時(shí),從擁有很多調(diào)試信息的版本開(kāi)始總會(huì)簡(jiǎn)單許多。或許這不是最適合的方法,但卻很有效。后面我們會(huì)講到。
RPC/RPCI
你可曾經(jīng)想過(guò)VM和主機(jī)之間的文件拖放功能是如何實(shí)現(xiàn)的?RPC在其中發(fā)揮了重要的作用。VMware內(nèi)部在0x5658端口上提供了一個(gè)接口作為“后門(mén)”。通過(guò)這個(gè)端口,虛擬機(jī)可以通過(guò)I/O指令來(lái)和主機(jī)進(jìn)行通信。
通過(guò)寄存器傳遞一個(gè)VMware可識(shí)別的魔數(shù),VMware會(huì)自動(dòng)解析附加的參數(shù)。I/O指令通常都是特權(quán)指令,但這個(gè)“后門(mén)”接口是個(gè)例外。這種例外是很少的。當(dāng)執(zhí)行一個(gè)后門(mén)I/O指令時(shí),VMware會(huì)進(jìn)行一系列的判斷,判斷該I/O指令是否來(lái)自擁有特權(quán)的虛擬機(jī)。
在這個(gè)“后門(mén)”接口的上層,VMware使用了RPC服務(wù)在主機(jī)和客戶(hù)機(jī)之間交換數(shù)據(jù)。在客戶(hù)機(jī)端,vmware-toolsd執(zhí)行“后門(mén)”命令的同時(shí),使用了RPC服務(wù)。
這就是為什么之后在安裝了vmware-toolsd的客戶(hù)機(jī)上,你才能使用像拖放文件這樣的功能。內(nèi)核驅(qū)動(dòng)和用戶(hù)空間功能的結(jié)合利用實(shí)現(xiàn)了這一功能。
在最初的“后門(mén)”接口中只能通過(guò)寄存器來(lái)傳遞數(shù)據(jù),面臨大量數(shù)據(jù)的傳輸時(shí),速度會(huì)變得很慢。為了解決這個(gè)問(wèn)題,VMware引入了另一個(gè)端口(0x5659)來(lái)實(shí)現(xiàn)高帶寬的“后門(mén)”。實(shí)際上這個(gè)端口是被RPC使用。
通過(guò)傳遞一個(gè)數(shù)據(jù)指針,vmware-vmx不用重復(fù)的調(diào)用IN指令,直接調(diào)用read/write API就可以完成數(shù)據(jù)的傳輸,Derek曾經(jīng)就在這個(gè)功能里發(fā)現(xiàn)了一個(gè)非常有趣的漏洞。
RPC接口提供了以下的功能:
- 打開(kāi)通道
 - 發(fā)送命令長(zhǎng)度
 - 發(fā)送數(shù)據(jù)
 - 接受回復(fù)的長(zhǎng)度
 - 接受數(shù)據(jù)
 - 結(jié)束互動(dòng)
 - 關(guān)閉通道
 
你可能會(huì)想如何防止進(jìn)程擾亂RPC的交互,建立一個(gè)通道時(shí),VMware會(huì)生產(chǎn)兩個(gè)cookie值,用它們來(lái)發(fā)送和接受數(shù)據(jù)。顯然,這兩個(gè)cookie是以安全的方式生成的。由于這兩個(gè)cookie就是兩個(gè)32位的無(wú)符號(hào)整數(shù),不能用memcmp和其他方式來(lái)比較它們。
在上層,VMware還用RPC命令來(lái)處理DnD,CnP,Unity和其他的事件。有些命令只能在虛擬機(jī)特權(quán)用戶(hù)下執(zhí)行。在虛擬機(jī)端。vmware-tool或open-vm-tools提供了rpctool用來(lái)和API交互。保存和獲取虛擬機(jī)信息的一個(gè)簡(jiǎn)單的例子如下:
- rpctool 'info-set guestinfo.foobar baz'
 - rpctool 'info-get guestinfo.foobar' -> baz
 
在vmware-vmx中保存信息并隨后提取出來(lái)。數(shù)據(jù)的儲(chǔ)存方式的細(xì)節(jié)不在本文的討論范圍內(nèi)。VMware內(nèi)部使用了VMDB,這是一個(gè)關(guān)鍵詞存儲(chǔ)的數(shù)據(jù)庫(kù),有為特定數(shù)據(jù)提供回調(diào)函數(shù)的功能。
然而,能在非特權(quán)虛擬機(jī)中調(diào)用的RPC命令數(shù)量有限。我們并不能提供一個(gè)完整的RPC命令列表,因?yàn)檫@和版本以及操作系統(tǒng)相關(guān)。最簡(jiǎn)單獲取命令列表的方式是從內(nèi)存中把命令列表dump下來(lái)。
令人欣慰的是,Linux版本的vmware-vmx提供了符號(hào),我們可以輕松的獲取到它。
最令人感興趣的攻擊點(diǎn)就是D&D,C&P和Unity了。然而我們并沒(méi)有研究它,原因有二。第一,lokihardt已經(jīng)在Pwnfest中成功利用它了。更重要的是,在Pwn2Own2016中,不允許使用Unity和虛擬打印中的漏洞。
由于這潛在的風(fēng)險(xiǎn),我們預(yù)計(jì)2017年VMware和ZDI會(huì)對(duì)與隔離設(shè)置無(wú)關(guān)的虛擬機(jī)逃逸更感興趣。雖然Pwn2Own 2017并沒(méi)給出比賽規(guī)則的細(xì)節(jié),但我們不愿意承擔(dān)著潛在的風(fēng)險(xiǎn)。最終,我們決定不挖RPC中的漏洞。
盡管如此,值得一提的是RPC中可以被攻擊利用的點(diǎn)很多,因?yàn)樗峁┝瞬倏囟褍?nèi)存的功能。
外圍設(shè)備的虛擬化
還有沒(méi)有其他的攻擊點(diǎn)呢?VMware的核心代碼實(shí)現(xiàn)了指令的虛擬化,同時(shí)也要為客戶(hù)機(jī)提供各種各樣的虛擬的外圍設(shè)備。這些設(shè)備包括了網(wǎng)絡(luò)、USB、藍(lán)牙、硬盤(pán)、圖像接口等等。
用戶(hù)空間服務(wù),虛擬機(jī)內(nèi)核驅(qū)動(dòng),和vmware-vmx一起來(lái)給虛擬化設(shè)備提供服務(wù)。例如,VMware在虛擬機(jī)內(nèi)部提供了SVGA圖形卡適配器,作為PCI顯示設(shè)備驅(qū)動(dòng)。
在Linux上,修改vmwgfx內(nèi)核模塊的X代碼,來(lái)建立一個(gè)vmware-vmx中SVGA3D/2D的接口層。我們認(rèn)為,在現(xiàn)代操作系統(tǒng)中,默認(rèn)開(kāi)啟的虛擬化的外圍設(shè)備是一個(gè)范圍很大的攻擊面。所以我們?cè)趯ふ夷J(rèn)啟用的,具有廣大攻擊面,并且能夠fuzz的模塊。最后我們選擇了圖形接口。
尋找渲染器中的漏洞
由于比賽平臺(tái)是Windows10上的VMware Workstation,我們決定在Windows而不是Linux上研究圖形接口。值得一提的是,Gallium的svga代碼中關(guān)于VMware圖形驅(qū)動(dòng)的開(kāi)源實(shí)現(xiàn)給了我們很大幫助,幫助我們分析vmware-vmx的相關(guān)部分。同樣的,微軟的圖形設(shè)備驅(qū)動(dòng)例程也對(duì)我們理解Windows驅(qū)動(dòng)的工作方式有很大幫助。
其他人曾經(jīng)攻擊過(guò)SVGA命令,我們決定深入研究,在這復(fù)雜的模塊的特定的功能中尋找漏洞:GPU渲染器的翻譯模塊。選擇渲染器字節(jié)碼而不是SVGA命令的一個(gè)重要原因是:渲染器字節(jié)碼可以從虛擬機(jī)內(nèi)部提供。
在linux和Mac上,渲染器是以O(shè)penGL實(shí)現(xiàn)。在Windows上,以Direct3D實(shí)現(xiàn)。因?yàn)閂Mware要支持不同的虛擬機(jī)操作系統(tǒng),各種渲染器的代碼都要被翻譯成主機(jī)上的渲染器行為。我們認(rèn)為,在這樣高度復(fù)雜的模塊中,隨之而來(lái)的是各種各樣的漏洞。
我們最初的分析是基于VMware Workstation 12.5.3的。
架構(gòu)
VMware中有兩種GPU的實(shí)現(xiàn)。一種是VGPU9(對(duì)應(yīng)DirectX 9.0),在Linux虛擬機(jī)和舊版本W(wǎng)indows虛擬機(jī)上使用。另一種是VGPU 10,在Windows10上使用。
對(duì)于3D加速圖形接口,VMware在Windows10虛擬機(jī)上使用WDDM(微軟顯示驅(qū)動(dòng)模型)驅(qū)動(dòng)。這個(gè)驅(qū)動(dòng)由用戶(hù)部分和內(nèi)核部分組成。用戶(hù)部分是vm3dum64_10.dll,內(nèi)核部分是vm3dp.sys。當(dāng)使用Direct 3D渲染器時(shí),字節(jié)碼要經(jīng)歷多次的翻譯。
由于VMware提供了虛擬3D支持,這些字節(jié)碼不能直接使用。它們會(huì)被進(jìn)一步的翻譯,Direct 3D API需要使用對(duì)應(yīng)的渲染器實(shí)現(xiàn)。因此,用戶(hù)空間驅(qū)動(dòng)實(shí)現(xiàn)了保存在D3D10DDI_DEVICEFUNC結(jié)構(gòu)中回調(diào)函數(shù)。它們把字節(jié)碼翻譯成對(duì)應(yīng)的API。
在這種情況下,VMWare SVGA3D定義了API,設(shè)置了渲染器。處理渲染器字節(jié)碼時(shí),用戶(hù)空間驅(qū)動(dòng)會(huì)調(diào)用內(nèi)核驅(qū)動(dòng)提供的pfnRenderCB回調(diào)函數(shù)。
任何需要GPU渲染器的Windows程序都要使用Windows D3D11 API。這些API負(fù)責(zé)翻譯文件中的渲染器字節(jié)碼,設(shè)置為不同種類(lèi)的渲染器。大致的翻譯過(guò)程如下圖。
這個(gè)過(guò)程包含了很多其他的細(xì)節(jié),涉及到的D3D11 API數(shù)量也很多。有興趣的讀者可以查看微軟提供的Direct3D11實(shí)例,并用Windbg來(lái)跟蹤調(diào)試它。(使用Windbg的wt命令)
- 0:000> x /D /f Tutorial03!i*
 - A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
 - 00000000`00da1900 Tutorial03!InitDevice (void)
 - 00000000`00da28f0 Tutorial03!InitWindow (struct HINSTANCE__ *, int)
 - 00000000`00da3630 Tutorial03!invoke_main (void)
 - 00000000`00da3620 Tutorial03!initialize_environment (void)
 - 00000000`00da4680 Tutorial03!is_potentially_valid_image_base (void *)
 - 00000000`00da637a Tutorial03!IsDebuggerPresent (<no parameter info>)
 - 00000000`00da63c8 Tutorial03!InitializeSListHead (<no parameter info>)
 - 00000000`00da63aa Tutorial03!IsProcessorFeaturePresent (<no parameter info>)
 - 0:000> bp Tutorial03!InitDevice
 - 0:000> g
 - Breakpoint 0 hit
 - Tutorial03!InitDevice:
 - 00da1900 55 push ebp
 - 0:000:x86> wt -l 8
 - Tracing Tutorial03!InitDevice to return address 00da2dfe
 - 259 0 [ 0] Tutorial03!InitDevice
 - 100 0 [ 1] USER32!GetClientRect
 - ...
 
構(gòu)造渲染器的輸入數(shù)據(jù)
在和VMware的渲染器交互時(shí),了解渲染器的原理是很重要的。
編寫(xiě)DirectX的渲染器,需要使用高層渲染語(yǔ)言(HLSL)。用D3D11 API或者fxc.exe程序把它編譯成字節(jié)碼。根據(jù)渲染器模型的不同,HLSL提供了不同種類(lèi)的渲染器特征。
HLSL編譯結(jié)果是以渲染器模型的匯編字節(jié)碼的形式給出的。VMware目前在內(nèi)部支持SM3和SM4,但不支持SM5和SM6。這對(duì)我們?cè)谀嫦騰mware-vmx中的翻譯單元是很重要的。
不幸的是,在windows平臺(tái)上,除了用HLSL外沒(méi)有其他生成渲染器字節(jié)碼的工具了。因此,構(gòu)造精確的輸入來(lái)觸發(fā)漏洞就顯得很有難度了。CSO文件還需要修復(fù)校驗(yàn)值。檢查校驗(yàn)值的函數(shù)是D3D11_3SDKLayers!DXBCVerifyHash
為了能給VMware提供任意的渲染器字節(jié)碼輸入,我們使用了強(qiáng)大的Frida工具來(lái)hook和修改渲染器字節(jié)碼。當(dāng)vm3dum64_10.dll把編譯好的字節(jié)碼放入內(nèi)存后,我們就改變成我們想輸入的任意字節(jié)碼。通過(guò)逆向工程,我們確定了相應(yīng)的memmove()位置并且hook了它。
下面是我們Frida代碼的一部分。
- var vm3d_base = Module.findBaseAddress("vm3dum64_10.dll");
 - console.log("base address: " + vm3d_base);
 - function ida2win(addr) {
 - var idaBase = ptr('0x180000000');
 - var off = ptr(addr).sub(idaBase);
 - var res = vm3d_base.add(off);
 - console.log("translated " + ptr(addr) + " -> " + res);
 - return res;
 - }
 - function start() {
 - var memmove_addr = ida2win(0x180012840);
 - var setShader_return = ida2win(0x180009bf4);
 - Interceptor.attach(memmove_addr, {
 - onLeave : function (retval) {
 - if (!this.hit) {
 - return;
 - }
 - Memory.writeU32(this.dest_addr.add(...), ...);
 - ....
 - },
 - onEnter : function (args) {
 - var shaderType = Memory.readU8(args[1].add(2));
 - if (!this.returnAddress.compare(setShader_return)) {
 - if (shaderType != 1) { return; }
 - this.dest_addr = args[0];
 - this.src_addr = args[1];
 - this.len = args[2].toInt32();
 - this.hit = 1;
 - ...
 - });
 - }
 
上面的代碼使用了Frida的劫持了vm3dum64_10中的memmove()的控制流。每當(dāng)代碼進(jìn)入memmove()時(shí),返回值和setShader()進(jìn)行比較。相同的話(huà),就在退出memmove()前修改內(nèi)存中的字節(jié)碼。
在我們的研究過(guò)程中,值得注意的是,我們了解到Marco Grassi和Peter Hlavaty展示過(guò)渲染器的fuzzing。其中提到VMware提供了一個(gè)渲染器的工具包和一些實(shí)例。這就是他們進(jìn)行fuzzing的基礎(chǔ),他們的研究成果可以在這里找到。
尋找漏洞
VMware是一個(gè)巨大的軟件,我們不知道如何從vmware-vmx中識(shí)別出渲染器的翻譯函數(shù)。只有兩種途徑:一種是直接識(shí)別渲染器的翻譯單元。第二種是通過(guò)SVGA3D命令處理函數(shù),通常是下面幾種:DXDDefine,DXBindShader,DefineSurface。
二進(jìn)制文件中查找字符串是相對(duì)簡(jiǎn)單的,下圖就是SVGA3d命令中使用的字符串。
用這些字符串并不直接找到對(duì)應(yīng)的處理函數(shù),但是,通過(guò)X引用可以找到內(nèi)存中另一張表。
用字符串表中的偏移把他們標(biāo)注出來(lái),就能找到直接的處理函數(shù)。由于它們最終用來(lái)控制渲染器的操作,跟著這些函數(shù)就能找到解析和翻譯的代碼。在內(nèi)部的實(shí)現(xiàn)中,內(nèi)核驅(qū)動(dòng)和vmware-vmx是以先進(jìn)先出的數(shù)據(jù)結(jié)構(gòu)實(shí)現(xiàn)的,用來(lái)把SVGA3D命令壓入堆中傳遞給監(jiān)管器。然后這些模塊取出數(shù)據(jù)并進(jìn)一步處理。
有兩種辦法能直接找到渲染器的代碼。第一種,使用vmware-vmx-debug中的字符串,能直接找到解析和翻譯的代碼。我們開(kāi)始跟隨了字符串“shaderParseSM4.c”和“shaderTransSM4.c”的交叉引用。但是審計(jì)debug版本的代碼漏洞有一個(gè)巨大的缺陷,debug版本有很多檢查函數(shù),這在發(fā)行版中是沒(méi)有的。
我們不清楚這是不是VMware設(shè)計(jì)上的缺陷,在審計(jì)vmware-vmx的代碼的過(guò)程中。在debug版本中,解析模塊和翻譯模塊中有著大量的嚴(yán)格的安全檢查。在發(fā)行版本中就沒(méi)有。
于是,我們利用在debug版本中搜索到的立即數(shù)參數(shù)來(lái)在非debug版本中定位,這大大的增強(qiáng)了IDA代碼的可讀性。多虧了mesa驅(qū)動(dòng)程序的幫助,我們才能知道我們需要搜索的是什么。
比如,mesa驅(qū)動(dòng)程序中關(guān)于VGPU10的定義對(duì)我們的分析有著很大的幫助。
當(dāng)vmware-vmx需要把虛擬機(jī)的渲染器代碼轉(zhuǎn)化為主機(jī)的渲染器代碼,它會(huì)首先解析虛擬機(jī)內(nèi)部庫(kù)函數(shù)打包好的渲染器字節(jié)碼。由于缺少底層的渲染器字節(jié)碼的資料,逆向這個(gè)解析函數(shù),并且構(gòu)造各種輸入,花費(fèi)了我們大量的時(shí)間。
最初的解析過(guò)程比較簡(jiǎn)單,ParserSM4()函數(shù)只是保存了參數(shù)。
parser解析字節(jié)碼時(shí),和其他的parser相似。渲染器代碼的長(zhǎng)度告訴parser應(yīng)該何時(shí)停止解析。每個(gè)字節(jié)碼都有一個(gè)種類(lèi),一個(gè)指令長(zhǎng)度,和一個(gè)值。具體的說(shuō),每個(gè)字節(jié)碼頭部的0:10位確定字節(jié)碼的種類(lèi),11:23位來(lái)編碼字節(jié)碼的數(shù)據(jù),30:24位來(lái)記錄字節(jié)碼的數(shù)據(jù)長(zhǎng)度,第31位記錄字節(jié)碼是否擴(kuò)展(通常情況下沒(méi)有)
由于大部分的字節(jié)碼包括的值都是一個(gè)字節(jié)長(zhǎng)度,parser都是把這個(gè)字節(jié)的值復(fù)制到未知的數(shù)據(jù)結(jié)構(gòu)中,上圖中的VGPU10_OPCODE_CUSTOMDATA是一個(gè)例外。因?yàn)樗艘粋€(gè)緩沖區(qū),dcl_immediateConstantBuffer有描述。
就像上面提到的,我們并不清楚內(nèi)部使用的數(shù)據(jù)結(jié)構(gòu)。但是,這對(duì)尋找翻譯單元中的漏洞無(wú)關(guān)緊要,因?yàn)閿?shù)據(jù)結(jié)構(gòu)中使用的偏移是一樣的。因此,如果我們知道了輸入的字節(jié)碼是什么,再來(lái)審計(jì)TransSM4的二進(jìn)制代碼就很方便了。
總體來(lái)說(shuō),這還是一個(gè)很耗費(fèi)時(shí)間的步驟。第一,我們開(kāi)始不了解渲染器和VMware的圖形接口虛擬化的知識(shí),尋找關(guān)鍵點(diǎn)就花了不少時(shí)間。第二,缺少直接生成sm4字節(jié)碼的工具,我們只能用Frida來(lái)動(dòng)態(tài)hook函數(shù)中的字節(jié)碼。
最后,了解SM4指令的細(xì)節(jié)和原理也是個(gè)巨大的工程。除了調(diào)試器外,還有一些能幫助我們進(jìn)行逆向工程的。vmware-vmx的debug版本中的ASSERT斷言能幫我們了解運(yùn)行錯(cuò)誤。
ParseSM4()函數(shù)提供了一個(gè)渲染器的反匯編函數(shù),能夠用來(lái)提取渲染器字節(jié)碼,記錄在VMware的log日志中。
成果
現(xiàn)在我們來(lái)看看在人工逆向后我們發(fā)現(xiàn)的一些成果,2017年3月17號(hào),在Pwn2Own,我們把這些漏洞和Poc提交給了ZDI。
1、翻譯dcl_immediateConstantBuffer字節(jié)碼時(shí)存在堆溢出
解析一個(gè)名為 VGPU10_OPCODE_CUSTOMDATA 的 token時(shí)(定義了一個(gè)緩沖區(qū)),執(zhí)行了下面的偽代碼:
- case VGPU10_OPCODE_CUSTOMDATA:
 - v41 = v23 >> 11;
 - *(_DWORD *)(_out_p_16_ptr + op_idx + 16) = v41;
 - if ( (_DWORD)v41 == VGPU10_CUSTOMDATA_DCL_IMMEDIATE_CONSTANT_BUFFER )
 - {
 - *(_DWORD *)(_out_p_16_ptr + op_idx + 32) = insn_l;
 - custom_data_alloc = (void *)mksMemMgr_alloc(v41, 0x10009u, 4LL * (unsigned int)insn_l);// int overflow safe
 - *(_QWORD *)(_out_p_16_ptr + op_idx + 24) = custom_data_alloc;
 - memcpy(custom_data_alloc, bc_tmp_ptr, 4LL * *(unsigned int *)(_out_p_16_ptr + op_idx + 32));
 - v37 = 0;
 - insn_start = (int *)bc_tmp_ptr;
 - }
 
這時(shí)insn_l表示一個(gè)在用戶(hù)數(shù)據(jù)指令中編碼的32位的常數(shù),一般渲染器指令不會(huì)用到32位長(zhǎng)度的值,所以這是一個(gè)比較特殊的情況。這個(gè)數(shù)表示了用戶(hù)數(shù)據(jù)塊長(zhǎng)度。代碼中沒(méi)有對(duì)這個(gè)長(zhǎng)度做任何限制。
mksMemMgr_alloc在內(nèi)部調(diào)用了calloc,分配了一個(gè)長(zhǎng)度為159384的堆。calloc函數(shù)是由msvcr90.dll提供的。我們發(fā)現(xiàn)msvcr90.dll總是被映射到4G內(nèi)存的最底端。
Windows 10上的calloc函數(shù)通過(guò)RtlAllocateHeap在NT堆中分配內(nèi)存。我們會(huì)在outbuf中使用這塊內(nèi)存,這塊內(nèi)存是完全被攻擊者控制的。
分配完這塊內(nèi)存后,翻譯階段就結(jié)束了。遇到VGPU10_OPCODE_COSTOMDATA這個(gè)token。memcpy會(huì)被調(diào)用,而沒(méi)有經(jīng)過(guò)進(jìn)一步的安全檢查。
- result = memcpy(outbuf + 106228, custom_data_alloc, 4 * len);
 
custom_data_alloc是我們?cè)谏厦娴膒arsing步驟中分配的緩沖區(qū)。這使我們能夠把精心設(shè)計(jì)好的數(shù)據(jù)寫(xiě)入到相鄰堆塊的頭部中。這些相鄰的堆塊是之前解析過(guò)的字節(jié)碼,它們也被分配這一個(gè)內(nèi)存區(qū)域。
2、翻譯dcl_indexableTemp字節(jié)碼時(shí)存在堆越界寫(xiě)入漏洞
在處理dcl_indexabletemp指令時(shí),渲染器解析模塊會(huì)調(diào)用下面的偽代碼
- case VGPU10_OPCODE_DCL_INDEXABLE_TEMP:
 - *(_DWORD *)(_out_p_16_ptr + op_idx + 16) = *insn_start;// index
 - *(_DWORD *)(_out_p_16_ptr + op_idx + 20) = insn_start[1];// index + value for array write operation in Trans
 - bc_tmp_ptr = insn_start + 3;
 - *(_DWORD *)(_out_p_16_ptr + op_idx + 24) = insn_start[2];
 
在上面的偽代碼中,指令的一部分被寫(xiě)到了op_idx中,這些值會(huì)在后面的翻譯模塊中用到。在解析過(guò)程中,沒(méi)有對(duì)這些值的任何限制。
下面的代碼表示了翻譯過(guò)程
- case VGPU10_OPCODE_DCL_INDEXABLE_TEMP:
 - v87 = *(_DWORD *)(bytecode_ptr + op_idx + 24);
 - svga3d_dcl_indexable_temp((__int64)__out,
 - *(_DWORD *)(bytecode_ptr + op_idx + 16),// idx
 - *(_DWORD *)(bytecode_ptr + op_idx + 20),// val
 - (1 << v87) - 1); // val2
 
我們可以看到,在調(diào)用svga3d_dcl_indexable_temp()時(shí),使用了相同的偏移值(20,16,24)。idx和val是被攻擊者直接控制的。第4個(gè)參數(shù)是由上面的第三個(gè)雙字運(yùn)算得出((1<<val2)-1)
進(jìn)入svga3d_dcl_indexable_temp()函數(shù)
- __int64 __fastcall svga3d_dcl_indexable_temp(__int64 a1, unsigned int idx, int val, char val2)
 - {
 - __int64 result; // rax@5
 - const char *v5; // rcx@7
 - const char *v6; // rsi@7
 - signed __int64 v7; // rdx@7
 - *(_DWORD *)(a1 + 8LL * idx + 0x1ED80) = val;
 - *(_BYTE *)(a1 + 8LL * idx + 0x1ED84) = val2;
 - *(_BYTE *)(a1 + 8LL * idx + 0x1ED85) = 1;
 - result = idx;
 - return result;
 
在上面的代碼中,a1是翻譯過(guò)程中使用的堆塊,和我們前面討論過(guò)的用戶(hù)數(shù)據(jù)塊是一樣的。以0x1ed80為基址,我們可以向任意偏移寫(xiě)入一個(gè)32位的dword值。
綜上,這個(gè)漏洞能讓我們?cè)谙惹疤岬降亩呀Y(jié)構(gòu)中向任意地址寫(xiě)入一個(gè)雙字值。還能夠?qū)懭雰蓚€(gè)字節(jié),由val來(lái)控制(剩下的兩個(gè)字節(jié)為0)。
3、翻譯dcl_resource字節(jié)碼時(shí)存在棧越界寫(xiě)入漏洞
翻譯過(guò)程中處理dcl_resource指令時(shí),下述代碼會(huì)被執(zhí)行:
- int hitme[128]; // [rsp+1620h] [rbp-258h]@196
 - int v144; // [rsp+1820h] [rbp-58h]@204
 - char v145; // [rsp+1824h] [rbp-54h]@303
 - bool v146; // [rsp+1830h] [rbp-48h]@14
 - char v147; // [rsp+1831h] [rbp-47h]@14
 - int v148; // [rsp+1880h] [rbp+8h]@1
 - __int64 v149; // [rsp+1890h] [rbp+18h]@1
 - __int64 v150; // [rsp+1898h] [rbp+20h]@14
 - ...
 - case VGPU10_OPCODE_DCL_RESOURCE:
 - v87 = sub_1403C2200(*(_DWORD *)(v14 + 32));
 - v88 = sub_1403C2200(*(_DWORD *)(v14 + 28));
 - v89 = sub_1403C2200(*(_DWORD *)(v14 + 24));
 - v90 = sub_1403C2200(*(_DWORD *)(v14 + 20));
 - sub_1402FCF10(&v107, (__int64)outptr, *(_DWORD *)(v14 + 80), v86, v90, v89, v88, v87);
 - v11 = 0i64;
 - hitme[(unsigned __int64)*(unsigned int *)(v14 + 80)] = *(_DWORD *)(v14 + 16);
 
我們沒(méi)有跟入研究sub_1403c2200()函數(shù)的細(xì)節(jié),這個(gè)函數(shù)無(wú)關(guān)緊要,因?yàn)樗鼘?duì)棧的結(jié)構(gòu)沒(méi)有影響。偏移(v14+80)又是完全受輸入的渲染器字節(jié)碼控制的。但是被寫(xiě)入的值是受到一定的限制。只能是0-31。
這意味著我們可以寫(xiě)入很多對(duì)齊的雙字。這里我們用了復(fù)數(shù)形式,因?yàn)檫@個(gè)漏洞可以被觸發(fā)多次,向hitme開(kāi)始到4G的地址寫(xiě)入0-31。
這個(gè)漏洞的可利用性跟VMware的版本和操作系統(tǒng)有關(guān)。很明顯,在不同的版本或操作系統(tǒng)中,棧的布局是不一樣的。
4、不安全的內(nèi)存映射導(dǎo)致繞過(guò)DEP
在vmware-vmx監(jiān)管進(jìn)程啟動(dòng)時(shí),創(chuàng)建了一些內(nèi)存映射。令人驚訝的時(shí),其中一塊內(nèi)存映射是以讀,寫(xiě),可執(zhí)行的權(quán)限創(chuàng)建的。在整個(gè)進(jìn)程的生命周期中都是如此。這樣的內(nèi)存映射只有一塊。這里創(chuàng)建的內(nèi)存映射是vmware-vmx進(jìn)程data區(qū)段的第一個(gè)頁(yè)。
- 7ff7`36b53000 7ff7`36b54000 0`00001000 MEM_IMAGE MEM_COMMIT PAGE_EXECUTE_READWRITE Image [vmware_vmx; "C:\Program Files (x86)\VMware\VMware Workstation\x64\vmware-vmx.exe"]
 - 0:018> dq 7ff7`36b53000 L 0n1000/8
 - 00007ff7`36b53000 ffffffff`ffffffff 00000001`fffffffe
 - 00007ff7`36b53010 00009f56`1b68b8ce ffff60a9`e4974731
 - 00007ff7`36b53020 00007ff7`36780a18 00007ff7`36780a08
 - 00007ff7`36b53030 00007ff7`367809f8 00007ff7`367809e8
 - 00007ff7`36b53040 00007ff7`367809d8 00000000`00000000
 - 00007ff7`36b53050 00007ff7`36780990 00007ff7`36780940
 - 00007ff7`36b53060 00007ff7`367808f0 00007ff7`367808a0
 - 00007ff7`36b53070 00007ff7`36780860 00007ff7`36780820
 - 00007ff7`36b53080 00007ff7`367807f0 00007ff7`367807a0
 - 00007ff7`36b53090 00007ff7`36780750 00007ff7`36780700
 
顯然,這大大的降低了虛擬機(jī)逃逸的漏洞,因?yàn)樗峁┝艘粋€(gè)完美的執(zhí)行shellcode的區(qū)域。
- data:0000000140B33000 _data segment para public 'DATA' use64
 - ...
 - 0000000140B33000 FF FF FF FF FF FF FF FF FE FF FF FF 01 00 00 00 ................
 - 0000000140B33010 32 A2 DF 2D 99 2B 00 00 CD 5D 20 D2 66 D4 FF FF 2..-.+...] .f...
 - 0000000140B33020 18 0A 76 40 01 00 00 00 08 0A 76 40 01 00 00 00 ..v@......v@....
 - 0000000140B33030 F8 09 76 40 01 00 00 00 E8 09 76 40 01 00 00 00 ..v@......v@....
 - 0000000140B33040 D8 09 76 40 01 00 00 00 00 00 00 00 00 00 00 00 ..v@............
 - 0000000140B33050 90 09 76 40 01 00 00 00 40 09 76 40 01 00 00 00 ..v@....@.v@....
 - 0000000140B33060 F0 08 76 40 01 00 00 00 A0 08 76 40 01 00 00 00 ..v@......v@....
 - 0000000140B33070 60 08 76 40 01 00 00 00 20 08 76 40 01 00 00 00 `.v@.... .v@....
 - 0000000140B33080 F0 07 76 40 01 00 00 00 A0 07 76 40 01 00 00 00 ..v@......v@....
 - 0000000140B33090 50 07 76 40 01 00 00 00 00 07 76 40 01 00 00 00 P.v@......v@....
 
如圖所示,內(nèi)存是以讀,寫(xiě),可執(zhí)行的方式分配的。Windbg中的dump只是想說(shuō)明它是和IDA中的data區(qū)段是相符的。
總結(jié)
前兩個(gè)漏洞是12.5.3版本中的0day漏洞,即使在比賽后,12.5.4版本還沒(méi)有修復(fù)這些漏洞。
在12.5.4版本發(fā)布后不久,VMWare又發(fā)布了VMSA-2017-0006修補(bǔ)了這兩個(gè)堆相關(guān)的漏洞。版本更新中的細(xì)節(jié)描述很模糊,我們并不知道這些漏洞是否被真正修補(bǔ)上了。
同樣的,vmware-vmx的調(diào)試版本有著產(chǎn)品版本中沒(méi)有的錯(cuò)誤檢查機(jī)制。根據(jù)ZDI的說(shuō)法,這和其他人提交的漏洞并不沖突,VMSA-2017-006只提及了ZDI的報(bào)告。
結(jié)果就是,我們不知道這些漏洞有沒(méi)有CVE ID,有沒(méi)有其他的研究員發(fā)現(xiàn)了這些漏洞。
值得注意的是,ASSERT斷言同樣影響了其他的SM4指令。我們確信,直到12.5.5版本,至少dcl_indexRange和dcl_constantBuffer有著相似的堆越界寫(xiě)入漏洞。
dcl_resource漏洞在12.5.5版本中沒(méi)有被修補(bǔ)。
目前我們認(rèn)為最初補(bǔ)丁是針對(duì)內(nèi)部代碼的重構(gòu),而不是針對(duì)我們提交的漏洞的。因?yàn)樵赿ebug版本中的修補(bǔ)和發(fā)行版一樣。使用了assert斷言而不是錯(cuò)誤處理函數(shù)。因此,我們依舊可以用這些漏洞來(lái)攻破VMware。
文中涉及的漏洞的PoC可以在https://github.com/comsecuris/vgpu_shader_pocs上找到。
其他
在這篇文章結(jié)束之前,我們還想說(shuō)說(shuō)我們工作中的一些發(fā)現(xiàn),希望對(duì)你們有幫助。有以下幾點(diǎn):
不同環(huán)境下逆向工程的難度
當(dāng)我們逆向分析vmware-vmx時(shí),Linux版本中一些奇怪的內(nèi)嵌函數(shù)特性使得逆向的難度大大提高(相比Windows和Mac)。比較后,我們發(fā)現(xiàn)竟然是Windows中的vwmare-vmx最適合逆向分析。
Linux版本中vmware-vmx的符號(hào)信息對(duì)逆向幫助很大。
設(shè)置
VMware為和渲染器的交互功能提供了很有用的設(shè)置選項(xiàng)。我們發(fā)現(xiàn)了mks.dx11.dumpShaders,mks.shim.dumpShaders和mks.gl.dumpShaders很有用。類(lèi)似的設(shè)置還有很多。
PIE
在linux中,如果去掉vmware-vmx ELF文件中的PIE選項(xiàng),vmware-vmx就不能正常工作了。在Mac中,使用change_macho_flage.py腳本可以成功處理vmware-vmx的重定位。這會(huì)讓調(diào)試變得更加方便。
二進(jìn)制翻譯模塊
完成上述的分析后,我們還在思考vmware-vmx中模擬x86指令的代碼在哪里。我們開(kāi)始認(rèn)為會(huì)很容易發(fā)現(xiàn)這部分代碼,然而,到目前為止,我們還找到相關(guān)的代碼。一種可能是硬件的虛擬化。然而,根據(jù)設(shè)置和架構(gòu),VMware是可以運(yùn)行在多種模式下的。這部分代碼肯定在某個(gè)位置。
- binwalk vmware-vmx.exe
 - DECIMAL HEXADECIMAL DESCRIPTION
 - --------------------------------------------------------------------------------
 - 0 0x0 Microsoft executable, portable (PE)
 - ...
 - 13126548 0xC84B94 ELF, 64-bit LSB relocatable, AMD x86-64, version 1 (SYSV)
 - 13126612 0xC84BD4 ELF, 64-bit LSB relocatable, AMD x86-64, version 1 (SYSV)
 - 14073118 0xD6BD1E Unix path: /build/mts/release/bora-4638234/bora/vmcore/lock/semaVMM.c
 - 14256073 0xD987C9 Sega MegaDrive/Genesis raw ROM dump, Name: "tSBASE", "E_TABLE_VA",
 - 14283364 0xD9F264 Sega MegaDrive/Genesis raw ROM dump, Name: "ncCRC32B64", "FromMPN",
 - 14942628 0xE401A4 ELF, 64-bit LSB relocatable, AMD x86-64, version 1 (SYSV)
 - 14949876 0xE41DF4 ELF, 64-bit LSB relocatable, AMD x86-64, version 1 (SYSV)
 - 14954108 0xE42E7C ELF, 64-bit LSB relocatable, AMD x86-64, version 1 (SYSV)
 - 14960892 0xE448FC ELF, 64-bit LSB relocatable, AMD x86-64, version 1 (SYSV)
 - 14991124 0xE4BF14 ELF, 64-bit LSB relocatable, AMD x86-64, version 1 (SYSV)
 
binwalk分析ELF的結(jié)果看起來(lái)很奇怪,肯定有一些錯(cuò)誤。我們暫時(shí)忽略這些錯(cuò)誤。我們注意到內(nèi)存中有個(gè)ELF頭。使用binwalk工具(通常不會(huì)用binwalk來(lái)分析PE或ELF文件),我們發(fā)現(xiàn)了一些有趣的事情。
一個(gè)最大ELF文件看起來(lái)很有意思,因?yàn)樗艘粋€(gè)巨大的函數(shù),更重要的事,它還帶有符號(hào)。
出乎我們的意料,這個(gè)內(nèi)嵌的ELF帶有x86反匯編的翻譯單元。我們對(duì)他的工作方式很好奇,但我們并沒(méi)有深入研究,畢竟這不是我們的目標(biāo),但這是一個(gè)很有趣的研究方向。
結(jié)語(yǔ)
很遺憾,在Pwn2Own上,我們沒(méi)能完成最初設(shè)定好的目標(biāo),實(shí)現(xiàn)完整的虛擬機(jī)逃逸。但是,能在我們計(jì)劃的時(shí)間內(nèi)完成,我們還是很滿(mǎn)意的。我們相信VMware不僅是一個(gè)漏洞挖掘的有趣的目標(biāo),而且VMware雖然實(shí)現(xiàn)了虛擬機(jī)的監(jiān)視功能,它與傳統(tǒng)的桌面程序差別并不大。
基于我們對(duì)攻擊點(diǎn)的發(fā)掘和探測(cè),我們相信VMware作為一個(gè)高度復(fù)雜且被廣泛使用的軟件,其中還有很多沒(méi)被挖掘利用的攻擊點(diǎn)。例如,我們僅僅分析了渲染器翻譯單元的表面結(jié)構(gòu)。
渲染器功能的內(nèi)部的復(fù)雜實(shí)現(xiàn)還有待分析。與此類(lèi)似,VMware的其他組件也被攻擊過(guò)。看完我們分析完的代碼和過(guò)去VMware提出的安全建議,最近的安全防御由被動(dòng)變成了主動(dòng)。
除了核心組件之外,還有很多值得研究的組件。比如vmware-hostd(支持SSL的web服務(wù)器)。我們希望能進(jìn)一步研究虛擬機(jī)的安全問(wèn)題,也歡迎其他人也來(lái)研究。




















 
 
 


 
 
 
 