国产日韩欧美一区二区三区三州_亚洲少妇熟女av_久久久久亚洲av国产精品_波多野结衣网站一区二区_亚洲欧美色片在线91_国产亚洲精品精品国产优播av_日本一区二区三区波多野结衣 _久久国产av不卡

?

COLLATE:控制相關(guān)數(shù)據(jù)的完整性保護(hù)

2023-11-17 13:26:26鄧穎川劉維杰王麗娜
關(guān)鍵詞:控制流污點(diǎn)指針

鄧穎川,張 桐,劉維杰,王麗娜

(1.武漢大學(xué) 國家網(wǎng)絡(luò)安全學(xué)院空天信息安全與可信計(jì)算教育部重點(diǎn)實(shí)驗(yàn)室,湖北 武漢 430040;2.螞蟻集團(tuán),浙江 杭州 310012)

1 引 言

使用C/C++編寫的程序可能包含安全漏洞??刂屏鹘俪止衾眠@些漏洞來篡改代碼指針,從而將程序的控制流轉(zhuǎn)移到目標(biāo)代碼片段上。通過篡改多個代碼指針,攻擊者可以構(gòu)造圖靈完備的代碼重用攻擊(Code-Reuse Attack),從而實(shí)現(xiàn)信息泄露或提權(quán)等目的。

針對控制流劫持攻擊而提出的防御機(jī)制嘗試確保間接函數(shù)調(diào)用時使用的函數(shù)指針為預(yù)期的值來阻止攻擊。指針完整性(Pointer Integrity,PI)[1-4]是其中具有代表性的一種,它維護(hù)了代碼指針和一部分?jǐn)?shù)據(jù)指針的完整性。此外,控制流完整性(Control-Flow Integrity,CFI)[5-13]通過檢查間接控制流跳轉(zhuǎn)(Indirect Control-Flow,ICT)的目標(biāo)是否符合預(yù)先分析得到的控制流圖(Control-Flow Graph,CFG),實(shí)現(xiàn)對前向間接控制流轉(zhuǎn)移的校驗(yàn),并將后向間接控制流轉(zhuǎn)移的校驗(yàn)交給影子堆棧(Shadow Stack)[14]。

然而,即便有了這些防御措施,攻擊者依然能夠“修改”函數(shù)指針的值,使它指向一個預(yù)期之外的目標(biāo)[15]。間接控制流轉(zhuǎn)移需要根據(jù)必要的運(yùn)行時數(shù)據(jù)來選擇目標(biāo)地址,這些運(yùn)行時數(shù)據(jù)就是它的依賴,也就是對應(yīng)函數(shù)指針的依賴。如果依賴被篡改了,那么控制流可能流向控制流圖中不同但是合法的路徑,而PI和CFI很難檢測到這類攻擊。例如,給定一個函數(shù)指針數(shù)組,其元素指向不同的函數(shù)。一個間接調(diào)用點(diǎn)從這個函數(shù)指針數(shù)組中根據(jù)索引取出對應(yīng)元素作為跳轉(zhuǎn)目標(biāo)。攻擊者可以通過將這個索引篡改為另一個合法的值,導(dǎo)致跳轉(zhuǎn)后執(zhí)行預(yù)期之外的函數(shù)。

文中引入了COLLATE,一個包含LLVM pass和運(yùn)行時支持庫的工具鏈,用來保護(hù)函數(shù)指針及其依賴等控制相關(guān)數(shù)據(jù)的完整性。COLLATE首先使用靜態(tài)分析識別出控制相關(guān)數(shù)據(jù),然后使用Intel 內(nèi)存保護(hù)密鑰(Memory Protection Keys,MPK)(Intel Corporation.Intel(R) 64 and IA-32 Architectures Software Developer’s Manual,2016.https:∥software.intel.com/en-us/articles/intel-sdm),將它們和普通數(shù)據(jù)進(jìn)行隔離,最后對程序進(jìn)行插樁以允許合法的內(nèi)存訪問操作。為了識別控制相關(guān)數(shù)據(jù),COLLATE首先利用基于類型的分析來找出所有的函數(shù)指針和它們歸屬的對象(如果有的話)作為污點(diǎn)源(source)。然而,在某些情況下,一個通用指針(如void *)也可以指向一個函數(shù)。這意味著基于類型的分析得到的是不完整的結(jié)果。因此,首先利用上下文敏感且流敏感的指針分析SVF(Static Value-Flow Analysis Framework for Source Code.https:∥github.com/SVF-tools/SVF)來識別這些潛在的函數(shù)指針。然后,COLLATE將間接調(diào)用點(diǎn)標(biāo)記為匯聚點(diǎn)(sink),并執(zhí)行污點(diǎn)傳播,收集污點(diǎn)指令及其操作數(shù)作為控制相關(guān)數(shù)據(jù)。

為了保護(hù)控制相關(guān)數(shù)據(jù),COLLATE為它們的內(nèi)存分配提供了一個受限的安全內(nèi)存域Ms,而為其它數(shù)據(jù)提供了一個常規(guī)內(nèi)存域Mn。對于控制相關(guān)數(shù)據(jù)中的全局和靜態(tài)變量,COLLATE首先將它們集中到可執(zhí)行文件中一個特殊的節(jié)(Section)中,然后在加載到內(nèi)存中后將這個節(jié)映射到Ms中。對于棧分配,COLLATE首先在Ms中維持了一個額外的Separated Stack,然后使用它的棧分配指令替換了原本的棧分配指令。對于堆分配,COLLATE提供了一個定制的堆管理器來管理Ms中動態(tài)分配的控制相關(guān)數(shù)據(jù)。

此外,COLLATE利用指針分析來識別那些在程序執(zhí)行時預(yù)期修改控制相關(guān)數(shù)據(jù)的可信指令。通過使用專用的call gate,這些可信指令被授權(quán)訪問受保護(hù)的內(nèi)存域。call gate在一條可信指令執(zhí)行前修改PKRU寄存器,授予它對Ms進(jìn)行讀寫的權(quán)限,并在它執(zhí)行完成后立刻收回權(quán)限。

