Jump to content

自從我們發布UEFI系列文章的最後一篇以來,已經過去了整整一年了。在此期間,固件安全社區比以往任何時候都更加活躍,並發表了一些高質量的出版物。值得注意的樣本包括發現新的UEFI植入物,如MoonBounce和ESPecter,以及最近由Binarly披露的不少於23個高危BIOS漏洞。

在過去的一年裡,我們也在努力尋找和利用SMM漏洞。在花了幾個月的時間後,我們注意到了SMM代碼中一些重複出現的反模式,並對漏洞的潛在可利用性形成了相當好的直覺。最終,在披露了13個這樣的漏洞後,我們成功地結束了2021年的工作,這些漏洞影響了行業中大多數知名的OEM廠商。此外,還有幾個漏洞仍在走負責任的披露流程,應該很快就會公開。

在這篇文章中,我們將為讀者分享與SMM漏洞相關的知識、工具和方法。我們希望,當大家讀完這篇文章時,自己也能挖掘這種固件漏洞。請注意,本文假設讀者已經熟悉SMM術語和內部結構,所以,如果您對這些內容還不夠熟悉的話,我們強烈建議大家先閱讀參考文獻部分列出的資料。現在,讓我們開始吧。

SMM漏洞的分類雖然在理論上,SMM代碼是與外界相互隔離的,但在現實中的某些情況下,非SMM代碼可以觸發甚至影響在SMM內部運行的代碼。因為SMM的架構非常複雜,裡面有很多“活動部件”,所以,因此攻擊面非常大,其中涉及通信緩衝區、NVRAM變量、支持DMA的設備等傳遞的數據,等等。

在下一節中,我們將介紹一些較常見的SMM安全漏洞。對於每種漏洞類型,我們將提供簡短的描述,相應的緩解措施以及檢測策略。請注意,該漏洞清單並不詳盡,只包含SMM環境中特有的漏洞。因此,其中並沒有提及某些非常常見的安全漏洞,如堆棧溢出和重複釋放(double-frees)等漏洞。

系統管理模式調出漏洞(SMM Callout)最基本的SMM漏洞類型被稱為“SMM調出”。每當SMM代碼調用位於SMRAM邊界之外的函數時(如SMRR所定義),就會出現這種漏洞。最常見的調出場景是SMI處理程序:它試圖調用作為其操作的一部分的UEFI啟動服務或運行時服務。擁有操作系統級權限的攻擊者可以在觸發SMI之前修改這些服務所在的物理頁面,從而在受影響的服務被調用後劫持特權執行流程。

1.png

圖1 SMM調出漏洞示意圖

緩解措施最明顯的方法,就是防止寫出這種有問題的代碼;此外,我們也可以在硬件層面提供相應的緩解措施。從第四代酷睿微架構(Haswell)開始,英特爾CPU支持一個名為SMM_Code_Chk_En的安全功能。如果這個安全功能被打開,一旦進入SMM,CPU將被禁止執行位於SMRAM區域之外的任何代碼。您可以將這個功能視為Supervisor Mode Execution Prevention(SMEP)的SMM等價物。

通過執行CHIPSEC的smm_code_chk模塊,可以查詢這種緩解措施的工作狀態。

1.png

圖2 使用chipsec查詢針對SMM調出漏洞的硬件緩解措施

檢測方法針對SMM調出漏洞的靜態檢測方法其實是非常簡單的。在分析給定的SMM二進製文件時,要仔細查找導致調用UEFI啟動或運行時服務的執行流程的SMI處理程序。這樣一來,尋找SMM調出漏洞的問題就被簡化為搜索調用圖中某些路徑的問題。幸運的是,我們根本不需要手動完成這項工作,因為efiXplorer IDA插件已經實現了這種啟發式方法。

正如我們在本系列的前幾篇文章中提到的,efiXplorer能夠提供一站式服務,它已經成為了用IDA分析UEFI二進製文件的事實上的標準方法。它提供了下列功能:

定位和重命名已知的UEFI GUID;

定位和重命名SMI處理程序;

定位和重命名UEFI啟動/運行時服務;

efiXplorer的最新版本使用Hex-Rays反編譯器來改進分析過程。其中一個特點是能夠為傳遞給LocateProtocol()或其SMM對應的SmmLocateProtocol()等方法的接口指針分配正確的類型。

