黃 寧,黃曙光,潘祖烈,常 超
(國防科技大學 電子對抗學院, 安徽 合肥 230000)
隨著信息技術的發展,軟件漏洞的發掘與利用成了一個熱點問題。針對不同類型的漏洞利用技術,各種保護機制也層出不窮。但是,多年的漏洞利用實踐證明,由于各方面條件的限制,依然存在許多可繞過這些保護機制,成功實施漏洞利用的技術手段。
以二進制程序漏洞利用為例,由于計算機無法區分內存空間中二進制碼的代碼或數據屬性,可能導致進程執行外部數據,從而造成控制流劫持攻擊[1]。針對這一問題,Linux和Windows等主流操作系統相繼引入了數據執行保護(Data Execution Prevention, DEP)機制[2]。該機制的基本原理是,通過標記程序內存頁為可執行/不可執行,實現內存空間代碼區和數據區的區分。在DEP環境下,位于數據區的惡意代碼將無法被執行,從而阻止控制流劫持攻擊[3]。
由于DEP機制不會攔截可執行頁面中的代碼指令,solar designer提出了ret to libc(ret2libc)方法。該方法通過劫持程序控制流,使程序跳轉至已有的系統函數。Schacham等[4-5]在ret2libc思想的基礎上,提出了返回導向式編程(Return Oriented Programming, ROP)技術。相比ret2libc,ROP使用更小的匯編指令片段(gadget),提高了該類方法的泛用性。Lu等[6]基于Rix的方法,提出了可壓縮、可打印的ROP構造方法,提高了ROP載荷的靈活性。
近年來出現多種針對控制流劫持類漏洞自動化分析[7]與測試用例自動生成技術[8-10]。Schwartz等提出了面向二進制程序漏洞的ROP自動構造框架Q[11-12]。其工作流程:首先,向Q框架可執行文件,搜索其中具備特定功能的gadget集合;對面向gadget的高級語言進行語義分析,構建中間指令序列;分析中間指令序列,為每條中間指令分配合適的gadget集合,形成ROP鏈。Q框架的局限性[13]在于,該方案生成的測試用例僅從功能實現的角度出發,未考慮ROP布局對程序內存可控性條件的要求,降低了ROP鏈的實用性。
為解決上述問題,本文提出了基于符號執行的ROP碎片化自動布局方法。該方法將ROP鏈以模塊為單位,切割成長度不一的碎片;使用導向式符號執行技術,引導源程序運行至控制流劫持點的同時,檢查程序中可控內存區域是否滿足ROP模塊布局要求;以ROP模塊切片與可控內存區域布局為根據,構建碎片化ROP鏈的數據約束;通過求解數據約束,自動生成滿足程序可控內存分布條件的碎片化ROP鏈。該方法解決了ROP鏈對內存可控性要求高的問題,提高了ROP鏈的實用性。
ROP技術基于ret2libc技術發展而來。該技術主要針對DEP機制未限制代碼頁中已有代碼的執行權限這一缺陷,實現DEP機制繞過[14]。其主要原理是,通過搜索程序內存代碼頁,構建以ret等跳轉指令結尾的匯編指令片段gadget集合。從gadget集合中篩選出符合條件的部分,組合成一段可實現特定功能的ROP代碼鏈。圖1表示了一個ROP鏈的代碼執行順序及其相應的??臻g數據分布。

圖1 ROP鏈在棧內存中布局結構示意圖Fig.1 ROP chain and the structure of stack
Q框架以自定義高級語言ROPL表示ROP鏈的目標功能程序。ROPL高級語言到匯編指令序列的轉換過程為:ROPL高級語言—ROPL中間表示序列—中間指令序列—gadget匯編指令序列(ROP鏈)。
目標程序模塊指的是一個可執行相對獨立功能的ROPL代碼序列的集合,其結構類似于C語言中的函數。
ROPL語言框架下,多模塊目標程序定義為:目標程序包含至少兩個以上模塊,且調用了至少一個以上非main模塊。多模塊ROP中,除main模塊外的ROP模塊的切換過程由以下三個子過程組成:目標模塊開始調用過程,目標模塊參數初始化過程,目標模塊返回調用過程。
已有的ROP自動生成技術主要解決了gadget搜索與分類,高級語言語義分析,以及面向中間指令的gadget分配與排列三個方面的問題,實現了ROP鏈自動構造。但從實際運用效果看,Q框架仍然無法滿足多數場景下源程序的內存可控性狀態對ROP鏈布局的限制。為解決這一問題,本文在Q框架的基礎上,提出了基于碎片化布局的ROP自動構造方法。該方法構造碎片化布局的ROP鏈過程如圖2所示。