實(shí)現(xiàn)了COLLATE的原型系統(tǒng),包含一個LLVM pass和一個運(yùn)行時支持庫。其中,LLVM pass負(fù)責(zé)控制相關(guān)數(shù)據(jù)的識別和插樁,動態(tài)庫提供了separated stack和heap的實(shí)現(xiàn)。系統(tǒng)保證了函數(shù)指針及其依賴的完整性,并將返回地址的保護(hù)交給影子堆棧(Shadow Stack)。

對COLLATE的有效性和性能進(jìn)行了評估。該評估測試了COLLATE面對3個真實(shí)世界的CVE漏洞和一個虛表指針劫持攻擊[16]的測試集的防御效果。結(jié)果顯示,COLLATE能夠很好地防御控制流劫持攻擊。而在SPEC CPU 2006 benchmarks上的測試結(jié)果顯示,COLLATE引入的平均額外開銷約為10.2%,在Nginx上更是只約有6.8%,這種開銷是可以接受的。因此,解決方案提供了足夠的安全保證,并且具備了實(shí)用性。

綜上所述,筆者做出了以下三點(diǎn)貢獻(xiàn)。

(1) 提出了控制相關(guān)數(shù)據(jù)的概念,即函數(shù)指針及其依賴。它們可以被用來影響代碼指針的值,從而彎曲控制流。

(2) 構(gòu)建了COLLATE的原型系統(tǒng),它有如下設(shè)計(jì)亮點(diǎn):使用指針分析識別指向函數(shù)的通用指針;使用過程間靜態(tài)污點(diǎn)分析識別控制相關(guān)數(shù)據(jù);將控制相關(guān)數(shù)據(jù)存儲到使用Intel MPK保護(hù)的安全內(nèi)存域中。

(3) 使用SPEC CPU2006、Nginx和一些具有代表性的漏洞徹底評估了原型的有效性和性能。

2 威脅模型及設(shè)計(jì)概覽

文中把控制流劫持攻擊防御措施的傳統(tǒng)對手模型作為威脅模型,即假設(shè)強(qiáng)大但現(xiàn)實(shí)的場景:攻擊者可以首先利用內(nèi)存漏洞訪問任意內(nèi)存,然后控制代碼指針來劫持或彎曲控制流。威脅模型與現(xiàn)有的相關(guān)工作[2-3,17-18]是一致的。DEP/NX(Data Execution Prevention)被啟用,并且在系統(tǒng)中部署了一個影子堆?;虬踩褩!9P者進(jìn)一步假設(shè)所有的硬件(如Intel MPK)和操作系統(tǒng)都是可信的,這意味著針對它們的攻擊不予考慮。

COLLATE的目標(biāo)是保證間接控制流轉(zhuǎn)移(ICTs)的目標(biāo)符合預(yù)期。圖1是COLLATE工作流程的概覽。在獲取目標(biāo)程序的LLVM bitcode文件后,COLLATE首先以函數(shù)指針和它們歸屬的對象作為污點(diǎn)源,ICTs作為污點(diǎn)匯聚點(diǎn)。接著,COLLATE根據(jù)提出的傳播規(guī)則執(zhí)行靜態(tài)污點(diǎn)傳播,從而識別出控制相關(guān)數(shù)據(jù)。然后,COLLATE在bitcode上進(jìn)行插樁,將控制相關(guān)數(shù)據(jù)的內(nèi)存分配到受保護(hù)的安全內(nèi)存域Ms中,并僅允許可信指令對它們進(jìn)行修改。最后,COLLATE將bitcode編譯為目標(biāo)文件,并鏈接相應(yīng)的運(yùn)行時支持庫,從而生成加固后的可執(zhí)行文件。

圖1 COLLATE工作流程

3 控制相關(guān)數(shù)據(jù)的識別

COLLATE的第1步是識別控制相關(guān)數(shù)據(jù)。首先,使用一個混合分析方法找出函數(shù)指針和它們所屬的對象作為污點(diǎn)源。接著,創(chuàng)建用于識別函數(shù)指針的依賴的污點(diǎn)傳播規(guī)則。最后,在過程間數(shù)據(jù)流圖上執(zhí)行污點(diǎn)傳播,并收集所有被污染的對象作為結(jié)果。

3.1 污點(diǎn)源識別

污點(diǎn)源是指那些屬于函數(shù)指針或至少包含一個函數(shù)指針的內(nèi)存對象。利用基于類型的分析來快速識別污點(diǎn)源,并利用指針分析的優(yōu)勢來對結(jié)果進(jìn)行補(bǔ)充?;陬愋偷姆治龅娜蝿?wù)是通過輕量級的類型匹配找到污點(diǎn)源。

基于類型的分析的第一步是識別污點(diǎn)源類型。借助LLVM類型系統(tǒng)的優(yōu)勢,可以遍歷程序中的所有類型,找到所有函數(shù)指針類型。污點(diǎn)源類型可能被嵌入到復(fù)合類型中(如結(jié)構(gòu)體類型),此時它們都應(yīng)當(dāng)被視為污點(diǎn)源類型。注意,之前對于污點(diǎn)源類型的定義是遞歸的,這意味著需要遞歸地檢查每一個復(fù)合類型及其復(fù)合類型字段。

