◆陳煒昊 于子洋
針對Windows下閉源二進制可執行文件熱補丁的研究
◆陳煒昊 于子洋
(中國礦業大學 計算機科學與技術學院 江蘇 221116)
隨著計算機科學技術的飛速發展,越來越多的廠商與作者為保護程序算法知識產權,選擇將自己的代碼程序以閉源的形式發布,導致很多閉源軟件存在不同程度的安全問題。如何對已經產生安全事件的閉源二進制文件進行修復已逐漸成了安全應用的重要課題,本文就此問題進行探討,以Windows x86架構為框架,詳細分析了文件靜態補丁技術的可行性,并給出了針對此問題的基本解決方案,最終達到在無源碼的情況下對文件漏洞修復的目的。
Windows x86;相對虛擬地址;動態鏈接庫
隨著計算機信息技術的快速發展,各行各業的人都加入了軟件開發的行列,他們會將自己的專業知識封裝成各種各樣的軟件,供有需求的人們去使用。開發出來的軟件主要分為兩種:開源軟件與閉源軟件。閉源軟件無法在源代碼的層級上對程序進行修改,刪除,更新等操作。由于開發人員沒有接觸過系統的安全教育以及如何進行底層CVE的發掘,導致大多數的閉源軟件都存在安全隱患。主要包括危險函數的誤用導致的棧溢出、內存管理釋放部分缺失導致的堆溢出等隱患。
本文主要是對Windows下閉源二進制可執行文件的修補方法進行研究,目的是修復無人維護的閉源軟件的安全問題和在官方未發布補丁時的1Day漏洞。
Windows自帶的的可執行文件格式是PE文件格式。PE文件中的內容會被劃分成一個個小塊,這一個個小塊就是Section(節)。Windows可執行程序擁有一個獨特的機制--導入表與導出表。導入表與導出表中保存了PE文件中調用的所有動態鏈接庫的函數,以及這些函數來自哪些動態鏈接庫。Windows加載器在運行PE時會將導入表中聲明的動態鏈接庫一并加載到進程的地址空間,并修正指令代碼中調用的函數地址。這個機制的存在保證了PE文件中的函數可以被重復調用。導入表與導出表機制決定了本小節需要分為以下兩種情況加以討論。
PE文件的導入表與導出表中存在相應函數,可以直接對函數進行調用,對更加安全的函數進行重復調用。在可執行文件調用了危險函數時,可以直接在原text段進行匯編的修改從而達到修復程序的目的。由于不同的函數有不同的參數個數種類等獨特的用法,所以一般情況下需要對函數前后的調用進行部分調整。比如程序中調用了一個gets函數,這個函數會導致堆棧的溢出,為程序帶來不可控的風險。而程序導入表與導出表中加載了read函數,此時需要使用read函數替換gets函數。對比兩個函數原型,可知兩個函數的調用參數不同,所以不可以直接進行簡單的替換,需要對函數傳遞進行適當的調整。在調整部分參數后,才可以使用read函數對gets函數進行整體替換。
對于導入表和導出表未加載同類型可替換的函數,一般在不借助其他方法的情況下,只能進行小修小改。危險函數在沒有同等功能的其他函數可以進行替換的情況下,只能對本身已有函數的參數進行修改,使其盡可能安全可靠。比如printf函數沒有使用規范的格式化字符會導致格式化字符串的漏洞,從而泄露棧中敏感信息。修復printf漏洞時可以在參數傳遞處加入相應的格式化字符參數,以提高整體程序的穩定性。相似的漏洞還有read函數造成的單字節溢出等漏洞。
本小節主要針對一些特定的函數修復,這些函數通常自身存在一些安全隱患,但是功能比較簡單,一般可以直接通過幾條簡單的匯編語言函數功能重現。比如stcopy函數,它在程序中很容易產生棧溢出漏洞,本節就將以它作為例子簡述函數的匯編代碼重寫方法。雖然Microsoft提供了更安全的strcpy_s()函數供編程人員調用,但是一些老舊程序并沒有載入這個函數,所以不能采取上節中的方法對stcopy函數進行直接替換。所以為了讓程序正常工作并不引發錯誤,需要對函數進行重寫以實現拷貝的功能。拷貝函數重寫如圖1。

圖1 拷貝函數重寫
重寫函數匯編字節碼字節數往往會多于原函數,額外空間的獲取就成了研究的關鍵。本節主要探討兩種獲取額外空間的方法,通過同義指令替換獲得額外空間與額外添加新段區。
由于編譯器的原因,每一個PE文件都會出現一些不那么簡潔的匯編指令。如moveax,0等需要傳遞常數的匯編指令,這類指令一般可以被簡化修改為xoreax,eax,進而為整個二進制文件節省出空間以便于插入補丁。
在所需空間很大的情況下,可以考慮為程序添加新的段區。新添段區的一般需要三步,首先需要PE頭部增添塊頭,其次為添加的區塊頭添加數據段,最后修正映像文件即可。這種方法可以為PE文件快速創建一個空的區塊段,以便填入補丁代碼,更改程序流程。
當補丁代碼過于復雜時,可以通過編寫第三方dll并將動態鏈接庫注入二進制文件中的方式來替換危險函數。基本注入流程如圖2所示。
程序調用動態鏈接庫需要先使用LoadLibrary函數對指定的動態鏈接庫進行加載,然后再使用GetProcAddress獲取目標鏈接庫中相關函數的地址。這兩個函數存在于Kernel32.dll中,絕大多數Windows程序都會加載ntdll.dll和kernel32.dll,故我們首先獲取kernel32.dll的基址,相關匯編代碼如圖3所示。

圖2 基本注入流程

圖3 相關匯編代碼

圖4 完成函數的調用
由于函數名字換算成ASCII碼后會占用比較大的空間,這里采用hash的方式對目標函數進行查找,在成功加載動態鏈接庫并獲取函數的地址后,就可以直接對函數進行調用了。在目標調用處插入調用代碼,即可完成函數的調用,如圖4。
將上述匯編編譯成二進制字節碼后,就可對目標程序進行注入,方法與第二三小節基本相同,在函數擁有足夠的空間時,可選擇在源碼處進行動態鏈接庫的加載和函數的調用,空間不足時,可為程序添加必要的段區。
本文主要探討了有關閉源二進制可執行文件的靜態補丁的基本理論方法,主要應用于未加密Windows x86的可執行文件。對于動態加密,程序的自校驗程序還需要做進一步的研究。
[1]何迎生,段明秀. 32位Windows系統下的PE文件結構及其應用[J]. 吉首大學學報(自然科學版),2003(01).
[2]劉科. 計算機科學技術的發展現狀及發展趨勢展望 [J]. 信息記錄材料,2020,21(07).
[3]昝道廣. 計算機科學技術的應用現狀及其發展前景探析 [J]. 通訊世界,2017(6):133.
[4]李晨燕. 計算機網絡信息安全問題策略探討[J]. 電子世界,2020(24).
[5]王眾魁. 探索計算機信息網絡安全技術及發展方向[J].電腦知識與技術,2021,17(08).