圖2 ROP碎片化自動布局過程示意圖Fig.2 Overview of ROP fragmented layout and automatic generation
在Q框架生成ROP載荷與目標程序符號表的基礎上,本文將結合對源程序可控內存區域的檢查結果,構建碎片化ROP約束,求解約束,生成碎片化ROP鏈。
為了避免符號執行對每條程序路徑遍歷導致的路徑爆炸問題,本文在符號執行工具S2E的基礎上,采用了經過路徑選擇算法優化的導向式符號執行技術[15-16]。以crash文件作為源程序的輸入文件,可引導源程序沿著確定的程序路徑動態運行,直至觸發控制流劫持狀態。圖3是通過導向式符號執行觸發源程序控制流劫持狀態的過程。

圖3 導向式符號執行路徑選擇過程示意圖Fig.3 Path selection of source program with path-oriented symbolic execution
本文在導向式符號執行過程中,收集程序堆棧狀態與可控內存區域狀態。結合ROP載荷,分析上述狀態是否滿足ROP鏈的布局條件,并構建相應的ROP數據約束。通過約束求解,可實現碎片化布局的ROP測試例自動生成。
如圖1所示,ROP鏈是由一個特定順序的gadget序列組成的。由于每個gadget均以ret指令結束,并以此控制程序跳轉至下一個gadget所在地址,其地址及gadget的相關操作數需存放于程序的堆棧中。當源程序處于控制流劫持狀態時(即指令寄存器中的數值為符號值),堆棧是否有足夠的可控空間用于ROP布局,決定了ROP是否適用于源程序。為此,本文首先對源程序內存狀態進行分析,確定ROP布局條件。
針對本文方法的內存分析過程涉及的關鍵數據檢查與狀態變化,本文有如下定義:
symbolicBlock
stackPtr:表示首次控制流劫持狀態下的當前棧頂指針。
stack_symbolicLength:若當前棧頂位置處于符號化區域中,該數值表示以stackPtr為起始地址的一段連續的符號化內存長度。
mLenid表示ROP中名稱為id的模塊占用內存長度。每個模塊的長度信息均記錄于中間表示符號表中。
在一般的溢出漏洞中(比如棧溢出漏洞),覆蓋當前棧頂位置的污點數據的起始地址通常位于上一個函數棧幀中。但對于執行gadget序列來說,需要關注的只是源程序的控制流劫持時刻的棧頂數據屬性。因此,本文將在源程序控制流劫持時刻,從棧頂位置開始進行符號化檢查。若源程序棧頂位置不為符號化數據,表示源程序無法跳轉至第二個gadget,不滿足ROP開始執行的初始條件;若棧頂數據為符號化數據,計算以棧頂位置開始的符號化數據長度。圖4顯示的是源程序控制流劫持時刻,滿足ROP布局條件的棧結構示意圖。

圖4 源程序控制流劫持時刻的棧內存結構示意圖Fig.4 Structure of stack at the time of control-flow hijacked
為滿足當前棧幀可控空間不足情況下ROP的布局要求,可控內存搜索算法將搜索并記錄進程用戶態內存空間中其余的符號化區域信息。其過程如算法1所示。