第一步是獲得所有內(nèi)存對象的類型,并確定它們是否是污點(diǎn)源。LLVM IR提供了alloca指令來分配棧內(nèi)存,并使用GlobalVariable類型來描述全局和靜態(tài)變量的內(nèi)存分配,這使得可以通過預(yù)定義的API獲得內(nèi)存對象的類型。然而,LLVM不能直接識別堆分配點(diǎn),因?yàn)樗槐硎緸橐粋€函數(shù)調(diào)用,但缺乏類型信息。通常情況下,開發(fā)者會利用標(biāo)準(zhǔn)庫函數(shù),如malloc和calloc,來為程序分配堆內(nèi)存。注意到LLVM的優(yōu)化pipeline(如O1)包括一個名為tbaa的別名分析,它在元數(shù)據(jù)標(biāo)簽中保留了Clang前端獲取的類型信息;這些元數(shù)據(jù)標(biāo)簽被附加到讀/寫之前分配的堆內(nèi)存的內(nèi)存操作指令(如load)中。因此,COLLATE利用輕量級的use-def分析來找到堆內(nèi)存對象最近的內(nèi)存操作指令,并獲得存儲在tbaa標(biāo)簽中的類型信息。有了所有對象的類型信息后,就可以通過類型匹配迅速識別污點(diǎn)源。

對基于類型的靜態(tài)分析所識別的污點(diǎn)來源可能是下近似的。即通用指針(如void *)也可能指向函數(shù),而這是無法通過基于類型的分析識別的。因此遺漏的函數(shù)指針和依賴沒有得到保護(hù),導(dǎo)致它們可以被攻擊者篡改。

如算法1所示,COLLATE利用指針分析來補(bǔ)全污點(diǎn)源集合。算法的輸入是通用指針集合,輸出是污點(diǎn)源集合。COLLATE用空集初始化額外的函數(shù)指針集合,用基于類型的分析結(jié)果初始化輸出(第①行)。對于通用指針集合中的每個元素,COLLATE找到其points-to set(第④行)。如果該指針是一個潛在的函數(shù)指針,COLLATE將其插入到函數(shù)指針集合中(第⑤~⑩行)。

算法1基于別名分析的污點(diǎn)源識別算法。

輸入:GPtrSet:程序中所有通用指針的集合

輸出:TaintSet:指向污點(diǎn)源的指針的集合

① ExFunPtrSet←?

② TaintSet ← typeBasedAnalysis()

③ foreach GPtr in GPtrSet do

④ GPTSet ← getPointToSet(GPtr)

⑤ foreach Target in GPTSet do

⑥ if Target isa Function then

⑦ ExFunPtrSet ← ExFunPtrSet∪{Target}

⑧ break

⑨ end

⑩ end

加入函數(shù)指針集合后,COLLATE首先使用后向數(shù)據(jù)流分析來找到該函數(shù)指針?biāo)诘膬?nèi)存對象(第行)。最后,COLLATE再次利用別名分析,找到所有作為污點(diǎn)源的內(nèi)存對象(第~行)。

3.2 污點(diǎn)傳播規(guī)則

在識別污點(diǎn)源之后,COLLATE分別創(chuàng)建前向傳播規(guī)則以找到所有污點(diǎn)路徑和創(chuàng)建后向傳播規(guī)則以識別依賴。對于前向污點(diǎn)傳播,COLLATE提出以下規(guī)則:

(1) 前向污點(diǎn)分析從污點(diǎn)源開始,在間接函數(shù)調(diào)用點(diǎn)結(jié)束。

(2) 如果一條指令的任何操作數(shù)被污染,這條指令也會被污染。

通過應(yīng)用規(guī)則(1)和規(guī)則(2),COLLATE確定了從污點(diǎn)源到間接調(diào)用點(diǎn)的所有污點(diǎn)路徑。每條污點(diǎn)路徑都由一組污點(diǎn)指令組成,描述了一個指針從創(chuàng)建到調(diào)用的生命周期。需要注意的是,在LLVM IR中,指令和它的結(jié)果在語義上是等價的,可以相互替換。

為了識別函數(shù)指針的依賴,COLLATE創(chuàng)建了如下后向傳播規(guī)則:

(3) 后向污點(diǎn)分析從(之前的)污點(diǎn)指令開始,在內(nèi)存分配點(diǎn)結(jié)束。

(4) 如果一條指令被污染,那么它的所有操作數(shù)都會被污染。

(5) 如果一條內(nèi)存操作指令被污染,那么它的指針操作數(shù)可能指向的所有目標(biāo)都會被污染。

(6) 如果一條phi指令被污染了,那么它所對應(yīng)的分支指令的條件也會被污染。

規(guī)則(3)和規(guī)則(4)描述了基本的后向污點(diǎn)傳播方向。 規(guī)則(5)介紹了內(nèi)存操作指令的污點(diǎn)傳播規(guī)則,這些指令在值操作數(shù)和指針操作數(shù)之間傳輸數(shù)據(jù)。具體來說,以load指令為例:如果一條load指令被污染,COLLATE就會污染所有在其指針操作數(shù)的points-to set中的內(nèi)存對象。

在LLVM IR中,phi指令用于實(shí)現(xiàn)靜態(tài)單賦值(Static Single Assignment,SSA)的Φ節(jié)點(diǎn)。當(dāng)一個變量會根據(jù)控制流的路徑的不同(例如,根據(jù)if或else分支)被賦予不同的值時,它將被表示為一個Φ節(jié)點(diǎn)。因此,正如規(guī)則(6)所述,phi指令的值不僅取決于前面的基本塊,而且還取決于對應(yīng)分支指令的條件。

3.3 污點(diǎn)傳播

為了進(jìn)行污點(diǎn)傳播,COLLATE首先構(gòu)建一個過程間的def-use鏈,然后根據(jù)前面提出的規(guī)則進(jìn)行污點(diǎn)分析。

事實(shí)上,LLVM為每個函數(shù)構(gòu)建了一個函數(shù)內(nèi)的def-use鏈。因此,COLLATE首先用指針分析和類型匹配構(gòu)建一個函數(shù)調(diào)用圖。函數(shù)調(diào)用圖表示了調(diào)用點(diǎn)和目標(biāo)函數(shù)間的映射關(guān)系,將它們關(guān)聯(lián)起來。然后,COLLATE根據(jù)函數(shù)調(diào)用圖將現(xiàn)有的def-use鏈擴(kuò)展為過程間的def-use鏈。具體來說,COLLATE以函數(shù)調(diào)用點(diǎn)的實(shí)參為def,目標(biāo)函數(shù)的形參為use,為def-use鏈創(chuàng)建新的前向邊。同時,COLLATE將目標(biāo)函數(shù)的返回值視為def,將函數(shù)調(diào)用點(diǎn)的結(jié)果值視為use以創(chuàng)建新的后向邊。