給Ghidra用戶的一點提示:我們還想補充的是,Ghidra插件efiSeek負責上述列表中的所有變更。但是,它不包括efiXplorer所提供的協議窗口和漏洞檢測功能等用戶界面元素。

在完成對輸入文件的分析後,efiXplorer將繼續檢查由SMI處理程序執行的所有調用,從而得到潛在調出的精選清單:

1.png

圖3 efiXplorer發現的調出

1.png

圖4 sub_7F8可以從SMI處理程序訪問,但仍然調用位於SMRAM之外的啟動服務

在大多數情況下,這個啟發式方法工作得很好,但是我們遇到了幾種邊緣情況,這時可能會產生誤報。其中,最常見的誤報是由於使用EFI_SMM_RUNTIME_SERVICES_TABLE所造成的。這是一個UEFI配置表,它暴露了與標準EFI_RUNTIME_SERVICES_TABLE完全相同的功能,唯一顯著的區別是,與它的“標準”對應物不同,它駐留在SMRAM中,因此適合被SMI處理程序所使用。許多SMM二進製文件在完成一些模板式的初始化任務後,經常將全局RuntimeServices指針重新映射到SMM特定的實現:

1.png

圖5 將全局RuntimeService指針重新映射到與SMM兼容的實現上

通過重新映射的指針調用運行時服務,會產生一種乍看之下似乎是調出的情況,儘管仔細檢查會發現並非如此。為了克服這個問題,分析人員應該始終在SMM二進製文件中搜索標識為EFI_SMM_RUNTIME_SERVICES_TABLE的GUID。如果找到這種GUID,則大部分涉及UEFI運行時服務的調出都有可能是誤報。但這並不適用於涉及啟動服務的調出。

1.png

圖6 通過重新映射的RuntimeService指針調用GetVariable()引起的誤報

另一個潛在的誤報來源是各種“雙模式”包裝器函數,這意味著它們可以從SMM和非SMM上下文中進行調用。在內部,如果調用方在SMM中運行,這些函數會派發對SMM服務的調用,否則會派發對等效的啟動/運行時服務的調用。我們在野外看到的最常見的樣本是EDK2的FreePool(),如果要釋放的緩衝區位於SMRAM中,它就調用gSmst-SmmFreePool(),否則就調用gBs-FreePool()。

1.png

圖7 EDK2的FreePool()實用函數是一個常見的誤報來源

正如這個例子所展示的,漏洞分析人員應該意識到,靜態代碼分析技術很難確定某些代碼路徑在實踐中不會被執行,因此,很可能將其標記為調出。在編譯後的二進製文件中識別這種函數的相關技巧和竅門,將在後文中加以介紹。

低址SMRAM損壞類型說明在正常情況下,用於向SMI處理器傳遞參數的通信緩衝區不得與SMRAM重疊。這個限制的理由很簡單:如果不是這樣,那麼每次SMI處理程序將某些數據寫入通信緩衝區的時候——例如,為了向調用方返回狀態代碼時,都會“順帶”修改SMRAM的某些部分,這是不可取的。

1.png

圖8 不應該出現的情況

在EDK2中,負責檢查給定緩衝區是否與SMRAM重疊的函數被稱為SmmIsBufferOutsideSmmValid()。這個函數在每次SMI調用時都會在通信緩衝區上被調用,以便執行這一限制。

1.jpg

圖9 EDK2禁止通信緩衝區與SMRAM重疊

唉,由於通信緩衝區的大小也在攻擊者的控制之下,這種檢查本身並不足以保證全面的保護,因此,一些額外的責任落在了固件開發者的肩上。我們很快就會看到,許多SMI處理程序在這裡出問題了,並留下了一個漏洞,攻擊者可以利用這個漏洞來繞過這個限制,進而破壞SMRAM的低址部分。為了了解為何會出現這種情況,讓我們仔細考察一個具體的例子。

1.jpg

圖10 一個存在安全漏洞的SMI處理程序

上面是現實生活中非常簡單的SMI處理程序。我們可以將其操作分為4步:

檢查參數;

將MSR_IDT_MCR5寄存器的值讀入局部變量;

從中計算一個64位值,然後將結果寫回通信緩衝區;

返回到調用方。

精明的讀者可能知道這樣一個事實,即在第3步中,一個8字節的值被寫入通信緩衝區,但在第1步中,代碼並沒有檢查緩衝區至少8字節長這一先決條件。由於缺乏這項檢查,攻擊者就可以通過以下方式發動攻擊:

