竟態攻擊:Hyper-V安全問題分析
*本文作者:Murkfox,本文屬 FreeBuf 原創獎勵計劃,未經許可禁止轉載。
前言
硬體虛擬化為廣大從事IT行業的朋友提供了極大的便利。同時也是“雲”這一概念的重要支援技術之一。微軟在前有VM,QEMU等產品的廣泛應用下毅然決然的釋出了微軟編寫的虛擬化產品Hyper-V,並運用到了Microsoft Azure 雲端計算平臺中。今年的 Blackhat 大會中也有議題對 Hyper-V 的安全問題展開了討論。今天我們將對其從安全形度上進行深度的分析。
0×01 工作原理
先上一張Hyper-V結構的佈局圖:
當用戶開啟虛擬化(Intel VT)支援後,物理機會在進入Windows 核心前啟動 hvix64.exe/hvax64.exe 初始化 Hypervisor 層再執行 Windows 核心程式完成系統載入。
當 Hypervisor 層被啟用後,宿主機在操作硬體時也會通過 Hyperisor 再傳至物理裝置,因此 Hyperisor 層的分級則為 Ring -1 (最新版的 Hyper-V 則會支援部分操作不進入Ring -1層直接傳至物理裝置進行操作)
宿主機核心與虛擬機器核心都是Ring 0級操作,只不過宿主機可以控制虛擬機器,所以,如果虛擬機發生了故障,不會影響到宿主機;宿主機發生了非致命故障,例如並非藍屏等Ring 0下的故障,也不會影響虛擬機器的正常執行。
宿主機與虛擬機器之間的資料傳輸通過VMBus(虛擬資料匯流排)實現,VMBus是微軟開發的虛擬資料傳輸匯流排,用來宿主機和虛擬機器之間交換資料和資訊。使用VMBus的優點就是能進行快速、高效、大資料量的資料傳輸,極大的提升了虛擬機器的效能。VMBus的原理和QEMU中的VirtIO裝置類似,通過共享環形記憶體,每當要傳輸的資料寫滿整個環形記憶體時,就把資料傳送到宿主機中。可以說,微軟成功的借鑑了VirtIO裝置的優點,取其優點用之,才會使得Hyper-V虛擬機器效能大幅提升。
和QEMU模擬裝置埠讀寫的方法不同的是,Hyper-V全部使用了虛擬裝置,而不是模擬硬體埠。Hyper-V不再使用傳統的讀寫模擬硬體埠的方式進行硬體裝置的虛擬化,而是使用了虛擬裝置作為裝置模擬。例如,在虛擬機器中網絡卡,硬碟等裝置的驅動都是由微軟提供,如果沒有這些驅動程式,虛擬機器則無法使用虛擬裝置,就像是QEMU中的VIRTIO裝置一樣,需要載入特殊驅動,而不是使用和真實硬體一樣的驅動。
下圖表示了虛擬機器如何通過宿主機訪問網路:
其中虛擬機器與物理機的資料互動如下圖:
虛擬機器與物理直接的資料互動通過環形記憶體的讀寫來完成。對環形記憶體的讀寫操作由VMBus來完成
下面我將通過這個例子來闡述虛擬機器是如何與物理機進行互動的:
虛擬機器使用者通過 foo.exe 發起一個 TCP 請求;
請求傳遞到虛擬機器的網絡卡驅動;
虛擬機器網絡卡驅動會通過 VMBus 將請求封裝成一個數據包傳遞到環形記憶體中;
在虛擬機器寫滿一個環形記憶體或將資料成功寫入環形記憶體後;
虛擬機器使用Hypercall指令通知宿主機,資料已經寫完,併產生一個VM-Exit事件陷入 Hyperisor 層執行程式碼;
這時 Hyperisor 層就會出發註冊在 WIndows 核心中的中斷例程(IDT);
Windows 核心接收到訊息後會根據虛擬裝置型別呼叫相對應的函式讀取環形記憶體的資料,並將資料分發到相對應的虛擬裝置驅動;
這樣資料就由虛擬機器成功的轉移到物理機,相關的虛擬驅動會繼續解析資料;
由物理機傳入資料至虛擬機器道理相同;
更細緻的分析可以參考 ofollow" rel="nofollow,noindex" target="_blank">文章 。
0×02 測試
根據 Joe Bialek 和 Nicolas Joly 在大會中所講,對於 Hyper V 的攻擊主要在於對宿主機核心態的攻擊,大部分情況是會造成宿主機的崩潰。
有關使用者態的攻擊通常無法造成實質的效果,一般情況會直接被異常接管,漏洞利用及其複雜。
以下是宿主機核心態元件:
VMSwitch.sys:提供半虛擬化網路 StorVSP.sys:提供半虛擬化儲存 VID.sys:虛擬化設施驅動 WinHVr.sys:核心到hypervisor的介面(hypercall) VMBusR.sys:VMBUS,負責guest與host的通訊 vPCI.sys:半虛擬化PCI
根據看雪ID: ifyou 的介紹,在使用者態中的 vmuidevices.dll 元件也非常容易受到攻擊,這是一個負責圖形顯示的元件,由於圖形顯示的程式編寫及其複雜,所以這裡出問題的概率也會大很多。
在 Blackhats 大會中 Joe Bialek 者闡述了一個通過VMSwitch.sys元件實施逃逸的案例。這個時候我們要詳細的講解一下有關於虛擬機器與宿主機的資料互動細節。 在原理中我們講到,宿主機與虛擬機器的資料互動通過VMBus控制,但資料並不直接由VMBus進行傳輸,而是快取到兩者之間的環形記憶體中,環形記憶體至少存在兩個 一個請求記憶體,一個應答記憶體。
下面是虛擬機器與宿主機通過環形記憶體進行資料互動的過程:
虛擬機發送的資料通過VMBus傳入請求記憶體。
隨後通過Hypercall的方式通知宿主機提取資料,產生VM-EXIT事件陷入Hyperisor層 當宿主機收到訊息後會觸發中斷,隨後讀取請求記憶體的資料,分發給相關的虛擬驅動程式處理。
資料處理完成後向虛擬機發送處理成功的訊息,並將應答給虛擬機器的資料,寫入應答記憶體。 虛擬機器讀取應答記憶體的資料並回應一個訊息告訴宿主機,應答資料有有效。
宿主機才會釋放執行緒進入下一個資料包的處理。 當然,環形記憶體是會被分割成數個子區。環形記憶體的描述(GPADL)由虛擬機器提供,環形記憶體中的子區大小及其數量由宿主機自適應。
舉個例子:一個應答環形記憶體的地址以及大小由虛擬機器使用GPADL通知宿主機,宿主機會根據GPADL;裡提供的指標索引到應答環形記憶體的地址,然後根據GPADL中提供的記憶體大小去調整這一段環形記憶體中子區的大小並生成適配這一段記憶體的子區分配表。
Blackhats 大會中 Joe Bialek 利用的漏洞便是針對於資料互動中,更新記憶體指標與更新和生成這一記憶體中的子區大小及其數量,並非是原子操作(中間存在時間差),提出了競態攻擊。
攻擊設想:
我們可以控制宿主機接收的資料;
我們傳入的資料可以通過竟態完成越界;
越界之後的資料可以執行可控的攻擊。
作者通過RNDIS control message responses來控制寫入的內容。這是虛擬機器要傳送的網路資料經過虛擬機器核心態中的虛擬網路元件構成的 RNDIS 協議資料。
第二點實際上是要求我們控制資料包的延遲,已達到,應答記憶體的指標已經更新到新的記憶體地址,但宿主機上依然保留著舊記憶體地址的子區分配表,這樣就會存在,子區分配表中所描述的總記憶體大小與新記憶體的大小不匹配,這會給我們的越界操作提供越界記憶體。
首先虛擬機發送GPADl通知記憶體地址指標,宿主機收到通知後更新記憶體指標,指到新的記憶體地址,但依舊保留這舊的子區分配表:
然後宿主機根據GPADL中通知的此段記憶體的大小分配子區大小及其數量,生成新的子區分配表:
生成子區分配表後,執行更新動作,將適配與原先記憶體的子區分配表量更新到新的子區分配表:
如圖所示,在更新記憶體指標之後,宿主機還沒有更新子區分配表,子區記憶體的總大小是和新記憶體不適配。這樣就存在越界記憶體 ,給我們提供了存放攻擊載荷的空間。
針對於 RNNIS 協議的資料,宿主機在處理完虛擬機發送的資料後會將應答給虛擬機器的資料封裝成 cmplt 資料包,宿主機處理完資料後傳送處理成功的訊息,並將 cmplt 包 寫入應答記憶體,等待虛擬機器的迴應。如果虛擬機器沒有迴應,則負責處理該資料的執行緒會一直處於等待狀態,不會處理下一個資料。
當然,並不是每一個 cmplt 包都會被虛擬機器迴應,比如畸形的 cmplt 資料包。
所以我們可以通過虛擬機器傳遞給宿主機的 RNDIS 協議資料 使宿主機在處理資料後生成畸形的 cmplt 資料包。達到阻塞宿主機資料處理執行緒,造成延遲。 而後在N個畸形資料包後附加一個有效的並帶有攻擊載荷的cmplt資料包,使其在宿主機更新到新的應答記憶體過程中,指標已經偏移但子區分配表未更新時,寫入越界記憶體。
當然,最重要的,是我們在成功越界寫入之後,所造成的效果是否是我們可控的,否則將無法造成有效攻擊。
Jordan Rabet 在大會中做出進一步分析。MDL(表明虛擬記憶體緩衝區的物理頁面佈局)對映到實體記憶體,而這些MDL會被對映到SystemPTE區域。這個區域裡通常存放的是其他的MDL以及核心棧。
毫無疑問,我們將核心棧作為我們攻擊的目標,Win的核心棧一共有7頁,6個頁面作為棧空間,而最後一個頁面在底部作為guard page。
那麼問題來了,我們怎樣將核心棧放到我們可以操控的越界空間中,並且我們怎樣開啟一個執行緒去執行這一步操作。
先來看一下 SystemPTE 分配記憶體的流程:
它基於一個可以進行擴充套件的Bitmap 分配以及檢索記憶體空間;
每個位代表一個頁面的狀態 位0表示空閒頁,1表示已分配使用 記憶體分配基元 進行記憶體分配;
從記憶體分配基元 的位置開始掃描 Bitmap;
如果需要改變某一個記憶體空間的狀態,則包裝此空間並改變此空間的位;
記憶體分配基元 會放在成功分配的空間的尾部;
如果找不到空閒記憶體,則展開Bitmap 搜尋新的可用記憶體。
下面是它分配頁面的一個示例:
由此發現,我們需要一個可控的記憶體分配基元幫助我們在 SystemPTE 區域中佈局記憶體。 但其實,回顧環形記憶體的特性,虛擬機器可以任意構造大小和數量不限的MDL(他們存在上限,但是非常高,以至於我們不用考慮大小的問題)。
由於宿主機應答給虛擬機器的資料(NVSP_MSG1_TYPE_REVOKE_RECY_BUF和NVSP_MSG1_TYPE_REVOKE_SEND_BUF)是可以被撤回的,當然這是一個bug,當多個撤銷訊息被處理時,除了最後一個工作執行緒,其它的工作執行緒都會被永久死鎖,但我們依然有方法通過這樣的機制去撤回記憶體的使用。
因此我們就有了記憶體分配和釋放的基元來幫助我們操控這個區域。但我們還需要生成新的核心棧,以便我們越界後對其進行操控。所以我們還需要堆疊分配基元。
vmswitch依賴系統工作執行緒執行非同步任務,這些執行緒被放在核心維護的執行緒池中,於是我們可以通過向裡邊新增執行緒的方式獲取這一核心棧的許可權。只有所有的程序都在忙的時候,我們才可以向裡邊加入新的執行緒。所以我們要做到:
多次快速的觸發非同步任務。如果產生的任務足夠快,那麼就會有新的執行緒加入。有幾類vmswitch訊息依賴於系統工作執行緒,例如我們使用的NVSP_MSG2_TYPE _SEND_NDIS_CONFIG。 漏洞發掘者 在實驗的過程中利用這樣的方法建立了5個執行緒。
如果無法建立更多的執行緒,說明執行緒池中已經有了足夠多的執行緒。這個時候我們就要通過上述的撤回bug,鎖死其他執行緒,造成一個受限的執行緒棧噴射。(在shellcode的前面加上大量的slide code(滑板指令),組成一個注入程式碼段。然後向系統申請大量記憶體,並且反覆用注入程式碼段來填充。這樣就使得程序的地址空間被大量的注入程式碼所佔據。)
這樣我們便可以生成一個靠近應答記憶體的核心棧:
核心的保護機制,能使任何堆緩衝區溢位到記憶體區域的末尾。我們需要多次申請記憶體以最大限度的接觸到 SystemPTE 的末尾,如圖中所示,實驗過程中,可替換的應答資料與核心棧距離已經很接近了。
最後的最後我們需要繞過空間地址隨機化防禦Bypassing KASLR。
這裡用到一個資訊洩露的漏洞,造成資訊洩露漏洞的結構體是nvsp_message,它在棧上進行分配,它只初始化了前8個位元組,卻返回了sizeof(nvsp_message)大小,因此32位元組未被初始化的棧記憶體會被髮回給guest,造成資訊洩露。通過洩露的資訊,我們能夠獲得vmswitch的返回地址,進而構造rop鏈。
綜上,整個利用過程便是:
使用infoleak定位vmswitch的返回地址;
使用資訊建 ROP 鏈,但由於我們並不知道我們破壞的是那個堆,所以我們要構建一個ROP NOP-sled 這意味著我們要連續執行一串RET 指令;
通過 SystemPTE massaging生成可控核心;
使用競爭條件覆蓋宿主機原 核心執行緒堆疊與ROP鏈;
在宿主機上執行ROP。
防禦手段:
上述漏洞只出現在Windows Server 2012 R2 中 Win 10並沒有這類漏洞。
虛擬機器管理程式強制執行的程式碼完整性(HVCI);
攻擊者無法將任意程式碼注入主機核心;
核心模式控制流保護(KCFG);
攻擊者無法通過劫持函式指標來實現核心ROP;
這裡作者採用的防禦手段是將核心棧遷移到單獨的區域,實現隔離。
後記
搭建 Windbg 雙機除錯時,網路雙機除錯失敗,依然是通過串列埠進行雙機除錯(延遲略高,除錯了一天。。)
Vmware 14.0 可以載入Windows Server 12 虛擬機器映象(VM12 不支援),如果採用虛擬機器巢狀,記憶體制少分配8G
測試 Joe Bialek 在大會上演示的逃逸漏洞,需要多次除錯,本人在測試中多次崩潰(操控SysPTE記憶體。。。不要灰心,你總能成功!
*本文作者:Murkfox,本文屬 FreeBuf 原創獎勵計劃,未經許可禁止轉載。