開源庫中的漏洞發現:分析 CVE-2020-11863
開源項目是任何軟件開發過程的基礎。隨著越來越多的產品使用開放源代碼,整體攻擊面的增長是不可避免的,尤其是當開放源代碼在使用前未經審核時。因此,建議徹底測試它的潛在漏洞,并與開發人員合作修復它們,最終緩解攻擊。研究人員還指出,正在研究Windows和Linux中的圖形庫,并報告Windows GDI和Linux矢量圖形庫libEMF中的多個漏洞。審核許多其他Linux圖形庫,因為它們是舊代碼,以前沒有經過嚴格的測試。
在第1部分中,通過概述libEMF庫中報告的漏洞,詳細描述了開源研究的重要性。我們還強調了使用內存清理器編譯代碼的重要性以及它如何幫助檢測各種內存損壞漏洞。總而言之,地址清理器(ASAN)截取了諸如malloc()/ free()之類的內存分配/釋放函數,并使用各自的填充字節(malloc_fill_byte / free_fill_byte)填充了內存。它還監視對這些內存位置的讀取和寫入,從而幫助檢測運行時的錯誤訪問。
研究人員對報告的漏洞之一CVE-2020-11863進行了更詳細的分析,該漏洞是由于使用了未初始化的內存而引起的。此漏洞與CVE-2020-11865有關,CVE-2020-11865是libEMF中GlobalObject :: Find()函數中的全局對象向量,超出了內存訪問范圍。但是,崩潰調用堆棧原來是不同的,這就是為什么我們決定進一步研究這一問題并制作此深入探討博客的原因。
ASAN提供的信息足以在模糊器外部重現漏洞崩潰。從ASAN信息來看,該漏洞似乎是空指針取消引用,但這不是實際的根本原因,正如研究人員將在下面討論的那樣。

查看調用堆棧,看來應用程序在動態轉換對象時崩潰了,這可能有多種原因。在似乎可能的那些可能原因中,應用程序試圖訪問不存在的虛擬表指針,或者從函數返回的對象地址是應用程序崩潰時訪問的通配地址。獲取有關此崩潰的更多上下文,我們在調試時遇到了一個有趣的寄存器值。下面顯示了反匯編中的崩潰點,指示不存在內存訪問。

如果我們查看崩潰點處的寄存器狀態,則特別有趣的是,注意到寄存器rdi的異常值是0xbebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebe 我們想更深入地研究一下該值如何進入寄存器,從而實現對內存的訪問。由于有了庫的源代碼,我們可以立即檢查該寄存器在訪問內存中對象方面的含義。

參考Address Sanitizer文檔,事實證明ASAN 默認情況下會將0xbe寫入新分配的內存,這實際上意味著已寫入此64位值,但未初始化內存。ASAN將此稱為malloc_fill_byte。它也通過在釋放內存時用free_fill_byte填充內存來實現相同的目的。這最終有助于識別內存訪問錯誤。

ASAN的這一特性也可以在這里的libsaniizer源代碼中得到驗證。下面是源文件的摘錄。

查看崩潰點處的堆棧跟蹤,如下所示,崩潰發生在SelectObject()函數中。該部分代碼負責處理增強型元文件(EMF)文件的EMR_SELECTOBJECT記錄結構,傳遞給該函數的圖形對象句柄為0x80000018。我們想研究代碼流,以檢查這是否直接來自輸入EMF文件,并且可以由攻擊者控制。

在SelectObject()函數中,在處理EMR_SELECTOBJECT記錄結構時,將GDI對象的句柄傳遞到GlobalObjects.find(),如上面的代碼片段所示,該代碼片段又通過掩蓋較高的順序來訪問全局庫存對象向量GDI對象句柄中的第1位并將其轉換為索引,最終使用轉換后的索引號從對象向量返回庫存對象參考。庫存對象枚舉指定可以在MS文檔中記錄的圖形操作中使用的預定義邏輯圖形對象的索引。例如,如果對象句柄為0x8000018,則將其與0x7FFFFFFF進行“與”運算,得出0x18,它將用作全局庫存對象向量的索引。然后,將這個庫存對象引用動態地轉換為圖形對象,然后調用EMF :: GRAPHICSOBJECT成員函數getType()確定圖形對象的類型,然后,在此函數的后面,將其再次轉換為合適的圖形對象(畫筆,筆,字體,調色板,擴展名),如下面的代碼片段所示。

EMF :: GRAPHICSOBJECT是從EMF :: OBJECT派生的類,并且EMF :: OBJECT類的繼承圖如下所示。

但是,如前所述,我們很想知道作為參數傳遞給SelectObject函數的對象句柄是否可以由攻擊者控制。為了獲得相關信息,讓我們看一下EMR_SELECTOBJECT記錄的格式,如下所示。

正如我們在這里注意到的,ihObject是4字節無符號整數,用于指定庫存對象枚舉的索引。在這種情況下,庫存對象參考保存在全局對象向量中。在這里,對象句柄0x80000018表示索引0x18將用于訪問全局庫存對象向量。如果在此期間對象向量的長度小于0x18,并且在訪問對象向量之前未進行長度檢查,則將導致超出范圍的內存訪問。
下面是處理EMR_SELECTOBJECT圖元文件記錄的直觀表示。

在調試此問題時,我們在GlobalObjects.find()處啟用了一個斷點,并繼續操作,直到獲得對象句柄0x80000018; 本質上,我們到達了上面突出顯示的EMR_SELECTOBJECT記錄的處理點。如下所示,對象句柄被轉換為索引(0x18 = 24)以訪問大小為(0x16 = 22)的對象向量,從而導致了超出范圍的訪問,我們將其報告為CVE-2020-11865。

進一步進入代碼,它進入STL矢量庫stl_vector.h,該庫實現std :: vectors的動態擴展。由于此時的對象向量只有22個元素,因此STL向量會將向量擴展為突出顯示的參數所指示的大小,并通過傳遞的索引訪問向量,并返回該對象引用處的值,如圖所示。下面的代碼段,被ASAN填充為0xbebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebebe

該代碼使用std:allocator來管理主要用于內存分配和釋放的向量內存。通過進一步分析,可以發現在這種情況下返回的值0xbebebebebebebebebebe不存在是庫存對象的虛擬指針,該對象在動態轉換期間被取消引用,從而導致崩潰。
結論
雖然在產品中使用第三方代碼肯定可以節省時間并提高開發速度,但潛在地,漏洞數量也會隨之增加,尤其是當代碼未經審核并未經任何測試集成到產品中時。對使用的開放源代碼庫進行模糊測試非常關鍵,這可以幫助發現開發周期中的漏洞,并提供在產品交付之前對其進行修復的機會,從而減輕了攻擊。必須加強漏洞研究人員與開放源代碼社區之間的協作,以繼續負責任的披露,使代碼的維護者能夠及時解決這些問題。