丁 寧,王軼駿,薛 質(zhì)
(上海交通大學(xué),上海 200240)
隨著計算機技術(shù)的不斷累積與進步,逆向破解一款軟件的成本變得越來越低。許多時候,逆向分析人員只需通過諸如IDA、OllyDbg 此類軟件,便可以在極短的時間內(nèi),使用極小成本的情況下,完成對一款商業(yè)軟件的破解。而遭受軟件破解的商業(yè)公司可能就將面臨開發(fā)成本無法回本,甚至更嚴重的經(jīng)濟損失。為了保護這些商業(yè)公司的軟件著作權(quán),技術(shù)人員們從技術(shù)角度提出了許多保護措施,其中最為廣泛使用的是軟件加殼保護技術(shù)。通過軟件加殼,可以對原有的程序代碼進行壓縮、加密甚至虛擬化,從而達到抗反匯編、抗逆向的效果。本文所提出的代碼虛擬化技術(shù)就屬于這種保護技術(shù)中的一種。在逆向技術(shù)與軟件保護技術(shù)的不斷對抗中,代碼虛擬化保護被證明是現(xiàn)今為止十分有效的一種抗逆向技術(shù)。
代碼虛擬化技術(shù)采用虛擬機思想,將受保護程序中的程序邏輯轉(zhuǎn)化為屬于自己虛擬機的虛擬機指令,這樣的代碼虛擬化轉(zhuǎn)化使得程序脫離了常規(guī)的寄存器和堆棧,從而使得逆向人員無法直接通過原先的知識積累進行逆向分析,若想分析此類受代碼虛擬化保護的軟件,需要對獨特設(shè)計的虛擬機指令集、虛擬機堆棧進行分析,這樣的保護有效地增加了逆向分析人員所需要付出的時間和經(jīng)濟成本。
近幾年,研究人員提出了許多對于代碼虛擬化保護方法的改進和加固方案。Wang 等[1]提出了NISLVMP方案,通過將虛擬機上下文復(fù)雜化的思想,實現(xiàn)了多組虛擬寄存器值的轉(zhuǎn)換算法,獲得最終虛擬機寄存器值來提高抗分析能力。Fang[2]等提出了一種多階段代碼虛擬化保護方法,初始階段對原生指令進行虛擬化保護,后續(xù)通過對前一階段產(chǎn)生的處理器中的虛擬指令進一步虛擬化,通過增加語義復(fù)雜度來提高逆向分析的難度。Averbuch 等[3]通過對虛擬指令分發(fā)器進行隱藏,防止逆向分析人員找到虛擬機部分,從而阻止對于虛擬機的逆向分析。房鼎益等[4]提出了基于時間執(zhí)行不同路徑而產(chǎn)生的多樣性來對抗時間積累的逆向分析。侯留洋提出了結(jié)合混淆思想、使用多套虛擬機環(huán)境隨機選擇來執(zhí)行構(gòu)造的混淆基本塊和關(guān)鍵代碼的代碼虛擬化保護方法[5]。由于私有虛擬環(huán)境使得混淆基本塊難以去除并且是隨機映射關(guān)系,無法累積字節(jié)碼知識進行后續(xù)分析,因此顯著增加了逆向分析的難度。
上述加固方法固然有效,但是對于動態(tài)跟蹤的調(diào)試方式,這些方法還是容易被跟蹤到虛擬機的分發(fā)器與虛擬機的虛擬機指令。
代碼虛擬化技術(shù)的核心原理在于設(shè)計一套擁有私有的虛擬機指令集和虛擬上下文,然后將待保護的關(guān)鍵代碼設(shè)計成在虛擬機上下文中執(zhí)行,從而使得逆向分析人員不得不對這套私有的虛擬機指令集和虛擬機上下文進行分析,而由于是獨特設(shè)計的虛擬機指令集和虛擬機上下文,逆向人員無法輕松定位關(guān)鍵代碼,也無法利用原有的知識積累來完成逆向,這種代碼保護技術(shù)顯著提升了逆向分析所需的成本[6]。代碼虛擬化保護方法的基本流程如圖1所示。

