作者: 林以、高凝@螞蟻安全實驗室
原文鏈接:https://mp.weixin.qq.com/s/1KYTZynabBqzNjoJhe1bWw

在今年的Black Hat Asia上,螞蟻安全實驗室共入選了5個議題和3個工具。本期分享的是螞蟻光年實驗室的議題《清道夫:誤用“錯誤處理代碼”導致的QEMU/KVM逃逸》。

本研究設計了針對QEMU Hypervisor系統中錯誤處理代碼的導向性模糊測試技術(Directed Fuzzing),利?距離引導(Distance-guided)的策略使模糊測試(Fuzzing)遍歷所有的錯誤處理代碼。通過Fuzzing發現了?個存在于錯誤處理上下?的漏洞,該漏洞的直接后果會導致釋放?塊未初始化的內存,我們將其命名為“清道夫”(scavenger)。

由于該漏洞的類型并不常規,并且當前針對虛擬機逃逸的漏洞案例?常少,可供參考的材料?缺,導致該漏洞實際利?的難度極其?,我們很難在QEMU代碼本身中找到有效的數據結構來完成漏洞利?所需的內存布局。由此,我們提出了?種新型的跨虛擬機內存域攻擊,這是在本領域?次提出這個概念,根據?戶天然地完全可控客戶機內存的特性,利?客戶機內存來構造任意讀寫的原語(primitive),從?輔助主機進程進?內存布局劫持控制流,最終完成漏洞利?實現虛擬機逃逸,從客戶機端獲取主機的完全控制權限。

01 背 景

1.1 QEMU

QEMU是一個通用的、開源的機器仿真器和虛擬機。它支持多種體系結構,如Ia32、x86-64、mips、 sparc、arm、risc-v等。此外,它還包括大量的仿真設備,包括NVMe控制器。同時,QEMU在安全研究中有著廣泛的應用,如物聯網固件仿真、用于黑盒Fuzzing的afl-QEMU、動態插樁平臺等。隨之而來的是,QEMU有很多攻擊?,特別是設備仿真,因為他允許攻擊者從客戶機向主機寫入數據。高質量的漏洞允許攻擊者從VM中逃逸出來控制主機。

1.2 NVMe虛擬設備

NVMe?于提供虛擬固態硬盤(SSD)服務,為 PCIe SSD的來賓和主機之間的通信定義了?個優化的寄存器接?、CMD和功能集。NVMe對 SR-IOV等 I/O虛擬化體系結構提供了?效的?持,這使得它在SSD設備仿真中越來越流?。QEMU中也同樣?持了對 NVMe設備的模擬。

02 針對錯誤處理代碼的導向性Fuzzing

2.1 動機

我們的?作受啟發于CVE-2020-25084漏洞,其原因是誤?錯誤處理代碼。該漏洞成因是出現在設置USB數據包時,設備沒有檢查 usb_packet_map函數的返回狀態,?在該函數內部存在?段釋放內存的錯誤處理代碼。

假如未判斷usb對象是否被釋放,則在后續使?對象的過程中會導致uaf漏洞。QEMU官?的 patch是通過添加錯誤檢查代碼來修復此漏洞,以便在usb_packet_map函數申請失敗時停?處理usb請求。此漏洞會導致拒絕服務并可能被逃逸利?。

2.2 設計

我們經過調查發現QEMU中的錯誤處理代碼可以分為以下?類:調試報告和資源釋放。其中調試報告代碼只執?調試報告,不操作運?時資源,此類別不會對QEMU造成內存損壞的有害?為。?資源釋放類別可以進?步分為釋放內存或?件處理程序,以及釋放鎖類別。對于內存釋放類別的錯誤處理代碼,如果調?者不檢查返回值/狀態并濫?釋放的內存,此?為可能會導致uaf漏洞。對于鎖釋放類別的錯誤處理代碼,如果不謹慎使?則可能導致競爭條件漏洞。

我們觀察到?多數的錯誤處理都是由goto跳轉?來。因此,我們設計了如下針對錯誤處理代碼的導向性 Fuzzing技術:

1.定位到代碼中的goto語句,通過反向切?分析,得到goto的調?者和goto語句體;

2.我們使?AFLGo作為我們的Directed Fuzzing引擎,在步驟1收集的信息被?來作為反饋給AFLGo;

3.在預處理階段,goto語句的代碼體是我們模糊處理過程的?標點,?于計算種?距離。在 Fuzzing loop過程中,我們基于距離引導策略來執?調度,并考慮到調?路徑覆蓋率,使 Fuzzing過程遍歷所有的錯誤處理代碼及其調?路徑。

03 漏洞分析