筆者用一個具體的例子來闡明污點(diǎn)傳播的過程。圖2中展示了一個函數(shù)的控制流圖。每個方框代表一個基本塊。基本塊之間的實(shí)線代表控制流,虛線表示兩個基本塊之間的支配(dominance)關(guān)系。例如,BB_1支配(dominates)BB_4,因?yàn)閺娜肟诠?jié)點(diǎn)到BB_4的每條路徑都必須經(jīng)過BB_1。

圖2 一個函數(shù)內(nèi)部的污點(diǎn)傳播過程

對于前向污點(diǎn)傳播,將fp_arr作為污點(diǎn)源(第⑦行),將間接控制流傳輸作為污點(diǎn)匯聚點(diǎn)(第行)。因此,總結(jié)出一條用實(shí)線下劃線標(biāo)出的前向污點(diǎn)路徑,即:fp_arr(第⑦行)→%8(第⑦行)→%9(第⑧行)→%14(第行)→call(第行)。

后向污點(diǎn)傳播從之前的污點(diǎn)指令開始,被污染的%0(第⑦行)用方框進(jìn)行了標(biāo)注;它是這個函數(shù)的一個參數(shù)。此外,一條被污染的phi指令%14(第行) 使得對應(yīng)的條件值%6(第⑥行)被污染,就像3.2節(jié)中規(guī)則(6)所描述的那樣。因此,COLLATE識別出另一條用虛線下劃線進(jìn)行標(biāo)識的后向污點(diǎn)路徑:%6 in br(第⑥行)→%6(第⑤行)→%5(第④行)→%4(第③行)→alloca(第①行)。最終,COLLATE將這些污點(diǎn)值視為控制相關(guān)數(shù)據(jù)。

4 插 樁

4.1 控制相關(guān)數(shù)據(jù)的內(nèi)存分配

為了將各種控制相關(guān)數(shù)據(jù)分配到受保護(hù)的內(nèi)存域,COLLATE利用各種策略來處理不同的內(nèi)存分配方式。

(1) 棧分配。COLLATE在Ms域中維護(hù)一個separated stack,以保存分配在棧中的控制相關(guān)數(shù)據(jù)。COLLATE模擬常規(guī)棧的操作,為局部變量分配和回收棧內(nèi)存。具體來說,COLLATE在Ms域分配一個內(nèi)存區(qū)域,并將separated stack的棧指針(即%sep_sp)初始化為該內(nèi)存區(qū)域的高地址,如圖3所示。給定一個局部變量,COLLATE首先根據(jù)類型和對齊方式計(jì)算其大小。然后,COLLATE利用sub指令來調(diào)整%sep_sp的位置以分配??臻g。最后,sub指令的結(jié)果被轉(zhuǎn)換為原本的類型,可用于引用這個棧對象。為了回收棧幀,COLLATE在函數(shù)序言之后保存了%sep_sp的位置,并在所有的ret指令之前恢復(fù)%sep_sp的位置。

(2) 堆分配。對于動態(tài)分配的控制相關(guān)數(shù)據(jù),COLLATE用一個專門的堆分配器取代了原來的glibc分配器。這個堆分配器將受保護(hù)的內(nèi)存區(qū)域劃分為16字節(jié)的chunk,從低地址開始分配動態(tài)內(nèi)存。在分配動態(tài)內(nèi)存時,堆分配器會找到一個由連續(xù)的chunk組成的、符合請求大小的block,并返回該block的基址。為了找到可用的塊,COLLATE維護(hù)了一個空閑鏈表,將未分配的block連接成一個鏈表,如圖3所示??臻e鏈表記錄了每個block的大小和后續(xù)block的地址,有利于分配和刪除操作的實(shí)施。

圖3 Separated stack和heap的內(nèi)存布局

(3) 靜態(tài)存儲區(qū)分配。COLLATE使用一個separated segment來保存靜態(tài)的控制相關(guān)數(shù)據(jù)。為了實(shí)現(xiàn)這一功能,COLLATE首先為它們創(chuàng)建一個特殊的ELF section,然后分別在該section的開始和結(jié)束處插入兩個填充變量,使得它按頁對齊。當(dāng)可執(zhí)行文件被加載到內(nèi)存中時,這兩個填充變量可以用來確定內(nèi)存中segment的實(shí)際地址范圍。最后,在程序執(zhí)行前,COLLATE將該segment映射到Ms以進(jìn)行保護(hù)。

4.2 可信指令

可信指令是那些原本就會寫入控制相關(guān)數(shù)據(jù)的內(nèi)存操作指令(例如,store)。對于每個可以寫入內(nèi)存的內(nèi)存操作指令,COLLATE利用指針分析來獲得其指針操作數(shù)的points-to set。如果points-to set中包含屬于控制相關(guān)數(shù)據(jù)的內(nèi)存對象,COLLATE將該指令視為可信指令。在外部call指令的情況下,如果其參數(shù)的points-to set中包含屬于控制相關(guān)數(shù)據(jù),COLLATE也將其視為可信指令。

4.3 內(nèi)存域切換

COLLATE利用Intel MPK將進(jìn)程空間分割成兩個內(nèi)存域:普通數(shù)據(jù)的域Mn和受保護(hù)數(shù)據(jù)的域Ms。當(dāng)CPU運(yùn)行在Mn上下文時,屬于在域Ms的內(nèi)存區(qū)域默認(rèn)為只讀,以保證完整性。為了確保程序的正常運(yùn)行,COLLATE用call gate來包裹可信指令,暫時允許它寫入受保護(hù)的內(nèi)存區(qū)域。