圖1 代碼虛擬化保護方案基本流程圖
通常的虛擬機軟件保護加固方法:
(1)函數(shù)識別:對目標程序中的函數(shù)進行識別,并且記錄所需要保護的關(guān)鍵部分的位置。
(2)代碼提取:對選中的關(guān)鍵部分進行提取抽離。
(3)代碼生成:用虛擬機指令集對待保護的關(guān)鍵部分進行翻譯編碼,生成符合虛擬機指令集的字節(jié)碼。
(4)目標重寫:將步驟3 中生成的字節(jié)碼和虛擬機運行所需的組件添加到目標程序的新節(jié)中,并將原來關(guān)鍵部分的起始處改為跳轉(zhuǎn)指令。關(guān)鍵代碼其余部分使用指定字符進行填充,最終生成一個符合規(guī)范的PE 文件。
代碼虛擬化保護方法通過在特別設(shè)計的虛擬環(huán)境上模擬x86 指令的執(zhí)行,通常來說能夠在任意指令處恢復(fù)原有的CPU 上下文,然后交由原CPU 繼續(xù)執(zhí)行原匯編指令,所以私有的虛擬指令集和原CPU 指令集之間的關(guān)系應(yīng)該為圖靈等價。
通常來說,軟件在受到代碼虛擬化保護后,再運行時流程如圖2 所示。
當(dāng)軟件受到代碼虛擬化保護后,再運行時首先會對未被保護的代碼部分正常執(zhí)行。由于在代碼虛擬化保護過程中已經(jīng)將關(guān)鍵代碼處的代碼替換為虛擬機代碼,而真正的關(guān)鍵代碼部分已經(jīng)轉(zhuǎn)換為特定虛擬指令集對應(yīng)的虛擬字節(jié)碼放于數(shù)據(jù)段[7],所以運行到受保護的關(guān)鍵代碼部分時,會直接執(zhí)行代碼虛擬化中的虛擬機初始化代碼,將原始CPU 上下文環(huán)境保存,并且創(chuàng)建一套虛擬化的CPU 上下文環(huán)境,然后由該虛擬機中的VMdisaptcher 部分讀取數(shù)據(jù)段中的虛擬字節(jié)碼,通過虛擬機字節(jié)碼確定虛擬機運行的指令和數(shù)據(jù),從而改變虛擬化的CPU 上下文環(huán)境。由于虛擬字節(jié)碼與原始關(guān)鍵代碼部分圖靈等價,所以讀取全部虛擬字節(jié)碼后,虛擬機VMexit 部分直接將虛擬化CPU 上下文環(huán)境恢復(fù)至原始CPU 上下文即可等價完成對關(guān)鍵代碼部分的執(zhí)行[8]。

圖2 受保護程序執(zhí)行流程
代碼虛擬化保護的關(guān)鍵部分在于將原有CPU的邏輯轉(zhuǎn)移到私有的虛擬環(huán)境中執(zhí)行。所以如何保護這個虛擬環(huán)境不被攻擊者輕易分析,增加攻擊者分析虛擬環(huán)境所帶來的成本是一個最基本的想法。為此,本文在此提出一種結(jié)合多線程技術(shù)的代碼虛擬化方法,設(shè)計并實現(xiàn)了Muti-Thread-Vmp 系統(tǒng),這樣攻擊者在分析私有虛擬環(huán)境時,不得不頻繁切換調(diào)試器所需跟蹤的線程,顯著增加了攻擊者分析所需要的時間和工作量,同時由于程序在被保護時,虛擬字節(jié)碼與虛擬指令之間做了隨機化映射,所以對于不同的受到Muti-Thread-Vmp 保護的程序,需要單獨分析,無法形成知識累積型攻擊。
Muti-Thread-Vmp 的保護對象為Windows 平臺上的PE 文件(.exe 和.dll 等),使用Muti-Thread-Vmp 時需要提供需要保護的關(guān)鍵代碼的函數(shù)地址,Muti-Thread-Vmp 將自動識別該函數(shù)的結(jié)束位置,并將該關(guān)鍵代碼進行代碼虛擬化保護。運行Muti-Thread-Vmp 系統(tǒng)時,一共經(jīng)過如下幾個步驟:
(1)通過使用者提供的關(guān)鍵代碼的函數(shù)地址,自動識別并提取關(guān)鍵代碼的結(jié)束位置。
(2)將原生字節(jié)反匯編轉(zhuǎn)換為匯編代碼,常用的反匯編引擎有capstone、ODDisassm、BeaEngine和udis86 等,本文采用的反匯編引擎為capstone,反匯編引擎將字節(jié)碼逐條翻譯成匯編指令,如8B C1 會被翻譯成mov eax,ecx。
(3)生成隨機操作碼表,虛擬函數(shù)映射表,按照該操作碼表對原反匯編處的匯編代碼進行翻譯,生成虛擬機字節(jié)碼。
(4)在受保護的程序創(chuàng)建兩個新的節(jié)區(qū),一個節(jié)區(qū)命名為VMP-0,用于置入虛擬機代碼,另一個節(jié)區(qū)命名為VMP-1,用于存放生成的虛擬機字節(jié)碼,使用垃圾代碼填充原始關(guān)鍵代碼,在原始關(guān)鍵代碼入口寫入跳轉(zhuǎn)指令,跳轉(zhuǎn)至VMP-0 中虛擬機VMinti 函數(shù)入口。
(5)將改動過的受保護程序另存為新的PE 文件,完成保護。
在Muti-Thread-Vmp 運用到的代碼虛擬化保護技術(shù)中,較以往代碼虛擬化保護技術(shù)的不同點在于以下幾個關(guān)鍵技術(shù),也是因為這些關(guān)鍵技術(shù)加強了保護強度。
(1)多線程化的虛擬機設(shè)計。本文設(shè)計的虛擬機將不同指令的模擬實現(xiàn)置于不同的線程中,虛擬CPU 上下文通過線程間的全局變量進行操作,通過VMdispatcher 讀取虛擬字節(jié)碼選擇線程進行運行,如圖3 所示。

