尋找VMware Workstation渲染器中的漏洞
背景
一月中旬,ZDI宣布了2017年比賽的規則,其中包括了攻破VMware,完成虛擬機逃逸的隊伍會獲得相當高額的獎金。VMware已經不是一個新目標了。在2016年,VMware就被確定為攻擊目標。
作為攻擊目標,VMware已經經歷過各種各樣的攻擊,攻擊點很多。
有趣的是,早在2006-2009年間,就有針對D&D和C&P的漏洞而完成虛擬機逃逸了。然而在2015年Kostya Kortchinsky和lokihardt又在D&D和C&P中發現了類似的漏洞。從此,研究員們開始對這些代碼更加深入的研究。
從我們旁觀者的角度,這一現象是令人深思的。我們在想,VMware的漏洞一共有多少?其中又有哪些能被我們發現?
雖然一系列的漏洞被曝光,但是在2016年的Pwn2Own上,沒有一支隊伍能夠成功完成虛擬機逃逸。雖然像VMware這樣的傳統桌面軟件不是我們的研究領域。但我們還是對尋找VMware中的漏洞非常感興趣。
我們決定面對這個挑戰,看看挖掘VMware中的漏洞到底有多困難。我們定了一個計劃,用一個月的業余時間來尋找漏洞。雖然我們沒能在Pwn2Own前完成,但我們確實發現了一些高危漏洞,并且嘗試通過這些漏洞找到VMware中可利用的攻擊點。
攻擊面
之前并不了解VMware的細節,我們開始不清楚實施攻擊應該從何處著手。關注指令模擬的內部細節會有幫助么?有些CPU支持VT,又有多少指令是被模擬的?為了避免與他人撞洞,除了打印和像D&D或C&P的主機客戶機交互外,還剩下什么呢?
下文是我們的研究成果,正如Pwn2Own規定的,所有的漏洞都要能被虛擬機里的普通用戶所利用。
VMWare模塊
在VMware的各種模塊中,GUI是最不受關注的部分。VMware在主機和虛擬機端都有內核模塊(至少有vmnet/VMCI),thnuclnt(負責虛擬打印),vmnet-dhcpd,vmnet-natd,vmnet-netifup,vmware-authdlaucher,vmnet-bridge,vmware-usbarbitrator,vmware-hostd,還有虛擬機端最重要的vmware-tools。
幾乎所有的這些模塊都是作為特權進程運行,這使得他們成為被研究者分析的對象。虛擬打印已經被攻擊多次了。
vmnet-dhcpd吸引了我們的注意,因為他以root模式運行并且是從ISC-DHCPD演變而來。更令人感興趣的是,vmware-dhcpd基于isc-dhcp2。我們開始把它作為攻擊目標。
然而,當我們發現公開的漏洞后(CVE-2011-2749,CVE-2011-2748)我們就放棄了這一想法。VMware為了防止漏洞,已經在最新的isc-dhcp中修補了漏洞。
于是我們決定在QEMU和AFL對vmware-dhcpd的一些小補丁進行fuzz測試。一個月的fuzzing并沒有顯示出任何漏洞。vmware-hostd也是一個令人感興趣的進程,它作為一個web服務器,用于虛擬機共享,而且可以從虛擬機內部訪問到。然而,我們還是決定把精力投入研究VMware的核心組件。
vmware-vmx是最主要的虛擬機監管模塊,在主機上作為root/系統進程運行,擁有一些令人感興趣的特性。事實上,它有兩個版本,vmware-vmx和vmware-vmx-debug。
如果VMware的設置中調試選項被啟用,那么使用的就是后者。這一點很重要,因為當我們進行逆向工程時,從擁有很多調試信息的版本開始總會簡單許多?;蛟S這不是最適合的方法,但卻很有效。后面我們會講到。
RPC/RPCI
你可曾經想過VM和主機之間的文件拖放功能是如何實現的?RPC在其中發揮了重要的作用。VMware內部在0x5658端口上提供了一個接口作為“后門”。通過這個端口,虛擬機可以通過I/O指令來和主機進行通信。
通過寄存器傳遞一個VMware可識別的魔數,VMware會自動解析附加的參數。I/O指令通常都是特權指令,但這個“后門”接口是個例外。這種例外是很少的。當執行一個后門I/O指令時,VMware會進行一系列的判斷,判斷該I/O指令是否來自擁有特權的虛擬機。
在這個“后門”接口的上層,VMware使用了RPC服務在主機和客戶機之間交換數據。在客戶機端,vmware-toolsd執行“后門”命令的同時,使用了RPC服務。
這就是為什么之后在安裝了vmware-toolsd的客戶機上,你才能使用像拖放文件這樣的功能。內核驅動和用戶空間功能的結合利用實現了這一功能。
在最初的“后門”接口中只能通過寄存器來傳遞數據,面臨大量數據的傳輸時,速度會變得很慢。為了解決這個問題,VMware引入了另一個端口(0x5659)來實現高帶寬的“后門”。實際上這個端口是被RPC使用。
通過傳遞一個數據指針,vmware-vmx不用重復的調用IN指令,直接調用read/write API就可以完成數據的傳輸,Derek曾經就在這個功能里發現了一個非常有趣的漏洞。
RPC接口提供了以下的功能:
- 打開通道
- 發送命令長度
- 發送數據
- 接受回復的長度
- 接受數據
- 結束互動
- 關閉通道
你可能會想如何防止進程擾亂RPC的交互,建立一個通道時,VMware會生產兩個cookie值,用它們來發送和接受數據。顯然,這兩個cookie是以安全的方式生成的。由于這兩個cookie就是兩個32位的無符號整數,不能用memcmp和其他方式來比較它們。
在上層,VMware還用RPC命令來處理DnD,CnP,Unity和其他的事件。有些命令只能在虛擬機特權用戶下執行。在虛擬機端。vmware-tool或open-vm-tools提供了rpctool用來和API交互。保存和獲取虛擬機信息的一個簡單的例子如下:
- rpctool 'info-set guestinfo.foobar baz'
- rpctool 'info-get guestinfo.foobar' -> baz
在vmware-vmx中保存信息并隨后提取出來。數據的儲存方式的細節不在本文的討論范圍內。VMware內部使用了VMDB,這是一個關鍵詞存儲的數據庫,有為特定數據提供回調函數的功能。
然而,能在非特權虛擬機中調用的RPC命令數量有限。我們并不能提供一個完整的RPC命令列表,因為這和版本以及操作系統相關。最簡單獲取命令列表的方式是從內存中把命令列表dump下來。
令人欣慰的是,Linux版本的vmware-vmx提供了符號,我們可以輕松的獲取到它。
最令人感興趣的攻擊點就是D&D,C&P和Unity了。然而我們并沒有研究它,原因有二。第一,lokihardt已經在Pwnfest中成功利用它了。更重要的是,在Pwn2Own2016中,不允許使用Unity和虛擬打印中的漏洞。
由于這潛在的風險,我們預計2017年VMware和ZDI會對與隔離設置無關的虛擬機逃逸更感興趣。雖然Pwn2Own 2017并沒給出比賽規則的細節,但我們不愿意承擔著潛在的風險。最終,我們決定不挖RPC中的漏洞。
盡管如此,值得一提的是RPC中可以被攻擊利用的點很多,因為它提供了操控堆內存的功能。
外圍設備的虛擬化
還有沒有其他的攻擊點呢?VMware的核心代碼實現了指令的虛擬化,同時也要為客戶機提供各種各樣的虛擬的外圍設備。這些設備包括了網絡、USB、藍牙、硬盤、圖像接口等等。
用戶空間服務,虛擬機內核驅動,和vmware-vmx一起來給虛擬化設備提供服務。例如,VMware在虛擬機內部提供了SVGA圖形卡適配器,作為PCI顯示設備驅動。
在Linux上,修改vmwgfx內核模塊的X代碼,來建立一個vmware-vmx中SVGA3D/2D的接口層。我們認為,在現代操作系統中,默認開啟的虛擬化的外圍設備是一個范圍很大的攻擊面。所以我們在尋找默認啟用的,具有廣大攻擊面,并且能夠fuzz的模塊。最后我們選擇了圖形接口。
尋找渲染器中的漏洞
由于比賽平臺是Windows10上的VMware Workstation,我們決定在Windows而不是Linux上研究圖形接口。值得一提的是,Gallium的svga代碼中關于VMware圖形驅動的開源實現給了我們很大幫助,幫助我們分析vmware-vmx的相關部分。同樣的,微軟的圖形設備驅動例程也對我們理解Windows驅動的工作方式有很大幫助。
其他人曾經攻擊過SVGA命令,我們決定深入研究,在這復雜的模塊的特定的功能中尋找漏洞:GPU渲染器的翻譯模塊。選擇渲染器字節碼而不是SVGA命令的一個重要原因是:渲染器字節碼可以從虛擬機內部提供。
在linux和Mac上,渲染器是以OpenGL實現。在Windows上,以Direct3D實現。因為VMware要支持不同的虛擬機操作系統,各種渲染器的代碼都要被翻譯成主機上的渲染器行為。我們認為,在這樣高度復雜的模塊中,隨之而來的是各種各樣的漏洞。
我們最初的分析是基于VMware Workstation 12.5.3的。
架構
VMware中有兩種GPU的實現。一種是VGPU9(對應DirectX 9.0),在Linux虛擬機和舊版本Windows虛擬機上使用。另一種是VGPU 10,在Windows10上使用。
對于3D加速圖形接口,VMware在Windows10虛擬機上使用WDDM(微軟顯示驅動模型)驅動。這個驅動由用戶部分和內核部分組成。用戶部分是vm3dum64_10.dll,內核部分是vm3dp.sys。當使用Direct 3D渲染器時,字節碼要經歷多次的翻譯。
由于VMware提供了虛擬3D支持,這些字節碼不能直接使用。它們會被進一步的翻譯,Direct 3D API需要使用對應的渲染器實現。因此,用戶空間驅動實現了保存在D3D10DDI_DEVICEFUNC結構中回調函數。它們把字節碼翻譯成對應的API。
在這種情況下,VMWare SVGA3D定義了API,設置了渲染器。處理渲染器字節碼時,用戶空間驅動會調用內核驅動提供的pfnRenderCB回調函數。
任何需要GPU渲染器的Windows程序都要使用Windows D3D11 API。這些API負責翻譯文件中的渲染器字節碼,設置為不同種類的渲染器。大致的翻譯過程如下圖。
這個過程包含了很多其他的細節,涉及到的D3D11 API數量也很多。有興趣的讀者可以查看微軟提供的Direct3D11實例,并用Windbg來跟蹤調試它。(使用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
- ...
構造渲染器的輸入數據
在和VMware的渲染器交互時,了解渲染器的原理是很重要的。
編寫DirectX的渲染器,需要使用高層渲染語言(HLSL)。用D3D11 API或者fxc.exe程序把它編譯成字節碼。根據渲染器模型的不同,HLSL提供了不同種類的渲染器特征。
HLSL編譯結果是以渲染器模型的匯編字節碼的形式給出的。VMware目前在內部支持SM3和SM4,但不支持SM5和SM6。這對我們在逆向vmware-vmx中的翻譯單元是很重要的。
不幸的是,在windows平臺上,除了用HLSL外沒有其他生成渲染器字節碼的工具了。因此,構造精確的輸入來觸發漏洞就顯得很有難度了。CSO文件還需要修復校驗值。檢查校驗值的函數是D3D11_3SDKLayers!DXBCVerifyHash
為了能給VMware提供任意的渲染器字節碼輸入,我們使用了強大的Frida工具來hook和修改渲染器字節碼。當vm3dum64_10.dll把編譯好的字節碼放入內存后,我們就改變成我們想輸入的任意字節碼。通過逆向工程,我們確定了相應的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()的控制流。每當代碼進入memmove()時,返回值和setShader()進行比較。相同的話,就在退出memmove()前修改內存中的字節碼。
在我們的研究過程中,值得注意的是,我們了解到Marco Grassi和Peter Hlavaty展示過渲染器的fuzzing。其中提到VMware提供了一個渲染器的工具包和一些實例。這就是他們進行fuzzing的基礎,他們的研究成果可以在這里找到。
尋找漏洞
VMware是一個巨大的軟件,我們不知道如何從vmware-vmx中識別出渲染器的翻譯函數。只有兩種途徑:一種是直接識別渲染器的翻譯單元。第二種是通過SVGA3D命令處理函數,通常是下面幾種:DXDDefine,DXBindShader,DefineSurface。
二進制文件中查找字符串是相對簡單的,下圖就是SVGA3d命令中使用的字符串。
用這些字符串并不直接找到對應的處理函數,但是,通過X引用可以找到內存中另一張表。
用字符串表中的偏移把他們標注出來,就能找到直接的處理函數。由于它們最終用來控制渲染器的操作,跟著這些函數就能找到解析和翻譯的代碼。在內部的實現中,內核驅動和vmware-vmx是以先進先出的數據結構實現的,用來把SVGA3D命令壓入堆中傳遞給監管器。然后這些模塊取出數據并進一步處理。
有兩種辦法能直接找到渲染器的代碼。第一種,使用vmware-vmx-debug中的字符串,能直接找到解析和翻譯的代碼。我們開始跟隨了字符串“shaderParseSM4.c”和“shaderTransSM4.c”的交叉引用。但是審計debug版本的代碼漏洞有一個巨大的缺陷,debug版本有很多檢查函數,這在發行版中是沒有的。
我們不清楚這是不是VMware設計上的缺陷,在審計vmware-vmx的代碼的過程中。在debug版本中,解析模塊和翻譯模塊中有著大量的嚴格的安全檢查。在發行版本中就沒有。
于是,我們利用在debug版本中搜索到的立即數參數來在非debug版本中定位,這大大的增強了IDA代碼的可讀性。多虧了mesa驅動程序的幫助,我們才能知道我們需要搜索的是什么。
比如,mesa驅動程序中關于VGPU10的定義對我們的分析有著很大的幫助。
當vmware-vmx需要把虛擬機的渲染器代碼轉化為主機的渲染器代碼,它會首先解析虛擬機內部庫函數打包好的渲染器字節碼。由于缺少底層的渲染器字節碼的資料,逆向這個解析函數,并且構造各種輸入,花費了我們大量的時間。
最初的解析過程比較簡單,ParserSM4()函數只是保存了參數。
parser解析字節碼時,和其他的parser相似。渲染器代碼的長度告訴parser應該何時停止解析。每個字節碼都有一個種類,一個指令長度,和一個值。具體的說,每個字節碼頭部的0:10位確定字節碼的種類,11:23位來編碼字節碼的數據,30:24位來記錄字節碼的數據長度,第31位記錄字節碼是否擴展(通常情況下沒有)
由于大部分的字節碼包括的值都是一個字節長度,parser都是把這個字節的值復制到未知的數據結構中,上圖中的VGPU10_OPCODE_CUSTOMDATA是一個例外。因為它包含了一個緩沖區,dcl_immediateConstantBuffer有描述。
就像上面提到的,我們并不清楚內部使用的數據結構。但是,這對尋找翻譯單元中的漏洞無關緊要,因為數據結構中使用的偏移是一樣的。因此,如果我們知道了輸入的字節碼是什么,再來審計TransSM4的二進制代碼就很方便了。
總體來說,這還是一個很耗費時間的步驟。第一,我們開始不了解渲染器和VMware的圖形接口虛擬化的知識,尋找關鍵點就花了不少時間。第二,缺少直接生成sm4字節碼的工具,我們只能用Frida來動態hook函數中的字節碼。
最后,了解SM4指令的細節和原理也是個巨大的工程。除了調試器外,還有一些能幫助我們進行逆向工程的。vmware-vmx的debug版本中的ASSERT斷言能幫我們了解運行錯誤。
ParseSM4()函數提供了一個渲染器的反匯編函數,能夠用來提取渲染器字節碼,記錄在VMware的log日志中。
成果
現在我們來看看在人工逆向后我們發現的一些成果,2017年3月17號,在Pwn2Own,我們把這些漏洞和Poc提交給了ZDI。
1、翻譯dcl_immediateConstantBuffer字節碼時存在堆溢出
解析一個名為 VGPU10_OPCODE_CUSTOMDATA 的 token時(定義了一個緩沖區),執行了下面的偽代碼:
- 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表示一個在用戶數據指令中編碼的32位的常數,一般渲染器指令不會用到32位長度的值,所以這是一個比較特殊的情況。這個數表示了用戶數據塊長度。代碼中沒有對這個長度做任何限制。
mksMemMgr_alloc在內部調用了calloc,分配了一個長度為159384的堆。calloc函數是由msvcr90.dll提供的。我們發現msvcr90.dll總是被映射到4G內存的最底端。
Windows 10上的calloc函數通過RtlAllocateHeap在NT堆中分配內存。我們會在outbuf中使用這塊內存,這塊內存是完全被攻擊者控制的。
分配完這塊內存后,翻譯階段就結束了。遇到VGPU10_OPCODE_COSTOMDATA這個token。memcpy會被調用,而沒有經過進一步的安全檢查。
- result = memcpy(outbuf + 106228, custom_data_alloc, 4 * len);
custom_data_alloc是我們在上面的parsing步驟中分配的緩沖區。這使我們能夠把精心設計好的數據寫入到相鄰堆塊的頭部中。這些相鄰的堆塊是之前解析過的字節碼,它們也被分配這一個內存區域。
2、翻譯dcl_indexableTemp字節碼時存在堆越界寫入漏洞
在處理dcl_indexabletemp指令時,渲染器解析模塊會調用下面的偽代碼
- 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
我們可以看到,在調用svga3d_dcl_indexable_temp()時,使用了相同的偏移值(20,16,24)。idx和val是被攻擊者直接控制的。第4個參數是由上面的第三個雙字運算得出((1<<val2)-1)
進入svga3d_dcl_indexable_temp()函數
- __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是翻譯過程中使用的堆塊,和我們前面討論過的用戶數據塊是一樣的。以0x1ed80為基址,我們可以向任意偏移寫入一個32位的dword值。
綜上,這個漏洞能讓我們在先前提到的堆結構中向任意地址寫入一個雙字值。還能夠寫入兩個字節,由val來控制(剩下的兩個字節為0)。
3、翻譯dcl_resource字節碼時存在棧越界寫入漏洞
翻譯過程中處理dcl_resource指令時,下述代碼會被執行:
- 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()函數的細節,這個函數無關緊要,因為它對棧的結構沒有影響。偏移(v14+80)又是完全受輸入的渲染器字節碼控制的。但是被寫入的值是受到一定的限制。只能是0-31。
這意味著我們可以寫入很多對齊的雙字。這里我們用了復數形式,因為這個漏洞可以被觸發多次,向hitme開始到4G的地址寫入0-31。
這個漏洞的可利用性跟VMware的版本和操作系統有關。很明顯,在不同的版本或操作系統中,棧的布局是不一樣的。
4、不安全的內存映射導致繞過DEP
在vmware-vmx監管進程啟動時,創建了一些內存映射。令人驚訝的時,其中一塊內存映射是以讀,寫,可執行的權限創建的。在整個進程的生命周期中都是如此。這樣的內存映射只有一塊。這里創建的內存映射是vmware-vmx進程data區段的第一個頁。
- 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
顯然,這大大的降低了虛擬機逃逸的漏洞,因為它提供了一個完美的執行shellcode的區域。
- 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@....
如圖所示,內存是以讀,寫,可執行的方式分配的。Windbg中的dump只是想說明它是和IDA中的data區段是相符的。
總結
前兩個漏洞是12.5.3版本中的0day漏洞,即使在比賽后,12.5.4版本還沒有修復這些漏洞。
在12.5.4版本發布后不久,VMWare又發布了VMSA-2017-0006修補了這兩個堆相關的漏洞。版本更新中的細節描述很模糊,我們并不知道這些漏洞是否被真正修補上了。
同樣的,vmware-vmx的調試版本有著產品版本中沒有的錯誤檢查機制。根據ZDI的說法,這和其他人提交的漏洞并不沖突,VMSA-2017-006只提及了ZDI的報告。
結果就是,我們不知道這些漏洞有沒有CVE ID,有沒有其他的研究員發現了這些漏洞。
值得注意的是,ASSERT斷言同樣影響了其他的SM4指令。我們確信,直到12.5.5版本,至少dcl_indexRange和dcl_constantBuffer有著相似的堆越界寫入漏洞。
dcl_resource漏洞在12.5.5版本中沒有被修補。
目前我們認為最初補丁是針對內部代碼的重構,而不是針對我們提交的漏洞的。因為在debug版本中的修補和發行版一樣。使用了assert斷言而不是錯誤處理函數。因此,我們依舊可以用這些漏洞來攻破VMware。
文中涉及的漏洞的PoC可以在https://github.com/comsecuris/vgpu_shader_pocs上找到。
其他
在這篇文章結束之前,我們還想說說我們工作中的一些發現,希望對你們有幫助。有以下幾點:
不同環境下逆向工程的難度
當我們逆向分析vmware-vmx時,Linux版本中一些奇怪的內嵌函數特性使得逆向的難度大大提高(相比Windows和Mac)。比較后,我們發現竟然是Windows中的vwmare-vmx最適合逆向分析。
Linux版本中vmware-vmx的符號信息對逆向幫助很大。
設置
VMware為和渲染器的交互功能提供了很有用的設置選項。我們發現了mks.dx11.dumpShaders,mks.shim.dumpShaders和mks.gl.dumpShaders很有用。類似的設置還有很多。
PIE
在linux中,如果去掉vmware-vmx ELF文件中的PIE選項,vmware-vmx就不能正常工作了。在Mac中,使用change_macho_flage.py腳本可以成功處理vmware-vmx的重定位。這會讓調試變得更加方便。
二進制翻譯模塊
完成上述的分析后,我們還在思考vmware-vmx中模擬x86指令的代碼在哪里。我們開始認為會很容易發現這部分代碼,然而,到目前為止,我們還找到相關的代碼。一種可能是硬件的虛擬化。然而,根據設置和架構,VMware是可以運行在多種模式下的。這部分代碼肯定在某個位置。
- 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的結果看起來很奇怪,肯定有一些錯誤。我們暫時忽略這些錯誤。我們注意到內存中有個ELF頭。使用binwalk工具(通常不會用binwalk來分析PE或ELF文件),我們發現了一些有趣的事情。
一個最大ELF文件看起來很有意思,因為他包含了一個巨大的函數,更重要的事,它還帶有符號。
出乎我們的意料,這個內嵌的ELF帶有x86反匯編的翻譯單元。我們對他的工作方式很好奇,但我們并沒有深入研究,畢竟這不是我們的目標,但這是一個很有趣的研究方向。
結語
很遺憾,在Pwn2Own上,我們沒能完成最初設定好的目標,實現完整的虛擬機逃逸。但是,能在我們計劃的時間內完成,我們還是很滿意的。我們相信VMware不僅是一個漏洞挖掘的有趣的目標,而且VMware雖然實現了虛擬機的監視功能,它與傳統的桌面程序差別并不大。
基于我們對攻擊點的發掘和探測,我們相信VMware作為一個高度復雜且被廣泛使用的軟件,其中還有很多沒被挖掘利用的攻擊點。例如,我們僅僅分析了渲染器翻譯單元的表面結構。
渲染器功能的內部的復雜實現還有待分析。與此類似,VMware的其他組件也被攻擊過。看完我們分析完的代碼和過去VMware提出的安全建議,最近的安全防御由被動變成了主動。
除了核心組件之外,還有很多值得研究的組件。比如vmware-hostd(支持SSL的web服務器)。我們希望能進一步研究虛擬機的安全問題,也歡迎其他人也來研究。