call gate首先使用WRPKRU指令將PKRU_ALLOW_D1寫入PKRU寄存器(第①~⑤行),允許之后的代碼寫入Ms。PKRU_ALLOW_D1是一個宏,代表了Mn和Ms都可讀可寫時PKRU的值。call gate的匯編代碼如下所示:

① xor ecx,ecx

② xor edx,edx

③ mov PKRU_ALLOWED1,eax

④ ;Write PKRU ALLOW DI to PKRU,allow access domain 1

⑤ WRPKRU

⑦ ;Execute trusted instruction

⑨ xor ecx,ecx

⑩ xor edx,edx

接著,可信指令在對Ms有完全的權(quán)限的情況下被執(zhí)行(第7行)。在執(zhí)行完成后,call gate再次將PKRU_DISALLOW_D1寫入PKRU寄存器(第⑨~行),禁止之后的代碼寫入Ms。

關(guān)于call gate的安全性,一個合理的擔(dān)憂是,WRPKRU指令是否會被攻擊者利用。答案是否定的,因?yàn)榭刂屏鹘俪止舯仨毷紫却鄹哪軌蛴绊懣刂屏鞯臄?shù)據(jù),而COLLATE通過保護(hù)控制相關(guān)數(shù)據(jù)的完整性,使得它在源頭上就不可能做到。

5 實(shí)驗(yàn)評估

筆者進(jìn)行了實(shí)驗(yàn)性的評估來展示COLLATE的效率和有效性。為了測試COLLATE的效率,將其應(yīng)用于SPEC CPU 2006 benchmark和一個真實(shí)世界的應(yīng)用程序上以測量它的開銷。為了測試COLLATE的有效性,使用3個CVE漏洞和CFIXX測試套件來測試它的防御效果。

在AWS EC2實(shí)例上進(jìn)行實(shí)驗(yàn)。 該實(shí)例的類型為c2n.xlarge,運(yùn)行64 bit Ubuntu 20.04系統(tǒng),配備8核Intel Xeon Platinum 8124 MB CPU和16 GB RAM。利用wllvm(Whole-program LLVM.https:∥github.com/travitch/whole-program-llvm.)編譯源代碼,然后提取出LLVM bitcode作為COLLATE的輸入。然后,用COLLATE分析輸入的bitcode并進(jìn)行插樁,生成轉(zhuǎn)換后的bitcode文件。最后,將bitcode編譯成目標(biāo)文件,并和COLLATE的運(yùn)行時支持庫鏈接起來,產(chǎn)生加固的可執(zhí)行文件。在編譯目標(biāo)程序時,使用默認(rèn)的優(yōu)化級別(SPEC CPU 2006為O2,Nginx為O1)和選項(xiàng)。

5.1 插樁開銷

修改指令的數(shù)量如表1中第3列所示,絕大多數(shù)C編寫的benchmark中的插樁的指令少于CPI,甚至少于它的1/10。極少數(shù)情況下(bzip2)插樁的指令數(shù)量多于CPI。

這是由兩者設(shè)計(jì)上的不同導(dǎo)致的:CPI的權(quán)限檢查需要通過插入判斷函數(shù),在軟件層面上判斷寫入內(nèi)存時寫入的地址是否超出預(yù)期內(nèi)存的邊界。因此它修改的指令中大部分都是這樣的判斷函數(shù)。而COLLATE使用MPK來保護(hù)數(shù)據(jù),因此權(quán)限檢查是交由MPK自動進(jìn)行的,這是在硬件層面上的檢查,因而不需要額外插樁。在一些benchmark上(如mcf和libquantum),沒有進(jìn)行插樁。這是因?yàn)檫@些benchmark中不存在間接函數(shù)調(diào)用。

存在受保護(hù)數(shù)據(jù)的函數(shù)數(shù)量(FNprotected)如表1中第4列所示,筆者的工作同樣少于CPI。這一方面是因?yàn)楸Wo(hù)的對象不同,CPI保護(hù)的是那些可能以內(nèi)存不安全的形式訪問的內(nèi)存對象,而COLLATE保護(hù)的是函數(shù)指針及其依賴;另一方面也是因?yàn)閮烧吲袛嗟姆绞讲煌?CPI僅僅是根據(jù)簡單的def-use關(guān)系判斷一個內(nèi)存對象是否安全,沒有考慮通過指針訪問這個內(nèi)存對象的情況,因而不夠準(zhǔn)確;而COLLATE同時考慮了這兩種情況,通過使用指針分析更精確地確定需要保護(hù)的內(nèi)存對象。當(dāng)然,也找到許多不屬于任何函數(shù)的全局控制相關(guān)數(shù)據(jù)。

表1 控制相關(guān)數(shù)據(jù)的識別和插樁的結(jié)果

控制相關(guān)數(shù)據(jù)的數(shù)量(CRD)如表1第5列所示,控制相關(guān)數(shù)據(jù)的數(shù)量與ICT的數(shù)量呈正比。因?yàn)榭刂葡嚓P(guān)數(shù)據(jù)與函數(shù)指針密切相關(guān)。對于 h264ref和nginx來說,它們的控制相關(guān)數(shù)據(jù)的數(shù)量遠(yuǎn)遠(yuǎn)多于其他benchmark,因?yàn)樗鼈冇谐^300個ICT。milc有不成比例的控制相關(guān)數(shù)據(jù)。這是因?yàn)樗?個ICT都用于回調(diào),直接使用函數(shù)作為參數(shù)。所以,沒有任何指令會修改函數(shù)指針。

5.2 性能開銷

SPEC CPU 2006共包含了17個C/C++語言編寫的benchmark。在實(shí)驗(yàn)中,測試了所有這些benchmark和一個真實(shí)世界的應(yīng)用程序Nginx。測試結(jié)果展示在圖4中。