圖3 多線程化虛擬機運行示意圖
目前實現(xiàn)的線程指令集如表1 所示。

表1 線程指令集統(tǒng)計表
(2)隨機化的線程選擇。在使用多線程將指令集模擬后,攻擊者可以通過跟蹤分析每一個線程,從而了解整個虛擬機運行環(huán)境以及虛擬操作碼和線程之間的關(guān)系。通過一定時間的累積便可完成對本系統(tǒng)的逆向分析。為了解決這個問題,在每一次進行虛擬化保護的時候,將線程與虛擬操作碼之間的關(guān)鍵進行隨機對應(yīng)。如保護A 軟件時0x01 操作碼原先對應(yīng)的可能是MOVI 線程,在保護B 軟件時,0x01 對應(yīng)的便可能為LODI。這樣的隨機化操作可以有效避免攻擊者對本系統(tǒng)的保護找到統(tǒng)一的分析方法。
(3)線程函數(shù)加密。僅從動態(tài)分析的角度進行防御顯然是不全面的,通過靜態(tài)分析,攻擊者可以輕松分析出每一個線程的含義,從而定位每個線程的指令,恢復(fù)原始代碼。為了對抗靜態(tài)分析,本系統(tǒng)將所有的線程函數(shù)在VMinit 前進行了XOR 加密,XOR 加密的密鑰為每個線程對應(yīng)的虛擬操作碼,在VMinit 運行后,再進行解密操作。這樣可以有效對抗靜態(tài)分析,進一步加強本系統(tǒng)的保護強度。
實驗環(huán)境為WIN10-1909,3.6GHz CPU,32G 內(nèi)存。由于只實現(xiàn)了部分對32 位CPU 指令集的模擬,故僅對32 位可執(zhí)行程序進行測試。
選用樣例程序進行保護,樣例程序源碼如圖4所示,保護前可以輕松使用IDA 逆向工具得到其程序邏輯。

圖4 未受保護的calc.exe
而在經(jīng)過Muti-Thread-Vmp 保護后,反編譯時,IDA 開始報錯,并且出現(xiàn)大量無法識別的字節(jié)碼,如圖5 所示。
通過測試,本文設(shè)計并實現(xiàn)的代碼虛擬化技術(shù)可以有效地對抗逆向分析,增大攻擊者的分析成本,從而對軟件進行保護。

圖5 受保護的calc.exe
對本系統(tǒng)進行實例測試,主要測試文件大小和性能損耗。對比實驗結(jié)果如表2、表3 所示。

表2 空間損耗測試

表3 時間損耗測試
通過實驗可以看出,本文提出的系統(tǒng)在保護程序時,會將虛擬機加入原始程序,造成額外的空間損耗;在執(zhí)行效率上也會造成額外的時間損耗。但是對于程序保護來說,這些額外的損耗是不可避免的。
由于目前代碼虛擬化保護技術(shù)仍存在著不斷被破解的情況,本文提出一種基于多線程模擬虛擬指令集的方法來對抗逆向分析,從而加強代碼虛擬化保護的強度。本文從設(shè)計、理論分析、功能驗證以及與商用軟件對比的角度上,介紹并證明了本系統(tǒng)的有效性。
在未來的研究中,本文提出的方法還可以進行進一步優(yōu)化與加強。
(1)將可以并行的匯編指令通過多線程并行運行,從而提升程序效率,理想的極端情況下,效率甚至?xí)仍汲绦蜻€要高。
(2)可以設(shè)置真假線程,如滿足一定條件的假線程啟動后,真線程才啟動,這樣可以進一步增大分析的難度。