跟踪動態內存分配中的內存存儲到目前為止,我們的所有分析都集中在堆棧內存作為信息披露的源緩衝區。這在很大程度上是由於堆棧內存洩漏錯誤的盛行,如KLEAK:實用內核內存洩漏檢測(PDF)中所述。其他內存區域(如堆)呢?我們可以對一些堆內存洩漏建模嗎?
在查找堆內存洩漏時,思路也是一樣的。我們仍在尋找調用具有已知大小值的sink函數。但源指針不是RegisterValueType.StackFrameOffset,我們檢查RegisterValueType.UndeterminedValue。考慮sys_statfs()的代碼:
sys_statfs()中的動態內存分配
此時copyout()中的內核指針rdi_1#2還是不確定,因為Binary Ninja並不知道分配器函數返回什麼。然而,通過使用SSA表單,我們可以手動跟踪rdi_1#2是否保存malloc()的返回值。例如,按上圖中突出顯示的說明進行操作。變量被分配為rax_1#1-r15#1-rdi_1#2。可以使用MLIL get_ssa_var_definition()API通過編程方式獲取此信息。一旦獲得SSA變量的定義位置,我們就可以使用CALL操作檢查變量是否被初始化。
那分析器如何知道分配器函數的定義?我們可以採用與提供靜態函數掛鉤信息相同的方法(請參閱上面的“靜態函數掛鉤和內存編寫API”一節)。向分析器提供一個帶有分配器函數列表和大小參數索引的JSON配置。對於任何具有已知目標(即MLIL_CONST_PTR)的CALL指令,獲取該符號以檢查已知的分配器函數。下面是一個用於分析的JSON配置示例:
一旦我們建立了源指針和分配器調用之間的連接,下一個問題是,將分配什麼指針值作為分配器調用的返回值?在Binary Ninja中跟踪為負偏移量的堆棧指針是這樣的:為了在堆棧指針和堆指針之間具有一個通用表示,我決定將堆分配器調用的返回值設置為分配大小的負值。對於sys_statfs()中的malloc()調用,rax_1#1設置為0x1d8作為起始地址。因此,需要初始化的內存區域的範圍從0x1d8到0不等。即使分配大小不確定,起始地址也可以設置為某些任意值,例如0x10000。最重要的是要知道copyout()訪問的連續內存區域是否已初始化。
使用dominator和後dominator過濾內存存儲下圖中的dominator提供了一些基本塊的執行順序信息。雖然我們已經在“處理指針對齊優化”一節中使用了dominator來處理指針對齊的優化,但本節將詳細介紹dominator在檢測支配流敏感(flow-sensitive)內存存儲操作中的使用。
為了分析未初始化的內存洩露,我們使用了兩種思路:dominator和後dominator。如果到Y的所有路徑都應經過X,則稱基本塊X支配另一個基本塊Y。如果從X到函數的任何返回塊的所有路徑均應經過Y,則稱基礎塊Y支配基本塊X。
dominator和後dominator的圖表
在所提供的圖中,節點B支配節點C、D、E和F,因為到這些節點的所有路徑都必須經過節點B。根據定義,每個節點都會進行自我支配,因此由節點B支配的所有節點集將是B、C、D,E和F。此外,節點A支配圖中的所有節點。因此,節點C、D、E、F的dominator是A和B。
同理,當A為函數入口節點,E和F為出口節點,則節點B為節點A的後dominator。這是因為從A到出口節點的所有路徑都必須經過B。
那麼,dominator和後dominator如何幫助我們進行分析呢?
我們可以對sink函數的調用者執行dominator分析。其思想是只記錄基本塊中的內存存儲,這些基本塊支配調用copyout()的基本塊,也就是說,將執行與分支決策無關的基本塊,代碼如下:
調用copyout()的基本塊的dominator
調用copyout()的基本塊是
在跨函數(inter-procedure)分析期間,對被調用函數進行後dominator分析。它的目的是在初始化它應該返回的內存區域之前,找到被調用者可能返回的漏洞。被調用者函數do_sys_waitid()如下所示:
do_sys_waitid()中函數輸入塊的後dominator
函數入口塊基於dominator和後dominator的分析試圖填補分析器執行的支配流不敏感分析中的空白。其假設是,在執行進一步的操作之前,內存被初始化或清除,因此支配其他基本塊。然而,這種假設並不總是正確的。例如,在某些情況下,單個代碼路徑可以執行與支配器中相同的操作。此外,當被調用者由於任何錯誤條件返回時,調用者可以在調用copyout()之前驗證返回值。因此,在此情況下基於dominator的分析容易出現大量誤報。
檢查未初始化的內存洩露一旦所有的內存存儲操作都被靜態地記錄了關於寫的偏移量和大小的信息,就可以使用copyout()對複製到用戶空間的內存區域進行評估,以進行未初始化的內存公開。 copyout()調用是這樣的:源指針為0x398,複製的大小為0x330字節。因此,分析器必須驗證內存範圍從-0x398到(-0x398 +0x330)的所有字節是否都已初始化,如果沒有,則將其標記為錯誤。
誤報和限制編寫分析器的目的是查找在任何可能的代碼路徑中從未寫入過的內存區域。如果無法跟踪內存存儲操作,則會出現誤報。以下是一些常見的誤報情況和限制情況:
1.分析儀不模擬分支指令。因此,在涉及支配流決策的代碼構造中會出現誤報。考慮一個內存區域,例如在循環操作中初始化的數組。在這種情況下,存儲操作將只檢測一次,因為分析器只訪問循環體一次,而不是像執行期間那樣在循環中訪問。
2.間接調用不會被靜態解析,因此,在間接調用期間執行的任何內存存儲都不會被跟踪。
3.優化可能會使跟踪內存存儲更加困難。在“處理x86 REP優化”和“處理指針對齊優化”部分中處理了一些常見的優化。
4.Binary Ninja可能會錯誤地檢測用於靜態掛鉤或copyout()等接收器函數的類型信息。由於我們的分析依賴於RegisterValueType信息,任何未能準確檢測函數原型的情況都可能導致錯誤的結果。在分析和更新之前驗證類型信息。
5.分析器僅查找內存源函數和sink函數位於同一函數中的代碼模式。在本地函數範圍之外,沒有對內存源的跟踪。
6.dominator分析是實驗性的。你應該僅將其用作執行代碼審查的指導原則。
當可以訪問源代碼時,可以通過更改優化標誌或展開循環來減少分支決策,從而解決其中一些誤報。
分析結果在Binary Ninja中加載目標內核可執行文件,生成BNDB分析數據庫。然後用分析器對數據庫進行分析,以便進行更快的分析。有兩個腳本:一個用於分析堆棧內存洩漏,另一個用於分析具有已知大小和未知源指針的sink函數。因為源指針可以來自堆分配器,所以提供一個帶有分配器函數列表的JSON配置作為參數。 dominator分析是實驗性的。需要時,請使用可選參數啟用它。
總結這些腳本在Binary Ninja版本2.4.2846上針對FreeBSD 11.4、NetBSD 9.2和OpenBSD 6.9內核進行了測試。在結果中,評估了非特權用戶可能訪問的代碼路徑。 OpenBSD漏洞在與IPv4和IPv6組播路由相關的系統中被發現,分別被命名為ZDI-22-073和ZDI-22-012。
在NetBSD中發現的4個漏洞(ZDI-22-075、ZDI-22-1036、ZDI22-1037和ZDI-21-1067)與支持舊版NetBSD向後兼容的系統調用有關的ZDI-22-2075和ZDI22-11036分別是NetBSD 3.0和NetBSD 5.0的VFS系統調用中的信息洩露。另外,ZDI-22-1037是NetBSD 4.3的getkerneinfo系統調用中的一個信息洩漏。目前,此漏洞已修復,但還存在許多其他潛在問題。
在版本11.4中發現的FreeBSD漏洞也與兼容性有關,在本例中,兼容性用於支持32位二進製文件。然而,在對64位inode進行較大更改期間,該漏洞被修復,但沒有被公開。作為64位inode項目的一部分,在copy_stat函數中清除了未初始化的結構字段。雖然此承諾是在2017年5月,但它被標記為12.0及以上版本。因此,該漏洞在11.4版中一直未被修復,直到2021年9月才被處理。
總而言之,大多數漏洞都是在BSD的兼容層中發現的。此外,所有這些漏洞都是堆棧內存洩漏。
Recommended Comments