圖4 歸一化的性能開銷,average顯示的是不包括Nginx的平均開銷

如圖4所示,時間開銷總體上比CPI略大。在SPEC CPU 2006上的結(jié)果顯示,CPI的平均開銷約為3.7%,而COLLATE的平均開銷約為10.2%。筆者的開銷約是CPI的3倍。考慮到受保護(hù)數(shù)據(jù)的范圍,這種額外的開銷是可以接受的。CPI只保護(hù)函數(shù)指針和用于訪問函數(shù)指針的數(shù)據(jù)指針,而COLLATE保護(hù)函數(shù)指針和與函數(shù)指針的計(jì)算有關(guān)的所有數(shù)據(jù)。因此,只用2倍的額外開銷就提供了更全面的保護(hù)。此外,筆者發(fā)現(xiàn)平均開銷的差異有很大一部分是由bzip2帶來的。忽略bzip2后,COLLATE的平均開銷低至約6%,僅僅不到CPI的2倍。對于nginx,CPI的開銷約是6.4%,而COLLATE的開銷約是6.8%,僅增加了約0.4%。顯然,COLLATE在實(shí)際應(yīng)用中的開銷比SPEC CPU 2006上的開銷要小,與CPI相差不大。綜上所述,COLLATE在保護(hù)了更多的對安全至關(guān)重要的數(shù)據(jù),提供了更加全面的安全保障的情況下,性能開銷仍然保持在可接受范圍內(nèi)。

很多情況下,盡管benchmark中應(yīng)用了COLLATE,但是它的性能卻沒有降低,反而還略微提高了,如hmmer、namd等。這種現(xiàn)象的原因有兩個:一是由于應(yīng)用了MPK,權(quán)限的切換僅需寫入專用的PKRU寄存器即可完成,使得修改后的指令增加的開銷很小;二是由于修改的指令原本的執(zhí)行頻率就相對較低。以hmmer為例,hmmer中COLLATE修改的指令約46%都位于hmmio.c中,用于IO操作。而相較于IO本身的開銷來說,COLLATE為指令本身增加的開銷幾乎可以忽略不計(jì)。同時,hmmer被修改的指令約有98%是標(biāo)準(zhǔn)庫函數(shù)調(diào)用,本身執(zhí)行頻率就遠(yuǎn)低于store等寫指令。對于這些指令,與它們本身的開銷相比,COLLATE引入的開銷幾乎可以忽略不計(jì)。

盡管在大多數(shù)benchmark上COLLATE的開銷都很正常,但是,在bzip2上,COLLATE的開銷異常的高(約80%)。事實(shí)上,筆者發(fā)現(xiàn),平均開銷上的很大一部分是由bzip2帶來的,在去除bzip2后,COLLATE的平均開銷減少到約6%。分析bzip2和COLLATE的工作機(jī)制后,得出原因如下。

bz_stream結(jié)構(gòu)體中包含有函數(shù)指針類型(第⑥行)。因此,COLLATE將所有bz_stream結(jié)構(gòu)體類型的數(shù)據(jù)都識別為需要保護(hù)的數(shù)據(jù),使用MPK進(jìn)行隔離,并且每一次對它的修改都需要使用call gate臨時授予權(quán)限。而bz_stream結(jié)構(gòu)體保存了所有與壓縮相關(guān)的數(shù)據(jù),它的字段包含用戶可見的全部數(shù)據(jù)。所以bzip2中修改這些數(shù)據(jù)的頻率特別高,再加上這些修改都是以字節(jié)為單位進(jìn)行的(第,行),最終導(dǎo)致了bzip2 benchmark的開銷異常得高。

bzip2的bz_stream結(jié)構(gòu)體類型和一條訪問其實(shí)例的指令如下:

① typedef struct {

② …

③ char *next_in

④ char *next_out

⑤ void *(*bzalloc)(void *,int,int)

⑥ …

⑦ } bz_stream

⑧ …

⑨ while (True) {

⑩ ∥type of s->stnn is bz_stream

5.3 有效性

為了證明COLLATE能夠提供足夠的安全保證,筆者使用真實(shí)世界的漏洞進(jìn)行了測試。首先復(fù)現(xiàn)了2個針對FFmpeg的堆溢出漏洞(CVE-2016-10190和CVE-2016-10191),以及Nginx的1個棧溢出漏洞(CVE-2013-2028)。復(fù)現(xiàn)完成后,利用這些漏洞發(fā)起前向控制流劫持攻擊。最后,在FFmpeg和Nginx上應(yīng)用COLLATE進(jìn)行防御,并檢查攻擊是否被阻止。此外,還以相同的方式使用CFIXX test suite測試COLLATE在C++程序上的有效性。測試結(jié)果顯示,COLLATE成功檢測到了所有的控制流劫持攻擊。

CVE-2016-10190:這是一個由整數(shù)溢出引起的堆溢出漏洞。利用溢出來覆蓋read_packet實(shí)例中的函數(shù)指針AVIOContext,以劫持控制流。在COLLATE中,由于read_packet是一個函數(shù)指針類型,AVIOContext的實(shí)例被分配在Ms中。當(dāng)攻擊者試圖通過堆溢出來修改它時,MPK會檢測到這種非法訪問,并報(bào)告錯誤。系統(tǒng)成功地檢測到了這種攻擊的發(fā)生。

CVE-2016-10191:這是一個堆溢出漏洞。利用溢出覆蓋RTMPPacket中的data指針構(gòu)建任意寫漏洞,最后修改write_packet實(shí)例中的AVOutputFormat函數(shù)指針,實(shí)施控制流劫持攻擊。在COLLATE中,由于write_packet是一個函數(shù)指針類型,AVOutputFormat結(jié)構(gòu)的實(shí)例被分配在Ms中。根據(jù)指針分析的結(jié)果,原本的data指針并沒有指向控制相關(guān)的數(shù)據(jù)。所以將data指針指向的數(shù)據(jù)寫入內(nèi)存的指令不允許修改控制相關(guān)數(shù)據(jù)。因此,當(dāng)通過任意寫入漏洞覆蓋 write_packet 指針時,MPK會檢測到這種非法訪問。

CVE-2013-2028:這是一個由整數(shù)溢出引起的棧溢出漏洞。利用溢出來覆蓋ngx_conf_t的實(shí)例中的函數(shù)指針handler,以劫持控制流。在COLLATE中,ngx_conf_t結(jié)構(gòu)的實(shí)例將被分配到Ms中。當(dāng)攻擊者試圖通過緩沖區(qū)溢出來覆蓋它時,COLLATE將檢測到這種非法訪問。

CFIXX test suite:這個測試套件演示了vtable指針被篡改的各種可能情況??偣灿?種類型,即FakeVT、FakeVT-sig、VTxchg、VTxchag-hier和COOP。以VTxchag-hier的測試為例。VTxchag-hier攻擊將虛表指針修改為同一個類層次結(jié)構(gòu)中另一個類的虛表指針。此時,虛函數(shù)調(diào)用的目標(biāo)就變成了另一個類的虛函數(shù),因?yàn)樘摫碇羔槺淮鄹牧?。在COLLATE中,虛表指針是控制相關(guān)數(shù)據(jù),當(dāng)程序調(diào)用構(gòu)造函數(shù)創(chuàng)建對象時,在Ms處進(jìn)行備份。當(dāng)調(diào)用虛函數(shù)時,將虛表指針與它的備份進(jìn)行比較。如果校驗(yàn)失敗,則說明虛表指針被攻擊者篡改,COLLATE將終止程序并在檢查后報(bào)告錯誤。