算法1 可控內存區域搜索
本文從可控內存區域集合memSet中尋找滿足ROP模塊布局條件的元素。候選的符號化區域長度symbolicSize需要至少滿足容納某一ROP模塊,且該區域包含的范圍與源程序控制流劫持狀態下的棧頂指針不應相互沖突。對集合memSet中的所有symbolicBlock元素進行第一輪過濾,選出若干符合ROP模塊長度條件的內存區域,將該元素加入對應ROP模塊的候選區域集合lenBlocksid中,該模塊的候選區域集合需滿足如式(1)所示條件。lenBlocksid表示經過第一輪過濾后,模塊名為id的ROP對應的所有候選區域集合。
lenBlocksid:
?block∈symbolicBlock|(symbolicSize>mLenid)∧[(stackPtr
(1)
在候選區域長度滿足ROP模塊長度要求的基礎上,通過比較候選區域數據可控性約束與ROP模塊數據約束的兼容性,對每個ROP模塊的候選區域集合進行第二輪過濾。第二輪過濾完成后,ROP模塊id對應的候選區域集合conBlocksid應滿足式(2)所示條件。
conBlocksid:
?block∈lenBlockid|(canArea?block)∧ (canArea.Size≥ropModuleid.Size)∧Eq(area,moduleid)=true
(2)
式中,canArea表示候選區域block中任意連續的可控內存區域;canArea.Size表示canArea的長度;ropModuleid.Size表示ROP模塊id的長度,moduleid表示該模塊的數據約束;函數Eq用于實現約束條件A與約束條件B的兼容性比較。
result=Eq(A,B)
當約束比較函數Eq(A,B)的返回值result為true時,表示約束條件A與B可兼容;為false時,表示A與B不可兼容。
當且僅當模塊ropModule的數據約束module與canArea區域可控性約束area的兼容性判斷結果為true時,該ROP模塊是可執行的。數據約束module與area相兼容的檢查條件包括以下兩項:module中所有連續字節的約束與area所有連續字節的可控約束相兼容;module中剩余的字節數應不大于area中剩余的字節數,即canArea應有足夠的可控空間容納該ROP模塊。
針對area與module的兼容性比較過程以字節為單位。對ropModuleid中的每個字節與canArea區域中每個字節的約束條件逐一比較,構造待求解的模塊數據約束mConstraint。該過程如算法2所示。

算法2 ROP模塊數據約束構造
算法2中,若isAvailable返回值為true,表示canArea區域滿足ROP模塊id的布局條件,并將canArea區域標記為不可控區域后,重新構建可控內存區域集合memSet。圖5表示完成碎片化自動布局后,多模塊ROP的執行過程。針對剩余ROP模塊,重復執行第一輪與第二輪過濾,直至所有ROP模塊完成布局區域分配。該過程如算法3所示。

圖5 碎片化多模塊ROP執行過程示意圖Fig.5 Execution process of fragmented multi-module ROP

算法3 ROP碎片化約束構造
以代碼1中的ROPL高級語言作為目標程序。通過對目標程序進行語義分析,其對應的高級語言中間表示符號表如代碼2所示。

代碼1

代碼2
針對代碼1所示目標程序生成的ROP鏈長度為224(0xE0)B。該ROP鏈分為3個模塊:模塊main的長度為76(0x4C)B;模塊f1的長度為76(0x4C)B;模塊foo的長度為72(0x48)B。
為驗證通過本文方法實現的ROP鏈在實際程序中的碎片化布局效果,選取了7個包含控制流劫持漏洞的源程序進行實驗驗證。本文將可能覆蓋程序內存關鍵位置的污點數據標記為符號值,并通過構建ROP數據約束及約束求解,判斷相應的內存位置是否滿足ROP布局條件。表1為各實驗程序的實驗環境。

表1 漏洞程序實驗環境
通過漏洞觸發代碼觸發源程序首次控制流劫持狀態。本文對首次控制流劫持時刻的程序內存狀態進行分析,內存各區域可控污點數據情況如表2所示。

表2 控制流劫持狀態時刻源程序可控內存區域分布情況Tab.2 Layout of controllable tainted data in memory at the first time of control flow hijacked
注:“*”表示棧內存中的可控數據長度從當前棧頂位置開始計算表示。
對比表1與表2中的數據可發現,部分漏洞程序的引入污點數據長度與首次控制流劫持狀態時刻的內存各部分可控污點數據長度并不完全一致。造成這一現象的原因主要有以下幾種:
1)引入污點數據通過復制等操作傳播至內存其他區域,造成部分污點數據的約束條件有重合部分。屬于該類情況的漏洞程序包括CVE-2017-11882等。
2)污點數據覆蓋其他內存區域的關鍵數據。屬于該類情況的漏洞程序包括CVE-2014-0322等。
此類漏洞利用方式為,通過數組越界讀寫,實現程序任意內存地址讀寫,進而導致函數地址覆蓋與程序控制流劫持。由于函數地址在內存代碼段,因此不在本文對ROP布局內存分析的范圍內。
3)污點數據符號屬性丟失或污點數據不可控。屬于該類情況的漏洞程序包括CVE-2010-3333等。
CVE-2010-3333是棧溢出漏洞,但在棧內存中,可控污點數據僅在棧頂位置向下16 B的范圍內。對于不可控的污點數據,由于不具有利用價值,因此,本文未做統計與分析。
在對源程序內存可控污點數據布局狀態分析的基礎上,針對代碼1目標程序構造的ROP鏈經過碎片化布局處理后,各模塊ROP在內存空間中的分布如表3所示。