通過上述Fuzzing過程我們發現了 Scavenger漏洞,我們利?該漏洞在2020天府杯原創漏洞演示賽上完成QEMU?默認設備逃逸。該漏洞的基本信息如下:

· 名稱:清道夫(Scavenger)

·類型:NVMe設備的未初始化釋放(Uninitialized Free)漏洞

· 影響版本:QEMU-5.1.0及以前版本

· 漏洞利?環境:主機Ubuntu20.04, 客戶機Ubuntu20.04, 保護全開(NX,ASLR, PIE等)

此漏洞位于nvme_map_prp函數中,函數有兩種初始化類型,類型1是iovec,類型2是 sglist,但是在錯誤處理中只針對sglist類型進?釋放。這種錯誤處理代碼的誤使?導致 malloc/free對不?致。

例如, 該函數預期是初始化類型 2 sglist,如果它進?錯誤狀態,它將轉到unmap label以釋放qsg結構。但是實際上,我們可以控制程序讓其?先初始化類型1 iovec,然后轉到錯誤處理代碼來釋放qsg結構。此時qsg是處于未初始化狀態,這會導致未初始化Free漏洞。

04 漏洞利用

4.1 思路分析

?先,讓我們看看這個錯誤處理函數 qemu_sglist_destroy中發?了什么。如上所述,通過不?致的malloc/free對,這?的參數 qsg并沒有初始化過。這個函數存在著?個危險的操作 - 釋放了這個未初始化結構中的第?個元素sg。雖然這是?個未初始化的變量,但其實它是可以被初始化的,因為攻擊者可以控制執?環境并在相應的內存位置放置惡意構造的數據。這意味著,如果攻擊者可以控制未初始化的內存,則可以達到?個任意地址Free的效果。

我們需要確認這個未初始化結構是否可以被攻擊者控制。所以我們想知道qsg是從哪?來的?通過審計源代碼并搜索找到nvme_map_prp函數的引?,我們發現有三個可以觸發未初始化 Free漏洞的函數的代碼路徑。它們分別屬于不同的函數調?棧:分別由 nvme_dma_read_prpnvme_dma_write_prp以及 nvme_rw調??漏洞函數。

在前兩條路徑中qsg屬于棧上的未初始化變量,但是由于NVMe設備的功能相對?較簡單, 我們在回溯函數調?鏈時,并沒有在對應偏移的棧上找到?戶可控的數據,也就意味著棧上的未初始化在這?是不可控的。

我們最終鎖定在第三條路徑,在該路徑上qsg屬于堆上的未初始化變量。其在nvme_init_sq中申請了全局堆變量io_req,其中qsg成員并未被初始化。然后在后續?到漏洞函數時會 使?該全局變量,觸發路徑為:nvme_process_sq->nvme_io_cmd->nvme_rw->nvme_map_prp。由于該變量是位于堆上,我們便可以使?堆??之類的技術控制其內容的。

現在給定了堆未初始化Free漏洞,我們需要確定具體去釋放什么對象。?個很直觀的想法是將未初始化Free轉換成UAF。這需要我們?先能找到?個?戶控制的結構體。在使?之前事先填充好結構 - 將 qsg偏移對應的字段指向在堆中可控的?個對象。然后再利?漏洞觸發未初始化Free,我們就能Free掉?個正在使?的對象,相當于得到了?個UAF。但事實上找到這樣的原語并不容易。我們?先需要?個N*0xa0??的結構,結構體的0x40偏移處有?個指針,同時該指針必須指向?個?戶可控的對象。最重要的是,該對象的分配和使?之間應該有?個時間窗?,這樣我們才能再對象被釋放后再次去使?它。

經過多次嘗試,我們并沒有找到這樣的原語,因為上述的限制太過于嚴苛。例如,我們發現 NVMe和其他傳統設備以及?些復雜設備的結 構都不能滿?這?要求。QEMU中的?多數結構不是?戶可控的,可以說 QEMU的原語是相當有限的。

然?,我們在 Virtio gpu設備中發現了?個有趣的結構。該設備在 virtio_gpu_create_mapping_iov 函數中分配了?個地址映射表。這張表是由指針和?度組成的序列。該結構的??可控,有指針成員,看起來是?個不錯的結構。但不同的是,這?的指針指向guest空間,是由dma_memory_map映射?來的,其代表 的是將guest物理內存映射到host的虛擬地址,通過該地址QEMU可以直接在host進程中去操縱guest內存。