將通信緩衝器放到盡可能靠近SMRAM基址的位置(例如SMRAM-1);

將通信緩衝區的大小設置為足夠小的整數值,例如1字節;

觸發易受攻擊的SMI。從原理上講,內存佈局如下所示:

1.jpg

圖11 調用SMI時的內存佈局

就SmmEntryPoint而言,通信緩衝區只有1個字節長,與SMRAM並不重疊。正因為如此,SmmIsBufferOutsideSmmValid()將成功執行,實際的SMI處理程序將被調用。在第3步中,處理程序將盲目地將一個QWORD值寫入通信緩衝區,這樣做會無意中也對SMRAM的低7個字節也執行了寫入操作。

1.jpg

圖12 損壞發生時的內存佈局

根據EDK2,TSEG(SMRAM的事實上的標準位置)的底部包含一個SMM_S3_RESUME_STATE類型的結構體,其工作是控制從S3睡眠狀態的恢復過程。正如下面所看到的,這個結構體包含了大量的成員和函數指針,它們的損壞會使攻擊者受益。

1.jpg

圖13 SMM_S3_RESUME_STATE對象的定義

緩解措施為了緩解這類漏洞,SMI處理程序必須顯式檢查提供的通信緩衝區和bailout的大小,以防實際大小與預期大小不同。這可以通過下列方式實現:

取消對所提供的CommBufferSize參數的引用,然後將其與預期的大小進行比較。該方法之所以有效,是因為我們已經看到SmmEntryPoint調用了SmmIsBufferOutsideSmmValid(CommBuffer, *CommBufferSize),以保證緩衝區的*CommBufferSize字節位於SMRAM之外。

1.jpg

圖14 可以通過檢查CommBufferSize參數來緩解低址SMRAM損壞漏洞

再次調用通信緩衝區上的SmmIsBufferOutsideSmmValid(),這一次使用處理程序所期望的大小。

檢測方法為了檢測這類漏洞,我們應該尋找那些沒有正確檢查通信緩衝區大小的SMI處理程序。這表明該處理程序沒有執行以下任何一項:

取消對CommBufferSize參數的引用。

在通信緩衝區上調用SmmIsBufferOutsideSmmValid()。

條件1很容易檢測,因為efiXplorer已經能夠定位SMI處理程序並為它們分配正確的函數原型。條件2也很容易驗證,但關鍵是:由於SmmIsBufferOutsideSmmValid()是靜態鏈接的代碼,所以,我們必須能夠在編譯後的二進製文件中識別它,相關的技巧和竅門將在後面加以介紹。

任意SMRAM損壞類型說明雖然在我們對SMM漏洞的分析中肯定是向前邁進了一大步,但之前的漏洞類別仍然存在幾個重要的限制,使得它們在現實生活場景中難以利用。一個更好、更強大的利用原語將允許我們破壞SMRAM中的任意位置,而不僅僅是那些毗鄰底部的位置。

這樣的利用原語通常可以在其通信緩衝區包含嵌套指針的SMI處理程序中找到。由於通信緩衝區的內部佈局事先並不知道,所以,SMI處理程序本身需要對其進行正確的解析和淨化處理,這通常歸結為對嵌套的指針調用SmmIsBufferOutsideSmmValid(),如果其中一個指針恰好與SMRAM重疊,就跳出。我們就可以在EDK2的SmmLockBox驅動中可以找到一個正確檢查這些條件的教科書式的例子。

1.jpg

圖15 SmmLockBoxSave的子處理程序,用於對嵌套指針進行淨化處理

為了向操作系統報告SMM已經實現了哪些最佳實踐,現代UEFI固件通常會創建並填充一個ACPI表,稱為Windows SMM Mitigations Table,或簡稱WSMT。除其他事項外,WSMT還維護一個名為COMM_BUFFER_NESTED_PTR_PROTECTION的標誌,如果存在該標誌,則表明SMI處理程序不會在未經事先淨化的情況下使用嵌套指針。這個表可以使用chipsec模塊common.wsmt進行轉儲和解析。

1.jpg

圖16 使用CHIPSEC來轉儲和解析WSMT表的內容

