陳耀陽(yáng),陳 偉
南京郵電大學(xué) 計(jì)算機(jī)學(xué)院,南京 210023
近年來(lái),隨著計(jì)算機(jī)技術(shù)的發(fā)展,針對(duì)軟件本身的保護(hù)逐漸引起人們的關(guān)注。對(duì)于軟件的攻擊形式有很多種,一般而言,軟件保護(hù)技術(shù)主要對(duì)以下三種類型[1]的攻擊進(jìn)行防護(hù):軟件盜版、逆向工程以及軟件篡改。在這幾種攻擊中,逆向工程是相對(duì)比較基礎(chǔ)同時(shí)也是危害性最大的一種攻擊形式。
逆向工程[2]是指對(duì)一項(xiàng)目標(biāo)產(chǎn)品進(jìn)行逆向分析和研究,在無(wú)法輕易獲得必要產(chǎn)品信息的情況下,直接從成品分析,推導(dǎo)產(chǎn)品的設(shè)計(jì)原理。在計(jì)算機(jī)領(lǐng)域中,特別是針對(duì)程序的二進(jìn)制代碼進(jìn)行的逆向工程也稱“代碼逆向工程”。一次成功的逆向,對(duì)于競(jìng)爭(zhēng)者而言可以輕易獲取產(chǎn)品中的敏感數(shù)據(jù)、算法以及設(shè)計(jì)思想;而對(duì)于惡意攻擊者來(lái)說(shuō),則可以獲取到軟件中漏洞,從而加以利用,造成嚴(yán)重后果。
靜態(tài)分析往往是逆向分析的第一步。以Java為代表的語(yǔ)言,需要編譯成與平臺(tái)無(wú)關(guān)的字節(jié)碼形式,所以可以通過(guò)解析字節(jié)碼文件輕易還原出源程序的實(shí)現(xiàn)信息。而對(duì)于以C語(yǔ)言為代表的需要編譯成機(jī)器碼的語(yǔ)言,也可以通過(guò)翻譯機(jī)器碼從而轉(zhuǎn)化為匯編指令來(lái)進(jìn)行代碼分析。所以,通過(guò)靜態(tài)分析可以輕易地構(gòu)建軟件的控制流信息,未經(jīng)保護(hù)的控制流信息暴露了包括源程序靜態(tài)結(jié)構(gòu)、代碼邏輯在內(nèi)的大量信息,因此對(duì)于許多逆向工具而言,控制流圖都是必須構(gòu)造的數(shù)據(jù)結(jié)構(gòu)。本文研究的主要目的是抵御靜態(tài)代碼分析。
抵抗靜態(tài)代碼分析特別是控制流分析的主要技術(shù)是代碼混淆,這個(gè)概念最早由Collerg等[3]提出并加以分類及實(shí)現(xiàn)。代碼混淆技術(shù)是將一些結(jié)構(gòu)良好、易于理解的代碼轉(zhuǎn)換為難以閱讀分析的代碼,但是還能完整地保留其所實(shí)現(xiàn)的功能,著名的國(guó)際C語(yǔ)言混亂代碼大賽[4]就是典型的例子。一般而言,混淆技術(shù)可以分為4類:結(jié)構(gòu)混淆、數(shù)據(jù)混淆、控制流混淆以及動(dòng)態(tài)混淆。其中控制流混淆的主要目的就是抵御逆向工具對(duì)代碼控制流信息的分析,目前應(yīng)用比較廣泛的控制流混淆技術(shù)有指令替換、虛假控制流、控制流平坦化等。但是目前這些技術(shù)都存在一些缺點(diǎn),如通用性差、性能消耗過(guò)大、控制流信息隱藏不徹底等。
鑒于這些現(xiàn)狀,本文提出一種通用的、輕量級(jí)的、細(xì)粒度代碼保護(hù)方案。通過(guò)對(duì)代碼的控制流信息進(jìn)行分析并建立原始控制流圖,之后從基本塊跳轉(zhuǎn)、函數(shù)調(diào)用與變量引用入手,隱藏這些敏感信息,減少靜態(tài)分析時(shí)所能獲取到的有價(jià)值信息,實(shí)現(xiàn)對(duì)程序的保護(hù)。同時(shí)通過(guò)構(gòu)建狀態(tài)轉(zhuǎn)移模型和實(shí)施基于環(huán)境密鑰的兩階段加密方案,解決了密鑰安全和密文重復(fù)出現(xiàn)問(wèn)題,彌補(bǔ)了其他方案的不足,提高了軟件保護(hù)的能力。
代碼混淆的概念由來(lái)已久,在20世紀(jì)90年代末這個(gè)概念就已經(jīng)被提出。它的形式有很多,其中最簡(jiǎn)單的一種形式是符號(hào)混淆,它通過(guò)將源碼中的函數(shù)名、類名以及變量名替換成一些無(wú)意義的符號(hào),來(lái)增加攻擊者的理解難度,具有代表性的項(xiàng)目是ProGuard[5],由于其并不改變?cè)a的結(jié)構(gòu),其抗逆向能力非常差,所以現(xiàn)在多作為一種優(yōu)化工具或者其他混淆技術(shù)的輔助工具來(lái)使用。
由于簡(jiǎn)單的符號(hào)混淆并不能滿足代碼保護(hù)的要求,一些研究人員又提出了結(jié)構(gòu)混淆的概念。結(jié)構(gòu)混淆是從代碼的類、函數(shù)等組成入手,破壞其整體結(jié)構(gòu),增加靜態(tài)分析的復(fù)雜度。這方面比較典型的是Foket等人[6]的研究成果,他們以Java語(yǔ)言為例闡述并實(shí)現(xiàn)了包括類繼承結(jié)構(gòu)平坦化、方法合并、接口合并以及對(duì)象創(chuàng)建工廠化在內(nèi)的多種結(jié)構(gòu)混淆技術(shù)。雖然結(jié)構(gòu)混淆能起到一定的抗逆向效果,但是其混淆的粒度較大,實(shí)際的逆向工程中,攻擊者很少會(huì)逆向全部代碼,一般都是從一部分關(guān)鍵代碼入手,也被稱為“局部攻擊”,這時(shí)就需要更細(xì)粒度的混淆。
控制流混淆是目前較為流行的方案,它通過(guò)修改程序控制流來(lái)阻礙攻擊者對(duì)代碼進(jìn)行靜態(tài)分析。常用的控制流混淆技術(shù)主要有同義指令替換、不透明分支以及控制流平坦化以及等。
同義指令替換[7]多是將一些簡(jiǎn)單的指令替換為有相同功能但形式非常復(fù)雜的指令,來(lái)增大代碼理解難度,如公式(1)所示;但是這種混淆方法有很大的局限性:首先并不是所有的指令都可以替換,多用在算術(shù)運(yùn)算上;其次一般所替換的指令都比較復(fù)雜,也降低了程序的性能。
虛假控制流又稱為插入不透明謂詞[8],它通過(guò)在程序的控制流中插入一些結(jié)果恒定的布爾表達(dá)式來(lái)引入虛假分支,如圖1所示;由于被插入的布爾表達(dá)在運(yùn)行過(guò)程中值恒等不變,所以虛假分支并不影響程序運(yùn)行,所以這是一種低成本高收益的混淆技術(shù)。生成虛假控制流的不透明謂詞可以分為靜態(tài)不透明謂詞[9]、上下文不透明謂詞[10]以及動(dòng)態(tài)不透明謂詞[11]等。但是由于不透明謂詞插入后的特征比較明顯,現(xiàn)在已有很多方案[12]可以去識(shí)別和去除。虛假控制流混淆雖然改變了代碼的控制流信息,但只是在局部上的修改,難以全局應(yīng)用,并且過(guò)多的不透明謂詞也會(huì)大幅降低程序性能。
圖1 虛假控制流Fig.1 Bogus control flow
控制流平坦化[13]混淆如圖2所示,是通過(guò)構(gòu)造分發(fā)器,將原本控制流重構(gòu)為一個(gè)類似switch-case的結(jié)構(gòu),由分發(fā)器在運(yùn)行時(shí)決定下一個(gè)將要執(zhí)行的基本塊。這樣可以徹底打亂原有的控制流圖,具有很強(qiáng)的反逆向能力,但是這種方法只是將控制流信息從形式上打亂,其實(shí)際的控制流信息還是不變,通過(guò)分析case的值,也可以局部或者整體還原代碼的控制流信息,重構(gòu)原始的控制流圖。
圖2 控制流平坦化Fig.2 Control flow flattening
前面介紹的控制流混淆方案大多都是通過(guò)引入額外基本塊來(lái)修改源程序控制流圖中基本塊之間的關(guān)系,從而實(shí)現(xiàn)代碼混淆。除此之外,還可以通過(guò)修改基本塊的跳轉(zhuǎn)形式來(lái)實(shí)現(xiàn)這個(gè)目的,也就是將顯示跳轉(zhuǎn)改為隱式跳轉(zhuǎn)。隱式跳轉(zhuǎn)又稱間接跳轉(zhuǎn),是將源程序中的直接跳轉(zhuǎn)形式改為間接形式,這樣無(wú)論是反編譯工具還是人為分析,都不能輕易分析任意基本塊的前后關(guān)系,這樣也就阻止了靜態(tài)分析,實(shí)現(xiàn)了程序保護(hù)。這方面比較典型的是Hikari[14]項(xiàng)目,它通過(guò)將程序中跳轉(zhuǎn)指令的目標(biāo)地址由明文改為數(shù)組匹配的方式來(lái)實(shí)現(xiàn)隱式跳轉(zhuǎn)。但作為一種混淆策略,其實(shí)現(xiàn)形式過(guò)于簡(jiǎn)單而導(dǎo)致安全性較差(詳見(jiàn)2.1節(jié)分析),同時(shí)也僅處理了基本塊之間的跳轉(zhuǎn),對(duì)于其他敏感信息并未做相關(guān)的安全保護(hù),所以在實(shí)際應(yīng)用中價(jià)值較低。
前一章介紹的幾種代碼保護(hù)技術(shù)中,都只是在宏觀上對(duì)控制流進(jìn)行修改,實(shí)際各個(gè)基本塊之間的跳轉(zhuǎn)關(guān)系還會(huì)保留,而且也不能保護(hù)函數(shù)調(diào)用以及變量引用這些敏感信息。因此,本文通過(guò)隱藏基本塊之間的跳轉(zhuǎn)關(guān)系,并在此基礎(chǔ)上對(duì)程序其他敏感信息如函數(shù)的調(diào)用以及變量引用也加以隱藏,從而實(shí)現(xiàn)程序的控制流混淆,以此來(lái)保護(hù)程序。
控制流圖是一個(gè)程序的抽象表示,最早由Frances E.Allen提出,它利用圖的形式將一個(gè)程序的所有可能執(zhí)行路徑表示出來(lái),不僅是靜態(tài)分析的重要工具,也是現(xiàn)代編譯器所需要維護(hù)的一種重要數(shù)據(jù)結(jié)構(gòu)。一個(gè)控制流圖可以表示為
圖3 控制流圖示例Fig.3 Example of control flow graph
在控制流圖中,控制流從一個(gè)基本塊到另外一個(gè)基本塊的轉(zhuǎn)移多是通過(guò)一系列跳轉(zhuǎn)指令實(shí)現(xiàn),所要跳轉(zhuǎn)的目標(biāo)地址在原始情況下是以明文方式作為指令的操作數(shù)出現(xiàn)的,這樣的跳轉(zhuǎn)形式稱之為顯示跳轉(zhuǎn),以圖3(b)為例,一共出現(xiàn)了兩個(gè)跳轉(zhuǎn)指令,分別是地址為040056B處的條件跳轉(zhuǎn)指令和地址為0400585處的無(wú)條件跳轉(zhuǎn)指令,這兩條指令的操作數(shù)都是直接的明文地址(圖中顯示的是LABEL偽指令,表示的是某個(gè)地址,目的是便于閱讀),由于是明文地址,無(wú)論從哪一個(gè)基本塊開(kāi)始都可以輕松地推斷出程序接下來(lái)的流程。
與顯示跳轉(zhuǎn)對(duì)應(yīng)的是隱式跳轉(zhuǎn),這種形式下,跳轉(zhuǎn)的目的地址需要通過(guò)一系列計(jì)算得出,圖4所示的是圖3(a)中的代碼經(jīng)過(guò)Hikari的間接分支處理后的部分匯編代碼,對(duì)應(yīng)的就是圖3(c)中的基本塊A,但是與之不同的是圖4中的基本塊最后的跳轉(zhuǎn)指令的操作數(shù)并不是一個(gè)直接地址,而以一個(gè)寄存器表示,根據(jù)寄存器中存儲(chǔ)的值,去跳轉(zhuǎn)的相應(yīng)地址,至于寄存器中所存儲(chǔ)的地址則是在跳轉(zhuǎn)前計(jì)算出來(lái)的,這就是隱式跳轉(zhuǎn)的一種形式。這里所做的處理是將某個(gè)基本塊的所有可能后繼基本塊插入一個(gè)全局跳轉(zhuǎn)表中,然后修改原始基本塊的內(nèi)容,將原來(lái)的明文地址替換為數(shù)組尋址的方式,如圖4中地址0400575處所示。這種隱式跳轉(zhuǎn)方法,由于隱藏了目標(biāo)地址,所以一些工具如IDA Pro[15]就不能通過(guò)靜態(tài)分析去構(gòu)建程序的控制流圖,僅能顯示如圖4所示的控制流圖的第一個(gè)基本塊。
雖然這種形式的隱式跳轉(zhuǎn)雖然能在一定程度上阻礙控制流圖被構(gòu)建,但是若對(duì)跳轉(zhuǎn)前部分指令的含義進(jìn)行簡(jiǎn)單分析還是可以推斷出該基本塊的后繼。以圖4為例,在輸入值與10做減法之后,根據(jù)EFLAGS寄存器的ZF標(biāo)志位的情況,在_LcondInBrTable數(shù)組中取不同的值存入rsi寄存器中進(jìn)行跳轉(zhuǎn),由此知道當(dāng)輸入等于10時(shí),取數(shù)組中第二個(gè)元素,否則取第一個(gè)元素。而_LcondInBrTable數(shù)組中存儲(chǔ)的都是明文地址,由此還是可以構(gòu)建出局部或者全局的控制流圖。
前一節(jié)中Hikari實(shí)現(xiàn)了較為初級(jí)的隱式跳轉(zhuǎn),但是依舊是將地址明文存儲(chǔ)到全局跳轉(zhuǎn)表中,這樣的效果等同于做了一次稍微復(fù)雜的間接尋址,雖然可以欺騙一些靜態(tài)分析工具,但是并沒(méi)有達(dá)到理想的保護(hù)效果。本文通過(guò)對(duì)地址加密來(lái)增強(qiáng)保護(hù),但是由于對(duì)軟件的靜態(tài)分析是處于一種MATE[16](Man At The End)環(huán)境下,此時(shí)攻擊者完全掌握軟件運(yùn)行環(huán)境,可以對(duì)軟件進(jìn)行徹底的反編譯和監(jiān)控,傳統(tǒng)的加密并不能明顯提升保護(hù)效果,所以本文通過(guò)構(gòu)建狀態(tài)轉(zhuǎn)移模型來(lái)確保密鑰安全,利用二者的結(jié)合來(lái)實(shí)現(xiàn)代碼保護(hù)。
S:狀態(tài)集合;
S0:初始狀態(tài);
I:輸入集合;
O:輸出集合;
T:轉(zhuǎn)移函數(shù),描述系統(tǒng)中各個(gè)狀態(tài)之間轉(zhuǎn)移的規(guī)則;
G:輸出函數(shù),描述系統(tǒng)在狀態(tài)轉(zhuǎn)移后的輸出。
首先定義一個(gè)程序控制流圖由n個(gè)基本塊構(gòu)成:,控制流圖的入口為BB0。假設(shè)控制流內(nèi)部的跳轉(zhuǎn)是根據(jù)基本塊的地址定位的,第i個(gè)基本塊的地址表示為Ai,該地址對(duì)應(yīng)的加密后密文表示為,其對(duì)應(yīng)的密鑰表示為key i,密鑰是在編譯期隨機(jī)生成的,不直接存儲(chǔ)在代碼中。
系統(tǒng)的各部分定義如下:系統(tǒng)的狀態(tài)集合即為全體密鑰的集合S={}key i|0≤i≤n,當(dāng)程序要從BB i跳轉(zhuǎn)到BB j時(shí),系統(tǒng)的當(dāng)前狀態(tài)即為k eyi,將來(lái)狀態(tài)為key j,系統(tǒng)的初始狀態(tài)為key0;系統(tǒng)某次狀態(tài)轉(zhuǎn)移時(shí)的輸入由兩部分組成,一部分用于計(jì)算新?tīng)顟B(tài),定義為k ij,該值直接存儲(chǔ)在BB i中;另一部分用于計(jì)算輸出,即BB j的密文;故系統(tǒng)的輸入可以用如下二元組的集合表示,系統(tǒng)在轉(zhuǎn)移后的輸出即基本塊地址的集合,O={A j|0≤i≤n}。
系統(tǒng)的輸出函數(shù)描述的就是狀態(tài)轉(zhuǎn)移后系統(tǒng)產(chǎn)生的一個(gè)輸出,這里以輸入的密文以及當(dāng)前狀態(tài)為參數(shù)并輸出基本塊地址,所以輸出函數(shù)就是解密函數(shù),它和編譯時(shí)的加密函數(shù)互為反函數(shù)。若加密函數(shù)定義為如下形式:
則輸出函數(shù)的形式為:
關(guān)于加解密函數(shù)的具體實(shí)現(xiàn)沒(méi)有特殊要求,但是由于需要在指令層面進(jìn)行運(yùn)行時(shí)解密,為了保證運(yùn)行時(shí)性能,要盡量選擇運(yùn)算量少的算法,如異或運(yùn)算。
系統(tǒng)的轉(zhuǎn)移函數(shù)描述了狀態(tài)轉(zhuǎn)移規(guī)則,其形式如下:
轉(zhuǎn)移函數(shù)實(shí)際上是以迭代的形式描述了如何從一個(gè)狀態(tài)轉(zhuǎn)移到下一個(gè)狀態(tài),這種形式的定義可以保證在靜態(tài)分析時(shí)中間狀態(tài)的隱蔽性,從而間接保證目標(biāo)地址的安全。攻擊者在對(duì)程序進(jìn)行靜態(tài)分析構(gòu)建控制流圖時(shí),由于完整的控制流圖一般都比較龐大,構(gòu)建起來(lái)耗時(shí)耗力,所以攻擊者一般更愿意從某個(gè)感興趣的基本塊入手,構(gòu)建局部的控制流圖,此時(shí)需要完成的目標(biāo)有如下兩點(diǎn):
(1)確定某個(gè)基本塊的所有直接后繼塊;
(2)確定某個(gè)基本塊的所有直接前驅(qū)塊。
在未處理之前,指令中所有目標(biāo)地址都是明文出現(xiàn)的,所以目標(biāo)1可以通過(guò)分析基本塊中的跳轉(zhuǎn)指令及相鄰基本塊的邏輯關(guān)系而實(shí)現(xiàn),目標(biāo)2可以通過(guò)匹配全局的絕對(duì)跳轉(zhuǎn)地址和相對(duì)跳轉(zhuǎn)地址來(lái)實(shí)現(xiàn)。在使用本文方案之后,聯(lián)合轉(zhuǎn)移函數(shù)與輸出函數(shù),若從BBi轉(zhuǎn)移到BB j,目標(biāo)基本塊地址需要經(jīng)過(guò)如下計(jì)算:
當(dāng)攻擊者從控制流圖中某一基本塊開(kāi)始靜態(tài)分析時(shí),所能獲取到的信息只有該基本塊的地址A i,以及所有可能的k ij和。由公式(5)可知,還缺少參數(shù)keyi,而keyi是系統(tǒng)的當(dāng)前狀態(tài),它在程序中的表現(xiàn)是一個(gè)可讀寫(xiě)的全局變量,它隨著控制流的轉(zhuǎn)移而更新,而直接從中間某一基本塊入手時(shí),是無(wú)法直接獲得當(dāng)前基本塊對(duì)應(yīng)狀態(tài)值,所以不能計(jì)算出下一基本塊的地址,這樣目標(biāo)1就很難完成。另外,由于在基本塊中,原始狀態(tài)下所有目標(biāo)地址都是以密文形式呈現(xiàn)的,當(dāng)攻擊者知道某一基本塊的明文地址,而要計(jì)算對(duì)應(yīng)的密文地址時(shí)需要進(jìn)行如下式的運(yùn)算(假設(shè)BB k是BBi的一個(gè)前驅(qū)塊):
根據(jù)系統(tǒng)的狀態(tài)轉(zhuǎn)移模型,key k是在執(zhí)行到基本塊BB k時(shí)的狀態(tài),k ki的值也僅存在于BB k內(nèi)部的,所以在前驅(qū)塊未知的情況下,想要計(jì)算當(dāng)前基本塊對(duì)應(yīng)的密文信息,以及以此為基礎(chǔ)去尋找所有前驅(qū)塊是比較困難的,所以目標(biāo)2也難以實(shí)現(xiàn)。
前文分析了模型在抵御靜態(tài)分析時(shí)的能力,證明了當(dāng)從控制流圖的非起始位置入手時(shí),并不能有效地還原出程序的控制流圖。但是,當(dāng)從控制流入口開(kāi)始靜態(tài)分析時(shí),系統(tǒng)狀態(tài)能否安全地初始化決定了程序控制流圖能否被順利構(gòu)建。目前,程序中類似的安全初始化問(wèn)題依舊是亟待解決的,不過(guò)一些現(xiàn)有的方法可以在一定程度上解決這個(gè)問(wèn)題,如白盒加密、外部輸入以及從環(huán)境元素生成狀態(tài)信息等。即使在無(wú)法保證安全初始化的情況下,在經(jīng)過(guò)本方案處理過(guò)之后,攻擊者也只能從控制流圖的入口開(kāi)始分析,當(dāng)程序足夠龐大時(shí),這將是一個(gè)漫長(zhǎng)的過(guò)程。另外本方案關(guān)注于隱藏控制流的跳轉(zhuǎn)信息,所以可以通過(guò)增加冗余的虛假基本塊、對(duì)基本塊進(jìn)行分裂或者結(jié)合已有的控制流混淆技術(shù)來(lái)增大靜態(tài)分析的難度。
除了控制流信息是抵抗靜態(tài)分析時(shí)需要保護(hù)的信息,函數(shù)的調(diào)用與變量的引用也是一類需要保護(hù)敏感信息。例如通過(guò)對(duì)關(guān)鍵函數(shù)的調(diào)用及特殊字符串的搜索可以快速定位到關(guān)鍵部分,提高靜態(tài)分析的效率。傳統(tǒng)的關(guān)于這兩類信息的保護(hù)都注重于內(nèi)容的保護(hù),如對(duì)函數(shù)進(jìn)行包裝、對(duì)字符串內(nèi)容加密等。這樣的處理不僅有較大性能損耗,同時(shí)也并不能達(dá)到理想的效果。本文在隱式跳轉(zhuǎn)的基礎(chǔ)上從對(duì)象與指令間的關(guān)系入手對(duì)這兩類信息進(jìn)行保護(hù)。
與基本塊的跳轉(zhuǎn)類似,在指令層面函數(shù)的調(diào)用與變量的引用都是以地址為基礎(chǔ)。如圖3(b)中的幾處call指令和包括0400571地址在內(nèi)的幾處變量引用指令,都是把目標(biāo)對(duì)象的地址直接作為操作數(shù)放在指令后。所以可以推廣隱式跳轉(zhuǎn)的思想,將對(duì)應(yīng)的地址加密隱藏,實(shí)現(xiàn)函數(shù)的隱式調(diào)用和變量的隱式引用。同樣在上一節(jié)構(gòu)建的狀態(tài)轉(zhuǎn)移系統(tǒng)的基礎(chǔ)上,依賴系統(tǒng)當(dāng)前狀態(tài)與輸入?yún)?shù)在運(yùn)行時(shí)解密出真實(shí)地址,而不直接存儲(chǔ)密鑰,使得靜態(tài)分析時(shí)無(wú)法確定真實(shí)地址,以此切斷相關(guān)對(duì)象和程序的關(guān)聯(lián),也就無(wú)法利用特殊的函數(shù)和變量去定位關(guān)鍵邏輯。另外,也可以插入相似的冗余函數(shù)和變量,進(jìn)一步干擾攻擊者的靜態(tài)分析。
前文介紹的模型雖能較好在靜態(tài)分析時(shí)保護(hù)程序各種敏感信息,但是各種加密行為都是在編譯期完成的,所以在后續(xù)的指令修改中,同樣的密文可能多次出現(xiàn),這樣做是有一定風(fēng)險(xiǎn)的。如在基本塊的隱式跳轉(zhuǎn)中,若BB j是BB i的直接后繼,雖然在BB i中無(wú)法直接計(jì)算出BB j的實(shí)際地址,但是將會(huì)出現(xiàn)在所有BB j的前驅(qū)塊中,此時(shí)通過(guò)搜索可以找到BB j的所有前驅(qū)節(jié)點(diǎn)。同樣的問(wèn)題也出現(xiàn)在函數(shù)隱式調(diào)用與全局變量隱式引用的處理中,利用對(duì)密文的全局搜索可以找到調(diào)用相同函數(shù)或引用相同變量的所有指令,再結(jié)合一些統(tǒng)計(jì)學(xué)的知識(shí),還是可以獲取一些有價(jià)值信息。
為了解決這個(gè)問(wèn)題,這里引入環(huán)境密鑰與兩階段加密的概念,基本思路是在不同環(huán)境中(即不同的基本塊中)表示出不同的密文。以基本塊隱式跳轉(zhuǎn)為例,除了使用主密鑰keyi,還需要一個(gè)環(huán)境密鑰ckeyi,每個(gè)基本塊對(duì)應(yīng)一個(gè)環(huán)境密鑰,為了保證唯一性,比較簡(jiǎn)單的就是取基本塊的地址作為環(huán)境密鑰。在編譯期進(jìn)行加密時(shí)使用兩個(gè)加密函數(shù)進(jìn)行兩階段加密,分別是使用環(huán)境密鑰進(jìn)行的預(yù)加密和使用主密鑰進(jìn)行的正式加密,這里定義預(yù)加密函數(shù)如下:
預(yù)加密函數(shù)需要保證在給出相同的A i,對(duì)于任意兩個(gè)ckey j,其結(jié)果一定不相同。此時(shí)結(jié)合2.2節(jié)中定義的加密函數(shù),完整的加密運(yùn)算如下所示(表示BBi的地址在BB j環(huán)境下所對(duì)應(yīng)的密文):
注意這里選取的預(yù)加密函數(shù)P可以和F一樣,但要保證函數(shù)對(duì)解密順序敏感,也就是解密時(shí)若先用環(huán)境密鑰再用主密鑰時(shí)不能獲得正確結(jié)果,以防靜態(tài)分析時(shí)可以對(duì)密文進(jìn)行預(yù)處理。在公式(8)中對(duì)于同一明文A j,對(duì)應(yīng)不同的ckey j則有不同的結(jié)果,也就實(shí)現(xiàn)同一個(gè)基本塊在不同的環(huán)境下展示不同的密文。
同樣對(duì)于函數(shù)的隱式調(diào)用和全局變量的隱式引用,也可以使用環(huán)境密鑰和兩階段解密實(shí)現(xiàn)密文不重復(fù)出現(xiàn)。但是對(duì)于這兩者,還存在一種特殊情況,也就是在同一個(gè)基本塊內(nèi)多次調(diào)用同一個(gè)函數(shù)或引用同一個(gè)全局變量,這樣仍然會(huì)出現(xiàn)密文重復(fù),有效的解決方法是對(duì)基本塊進(jìn)行分裂,在兩個(gè)相同的函數(shù)調(diào)用或者變量引用之間插入無(wú)條件跳轉(zhuǎn)指令,將一個(gè)基本塊分裂為多個(gè)基本塊,保證兩次相同的調(diào)用或引用不出現(xiàn)在一個(gè)基本塊內(nèi),再進(jìn)行隱式轉(zhuǎn)換,就可以保證密文的唯一。
利用環(huán)境密鑰的兩階段加密沒(méi)有必要對(duì)全部對(duì)象都使用,在使用前可以統(tǒng)計(jì)對(duì)象在源程序中的出現(xiàn)次數(shù),若僅出現(xiàn)一次,就可以省去兩階段加密以減少性能損耗。
為了評(píng)估本文所提出的方案,這里選取了一些樣本進(jìn)行測(cè)試,并與原始程序和其他已有方案進(jìn)行比較從而得出結(jié)論,本文的方案實(shí)現(xiàn)是基于OLLVM[17]實(shí)現(xiàn)的。
本文從編譯后程序的空間開(kāi)銷和時(shí)間開(kāi)銷兩個(gè)維度進(jìn)行測(cè)試。這兩個(gè)維度使用不同的樣本集,分別是GNU Coreutils[18]內(nèi)的部分模塊以及排序、散列、加密以及壓縮等領(lǐng)域中有代表性的程序,樣本源碼語(yǔ)言都為C語(yǔ)言。另外關(guān)于方案的正確性與通用性可以通過(guò)觀察樣本是否能正確編譯以及正確執(zhí)行來(lái)判斷。具體樣本信息如表1所示。
表1 樣本詳情Table 1 Sample details
在實(shí)驗(yàn)中分別對(duì)每個(gè)樣本都進(jìn)行如表2所述的幾組實(shí)驗(yàn)。
表2 實(shí)驗(yàn)內(nèi)容Table 2 Experiment details
其中實(shí)驗(yàn)B、C因?yàn)榭梢詫?shí)現(xiàn)類似效果,所以有較直觀的對(duì)比。本次實(shí)驗(yàn)的環(huán)境如表3所示。
表3 實(shí)驗(yàn)環(huán)境Table 3 Experimental environment
按表2描述的四種處理方法對(duì)10個(gè)樣本進(jìn)行編譯,首先各個(gè)樣本均能通過(guò)編譯并正常運(yùn)行,這一點(diǎn)說(shuō)明本文方案對(duì)一般源代碼程序的處理具有較好的通用性和正確性,具體編譯后可執(zhí)行文件的大小如表4所示。
表4 可執(zhí)行文件大小Table 4 Executable file size Byte
通過(guò)表4可以看出,任何一種處理手段都會(huì)伴隨出現(xiàn)文件體積增大現(xiàn)象。其中,僅做控制流隱式跳轉(zhuǎn)處理的對(duì)照組中,文件體積平均增大29%左右,在此基礎(chǔ)上若對(duì)函數(shù)調(diào)用與變量引用也做處理,文件體積再增加15%左右。
通過(guò)分析編譯后程序的匯編指令,可以得出文件增大的原因。以實(shí)驗(yàn)B為例,若要實(shí)現(xiàn)一個(gè)簡(jiǎn)單的無(wú)條件跳轉(zhuǎn)指令從顯式到隱式的轉(zhuǎn)換,需要在基本塊中添加:當(dāng)前系統(tǒng)狀態(tài)讀取、輔助變量讀取與密鑰計(jì)算、密文讀取與解密、系統(tǒng)狀態(tài)更新以及跳轉(zhuǎn)指令替換等操作,在實(shí)際轉(zhuǎn)換中,這些操作將在原基本塊中增加約10條指令。另外,對(duì)于有條件跳轉(zhuǎn)指令及函數(shù)調(diào)用指令等一些較為復(fù)雜的指令,在實(shí)際轉(zhuǎn)換將會(huì)增加更多指令。其次,隨著基本塊數(shù)量的增多,控制流圖中邊的數(shù)量也越多,要轉(zhuǎn)換的指令也越多,所以編譯后文件體積會(huì)有一定的增加。
雖然本文方案最后文件體積會(huì)有一定增加,但相比控制流平坦化而言,在達(dá)到類似效果的基礎(chǔ)上,實(shí)際實(shí)驗(yàn)中二者文件的體積上并沒(méi)有明顯的差異。而且原始的控制流平坦化方案只是在形式上打亂了控制流,若要更加安全地保護(hù)控制流信息則需要對(duì)分發(fā)器的邏輯復(fù)雜化,這樣只會(huì)進(jìn)一步增加文件體積。
實(shí)驗(yàn)的第二項(xiàng)內(nèi)容是測(cè)試經(jīng)過(guò)處理后程序的性能對(duì)比,關(guān)于性能可以通過(guò)程序運(yùn)行時(shí)間得到直觀的表現(xiàn)。這里對(duì)后四種樣本進(jìn)行測(cè)試,其中每組實(shí)驗(yàn)之后得到的二進(jìn)制文件都執(zhí)行如表5所示的一定操作并記錄執(zhí)行時(shí)間,重復(fù)執(zhí)行100次,最后取平均時(shí)間。
表5 操作詳情Table 5 Operation details
各個(gè)樣本的最終運(yùn)行時(shí)間如圖5所示。
圖5 運(yùn)行時(shí)間Fig.5 Execution time
通過(guò)圖5可知,在經(jīng)過(guò)控制流扁平化處理之后的程序耗時(shí)是最多的,本文所提供的方案中,若只進(jìn)行控制流隱式跳轉(zhuǎn),則平均增加了44%的耗時(shí),若全部處理,則再增加約15%的耗時(shí),雖然最后運(yùn)行時(shí)間相對(duì)原始程序都有一定增加,但是相對(duì)于控制流平坦化,執(zhí)行耗時(shí)上平均減少約23%,有較大的提升。
通過(guò)前面的分析可知由于增加了額外指令,所以在運(yùn)行時(shí)不可避免地增加了運(yùn)行時(shí)間。雖然本文所提方案與控制流平坦化方案都是通過(guò)增加額外指令實(shí)現(xiàn)控制流混淆,但是本文所增加的額外指令都直接插入到原始的基本塊中,而且與原基本塊內(nèi)的指令在地址上是相鄰的,同時(shí)大多數(shù)是一些簡(jiǎn)單的數(shù)據(jù)存儲(chǔ)指令。而控制流平坦化方案中由于匯編指令無(wú)法直接實(shí)現(xiàn)類似switch結(jié)構(gòu)的分發(fā)器,所以只能通過(guò)增加大量子分發(fā)器將控制流圖轉(zhuǎn)為一個(gè)多層的樹(shù)型結(jié)構(gòu),這樣控制流從主分發(fā)器出發(fā),要通過(guò)大量子分發(fā)器經(jīng)過(guò)層層比較與跳轉(zhuǎn)才能到達(dá)源程序的有效邏輯塊,另外這些子分發(fā)器在地址上多數(shù)都是不相鄰的,也會(huì)造成一定影響。所以相比較來(lái)看控制流平坦化方案比本文的方案會(huì)造成更大的影響。
通過(guò)上面兩方面的實(shí)驗(yàn)可以得出,不管何種保護(hù)方法都是有一定負(fù)面效應(yīng)的,不過(guò)這也是不可避免的,由于在實(shí)驗(yàn)中默認(rèn)對(duì)全部代碼都進(jìn)行保護(hù)處理,所以影響比較明顯,實(shí)際過(guò)程中可以有選擇性地對(duì)關(guān)鍵代碼進(jìn)行保護(hù)處理,從而降低對(duì)性能的影響。
本文提出并實(shí)現(xiàn)了一種采用隱式跳轉(zhuǎn)的控制流混淆方案,它不僅能隱藏程序的控制流信息,同時(shí)也能保護(hù)包括函數(shù)調(diào)用及變量引用在內(nèi)的多種敏感信息,增強(qiáng)程序的抗靜態(tài)分析能力。由于是建立在抽象的控制流圖基礎(chǔ)上,所以該方案不受具體編程語(yǔ)言及代碼結(jié)構(gòu)的限制,也不需要對(duì)源碼進(jìn)行任何修改,是一種通用的控制流混淆方案。并且還能結(jié)合已有的混淆方案保護(hù)實(shí)現(xiàn)更好的保護(hù)效果。未來(lái)的研究工作將從如下兩點(diǎn)進(jìn)行:首先是優(yōu)化狀態(tài)轉(zhuǎn)移系統(tǒng)進(jìn)一步減小編譯后文件體積的大?。黄浯问莾?yōu)化轉(zhuǎn)移函數(shù)和輸出函數(shù),使性能損耗降到最低。