背景
一月中旬击喂,ZDI宣布了2017年比賽的規(guī)則算墨,其中包括了攻破VMware,完成虛擬機(jī)逃逸的隊伍會獲得相當(dāng)高額的獎金徘意。VMware已經(jīng)不是一個新目標(biāo)了苔悦。在2016年,VMware就被確定為攻擊目標(biāo)椎咧。
作為攻擊目標(biāo)玖详,VMware已經(jīng)經(jīng)歷過各種各樣的攻擊,攻擊點(diǎn)很多勤讽。
有趣的是蟋座,早在2006-2009年間,就有針對D&D和C&P的漏洞而完成虛擬機(jī)逃逸了脚牍。然而在2015年Kostya Kortchinsky和lokihardt又在D&D和C&P中發(fā)現(xiàn)了類似的漏洞向臀。從此,研究員們開始對這些代碼更加深入的研究诸狭。
從我們旁觀者的角度飒硅,這一現(xiàn)象是令人深思的。我們在想作谚,VMware的漏洞一共有多少?其中又有哪些能被我們發(fā)現(xiàn)庵芭?
雖然一系列的漏洞被曝光妹懒,但是在2016年的Pwn2Own上,沒有一支隊伍能夠成功完成虛擬機(jī)逃逸双吆。雖然像VMware這樣的傳統(tǒng)桌面軟件不是我們的研究領(lǐng)域眨唬。但我們還是對尋找VMware中的漏洞非常感興趣。
我們決定面對這個挑戰(zhàn)好乐,看看挖掘VMware中的漏洞到底有多困難匾竿。我們定了一個計劃,用一個月的業(yè)余時間來尋找漏洞蔚万。雖然我們沒能在Pwn2Own前完成岭妖,但我們確實發(fā)現(xiàn)了一些高危漏洞,并且嘗試通過這些漏洞找到VMware中可利用的攻擊點(diǎn)。
攻擊面
之前并不了解VMware的細(xì)節(jié)昵慌,我們開始不清楚實施攻擊應(yīng)該從何處著手假夺。關(guān)注指令模擬的內(nèi)部細(xì)節(jié)會有幫助么?有些CPU支持VT斋攀,又有多少指令是被模擬的已卷?為了避免與他人撞洞,除了打印和像D&D或C&P的主機(jī)客戶機(jī)交互外淳蔼,還剩下什么呢侧蘸?
下文是我們的研究成果,正如Pwn2Own規(guī)定的鹉梨,所有的漏洞都要能被虛擬機(jī)里的普通用戶所利用讳癌。
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)行泡仗,這使得他們成為被研究者分析的對象。虛擬打印已經(jīng)被攻擊多次了猜憎。
vmnet-dhcpd吸引了我們的注意娩怎,因為他以root模式運(yùn)行并且是從ISC-DHCPD演變而來。更令人感興趣的是胰柑,vmware-dhcpd基于isc-dhcp2截亦。我們開始把它作為攻擊目標(biāo)。
然而柬讨,當(dāng)我們發(fā)現(xiàn)公開的漏洞后(CVE-2011-2749,CVE-2011-2748)我們就放棄了這一想法崩瓤。VMware為了防止漏洞,已經(jīng)在最新的isc-dhcp中修補(bǔ)了漏洞踩官。
于是我們決定在QEMU和AFL對vmware-dhcpd的一些小補(bǔ)丁進(jìn)行fuzz測試却桶。一個月的fuzzing并沒有顯示出任何漏洞。vmware-hostd也是一個令人感興趣的進(jìn)程蔗牡,它作為一個web服務(wù)器颖系,用于虛擬機(jī)共享,而且可以從虛擬機(jī)內(nèi)部訪問到辩越。然而嘁扼,我們還是決定把精力投入研究VMware的核心組件。
vmware-vmx是最主要的虛擬機(jī)監(jiān)管模塊黔攒,在主機(jī)上作為root/系統(tǒng)進(jìn)程運(yùn)行趁啸,擁有一些令人感興趣的特性强缘。事實上,它有兩個版本莲绰,vmware-vmx和vmware-vmx-debug欺旧。
如果VMware的設(shè)置中調(diào)試選項被啟用,那么使用的就是后者蛤签。這一點(diǎn)很重要辞友,因為當(dāng)我們進(jìn)行逆向工程時,從擁有很多調(diào)試信息的版本開始總會簡單許多震肮〕屏或許這不是最適合的方法,但卻很有效戳晌。后面我們會講到鲫尊。
RPC/RPCI
你可曾經(jīng)想過VM和主機(jī)之間的文件拖放功能是如何實現(xiàn)的?RPC在其中發(fā)揮了重要的作用沦偎。VMware內(nèi)部在0x5658端口上提供了一個接口作為“后門”疫向。通過這個端口,虛擬機(jī)可以通過I/O指令來和主機(jī)進(jìn)行通信豪嚎。
通過寄存器傳遞一個VMware可識別的魔數(shù)搔驼,VMware會自動解析附加的參數(shù)。I/O指令通常都是特權(quán)指令侈询,但這個“后門”接口是個例外舌涨。這種例外是很少的。當(dāng)執(zhí)行一個后門I/O指令時扔字,VMware會進(jìn)行一系列的判斷囊嘉,判斷該I/O指令是否來自擁有特權(quán)的虛擬機(jī)。
在這個“后門”接口的上層革为,VMware使用了RPC服務(wù)在主機(jī)和客戶機(jī)之間交換數(shù)據(jù)扭粱。在客戶機(jī)端,vmware-toolsd執(zhí)行“后門”命令的同時震檩,使用了RPC服務(wù)琢蛤。
這就是為什么之后在安裝了vmware-toolsd的客戶機(jī)上,你才能使用像拖放文件這樣的功能恳蹲。內(nèi)核驅(qū)動和用戶空間功能的結(jié)合利用實現(xiàn)了這一功能。
在最初的“后門”接口中只能通過寄存器來傳遞數(shù)據(jù)俩滥,面臨大量數(shù)據(jù)的傳輸時嘉蕾,速度會變得很慢。為了解決這個問題霜旧,VMware引入了另一個端口(0x5659)來實現(xiàn)高帶寬的“后門”错忱。實際上這個端口是被RPC使用儡率。
通過傳遞一個數(shù)據(jù)指針,vmware-vmx不用重復(fù)的調(diào)用IN指令以清,直接調(diào)用read/write
API就可以完成數(shù)據(jù)的傳輸儿普,Derek曾經(jīng)就在這個功能里發(fā)現(xiàn)了一個非常有趣的漏洞。
RPC接口提供了以下的功能:
打開通道
發(fā)送命令長度
發(fā)送數(shù)據(jù)
接受回復(fù)的長度
接受數(shù)據(jù)
結(jié)束互動
關(guān)閉通道
你可能會想如何防止進(jìn)程擾亂RPC的交互掷倔,建立一個通道時眉孩,VMware會生產(chǎn)兩個cookie值,用它們來發(fā)送和接受數(shù)據(jù)勒葱。顯然浪汪,這兩個cookie是以安全的方式生成的。由于這兩個cookie就是兩個32位的無符號整數(shù)凛虽,不能用memcmp和其他方式來比較它們死遭。
在上層,VMware還用RPC命令來處理DnD,CnP,Unity和其他的事件凯旋。有些命令只能在虛擬機(jī)特權(quán)用戶下執(zhí)行呀潭。在虛擬機(jī)端。vmware-tool或open-vm-tools提供了rpctool用來和API交互至非。保存和獲取虛擬機(jī)信息的一個簡單的例子如下:
rpctool 'info-set guestinfo.foobar baz'
rpctool 'info-get guestinfo.foobar' -> baz
在vmware-vmx中保存信息并隨后提取出來钠署。數(shù)據(jù)的儲存方式的細(xì)節(jié)不在本文的討論范圍內(nèi)。VMware內(nèi)部使用了VMDB,這是一個關(guān)鍵詞存儲的數(shù)據(jù)庫睡蟋,有為特定數(shù)據(jù)提供回調(diào)函數(shù)的功能踏幻。
然而,能在非特權(quán)虛擬機(jī)中調(diào)用的RPC命令數(shù)量有限戳杀。我們并不能提供一個完整的RPC命令列表该面,因為這和版本以及操作系統(tǒng)相關(guān)。最簡單獲取命令列表的方式是從內(nèi)存中把命令列表dump下來信卡。
令人欣慰的是隔缀,Linux版本的vmware-vmx提供了符號,我們可以輕松的獲取到它傍菇。
最令人感興趣的攻擊點(diǎn)就是D&D,C&P和Unity了猾瘸。然而我們并沒有研究它,原因有二丢习。第一牵触,lokihardt已經(jīng)在Pwnfest中成功利用它了。更重要的是咐低,在Pwn2Own2016中揽思,不允許使用Unity和虛擬打印中的漏洞。
由于這潛在的風(fēng)險见擦,我們預(yù)計2017年VMware和ZDI會對與隔離設(shè)置無關(guān)的虛擬機(jī)逃逸更感興趣钉汗。雖然Pwn2Own
2017并沒給出比賽規(guī)則的細(xì)節(jié)羹令,但我們不愿意承擔(dān)著潛在的風(fēng)險。最終损痰,我們決定不挖RPC中的漏洞福侈。
盡管如此,值得一提的是RPC中可以被攻擊利用的點(diǎn)很多卢未,因為它提供了操控堆內(nèi)存的功能肪凛。
外圍設(shè)備的虛擬化
還有沒有其他的攻擊點(diǎn)呢?VMware的核心代碼實現(xiàn)了指令的虛擬化尝丐,同時也要為客戶機(jī)提供各種各樣的虛擬的外圍設(shè)備显拜。這些設(shè)備包括了網(wǎng)絡(luò)、USB爹袁、藍(lán)牙远荠、硬盤、圖像接口等等失息。
用戶空間服務(wù)譬淳,虛擬機(jī)內(nèi)核驅(qū)動,和vmware-vmx一起來給虛擬化設(shè)備提供服務(wù)盹兢。例如邻梆,VMware在虛擬機(jī)內(nèi)部提供了SVGA圖形卡適配器,作為PCI顯示設(shè)備驅(qū)動绎秒。
在Linux上浦妄,修改vmwgfx內(nèi)核模塊的X代碼,來建立一個vmware-vmx中SVGA3D/2D的接口層见芹。我們認(rèn)為剂娄,在現(xiàn)代操作系統(tǒng)中,默認(rèn)開啟的虛擬化的外圍設(shè)備是一個范圍很大的攻擊面玄呛。所以我們在尋找默認(rèn)啟用的阅懦,具有廣大攻擊面,并且能夠fuzz的模塊徘铝。最后我們選擇了圖形接口耳胎。
尋找渲染器中的漏洞
由于比賽平臺是Windows10上的VMware Workstation,我們決定在Windows而不是Linux上研究圖形接口惕它。值得一提的是怕午,Gallium的svga代碼中關(guān)于VMware圖形驅(qū)動的開源實現(xiàn)給了我們很大幫助,幫助我們分析vmware-vmx的相關(guān)部分淹魄。同樣的郁惜,微軟的圖形設(shè)備驅(qū)動例程也對我們理解Windows驅(qū)動的工作方式有很大幫助。
其他人曾經(jīng)攻擊過SVGA命令揭北,我們決定深入研究扳炬,在這復(fù)雜的模塊的特定的功能中尋找漏洞:GPU渲染器的翻譯模塊。選擇渲染器字節(jié)碼而不是SVGA命令的一個重要原因是:渲染器字節(jié)碼可以從虛擬機(jī)內(nèi)部提供搔体。
在linux和Mac上恨樟,渲染器是以O(shè)penGL實現(xiàn)。在Windows上疚俱,以Direct3D實現(xiàn)劝术。因為VMware要支持不同的虛擬機(jī)操作系統(tǒng),各種渲染器的代碼都要被翻譯成主機(jī)上的渲染器行為呆奕。我們認(rèn)為养晋,在這樣高度復(fù)雜的模塊中,隨之而來的是各種各樣的漏洞梁钾。
我們最初的分析是基于VMware Workstation 12.5.3的绳泉。
架構(gòu)
VMware中有兩種GPU的實現(xiàn)。一種是VGPU9(對應(yīng)DirectX 9.0)姆泻,在Linux虛擬機(jī)和舊版本W(wǎng)indows虛擬機(jī)上使用零酪。另一種是VGPU 10,在Windows10上使用拇勃。
對于3D加速圖形接口四苇,VMware在Windows10虛擬機(jī)上使用WDDM(微軟顯示驅(qū)動模型)驅(qū)動。這個驅(qū)動由用戶部分和內(nèi)核部分組成方咆。用戶部分是vm3dum64_10.dll月腋,內(nèi)核部分是vm3dp.sys。當(dāng)使用Direct 3D渲染器時瓣赂,字節(jié)碼要經(jīng)歷多次的翻譯榆骚。
由于VMware提供了虛擬3D支持,這些字節(jié)碼不能直接使用钩述。它們會被進(jìn)一步的翻譯寨躁,Direct 3D API需要使用對應(yīng)的渲染器實現(xiàn)。因此牙勘,用戶空間驅(qū)動實現(xiàn)了保存在D3D10DDI_DEVICEFUNC結(jié)構(gòu)中回調(diào)函數(shù)职恳。它們把字節(jié)碼翻譯成對應(yīng)的API。
在這種情況下方面,VMWare SVGA3D定義了API放钦,設(shè)置了渲染器。處理渲染器字節(jié)碼時恭金,用戶空間驅(qū)動會調(diào)用內(nèi)核驅(qū)動提供的pfnRenderCB回調(diào)函數(shù)操禀。
任何需要GPU渲染器的Windows程序都要使用Windows D3D11 API。這些API負(fù)責(zé)翻譯文件中的渲染器字節(jié)碼横腿,設(shè)置為不同種類的渲染器颓屑。大致的翻譯過程如下圖斤寂。
這個過程包含了很多其他的細(xì)節(jié),涉及到的D3D11 API數(shù)量也很多揪惦。有興趣的讀者可以查看微軟提供的Direct3D11實例遍搞,并用Windbg來跟蹤調(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 ()
00000000`00da63c8 Tutorial03!InitializeSListHead ()
00000000`00da63aa Tutorial03!IsProcessorFeaturePresent ()
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的渲染器交互時器腋,了解渲染器的原理是很重要的溪猿。
編寫DirectX的渲染器,需要使用高層渲染語言(HLSL)纫塌。用D3D11
API或者fxc.exe程序把它編譯成字節(jié)碼诊县。根據(jù)渲染器模型的不同,HLSL提供了不同種類的渲染器特征措左。
HLSL編譯結(jié)果是以渲染器模型的匯編字節(jié)碼的形式給出的依痊。VMware目前在內(nèi)部支持SM3和SM4,但不支持SM5和SM6怎披。這對我們在逆向vmware-vmx中的翻譯單元是很重要的抗悍。
不幸的是,在windows平臺上钳枕,除了用HLSL外沒有其他生成渲染器字節(jié)碼的工具了缴渊。因此,構(gòu)造精確的輸入來觸發(fā)漏洞就顯得很有難度了鱼炒。CSO文件還需要修復(fù)校驗值衔沼。檢查校驗值的函數(shù)是D3D11_3SDKLayers!DXBCVerifyHash
為了能給VMware提供任意的渲染器字節(jié)碼輸入,我們使用了強(qiáng)大的Frida工具來hook和修改渲染器字節(jié)碼昔瞧。當(dāng)vm3dum64_10.dll把編譯好的字節(jié)碼放入內(nèi)存后指蚁,我們就改變成我們想輸入的任意字節(jié)碼漱病。通過逆向工程锚赤,我們確定了相應(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()時搓劫,返回值和setShader()進(jìn)行比較。相同的話混巧,就在退出memmove()前修改內(nèi)存中的字節(jié)碼枪向。
在我們的研究過程中,值得注意的是咧党,我們了解到Marco Grassi和Peter Hlavaty展示過渲染器的fuzzing秘蛔。其中提到VMware提供了一個渲染器的工具包和一些實例。這就是他們進(jìn)行fuzzing的基礎(chǔ),他們的研究成果可以在這里找到深员。
尋找漏洞
VMware是一個巨大的軟件负蠕,我們不知道如何從vmware-vmx中識別出渲染器的翻譯函數(shù)。只有兩種途徑:一種是直接識別渲染器的翻譯單元倦畅。第二種是通過SVGA3D命令處理函數(shù)虐急,通常是下面幾種:DXDDefine,DXBindShader,DefineSurface。
二進(jìn)制文件中查找字符串是相對簡單的滔迈,下圖就是SVGA3d命令中使用的字符串。
用這些字符串并不直接找到對應(yīng)的處理函數(shù)被辑,但是燎悍,通過X引用可以找到內(nèi)存中另一張表。
用字符串表中的偏移把他們標(biāo)注出來盼理,就能找到直接的處理函數(shù)谈山。由于它們最終用來控制渲染器的操作,跟著這些函數(shù)就能找到解析和翻譯的代碼宏怔。在內(nèi)部的實現(xiàn)中奏路,內(nèi)核驅(qū)動和vmware-vmx是以先進(jìn)先出的數(shù)據(jù)結(jié)構(gòu)實現(xiàn)的,用來把SVGA3D命令壓入堆中傳遞給監(jiān)管器臊诊。然后這些模塊取出數(shù)據(jù)并進(jìn)一步處理鸽粉。
有兩種辦法能直接找到渲染器的代碼。第一種抓艳,使用vmware-vmx-debug中的字符串触机,能直接找到解析和翻譯的代碼。我們開始跟隨了字符串“shaderParseSM4.c”和“shaderTransSM4.c”的交叉引用玷或。但是審計debug版本的代碼漏洞有一個巨大的缺陷儡首,debug版本有很多檢查函數(shù),這在發(fā)行版中是沒有的偏友。
我們不清楚這是不是VMware設(shè)計上的缺陷蔬胯,在審計vmware-vmx的代碼的過程中。在debug版本中位他,解析模塊和翻譯模塊中有著大量的嚴(yán)格的安全檢查氛濒。在發(fā)行版本中就沒有。
于是鹅髓,我們利用在debug版本中搜索到的立即數(shù)參數(shù)來在非debug版本中定位泼橘,這大大的增強(qiáng)了IDA代碼的可讀性。多虧了mesa驅(qū)動程序的幫助迈勋,我們才能知道我們需要搜索的是什么炬灭。
比如,mesa驅(qū)動程序中關(guān)于VGPU10的定義對我們的分析有著很大的幫助。
當(dāng)vmware-vmx需要把虛擬機(jī)的渲染器代碼轉(zhuǎn)化為主機(jī)的渲染器代碼重归,它會首先解析虛擬機(jī)內(nèi)部庫函數(shù)打包好的渲染器字節(jié)碼米愿。由于缺少底層的渲染器字節(jié)碼的資料,逆向這個解析函數(shù)鼻吮,并且構(gòu)造各種輸入育苟,花費(fèi)了我們大量的時間。
最初的解析過程比較簡單椎木,ParserSM4()函數(shù)只是保存了參數(shù)违柏。
parser解析字節(jié)碼時,和其他的parser相似香椎。渲染器代碼的長度告訴parser應(yīng)該何時停止解析漱竖。每個字節(jié)碼都有一個種類,一個指令長度畜伐,和一個值馍惹。具體的說,每個字節(jié)碼頭部的0:10位確定字節(jié)碼的種類玛界,11:23位來編碼字節(jié)碼的數(shù)據(jù)万矾,30:24位來記錄字節(jié)碼的數(shù)據(jù)長度,第31位記錄字節(jié)碼是否擴(kuò)展(通常情況下沒有)
由于大部分的字節(jié)碼包括的值都是一個字節(jié)長度慎框,parser都是把這個字節(jié)的值復(fù)制到未知的數(shù)據(jù)結(jié)構(gòu)中良狈,上圖中的VGPU10_OPCODE_CUSTOMDATA是一個例外。因為它包含了一個緩沖區(qū)笨枯,dcl_immediateConstantBuffer有描述们颜。
就像上面提到的,我們并不清楚內(nèi)部使用的數(shù)據(jù)結(jié)構(gòu)猎醇。但是窥突,這對尋找翻譯單元中的漏洞無關(guān)緊要,因為數(shù)據(jù)結(jié)構(gòu)中使用的偏移是一樣的硫嘶。因此阻问,如果我們知道了輸入的字節(jié)碼是什么,再來審計TransSM4的二進(jìn)制代碼就很方便了沦疾。
總體來說称近,這還是一個很耗費(fèi)時間的步驟。第一哮塞,我們開始不了解渲染器和VMware的圖形接口虛擬化的知識刨秆,尋找關(guān)鍵點(diǎn)就花了不少時間。第二忆畅,缺少直接生成sm4字節(jié)碼的工具衡未,我們只能用Frida來動態(tài)hook函數(shù)中的字節(jié)碼。
最后,了解SM4指令的細(xì)節(jié)和原理也是個巨大的工程缓醋。除了調(diào)試器外如失,還有一些能幫助我們進(jìn)行逆向工程的。vmware-vmx的debug版本中的ASSERT斷言能幫我們了解運(yùn)行錯誤送粱。
ParseSM4()函數(shù)提供了一個渲染器的反匯編函數(shù)褪贵,能夠用來提取渲染器字節(jié)碼,記錄在VMware的log日志中抗俄。
成果
現(xiàn)在我們來看看在人工逆向后我們發(fā)現(xiàn)的一些成果脆丁,2017年3月17號,在Pwn2Own动雹,我們把這些漏洞和Poc提交給了ZDI槽卫。
1、翻譯dcl_immediateConstantBuffer字節(jié)碼時存在堆溢出
解析一個名為 VGPU10_OPCODE_CUSTOMDATA 的 token時(定義了一個緩沖區(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;
}
這時insn_l表示一個在用戶數(shù)據(jù)指令中編碼的32位的常數(shù),一般渲染器指令不會用到32位長度的值裆馒,所以這是一個比較特殊的情況姊氓。這個數(shù)表示了用戶數(shù)據(jù)塊長度。代碼中沒有對這個長度做任何限制喷好。
mksMemMgr_alloc在內(nèi)部調(diào)用了calloc翔横,分配了一個長度為159384的堆。calloc函數(shù)是由msvcr90.dll提供的梗搅。我們發(fā)現(xiàn)msvcr90.dll總是被映射到4G內(nèi)存的最底端禾唁。
Windows 10上的calloc函數(shù)通過RtlAllocateHeap在NT堆中分配內(nèi)存。我們會在outbuf中使用這塊內(nèi)存无切,這塊內(nèi)存是完全被攻擊者控制的荡短。
分配完這塊內(nèi)存后,翻譯階段就結(jié)束了哆键。遇到VGPU10_OPCODE_COSTOMDATA這個token掘托。memcpy會被調(diào)用,而沒有經(jīng)過進(jìn)一步的安全檢查籍嘹。
result = memcpy(outbuf + 106228, custom_data_alloc, 4 * len);
custom_data_alloc是我們在上面的parsing步驟中分配的緩沖區(qū)闪盔。這使我們能夠把精心設(shè)計好的數(shù)據(jù)寫入到相鄰堆塊的頭部中。這些相鄰的堆塊是之前解析過的字節(jié)碼辱士,它們也被分配這一個內(nèi)存區(qū)域泪掀。
2、翻譯dcl_indexableTemp字節(jié)碼時存在堆越界寫入漏洞
在處理dcl_indexabletemp指令時颂碘,渲染器解析模塊會調(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];
在上面的偽代碼中异赫,指令的一部分被寫到了op_idx中,這些值會在后面的翻譯模塊中用到。在解析過程中祝辣,沒有對這些值的任何限制贴妻。
下面的代碼表示了翻譯過程
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()時蝙斜,使用了相同的偏移值(20,16,24)名惩。idx和val是被攻擊者直接控制的。第4個參數(shù)是由上面的第三個雙字運(yùn)算得出((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是翻譯過程中使用的堆塊娩鹉,和我們前面討論過的用戶數(shù)據(jù)塊是一樣的。以0x1ed80為基址稚伍,我們可以向任意偏移寫入一個32位的dword值弯予。
綜上,這個漏洞能讓我們在先前提到的堆結(jié)構(gòu)中向任意地址寫入一個雙字值个曙。還能夠?qū)懭雰蓚€字節(jié)锈嫩,由val來控制(剩下的兩個字節(jié)為0)。
3垦搬、翻譯dcl_resource字節(jié)碼時存在棧越界寫入漏洞
翻譯過程中處理dcl_resource指令時呼寸,下述代碼會被執(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);
我們沒有跟入研究sub_1403c2200()函數(shù)的細(xì)節(jié),這個函數(shù)無關(guān)緊要猴贰,因為它對棧的結(jié)構(gòu)沒有影響对雪。偏移(v14+80)又是完全受輸入的渲染器字節(jié)碼控制的。但是被寫入的值是受到一定的限制米绕。只能是0-31瑟捣。
這意味著我們可以寫入很多對齊的雙字。這里我們用了復(fù)數(shù)形式栅干,因為這個漏洞可以被觸發(fā)多次迈套,向hitme開始到4G的地址寫入0-31。
這個漏洞的可利用性跟VMware的版本和操作系統(tǒng)有關(guān)碱鳞。很明顯交汤,在不同的版本或操作系統(tǒng)中,棧的布局是不一樣的劫笙。
4芙扎、不安全的內(nèi)存映射導(dǎo)致繞過DEP
在vmware-vmx監(jiān)管進(jìn)程啟動時,創(chuàng)建了一些內(nèi)存映射填大。令人驚訝的時戒洼,其中一塊內(nèi)存映射是以讀,寫允华,可執(zhí)行的權(quán)限創(chuàng)建的圈浇。在整個進(jìn)程的生命周期中都是如此寥掐。這樣的內(nèi)存映射只有一塊。這里創(chuàng)建的內(nèi)存映射是vmware-vmx進(jìn)程data區(qū)段的第一個頁磷蜀。
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ī)逃逸的漏洞,因為它提供了一個完美的執(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)存是以讀,寫庶弃,可執(zhí)行的方式分配的衫贬。Windbg中的dump只是想說明它是和IDA中的data區(qū)段是相符的。
總結(jié)
前兩個漏洞是12.5.3版本中的0day漏洞歇攻,即使在比賽后固惯,12.5.4版本還沒有修復(fù)這些漏洞。
在12.5.4版本發(fā)布后不久缴守,VMWare又發(fā)布了VMSA-2017-0006修補(bǔ)了這兩個堆相關(guān)的漏洞葬毫。版本更新中的細(xì)節(jié)描述很模糊,我們并不知道這些漏洞是否被真正修補(bǔ)上了屡穗。
同樣的贴捡,vmware-vmx的調(diào)試版本有著產(chǎn)品版本中沒有的錯誤檢查機(jī)制。根據(jù)ZDI的說法鸡捐,這和其他人提交的漏洞并不沖突栈暇,VMSA-2017-006只提及了ZDI的報告麻裁。
結(jié)果就是箍镜,我們不知道這些漏洞有沒有CVE ID,有沒有其他的研究員發(fā)現(xiàn)了這些漏洞煎源。
值得注意的是色迂,ASSERT斷言同樣影響了其他的SM4指令。我們確信手销,直到12.5.5版本歇僧,至少dcl_indexRange和dcl_constantBuffer有著相似的堆越界寫入漏洞。
dcl_resource漏洞在12.5.5版本中沒有被修補(bǔ)锋拖。
目前我們認(rèn)為最初補(bǔ)丁是針對內(nèi)部代碼的重構(gòu)诈悍,而不是針對我們提交的漏洞的。因為在debug版本中的修補(bǔ)和發(fā)行版一樣兽埃。使用了assert斷言而不是錯誤處理函數(shù)侥钳。因此,我們依舊可以用這些漏洞來攻破VMware柄错。
文中涉及的漏洞的PoC可以在https://github.com/comsecuris/vgpu_shader_pocs上找到舷夺。
其他
在這篇文章結(jié)束之前苦酱,我們還想說說我們工作中的一些發(fā)現(xiàn),希望對你們有幫助给猾。有以下幾點(diǎn):
不同環(huán)境下逆向工程的難度
當(dāng)我們逆向分析vmware-vmx時疫萤,Linux版本中一些奇怪的內(nèi)嵌函數(shù)特性使得逆向的難度大大提高(相比Windows和Mac)。比較后敢伸,我們發(fā)現(xiàn)竟然是Windows中的vwmare-vmx最適合逆向分析扯饶。
Linux版本中vmware-vmx的符號信息對逆向幫助很大。
設(shè)置
VMware為和渲染器的交互功能提供了很有用的設(shè)置選項详拙。我們發(fā)現(xiàn)了mks.dx11.dumpShaders,mks.shim.dumpShaders和mks.gl.dumpShaders很有用帝际。類似的設(shè)置還有很多。
PIE
在linux中饶辙,如果去掉vmware-vmx ELF文件中的PIE選項蹲诀,vmware-vmx就不能正常工作了。在Mac中弃揽,使用change_macho_flage.py腳本可以成功處理vmware-vmx的重定位脯爪。這會讓調(diào)試變得更加方便。
二進(jìn)制翻譯模塊
完成上述的分析后矿微,我們還在思考vmware-vmx中模擬x86指令的代碼在哪里痕慢。我們開始認(rèn)為會很容易發(fā)現(xiàn)這部分代碼,然而涌矢,到目前為止掖举,我們還找到相關(guān)的代碼。一種可能是硬件的虛擬化娜庇。然而塔次,根據(jù)設(shè)置和架構(gòu),VMware是可以運(yùn)行在多種模式下的名秀。這部分代碼肯定在某個位置励负。
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é)果看起來很奇怪,肯定有一些錯誤匕得。我們暫時忽略這些錯誤继榆。我們注意到內(nèi)存中有個ELF頭。使用binwalk工具(通常不會用binwalk來分析PE或ELF文件)汁掠,我們發(fā)現(xiàn)了一些有趣的事情略吨。
一個最大ELF文件看起來很有意思,因為他包含了一個巨大的函數(shù)考阱,更重要的事翠忠,它還帶有符號。
出乎我們的意料羔砾,這個內(nèi)嵌的ELF帶有x86反匯編的翻譯單元负间。我們對他的工作方式很好奇偶妖,但我們并沒有深入研究,畢竟這不是我們的目標(biāo)政溃,但這是一個很有趣的研究方向趾访。
結(jié)語
很遺憾,在Pwn2Own上董虱,我們沒能完成最初設(shè)定好的目標(biāo)扼鞋,實現(xiàn)完整的虛擬機(jī)逃逸。但是愤诱,能在我們計劃的時間內(nèi)完成云头,我們還是很滿意的。我們相信VMware不僅是一個漏洞挖掘的有趣的目標(biāo)淫半,而且VMware雖然實現(xiàn)了虛擬機(jī)的監(jiān)視功能溃槐,它與傳統(tǒng)的桌面程序差別并不大。
基于我們對攻擊點(diǎn)的發(fā)掘和探測科吭,我們相信VMware作為一個高度復(fù)雜且被廣泛使用的軟件昏滴,其中還有很多沒被挖掘利用的攻擊點(diǎn)。例如对人,我們僅僅分析了渲染器翻譯單元的表面結(jié)構(gòu)谣殊。
渲染器功能的內(nèi)部的復(fù)雜實現(xiàn)還有待分析。與此類似牺弄,VMware的其他組件也被攻擊過姻几。看完我們分析完的代碼和過去VMware提出的安全建議势告,最近的安全防御由被動變成了主動蛇捌。
除了核心組件之外,還有很多值得研究的組件培慌。比如vmware-hostd(支持SSL的web服務(wù)器)豁陆。我們希望能進(jìn)一步研究虛擬機(jī)的安全問題柑爸,也歡迎其他人也來研究吵护。