不幸的是,實踐表明,大部分情況下報告的緩解措施和現實之間的相關性是很小的。即使存在WSMT,並且報告指出所有支持的緩解措施都是有效的,也經常發現SMM驅動程序完全忘了對通信緩衝區進行淨化處理。利用這一點,攻擊者可以用一個指向SMRAM內存的嵌套指針來觸發有漏洞的SMI。根據特定處理程序的性質,這可能導致指定地址的損壞或從該地址讀取的敏感信息。下面,讓我們來看一個具體的例子。

1.jpg

圖17 SMI處理程序沒有對嵌套指針進行淨化處理,使其容易受到內存損壞的攻擊

在上面的片段中,有一個SMI處理程序,它通過通信緩衝區獲得一些參數。根據反編譯的偽代碼,我們可以推斷出,緩衝區的第一個字節被解釋為一個OpCode字段,指示處理程序接下來應該做什麼(1)。我們可以看出(2),這個字段的有效值是0、2或3。如果實際值與這些值不同,將執行默認子句(3)。在這個子句中,一個錯誤代碼被寫到通訊緩衝區第2個字段所指向的內存位置。由於這個字段和通信緩衝區的全部內容都在攻擊者的控制之下,因此,他們可以在觸發SMI之前對其進行如下設置。

1.jpg

圖18 導致SMRAM損壞的通信緩衝區內容

隨著處理程序的執行,OpCode字段的值將迫使它退回到缺省子句,而地址字段將由攻擊者根據他或她想要破壞的SMRAM的確切部分提前選擇。

緩解措施若要緩解此類漏洞,SMI處理程序必須在使用通信緩衝區之前對其傳遞的任何指針值進行淨化。指針驗證可以通過以下兩種方式之一執行:

調用SmmIsBufferOutsideSmmValid函數:正如前面提到的,SmmIsBufferOutsideSmmValid()是EDK2提供的一個實用函數,用於檢查給定的緩衝區是否與SMRAM重疊。淨化外部輸入指針時,這是首先方法。

另外,一些基於AMI代碼庫的UEFI實現並沒有使用SmmIsBufferOutsideSmmValid(),而是通過一個名為AMI_SMM_BUFFER_VALIDATION_PROTORT的專用協議提供了類似的功能。除了調用函數和使用UEFI協議的語義差異之外,這兩種方法的工作方式大致相同。在下一節,我們將為讀者介紹如何將該協議的定義正確導入IDA。

1.jpg

圖19 AMI_SMM_BUFFER_VALIDATION_PROTOCOL是一種操作

檢測方法

檢測這類漏洞的基本思路是尋找不調用SmmIsBufferOutsideSmmValid()或利用等價的AMI_SMM_BUFFER_VALIDATION_PROTOCOL的SMI處理程序。然而,一些邊緣情況也必須被考慮到。如果不這樣做,可能會引入不必要的假陽性或假陰性。

對通信緩衝區本身調用SmmIsBufferOutsideSmmValid()函數:這只能保證通信緩衝區不與SMRAM重疊(詳見低址SMRAM損壞漏洞),但它對嵌套的指針沒有效果。因此,當試圖評估處理程序對rouge指針值的魯棒性時,這些情況不應該被考慮在內。

完全不使用嵌套指針。一些SMI處理程序可能不會調用SmmIsBufferOutsideSmmValid(),僅僅是因為通信緩衝區沒有保存任何嵌套指針,而是保存了其他數據類型,如整數、布爾標誌等。為了區分這種良性的情況和易受攻擊的情況,我們必須清楚了解通信緩衝區的內部佈局。

雖然這可以作為逆向工程過程的一部分手動完成,但幸運的是,如今自動類型重構已經絕非科幻小說,相反,已經存在相應的工具,並且都可以作為現成的解決方案隨時使用。對於這種類型的漏洞來說,兩個最突出和最成功的IDA插件是HexRaysPyTools和HexRaysCodeXplorer。使用這些工具中的任何一個,都可以對原始指針訪問表示法進行相應的轉換,例如:

1.jpg

圖20 使用原始CommBuffer的SMI處理器

變成更友好和更容易理解的點到成員表示法:

1.jpg

圖21 使用重構CommBuffer的SMI處理器

更重要的是,這些插件可以跟踪各個字段的訪問方式。基於訪問模式,它們完全有能力重構包含結構體的佈局,並推斷出成員的數量、大小、類型、屬性,等等。當應用於通信緩衝區時,這種方法可以幫助我們快速查找其中是否含有嵌套指針。

0 Comments

Recommended Comments

There are no comments to display.

Guest
Add a comment...