王雅儀,劉 琛,黃天波,文偉平
(北京大學(xué) 軟件與微電子學(xué)院,北京 102600)
從早期的個(gè)人計(jì)算機(jī)發(fā)展至今,軟件的攻擊與保護(hù)之爭(zhēng)從未停止過,在軟件種類和平臺(tái)呈現(xiàn)百花齊放的當(dāng)下,如何有效地保護(hù)軟件,始終是一個(gè)值得探究的問題。
作為軟件保護(hù)技術(shù)之一的代碼混淆因使用上的靈活性和較低成本帶來了可觀的安全性,在工業(yè)界和學(xué)術(shù)界得到不斷關(guān)注和發(fā)展。軟件攻擊的目的在于理解或部分理解原生代碼,從而添加或者移除代碼邏輯,以滿足攻擊者自身的利益訴求。代碼混淆預(yù)期的目標(biāo)是增大原生程序的理解難度,包括阻止自動(dòng)化工具的反編譯,增加原生程序本身的復(fù)雜度等。Collberg 等[1]在1997 年首次對(duì)代碼混淆技術(shù)進(jìn)行了明確的分類,包括布局混淆、數(shù)據(jù)混淆、控制流混淆和預(yù)防性混淆。此后,又出現(xiàn)了各種形式的代碼混淆研究,應(yīng)用于軟件的不同階段,如在實(shí)現(xiàn)[2]或編譯[3]時(shí),涵蓋了代碼的不同部分,如在源代碼[4]、二進(jìn)制代碼[5-6]或中間代碼[7]級(jí)別。
不同于文獻(xiàn)[1]的分類方式,本文按照混淆的粒度,將代碼混淆方法分為函數(shù)混淆、基本塊混淆和指令混淆。指令作為高級(jí)程序的底層組織,面向指令層級(jí)的混淆可以做一些更細(xì)致精準(zhǔn)的操作。如果攻擊者能夠破解程序,首先面對(duì)的便是破解后的程序指令,因此指令混淆對(duì)此階段的代碼保護(hù)具有重要作用。這也正是本文的工作聚焦于指令混淆的原因。
底層虛擬機(jī)混淆器(Obfuscator Low Level Virtual Machine,OLLVM)[7]是一款經(jīng)典的開源混淆項(xiàng)目,它支持指令替換、虛假控制流、控制流扁平化等功能。然而,在指令混淆層面,它只提供指令替換一種功能,并不支持指令加花,并且指令替換只支持5 種運(yùn)算符,共計(jì)13 種替換方案。
針對(duì)OLLVM 的不足,本文設(shè)計(jì)了一種改進(jìn)的指令混淆框架,命名為InsObf,旨在對(duì)其中的指令混淆進(jìn)行補(bǔ)充。已實(shí)現(xiàn)的優(yōu)化工作包括:拓展指令替換功能至13 種運(yùn)算符,共計(jì)52 種指令替換方案;增加指令加花的功能,包含疊加跳轉(zhuǎn)和虛假循環(huán)兩種指令加花方案。實(shí)驗(yàn)結(jié)果證明,本文提出的指令混淆框架的效果相較于OLLVM,在時(shí)間開銷增長(zhǎng)約10個(gè)百分點(diǎn),空間開銷增長(zhǎng)約20 個(gè)百分點(diǎn)的情況下,圈復(fù)雜度和抗逆向能力均提升了近4 倍;同基于OLLVM 改進(jìn)的Armariris 和Hikari 相比,本文提出的框架在同一量級(jí)時(shí)空開銷下,圈復(fù)雜度增長(zhǎng)最高,達(dá)到了混淆前的6 倍,并將指令相似度降低到23.5%,可以提供更高的代碼復(fù)雜度。
指令混淆是針對(duì)程序中的指令進(jìn)行添加、修改等操作的一種混淆方法,包括指令加花[8-10]、指令替換[7,11]、指令加密[12-13]和指令重組[14]。向指令流[15]中添加死代碼[16]、冗余操作數(shù)[17]、新段[18]等,都屬于這類轉(zhuǎn)換。相較于指令加密和指令重組帶來的時(shí)空開銷高、程序健壯性差等問題,指令加花和指令替換相對(duì)更為成熟,且使用場(chǎng)景更為廣泛,特別地,在OLLVM 僅實(shí)現(xiàn)了指令替換,存在指令加花的技術(shù)空白,因此本文研究一方面聚焦于填補(bǔ)OLLVM 指令加花的空白,另一方面著眼于進(jìn)一步加強(qiáng)目前已有的指令替換方案。下文將介紹指令加花和指令替換的相關(guān)研究工作。
1.1.1 指令加花
指令加花的目的是向程序中添加一些冗余信息,這些信息不會(huì)改變程序預(yù)期的行為,但可增大指令的理解難度,擾亂指令的原生特征。
常見的方式是插入一些不會(huì)被執(zhí)行或執(zhí)行后不影響程序預(yù)期執(zhí)行結(jié)果的指令。Zhang 等[19]通過引入動(dòng)態(tài)不透明謂詞在兩個(gè)基本塊之間插入一個(gè)冗余的基本塊,從而擾亂原始基本塊間的依賴關(guān)系;Cho 等[17]提出插入無關(guān)的循環(huán)變量和冗余的操作數(shù),復(fù)雜化原有的計(jì)算表達(dá)式;文獻(xiàn)[20]中通過插入虛假的調(diào)用,在后續(xù)指令中使用調(diào)用結(jié)果來干擾程序的分析;Peng 等[21]提出在循環(huán)條件前利用不透明謂詞插入指令,復(fù)雜化程序結(jié)構(gòu)。也有一些研究[22-24]采用插入NOP(No Operation)指令的方式,盡管NOP 指令不執(zhí)行任何操作,但其隨機(jī)性的存在可以使代碼片段在內(nèi)存中位置的預(yù)測(cè)變得更加困難。
另一類是在指令的特定位置插入垃圾指令來阻止反匯編工具的處理。Linn 等[25]通過在指令流的特定位置插入不完整的字節(jié)來引入反匯編錯(cuò)誤;文獻(xiàn)[26]中針對(duì)ARM 指令進(jìn)行混淆處理,通過添加無效指令和構(gòu)造隨機(jī)數(shù)據(jù),實(shí)現(xiàn)對(duì)線性掃描反匯編同步的延遲,以阻止自動(dòng)化反編譯器對(duì)指令的約簡(jiǎn)。
1.1.2 指令替換
指令替換是指用功能相同但更復(fù)雜的指令序列替換源程序中的指令運(yùn)算符或操作數(shù),使得程序中的指令更難理解。例如,指令中的操作數(shù)可以用一段代碼替換[20],拆分為多個(gè)變量[17,27],反之,也可以把多個(gè)變量合并在一起。Rajba等[28]使用按位與、左移和異或運(yùn)算符對(duì)加法運(yùn)算符進(jìn)行混淆。對(duì)布爾變量進(jìn)行分割[29]、將常量替換為一串運(yùn)算表達(dá)式[30]或者利用代碼動(dòng)態(tài)生成[29]也是常見的替換方案。Kanzaki 等[31-32]提出首先通過代碼自修改機(jī)制將原始指令替換成另一套偽指令集,然后在代碼運(yùn)行時(shí)自動(dòng)恢復(fù)成原始代碼片段,使得逆向人員只能拿到替換后的偽指令集。Darwish 等[33]針對(duì)匯編語言,指出系統(tǒng)中任何操作都可以通過不同方式實(shí)現(xiàn),因此可以從原指令的可能替換組中隨機(jī)生成等價(jià)的代碼替換。
1.2.1 OLLVM
底層虛擬機(jī)(Low Level Virtual Machine,LLVM)[34]為代碼混淆技術(shù)跨平臺(tái)使用提供了可能。OLLVM 是第一個(gè)基于LLVM 的開源混淆項(xiàng)目,借助于LLVM 的跨平臺(tái)性,OLLVM適用于任何LLVM 支持的編程語言,例如C/C++、Objective-C、Fortran 等。
在指令替換部分,OLLVM 支持整數(shù)ADD、SUB 和布爾運(yùn)算符AND、OR 和XOR。表1 是具體支持的替換方案。
表1 OLLVM指令替換方案Tab.1 Instruction substitution schemes in OLLVM
1.2.2 Armariris
Armariris[35]是由上海交通大學(xué)密碼與計(jì)算機(jī)安全實(shí)驗(yàn)室維護(hù)的LLVM 混淆框架,目前提供了字符串加密、控制流扁平化和指令替換三個(gè)功能。項(xiàng)目本身相較于OLLVM 的改進(jìn)在于字符串加密方案的補(bǔ)充,雖然僅針對(duì)源代碼中的常量字符串提供異或加密的方式,但是為基于LLVM 的字符串加密提供了新的可能性。
1.2.3 Hikari
Hikari[36]是一個(gè)基于OLLVM 進(jìn)行優(yōu)化的二進(jìn)制加固工具,主要改進(jìn)了控制流混淆和字符串混淆等功能,未涉及指令層級(jí)的混淆優(yōu)化。其中,控制流混淆的改進(jìn),如直接跳轉(zhuǎn)間接化,通過將程序中跳轉(zhuǎn)指令的目標(biāo)地址由明文改為數(shù)組匹配的方式,實(shí)現(xiàn)間接跳轉(zhuǎn)。在字符串層面,不同于Armariris 的字符串處理方式,加密是在函數(shù)內(nèi)部完成而非全局處理,且只有運(yùn)行過的函數(shù)才會(huì)將解密過的字符串存放在內(nèi)存中。
InsObf 指令混淆框架的工作原理如圖1 所示,可以被分為三層:源代碼層、LLVM 中間表示(LLVM-Intermediate Representation,LLVM-IR)層和目標(biāo)平臺(tái)層。源代碼層包含待混淆的源程序。在LLVM-IR 層中,源程序通過LLVM 前端編譯器(例如Clang[37])轉(zhuǎn)換為L(zhǎng)LVM-IR 文件,然后由指令混淆生成混淆后的LLVM-IR 文件。根據(jù)預(yù)期的混淆強(qiáng)度,可以選擇繼續(xù)混淆或使用LLVM 后端生成基于特定目標(biāo)平臺(tái)的可執(zhí)行程序。
圖1 InsObf整體架構(gòu)Fig.1 Overall architecture of InsObf
InsObf 指令混淆框架包含兩種混淆方法:指令加花和指令替換。在混淆的過程中,InsObf 首先通過指令加花方法來混淆LLVM-IR 文件,插入一些冗余指令組成的基本塊。然后用指令替換方法處理操作符和操作數(shù),進(jìn)一步加強(qiáng)安全性。
指令加花通過結(jié)合指令的數(shù)據(jù)依賴分析,隨機(jī)選擇花指令插入的位置,保證混淆結(jié)果的多樣性和隨機(jī)性。插入的疊加跳轉(zhuǎn)指令和原始基本塊內(nèi)的指令高度相似但不同,虛假循環(huán)指令使用原始基本塊中的變量作為循環(huán)控制條件,兩種形式的花指令在充分利用源程序中指令信息的同時(shí),能夠有效干擾逆向人員的分析,加大分析的難度。指令替換可以從生成的多個(gè)等價(jià)表達(dá)式中隨機(jī)選擇替換方案,來實(shí)現(xiàn)對(duì)原有語義的隱藏,給程序帶來多樣化。當(dāng)指令替換同指令加花進(jìn)行效果疊加后,這種隱藏能力被最大限度地發(fā)揮:既作用于源程序的真實(shí)指令語義,破壞反混淆器既定的模式識(shí)別;又作用于添加的花指令,進(jìn)一步隱藏真實(shí)指令和源程序指令之間的相似度,使得通過利用程序地址空間的攻擊更難實(shí)現(xiàn)。
本文提出了兩種指令加花方法:一種是疊加跳轉(zhuǎn),另一種是虛假循環(huán)。文獻(xiàn)[19,38]將垃圾指令添加到基本塊之間,然而基本塊內(nèi)部指令在語義上更有連貫性,相應(yīng)地,直接破壞基本塊內(nèi)部指令結(jié)構(gòu)將給程序的理解上帶來更大的難度。因此,本文在原有基本塊內(nèi)部添加花指令,同時(shí)考慮到程序的三種基本結(jié)構(gòu)中,相較于順序結(jié)構(gòu),分支和循環(huán)結(jié)構(gòu)包含更多程序的控制流信息,往往是攻擊者關(guān)注的重點(diǎn),大量分支和循環(huán)結(jié)構(gòu)的存在會(huì)加大分析的難度。因此,在花指令的設(shè)計(jì)形式上,構(gòu)造疊加跳轉(zhuǎn)和虛假循環(huán)的形式。為了構(gòu)造形式的完整性,本文將構(gòu)造的花指令以新的基本塊的形式進(jìn)行添加,首先對(duì)原有的基本塊進(jìn)行拆分,然后在拆分后的基本塊間插入構(gòu)造的花指令基本塊,并通過不透明謂詞進(jìn)行有效的整合。
2.1.1 疊加跳轉(zhuǎn)
定義1疊加跳轉(zhuǎn)。記程序的基本塊集合為originBBs,對(duì)?b∈originBBs,將其分割為b1和b2,并根據(jù)分割后的基本塊生成新的虛假塊集合bogusBBs,在保證程序語義的前提下,構(gòu)造bogusBBs內(nèi)虛假塊與b1、b2的跳轉(zhuǎn)關(guān)系,形成具有層級(jí)的基本塊關(guān)系。
添加疊加跳轉(zhuǎn)指令的過程中有如下兩點(diǎn)需要討論說明:
1)基本塊內(nèi)的指令之間往往存在數(shù)據(jù)依賴關(guān)系,如何在不破壞數(shù)據(jù)依賴關(guān)系的情況下對(duì)基本塊進(jìn)行分割?
2)插入基本塊給程序的運(yùn)行帶來了新的開銷,如何盡可能地降低增加的時(shí)空開銷?
為了解決問題1),本文設(shè)計(jì)了一種數(shù)據(jù)依賴分析算法,詳細(xì)的步驟如算法1 所示。在對(duì)基本塊進(jìn)行分割之前,需要先分析基本塊指令間的數(shù)據(jù)依賴關(guān)系,進(jìn)而將原本單一基本塊內(nèi)的所有指令劃分為幾個(gè)獨(dú)立的指令集合。每個(gè)集合內(nèi)的指令是高度內(nèi)聚的,即具有強(qiáng)相關(guān)性;不同集合中的指令無數(shù)據(jù)依賴關(guān)系。極端情況下,整個(gè)基本塊內(nèi)的指令均存在強(qiáng)相關(guān)性,此時(shí),一個(gè)基本塊即是一個(gè)集合,將不對(duì)基本塊做任何處理。
算法1 的輸入為基本塊內(nèi)所有的LLVM-IR 指令,輸出為存儲(chǔ)指令依賴分析結(jié)果的嵌套堆棧集合,集合內(nèi)的每一個(gè)子集也是一個(gè)棧,用于存儲(chǔ)一組相互依賴的指令。算法實(shí)現(xiàn)的中間過程需要借助棧和隊(duì)列來完成,前者用于存儲(chǔ)相關(guān)依賴指令,最后作為子集存儲(chǔ)到依賴分析結(jié)果集中;后者用于存儲(chǔ)當(dāng)前指令搜索過程中涉及的直接依賴指令和間接依賴指令,并不斷向外擴(kuò)展,直到?jīng)]有新的依賴指令。針對(duì)每一條新加入的指令,均需要備份并對(duì)其依賴關(guān)系進(jìn)行重新檢查,通過隊(duì)列實(shí)現(xiàn)每一條加入的新指令,其依賴的指令也會(huì)同步加入指令堆棧中。在指令依賴關(guān)系分析過程中,若出現(xiàn)所依賴的指令已經(jīng)添加到其他的指令集合中,則生成原生指令的備份存儲(chǔ)到新的集合中,以避免數(shù)據(jù)流的斷裂問題。在基本塊分割時(shí),則從指令依賴分析結(jié)果集之間隨機(jī)選擇一個(gè)位置,如原始的基本塊根據(jù)算法1 的分析結(jié)果被分割成startBB和mainBB,效果如圖2 所示。
圖2 拆分基本塊示意圖Fig.2 Schematic diagram of splitting basic blocks
問題2),因受限于算法本身花指令的設(shè)計(jì),在空間開銷上未作出較大優(yōu)化。對(duì)于時(shí)間開銷,本文使用Palsberg[39]引入的動(dòng)態(tài)不透明謂詞,將花指令添加到運(yùn)行時(shí)不可達(dá)的位置來盡可能降低時(shí)間上的開銷。如在圖2 的基礎(chǔ)上將mainBB用作原型副本,復(fù)制得到新的基本塊,并隨機(jī)替換其中一些指令(不需要等價(jià)),從而獲得bogusBB1、bogusBB2等。這些新被創(chuàng)建的基本塊和mainBB 高度相似但不同,在充分利用程序信息的同時(shí)起到混淆視聽的效果。然后,這些創(chuàng)建的新基本塊將通過不透明謂詞控制,插入到startBB 和mainBB 之間。圖3 為插入疊加跳轉(zhuǎn)指令后的控制流示意圖。另外,創(chuàng)建的新基本塊的數(shù)量可以由用戶指定,針對(duì)不同規(guī)模的程序提供定制化的功能,從而提供更高的靈活性,默認(rèn)數(shù)值為2。
圖3 插入疊加跳轉(zhuǎn)指令后的控制流Fig.3 Control flow after inserting multiple jump instruction
2.1.2 虛假循環(huán)
定義2虛假循環(huán)。構(gòu)造循環(huán)對(duì)應(yīng)的基本塊集合loopBBs,記程序的基本塊集合為 originBBs,對(duì)?b∈originBBs,將其分割為基本塊b1和b2,在保證程序語義的前提下,通過構(gòu)造跳轉(zhuǎn)關(guān)系,在b1和b2中嵌入loopBBs 等虛假塊。
為了增加混淆效果的多樣性,可以由用戶指定混淆概率,對(duì)程序中的所有基本塊以設(shè)定的概率依次決定是否要添加虛假循環(huán),默認(rèn)混淆概率為80%。
以一段用C 語言編寫的循環(huán)程序?yàn)槔?,為了敘述上的?jiǎn)便性,這里的loopBBs 為程序?qū)?yīng)的基本塊集合,loopBBs={entryBB,loopCondition,loopBody}。entryBB 對(duì)應(yīng)于第1)~2)行循環(huán)語句的前序聲明對(duì)應(yīng)的基本塊,loopCondition 為第3)行循環(huán)條件判斷對(duì)應(yīng)的基本塊,loopBody 為第4)行內(nèi)部循環(huán)體對(duì)應(yīng)的基本塊。
圖4 為插入虛假循環(huán)指令后的控制流示意圖。在startBB 和bogusBB 之間插入loopBBs,如 圖4 中深色區(qū)域所示。值得注意的是,循環(huán)指令的設(shè)計(jì)具有高度靈活性,在實(shí)際應(yīng)用時(shí)可以使用與源程序有著數(shù)據(jù)相關(guān)性、更加復(fù)雜的形式來代替。例如,在循環(huán)體內(nèi)添加源程序中相關(guān)變量的運(yùn)算操作,在循環(huán)條件中引入源程序中的變量等,從而進(jìn)一步和源程序產(chǎn)生數(shù)據(jù)關(guān)聯(lián)。
圖4 插入虛假循環(huán)指令后的控制流Fig.4 Control flow after inserting bogus loop instruction
指令替換混淆是指令的等效替換,即用語義相同但形式不同的一條或多條指令來替換。在OLLVM 中,指令替換僅支持5 種運(yùn)算符,共計(jì)13 種替換方案。本文基于OLLVM,在支持的指令操作符數(shù)量和替換方案上做了補(bǔ)充,共計(jì)13 種運(yùn)算符,52 種替換方案,具體的運(yùn)算符和替換方案如表2 所示。在實(shí)際應(yīng)用時(shí),指令替換可以隨機(jī)選擇替換方案,給程序帶來多樣性。
表2 OLLVM和InsObf支持的運(yùn)算符和替換方案數(shù)Tab.2 The number of operators and substitution schemes supported in OLLVM and InsObf
在OLLVM 的指令替換方案中,主要關(guān)注的是指令中操作符的替換,在操作數(shù)方面僅提供了增加冗余操作數(shù)的替換方案,考慮到混淆優(yōu)化處于LLVM-IR 層,在后端編譯時(shí),如果使用高級(jí)別的優(yōu)化選項(xiàng),例如-O2 和-O3,OLLVM 中提出的類似于a=b+c替換為a=b-(-c)的方案可以被編譯器優(yōu)化恢復(fù)。因此,除OLLVM 中提供的替換思路,本文還額外增加了兩種更高強(qiáng)度的方法:數(shù)據(jù)拆分替換和循環(huán)替換,來對(duì)指令中的操作數(shù)進(jìn)行破壞,消除后端編譯器優(yōu)化的影響,目前這兩種方法只支持整數(shù)類型的變換。
定義3數(shù)據(jù)拆分替換。程序的運(yùn)算操作集記為O,O所涉及的常量和變量集合記為D,Δx記為運(yùn)算中產(chǎn)生的進(jìn)位或借位信息。對(duì)?N∈D,將N拆分為高位數(shù)據(jù)Nhigh和低位數(shù)據(jù)Nlow,?o∈O轉(zhuǎn)變?yōu)獒槍?duì)Nhigh和Nlow的操作,并同步更新Δx的數(shù)值,默認(rèn)為0。
以32 位整數(shù)的ADD 指令為例,數(shù)據(jù)拆分替換會(huì)將該整數(shù)拆分為高16 位和低16 位,具體的方案如圖5 所示。
圖5 ADD指令數(shù)據(jù)拆分替換示例Fig.5 Example of data splitting substitution for ADD instruction
在數(shù)據(jù)拆分替換中,因?yàn)樵贚LVM-IR 層8 位整數(shù)不能被拆分為更低位的數(shù)字,因此只關(guān)注16 位、32 位、64 位和128位的整數(shù)。
考慮到數(shù)據(jù)拆分替換后的混淆特征較為明顯,進(jìn)一步提出循環(huán)替換算法,對(duì)操作符進(jìn)行降級(jí)處理,例如將左移指令轉(zhuǎn)變?yōu)槌朔ㄖ噶睢⒊朔ㄖ噶钷D(zhuǎn)變?yōu)榧臃ㄖ噶畹取?/p>
定義4循環(huán)替換。記操作的閾值為N,操作Op1 為將程序中的左移指令轉(zhuǎn)變?yōu)檠h(huán)形式的乘法指令,操作Op2 為將乘法指令轉(zhuǎn)變?yōu)檠h(huán)形式的加法指令。使用Op1 或Op2對(duì)?i∈{inst|inst∈{MUL,SHL}}進(jìn)行處理,若循環(huán)的處理次數(shù)超過閾值N,閾值內(nèi)的循環(huán)邏輯保留,超出的部分仍使用之前的指令形式。
以操作Op2(乘法指令轉(zhuǎn)變?yōu)檠h(huán)形式的加法指令)為例,循環(huán)替換的過程如圖6 所示。需要注意的是,為了保證程序運(yùn)行的效率,閾值N可由用戶設(shè)定。如用戶未設(shè)定,在程序首次使用時(shí),統(tǒng)計(jì)程序中所有乘法指令轉(zhuǎn)變?yōu)榧臃ㄖ噶畹念l次分布,取中位數(shù)作為默認(rèn)值。
圖6 乘法指令循環(huán)替換示例Fig.6 Example of loop substitution for MUL instruction
本文選擇了20 個(gè)典型的C++程序[40]為實(shí)驗(yàn)數(shù)據(jù)集,包括數(shù)組、樹、圖等數(shù)據(jù)結(jié)構(gòu)和搜索、排序、加密等算法。實(shí)驗(yàn)環(huán)境為8 GB 內(nèi)存的Ubuntu 18.04 64 位機(jī),實(shí)驗(yàn)基于LLVM 10 release 版進(jìn)行。為了驗(yàn)證本文提出的指令混淆框架InsObf 的混淆效果,分別從指令混淆的優(yōu)化效果和不同混淆方法的對(duì)比上進(jìn)行實(shí)驗(yàn)分析。InsObf 指令混淆框架的指令加花子模塊實(shí)現(xiàn),記為InsObf-junk;InsObf 指令混淆框架的指令替換子模塊實(shí)現(xiàn),記為InsObf-sub;InsObf 指令混淆框架整體實(shí)現(xiàn),記為InsObf-mix;OLLVM 的指令替換模塊,記為OLLVM-sub。
指令混淆優(yōu)化效果從混淆效果分析和性能分析兩方面進(jìn)行評(píng)估。對(duì)于混淆效果分析,采用Collberg[1]提出的定量的混淆指標(biāo):混淆強(qiáng)度(potency)[14]和抗逆向(resilience)[14];對(duì)于性能分析,采用時(shí)空開銷作為測(cè)試指標(biāo)。
3.1.1 混淆強(qiáng)度
圈復(fù)雜度(Cyclomatic Complexity,CC)由McCabe[41]1976年提出,用來描述程序的復(fù)雜性?;煜龔?qiáng)度用來衡量自然人理解程序的難度[1],常用CC 進(jìn)行評(píng)估。在數(shù)學(xué)上,程序控制流圖G的圈復(fù)雜度V(G)可以定義為:
其中:E是G中邊的數(shù)量,N是節(jié)點(diǎn)的數(shù)量。
目前公開的圈復(fù)雜度工具需依據(jù)源碼進(jìn)行數(shù)值處理,因此本文使用LLVM-CBE[42]從混淆前后的可執(zhí)行文件中獲取到對(duì)應(yīng)的C 源代碼,然后使用Lizard[43]得到混淆前后的圈復(fù)雜度。結(jié)果如表3 所示。
表3 InsObf和OLLVM圈復(fù)雜度分析 單位:%Tab.3 Cyclomatic complexity analysis of InsObf and OLLVM unit:%
從表3 的結(jié)果可以看出,同ollvm-sub 相比,InsObf-sub 的圈復(fù)雜度提升近1 倍,印證了新增的替換模式能夠有效增大程序的復(fù)雜性。InsObf-junk 通過構(gòu)造基本塊和跳轉(zhuǎn)關(guān)系,對(duì)源程序的控制流進(jìn)行了破壞,從而實(shí)現(xiàn)圈復(fù)雜度相較于ollvm-sub 提升近2 倍。而指令混淆框架的整體實(shí)現(xiàn)InsObfmix 的圈復(fù)雜度相較于ollvm-sub 更是提升了近4 倍,有效加大了攻擊者分析和理解程序的難度。
3.1.2 抗逆向
混淆后的抗逆向能力,表示混淆后的程序抵御自動(dòng)去混淆攻擊的能力[1],為了有效量化該指標(biāo),本文進(jìn)一步做了如下定義:
定義5抗逆向。針對(duì)混淆添加的程序代碼,使用去混淆工具無法移除的代碼比重。比重越大,即不可移除的混淆代碼越多,則混淆方法的抗逆向能力越強(qiáng);反之,抗逆向能力越弱??鼓嫦蚰芰τ?jì)算方式為:
其中:Lorigin是混淆前文件的代碼行數(shù),Lobfus是混淆后文件的代碼行數(shù),Ldeobf是去混淆處理后文件的代碼行數(shù)。
本文使用IDA Pro[44]獲取混淆前后文件的代碼行數(shù),然后使用插件D-810[45]對(duì)混淆后的文件進(jìn)行去混淆處理,得到對(duì)應(yīng)的去混淆文件代碼。分別統(tǒng)計(jì)針對(duì)ollvm-sub、InsObfsub、InsObf-junk 和InsObf-mix 抗逆向的效果,并以ollvm-sub作為對(duì)比基準(zhǔn),結(jié)果如表4 所示。
表4 InsObf和OLLVM抗逆向分析 單位:%Tab.4 Resilience analysis of InsObf and OLLVM unit:%
從表4 可以看出,InsObf-sub 的抗逆向能力在ollvm-sub基礎(chǔ)上提升近2 倍,InsObf-junk 提升近3 倍,可印證兩種方法在防反混淆工具處理層面是有明顯的增強(qiáng)效果的;同時(shí)考慮到指令加花添加了更多的指令,因此數(shù)值上會(huì)高于指令替換。指令混淆框架的整體實(shí)現(xiàn)InsObf-mix 更是提升近4 倍,可有效抵抗反混淆工具的攻擊。
3.1.3 時(shí)空開銷
為了獲取混淆前后增長(zhǎng)的時(shí)空開銷,首先針對(duì)測(cè)試數(shù)據(jù)集中的每一個(gè)文件進(jìn)行混淆處理,然后使用腳本獲取具體的混淆前后的時(shí)空開銷:時(shí)間統(tǒng)計(jì)上取多次運(yùn)行的平均值,空間上取可執(zhí)行文件的大小。針對(duì)數(shù)據(jù)集,分別使用ollvmsub、InsObf-sub、InsObf-junk 與InsObf-mix 進(jìn)行如上處理,數(shù)值記錄如表5 所示。
表5 InsObf和OLLVM時(shí)空開銷分析 單位:%Tab.5 Time and space cost analysis of InsObf and OLLVM unit:%
從表5 可以看出,InsObf-sub 混淆前后時(shí)空開銷增長(zhǎng)和ollvm-sub 差距不大,均在5 個(gè)百分點(diǎn)以內(nèi)。同時(shí)InsObf-junk相較于ollvm-sub 在時(shí)間開銷上降低約6 個(gè)百分點(diǎn),空間開銷增加約16 個(gè)百分點(diǎn)。在時(shí)間開銷上,雖然InsObf-junk 使用到的不透明謂詞有所增加,但相較于ollvm-sub 還是有明顯的優(yōu)勢(shì)。另一方面,算法本身添加的大量的花指令使其空間開銷高于ollvm-sub。整體的指令混淆框架,因?yàn)橹噶罴踊ê椭噶钐鎿Q的疊加效果,相較于ollvm-sub 時(shí)間上增加約10 個(gè)百分點(diǎn),空間上約20 個(gè)百分點(diǎn)。
綜合表3~5,可以得到如下結(jié)論:
1)InsObf-sub 在OLLVM 的基礎(chǔ)上,以增加5 個(gè)百分點(diǎn)以內(nèi)的時(shí)空開銷為代價(jià),可有效增加混淆后的程序圈復(fù)雜度和抗逆向的能力。
2)InsObf-junk,相較于InsObf-sub,在增加10 個(gè)百分點(diǎn)的空間開銷的情況下,圈復(fù)雜度可以提升近一半,抗逆向能力可以提升近一倍。
3)相較于OLLVM,InsObf 在時(shí)間開銷增加約10 個(gè)百分點(diǎn),空間開銷增加約20 個(gè)百分點(diǎn)的情況下,可提高4 倍的圈復(fù)雜度和抗逆向能力。
不同的混淆方法在實(shí)現(xiàn)時(shí)側(cè)重于程序不同的安全屬性,如指令混淆側(cè)重于指令的替換和復(fù)雜化,控制流混淆側(cè)重于隱藏或擾亂程序的原生路徑,字符串混淆側(cè)重于隱藏或保護(hù)程序中的字符串等常量信息,所以目前沒有統(tǒng)一的指標(biāo)可以在混淆所帶來的安全性角度,對(duì)不同混淆方法給出一個(gè)定性或定量、足夠完備或公平的對(duì)比說明,但從廣義上的程序復(fù)雜性角度,可以使用時(shí)空開銷和圈復(fù)雜度進(jìn)行效果的說明。因此,本文將從時(shí)空開銷和圈復(fù)雜度的角度比較InsObf 和目前業(yè)界針對(duì)OLLVM 的兩大改進(jìn)版:Armariris 和Hikari 的效果。分別統(tǒng)計(jì)混淆前后可執(zhí)行文件在時(shí)空和圈復(fù)雜度上的增長(zhǎng),表6 是不同混淆方法對(duì)比的效果,Armariris-string 為Armariris 改進(jìn)的字符串加密(StringObfuscation)的實(shí)現(xiàn);Hikari-string 為Hikari 改進(jìn)的字符串加密(StringEncryption)的實(shí)現(xiàn);Hikari-cf 為Hikari 改進(jìn)的控制流混淆方法的疊加使用,包 括 FunctionCallObfuscate、FunctionWrapper 和IndirectBranching 三種。
表6 不同混淆方法的混淆效果 單位:%Tab.6 Obfuscation effects of different methods unit:%
時(shí)間開銷上,Armariris 的字符串加密混淆使用了異或的加解密方式,所以時(shí)間增長(zhǎng)上最少,Hikari 的控制流混淆涉及控制流的多級(jí)處理,在時(shí)間開銷上尤甚??臻g開銷上,本文提出的指令混淆框架增加最多,Armariris 的字符串加密混淆增長(zhǎng)最少,幾乎為0,考慮到本文方法添加了較多的花指令,而Armariris 使用異或進(jìn)行加解密,未在空間上引入額外開銷,結(jié)果在預(yù)料內(nèi)。圈復(fù)雜度數(shù)值處理上,需要使用llvmcbe 獲取混淆前后的源文件,但Hikari 混淆后的文件無法使用LLVM-CBE 有效生成源文件,因此表6 中Hikari-cf 一欄置空。但從已有的數(shù)據(jù)中,InsObf-mix 混淆前后的圈復(fù)雜度增長(zhǎng)最多,達(dá)到了6 倍。
為了同Hikari 改進(jìn)的控制流混淆方法進(jìn)行有效對(duì)比,使用BinDiff[46]比 較Hikari 和InsObf 混淆前后程序的指令相似度,結(jié)果如表7 所示。
表7 InsObf和Hikari的相似度分析 單位:%Tab.7 Similarity of InsObf and Hikari unit:%
表7 的結(jié)果顯示,InsObf 混淆前后在指令相似度的降低上優(yōu)于Hikari,這是因?yàn)橐环矫鍵nsObf-junk 通過不透明謂詞引入了大量的跳轉(zhuǎn)指令,這也就導(dǎo)致JUMP 指令的相似度也隨之降低;另一方面由于InsObf-sub 對(duì)指令中的運(yùn)算符和操作數(shù)進(jìn)行了大量的替換工作,最終使得指令相似度大幅降低。而Hikari 因?yàn)镕unctionWrapper 針對(duì)函數(shù)調(diào)用做了較多混淆處理,所以其CALL 指令的相似度低于InsObf。
綜合表6 和表7,可以得到如下結(jié)論:本文提出的指令混淆框架InsObf 在空間開銷上和Hikari 的控制流混淆處于同一量級(jí);在時(shí)間開銷上,高于Armariris 的字符串混淆,低于Hikari 的字符串混淆和控制流混淆,處于中等水平;在圈復(fù)雜度和指令相似度方面,有明顯的增加。在同基于OLLVM優(yōu)化的多種混淆方法的對(duì)比中,可以證明本框架可對(duì)程序指令進(jìn)行完備的混淆處理,提供指令層級(jí)的有效保護(hù)。
本文基于OLLVM 的指令混淆模塊進(jìn)行了改進(jìn),拓展了指令替換功能支持的運(yùn)算符數(shù)和替換方案數(shù),并額外增加了指令加花的功能。其中,指令替換支持13 種運(yùn)算符共計(jì)52種方案,可以從生成的多個(gè)等價(jià)表達(dá)式中隨機(jī)選擇替換方案,使得通過利用程序地址空間的攻擊更難實(shí)現(xiàn)。指令加花通過插入疊加跳轉(zhuǎn)指令和虛假循環(huán)指令,在充分利用源程序中指令信息的同時(shí),能夠有效破壞程序的結(jié)構(gòu),加大攻擊者分析、理解程序的難度。實(shí)驗(yàn)結(jié)果表明,與OLLVM 的指令替換功能相比,本文提出的框架在時(shí)間開銷增加約10 個(gè)百分點(diǎn),空間開銷增加約20 個(gè)百分點(diǎn)的情況下,圈復(fù)雜度和抗逆向能力均可提升4 倍;在同Armariris 和Hikari 的對(duì)比中,通過時(shí)空和圈復(fù)雜度、指令相似度等指標(biāo),論證本文提出的指令層級(jí)的混淆方案,在同一量級(jí)的時(shí)空開銷下可以提供更高的代碼復(fù)雜度。
未來工作中可以針對(duì)時(shí)空開銷的降低和增加浮點(diǎn)運(yùn)算符的指令替換進(jìn)行處理,且在指令替換中結(jié)合加密算法,如密碼學(xué)中的同態(tài)加密,進(jìn)一步提高代碼的安全性。