表3 各模塊ROP在源程序內存中的布局情況
MS06-055的棧內存僅滿足main模塊的布局條件。本文通過堆噴射漏洞觸發代碼,實現源程序內存中的堆塊大量布局,并將寫入堆塊的數據標記為污點數據。通過內存分析,確定進程的堆內存滿足f1與foo模塊的布局條件。
CVE-2010-3333的棧內存不滿足任一模塊布局條件。本文對該漏洞實驗做了手工調整,在其棧內存中布置了簡化的堆棧偽造指令,使其堆棧指針指向堆內存區域。通過分析,該進程堆中的可控數據區域滿足代碼2所有模塊的布局條件。
CVE-2012-0158漏洞的棧內存布局情況滿足代碼2所有模塊的布局條件,因此未將任何一個模塊布置于其他內存區域中。
CVE-2014-0322與CVE-2015-5119漏洞的棧內存不符合代碼1中模塊布局條件。為了驗證碎片化布局方法,本文首先通過偽造棧空間的方法,在堆內存中開辟一段偽造的棧內存。與上述兩個漏洞布局進行對比,f1與foo模塊均布置于堆內存中。實驗結果證明,CVE-2014-0322與CVE-2015-5119的實驗程序內存滿足表3所示的布局條件。
根據表2,CVE-2017-11882棧內存的可控空間大于代碼1 ROP所需的內存空間長度。但通過進一步分析發現,該漏洞程序的棧內存空間過于碎片化,其中最大的一塊棧內存可控區域長度僅為120 B,不足以容納代碼1中的任意兩個模塊。因此,本文僅將main模塊布局于棧內存中,f1與foo模塊布局于堆內存中。CVE-2017-11882的ROP碎片化布局實驗結果如表3所示。
CVE-2018-8174漏洞程序的棧內存可控空間僅滿足進程跳轉功能,不滿足代碼1 ROP模塊布局功能,因此,該漏洞程序的ROP模塊全部布局于堆內存中。
本文挑選了CVE-2014-0322漏洞實驗樣本,對其ROP碎片化布局過程進行具體分析與闡述。
當符號執行工具S2E檢測CVE-2014-0322樣本程序進入控制流劫持狀態時,首先檢查源程序狀態結果為:
棧頂指針寄存器ESP:0x0307B7D4;
符號化寄存器:EAX;EIP;
符號化內存區域:0x0307B7D4~0x0307B7D7;0x10000000~0x28180000;0x28180000~0x28280000。
如上文所述,控制流劫持狀態下的樣本程序棧內存不符合任何一個ROP模塊的布局要求。為此,需要向樣本程序中加入偽造??臻g的步驟。該步驟通過下述gadget完成。
XCHG EAX, ESP;
RETN;
該gadget的內存地址為0x5ED6E850。
偽造的??臻g棧頂指針為:0x1A1B3010。根據對比,發現該區域位于符號化區域中。
針對main 模塊進行兩輪可控內存區域過濾后,該模塊的最終布局范圍為:0x1A1B3010~0x1A1B305C。
針對f1模塊進行兩輪可控內存區域過濾后,該模塊的最終布局范圍為:0x1A1B4010~0x1A1B405C。
針對foo模塊進行兩輪可控內存區域過濾后,該模塊的最終布局范圍為:0x1A1B5010~0x1A1B5058。
為驗證本文方法的時間開銷,本文記錄了該方法的時間開銷為t1。時間開銷t1的定義為:從源程序開始符號執行,到生成碎片化布局的ROP測試用例所消耗時間。
作為對比,本文還記錄了符號執行工具S2E對源程序進行分析的時間開銷t2。時間開銷t2的定義為:從源程序開始符號執行,到生成控制流劫持路徑測試用例的消耗時間。對各實驗樣本進行10次實驗,各樣本的平均時間消耗如圖6所示。

圖6 碎片化ROP布局方法與S2E系統時間開銷對比Fig.6 Comparison of analysis time by ROP fragmented layout method and S2E
由于本文提出的碎片化ROP布局方法是在源程序控制流劫持狀態下,通過內存分布狀態分析實現的,因此,圖6中各實驗樣本的時間開銷之差,即t1-t2,基本可被視為可控內存區域分析過程的時間消耗。
圖6所示實驗樣本中,t1與t2的差值較大。原因是crash文件觸發源程序控制流劫持狀態過程中,使用了堆噴射技術,使源程序內存空間存在大量可控區域,導致內存搜索算法運行時間增加。
針對目前ROP自動化構造過程中存在的空間效率低與內存布局要求高等問題,提出了基于符號執行的多模塊ROP碎片化自動布局方法。該方法以ROP模塊為單位,在動態分析源程序可控污點數據分布情況的基礎上,實現各模塊ROP的碎片化分布,降低了ROP布局對源程序內存布局條件的要求。
此外,本文提出的基于碎片化布局的多模塊ROP自動生成方法仍然存在局限性。首先,ROP模塊調用過程未考慮地址隨機化對目標模塊地址尋找的影響。其次,符號執行過程中造成的污點數據符號屬性丟失等問題會影響該方法對源程序內存狀態分析的結果。如何減少上述問題對ROP自動生成過程的影響,是下一步工作的研究重點。