6 討 論

6.1 安全分析

分析哪些類型的攻擊可以被COLLATE阻止。

(1) 直接修改函數(shù)指針的攻擊,在進(jìn)行修改時就會被發(fā)現(xiàn)并阻止。

(2) 修改控制相關(guān)數(shù)據(jù)(如函數(shù)指針數(shù)組的下標(biāo)和虛表指針)的攻擊,由于這些數(shù)據(jù)受到保護(hù),同樣會被阻止。

(3) 修改user identity data和decision-making data等關(guān)鍵數(shù)據(jù)的非控制數(shù)據(jù)攻擊無法被阻止,因?yàn)樗鼈儾辉诒Wo(hù)范圍內(nèi)。

(4) DOP攻擊無法阻止,因?yàn)樗耆簧婕翱刂屏鳌?/p>

展望未來,COLLATE也許能夠防御(3)中的一部分,user identity data等數(shù)據(jù)經(jīng)常用在分支語句中,并決定直接控制流轉(zhuǎn)移的目標(biāo)。如果將直接控制流轉(zhuǎn)移納入考慮,那么這些數(shù)據(jù)就可以認(rèn)為是控制相關(guān)數(shù)據(jù),并用COLLATE來保護(hù)。

6.2 指針分析的精度

筆者大量使用了指針分析的結(jié)果,包括使用它來識別可信指令。然而指針分析的精度并不高,運(yùn)行使用COLLATE保護(hù)后的SPEC CPU 2006 benchmarks,并記錄在運(yùn)行時修改控制相關(guān)數(shù)據(jù)的指令。結(jié)果顯示,這些指令只占可信指令的1/5。如果能提高指針分析的精度,那么COLLATE的開銷將會進(jìn)一步降低。也許未來可以結(jié)合靜態(tài)和動態(tài)指針分析來提高精度,或者寄希望于靜態(tài)指針分析的進(jìn)步。

6.3 保護(hù)粒度

現(xiàn)在保護(hù)的粒度是整個內(nèi)存對象,也就是說,即便函數(shù)指針依賴的只是結(jié)構(gòu)體中的一個元素,依然要將整個結(jié)構(gòu)體的內(nèi)存分配到安全內(nèi)存中。同時,即便是訪問這個內(nèi)存中其它元素的指令,也必須被允許訪問受保護(hù)數(shù)據(jù)。如果能將保護(hù)的粒度降低到字段級別,那么就能進(jìn)一步減少COLLATE的開銷。

7 相關(guān)工作

在保護(hù)forward-edge control flow的過程中,筆者的工作和以往的相關(guān)工作有一些相似處和不同處。在這一節(jié)中,對相關(guān)工作進(jìn)行簡要的介紹。

CFI是常用的控制流劫持攻擊的防御措施。早期的CFI是無狀態(tài)的(stateless)[5,10-11,13],它們通過靜態(tài)分析生成CFG,不考慮上下文信息。Intel更是在芯片上添加了對CFI的支持(Intel control-flow enforcement technology),這個硬件特性還被最新的研究用于內(nèi)存隔離[19]。這些CFI無法防御控制流彎曲攻擊[15],因?yàn)樗鼈兊牡葍r類(Equivalence Class,EC)很大。最近的上下文敏感的CFI利用上下文信息來減少EC的大小。PathArmor[12]在運(yùn)行時使用last branch record (LBR)獲取最近的16個分支信息作為上下文。然而LBR的容量太小,導(dǎo)致PathArmor無法防御history flushing攻擊[20]。PITTYPAT[6]和μCFI[17]都利用Intel PT記錄的執(zhí)行路徑信息來縮小EC,μCFI還額外利用了約束數(shù)據(jù)(constraining data)。但是Intel PT的性能和存儲開銷很大,可能帶來實(shí)用性上的問題。此外,在靜態(tài)分析時,CFI-LB[21]使用函數(shù)調(diào)用點(diǎn)(call-site)作為上下文,OS-CFI[8]則選擇了函數(shù)指針和對象的起源(origin)。但是,CScan[9]的測試結(jié)果顯示它們縮小EC的實(shí)際效果和聲稱的存在差距。在上述研究聚焦于縮小EC時,最新的研究[7]認(rèn)為這并不能很好地提高程序的安全性,并提出使用不同基本塊對攻擊者的有用程度作為新的衡量標(biāo)準(zhǔn)。