由Virtio-gpu的映射表所啟發,或許我們不需要在主機進程上尋找?個?戶可控的讀寫原語。對于QEMU進程來說,客戶機內存和堆內存都是map出來的?段地址空間,?定程度上來說客戶機內存也可以視作是?種堆內存。客戶機內存既映射到QEMU主機進程,也由客戶機VM所控制,我們可以認為它是主機和客戶機之間的共享內存。

主機進程對該物理映射區域所做的任何更改都會作?于客戶機內存中。同時對于客戶機來說,其本身是可以在任意時間任意讀寫?身內存的。那么如果我們直接在客戶機空間中釋放?個偽造的堆塊 會怎么樣呢?

4.2 跨虛擬機內存域攻擊

由此,我們提出了跨虛擬機內存域攻擊 - 根據?戶天然地完全可控客戶機內存的特性,利?客戶機內存來構造任意讀寫的原語(primitive)。具體步驟如下:

1.在本環境中客戶機和主機均為Ubuntu,我們便只需在客戶機空間正常malloc操作就能得到偽造堆塊;

2.預先填充好堆內存,其中指針指向客戶機空間的偽造堆塊;

3.申請未初始化的req結構體;

4.觸發未初始化Free,這樣QEMU進程便會把客戶機中的偽造堆塊添加到主機堆空間的Freelist中了;

5.由于攻擊者在客戶機空間天然具有讀寫權限,此時對該偽造堆塊操作就能達到UAF的效果。

4.3 完整利用鏈

現在已經將未初始化Free漏洞利?轉換成 UAF的漏洞利?,剩下的就是常規UAF的利?思路了。?先,我們需要找到?個信息泄漏繞過 ASLR。然后我們需要操縱堆布局來劫持控制流。最后執?任意命令,在主機上執?代碼。

1.堆噴

?先,我們需要做?些堆噴,以獲得穩定的系統堆布局。我們頻繁調?nvme_init_sq函數來噴 射?量的塊來清空tcache Freelist。通過這種?式,我們可以防?接下來釋放的塊合并到?塊中,以得到更可靠的利?。

2.繞過ASLR

· Guest空間申請?個0x290的偽造堆塊;

· Host空間申請Virtio-gpu的映射表,其中對應的指針指向偽造堆塊;

· 釋放映射表,在堆中留下預先設置好的狀態;

· 申請io_req結構體,其中未初始化的域正好指向偽造堆塊;

· 利?漏洞釋放掉偽造堆塊,主機將把它當作?個正常的堆塊,并將其加?tcache bins中;

· 釋放的偽造堆塊中將留下主機的堆地址,在客戶機中得到堆地址泄漏;

· 在主機中再次申請?個0x290的映射表,這塊表將會被分配到客戶機中(從 tcache bins頭部取出);

· 客戶機再次讀該堆塊得到physical map地址泄漏;

· nvme_init_sq中存在分配timer的原語,我們重復上述步驟將QEMU timer分配到客戶機空間中;

· 客戶機讀timer中的cb函數指針得到 QEMU binary地址泄漏。

3.劫持控制流

· 根據QEMU binary地址,計算得system函數偏移;

· 修改timer的cb函數指針為system地址,opaque 參數為“;gnome-calculator”;

· 運?定時器,控制RIP。

05 總結

先前的QEMU逃逸相?,Scavenger有三點不同之處。在攻擊?上,先前的逃逸漏洞CVE-2020-14364位于USB模塊,CVE-2019-14378和 CVE-2019-14835位于Slirp模塊,CVE-2019-5049位于AMD ATIDXX64.DLL driver,? Scavenger位于 NVMe存儲設備。

在漏洞類型上,?乎所有已知的漏洞是由于緩沖區溢出或者UAF引起的,然?Scavenger是錯誤處理代碼中的未初始化Free漏洞,這需要在漏洞利???有更多的直覺和技巧。在漏洞利?技術上,現有的利?技術?乎都是試圖在主機進程上找到可控數據,并構造攻擊原語,如任意讀寫。相反,我們利?客戶機內存來輔助主機的內存布局,通過跨 hypervisor域操縱內存,為我們提供了 讀/寫原語。

跨虛擬機內存域攻擊為我們提供了?個全新的攻擊維度。這種跨虛擬機內存域攻擊技術是通?的,只要攻擊者有漏洞可以任意控制要Free的對象,那么攻擊者就可以達到遠程代碼執?(RCE),實現虛擬機逃逸。在本環境中主機和客戶機均為 Ubuntu,這為我們偽造堆塊帶來了便利,因為 Linux中的基本堆塊沒有加密。但是如果堆頭是加密的,攻擊難度就會更??些,例如在 Windows中。我們相信這種攻擊也會影響其他Hypervisor程序,如 VirtualBox、VMware。


Paper 本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/1619/