PI通過保護(hù)指針不被篡改來緩解攻擊。PointGuard[1]和CCFI[4]都對要保護(hù)的指針進(jìn)行加密,并在使用時進(jìn)行解密。但是,PointGuard通過異或進(jìn)行加解密,只要獲取多個指針就能推測出密鑰。CCFI則沒有保護(hù)嵌入C的結(jié)構(gòu)體中的指針。CPI[2]將函數(shù)指針及用于訪問它們的數(shù)據(jù)指針隔離到用信息隱藏(x86-64)或段寄存器(x86-32)保護(hù)的安全區(qū)域中。然而,信息隱藏(information hiding)已經(jīng)被基于時間的側(cè)信道攻擊(timing side-channel attacks)[22]繞過,不再安全。ARMv8-A架構(gòu)中增加了對指針認(rèn)證 (Pointer Authentication,PA)的支持,并且已經(jīng)被證明能夠有效地保護(hù)代碼指針和返回地址[23],甚至實(shí)現(xiàn)內(nèi)存安全[24]。類似地,ZeR?[25]提出在ISA中增加一些專用于讀寫指針的指令以保護(hù)代碼和數(shù)據(jù)指針的完整性,并設(shè)計(jì)了一個新型的元數(shù)據(jù)編碼方案。

許多工作使用基于硬件的隔離來保護(hù)敏感數(shù)據(jù)或安全區(qū)域。xMP[26]使用Intel虛擬化技術(shù)(Intel VT-x)來保護(hù)它的不連續(xù)的xMP域,不過這需要對內(nèi)核進(jìn)行大量修改。Hodor[27]和ERIM[28]都使用MPK來實(shí)現(xiàn)進(jìn)程內(nèi)隔離,并分別在高吞吐率和高切換率的情況下取得了優(yōu)異的性能。VIP[18]采取了另一種思路,將安全敏感數(shù)據(jù)的值備份在MPK保護(hù)的HYPERSPACE中,并在使用時取出記錄進(jìn)行驗(yàn)證。為了提高性能,VIP中普通內(nèi)存和HYPERSPACE是直接映射的,這導(dǎo)致內(nèi)存開銷最大可約達(dá)103.1%。此外,VIP不能自動識別安全敏感數(shù)據(jù),需要人工標(biāo)注,主要用來加固其它安全措施。與之相比,能夠自動識別控制相關(guān)數(shù)據(jù),并且沒有額外的內(nèi)存開銷,只是改變了要保護(hù)的數(shù)據(jù)的分配位置。cryptoMPK[29]也能首先自動識別加密(crypto)相關(guān)的緩沖區(qū)和操作,然后在源代碼層面上將它們轉(zhuǎn)移到MPK保護(hù)的域中。TDI[30]則在隔離方面有更細(xì)的粒度,它將不同類型的內(nèi)存對象隔離在不同的內(nèi)存區(qū)域,并對這些區(qū)域的加載操作進(jìn)行限制。PKRU-SAFE[31]將在混合語言環(huán)境中將安全語言和不安全語言的內(nèi)存隔離開來,從而防止攻擊者通過攻擊不安全語言編寫的部分來破壞安全語言的安全保證。PKRU-SAFE只考慮堆內(nèi)存,并使用動態(tài)分析識別需要保護(hù)的內(nèi)存,而非COLLATE采用的靜態(tài)分析。這雖然避免了假陽性,卻會導(dǎo)致假陰性的出現(xiàn),從而發(fā)生漏報(bào)。Jenny[32]則用MPK來保護(hù)自己的系統(tǒng)調(diào)用監(jiān)視器(syscall monitor)相關(guān)的代碼和數(shù)據(jù),對系統(tǒng)調(diào)用進(jìn)行過濾。

8 結(jié)束語

文中提出了COLLATE,一種新型的保證間接控制流轉(zhuǎn)移的目標(biāo)為預(yù)期值的防御措施。COLLATE的關(guān)鍵思想是保護(hù)函數(shù)指針和它們的依賴不被篡改。實(shí)現(xiàn)了COLLATE的原型并評估了它的性能和有效性。評估結(jié)果表明,COLLATE在實(shí)際應(yīng)用和測試套件中成功地阻止了控制流劫持,并將平均開銷保持在可接受范圍內(nèi)。這證明COLLATE是有效的且是低開銷的,具有實(shí)用性。

猜你喜歡
控制流污點(diǎn)指針
基于代碼重寫的動態(tài)污點(diǎn)分析
抵御控制流分析的Python 程序混淆算法
工控系統(tǒng)中PLC安全漏洞及控制流完整性研究
電子科技(2021年2期)2021-01-08 02:25:58
抵御控制流分析的程序混淆算法
偷指針的人
為什么表的指針都按照順時針方向轉(zhuǎn)動
使用Lightroom污點(diǎn)去除工具清理照片中的瑕疵
我國“污點(diǎn)證人”刑事責(zé)任豁免制度的構(gòu)建
基于改進(jìn)Hough變換和BP網(wǎng)絡(luò)的指針儀表識別
電測與儀表(2015年5期)2015-04-09 11:30:42
基于控制流隱藏的代碼迷惑
秦安县| 会昌县| 蒙自县| 仁布县| 比如县| 剑河县| 武夷山市| 和顺县| 盐亭县| 汾阳市| 焦作市| 剑河县| 买车| 武平县| 兴义市| 丰宁| 抚顺市| 南昌市| 新田县| 越西县| 勐海县| 南丹县| 东源县| 阳山县| 浙江省| 平利县| 博白县| 铜陵市| 闻喜县| 德格县| 仙居县| 贵定县| 绥滨县| 微博| 四川省| 曲阳县| 昂仁县| 左云县| 威海市| 陆川县| 新泰市|