本發(fā)明涉及Android應(yīng)用加固方法,尤其涉及一種基于smali流程混淆技術(shù)的Android應(yīng)用加固方法。
背景技術(shù):
在互聯(lián)網(wǎng)時代,隨著移動智能終端的日益普及以及網(wǎng)絡(luò)寬帶化的高速發(fā)展,我們的生活幾乎處處充滿了互聯(lián)網(wǎng)的氣息。然而,兼具開發(fā)性與靈活性的終端系統(tǒng),及其應(yīng)用中潛在的信息安全問題卻可能對其用戶、承載網(wǎng)絡(luò)等各個環(huán)節(jié)造成影響,特別是軟件逆向技術(shù)。逆向分析技術(shù)可以被用來在不知道應(yīng)用程序源代碼的情況下分析應(yīng)用程序的功能流程、篡改應(yīng)用程序的數(shù)據(jù)代碼等,逆向分析技術(shù)如果被不加限制的惡意使用,利用者可以分析獲取應(yīng)用程序的核心技術(shù),也可以篡改應(yīng)用程序的簽名和作者信息,還可以將惡意代碼注入到已有的應(yīng)用程序中并通過二次打包進行偽裝,這些行為都極大的危害了應(yīng)用程序開發(fā)者的利益,嚴重損害了廣大用戶的隱私安全,甚至危及國家安全與社會穩(wěn)定,阻礙其健康發(fā)展。
在Android系統(tǒng)中,使用Java語言編寫的應(yīng)用程序,被編譯成Java類文件(.class格式);Android不會直接運行這些.class文件,而是將這些.class格式的類文件再次編譯成dex格式,交由Android平臺運行。所以dex文件是應(yīng)用程序的核心部分,為安裝包中不可或缺的部分。但是dex文件本質(zhì)上是Java字節(jié)碼,容易被反編譯為Java源代碼,如果沒有經(jīng)過混淆處理,通過逆向工程可以輕易獲取到可讀的Java源碼,進而達到竊取安卓應(yīng)用程序的核心邏輯代碼。
代碼混淆技術(shù)是近幾年才發(fā)展并興起的一種新技術(shù)。第一次對代碼混淆進行系統(tǒng)研究是在上世紀90年代末開始的,是Java語言的迅速發(fā)展引起了對混淆技術(shù)的研究熱潮。這是因為Java目標代碼—字節(jié)代碼(bytecode)很容易被反編譯為Java源代碼,這就迫切要求有效的保護字節(jié)代碼的方法。這一領(lǐng)域中的大部分工作是由Collberg、Thomborson、Low和Chenxi Wang做出的。
Collberg第一次對混淆轉(zhuǎn)換進行了詳細的總結(jié)和分類,也首次提出了混淆轉(zhuǎn)換的有效性與性能的若干評價標準。他將混淆轉(zhuǎn)換分為四類:外形混淆轉(zhuǎn)換、控制混淆轉(zhuǎn)換、數(shù)據(jù)混淆轉(zhuǎn)換以及預(yù)防性混淆轉(zhuǎn)換。
Chenxi Wang在文獻中實現(xiàn)了在C語言源代碼上的若干種控制混淆轉(zhuǎn)換與數(shù)據(jù)混淆轉(zhuǎn)換。Wang還給出了混淆轉(zhuǎn)換造成的性能過載以及混淆轉(zhuǎn)換對靜態(tài)分析工具IBM NPIC tool以及Rutger PAF toolkit的有效性。
Hohhl提出用帶有時間限制的黑盒(time-limited black box)方法來保護移動Agent。這里的黑盒就是指混淆轉(zhuǎn)換過的Agent程序。對移動Agent進行逆向工程,做出有意義的發(fā)現(xiàn)或修改需要一定的時間,據(jù)此,限制移動Agent在目的主機上運行的時間。在派發(fā)移動Agent的之前,需要對其進行混淆轉(zhuǎn)換,這增加了逆向工程的代價,從而延長在目的主機上的運行時間。
Cullen Linn從另一個角度研究了目標代碼混淆技術(shù),通過對反匯編過程的分析,采用一種混淆方法能夠阻撓逆向工程,使得獲取程序的匯編指令非常困難或不能正確得到程序的匯編指令。
綜上所述,現(xiàn)有工作雖然提出了一些加擾混淆方法,但只是增加了逆向閱讀的難度而已,而不能夠真正地解決防止逆向的問題。
技術(shù)實現(xiàn)要素:
為了克服上述現(xiàn)有技術(shù)的不足,本發(fā)明提供一種基于smali流程混淆技術(shù)的Android應(yīng)用加固方法,應(yīng)用加密算法、動態(tài)加載技術(shù)、JNI(Java Native Interface,Java本地接口)編程技術(shù)、完整性校驗技術(shù)和代碼混淆技術(shù)等,通過改變Android應(yīng)用程序的加載流程,使得加固后的應(yīng)用程序難以逆向,另外,需保證加固之后并不影響程序的正常執(zhí)行;以達到保護版權(quán),防止他人剽竊軟件中的智力成果或?qū)浖M行有目的的篡改的目的。
本發(fā)明提供的技術(shù)方案是:
一種Android應(yīng)用加固方法,基于smali流程混淆技術(shù),應(yīng)用加密、動態(tài)加載、JNI編程、完整性校驗和代碼混淆方法,通過改變Android應(yīng)用源程序的加載流程,使得加固后的應(yīng)用程序難以逆向,同時保證加固之后并不影響程序的正常執(zhí)行;包括如下步驟:
1)在動態(tài)加載Android應(yīng)用真正的dex文件之前,用AES加密算法加密真正的dex文件,得到加密后的dex文件class.dex.jar;
2)編寫so函數(shù)庫,執(zhí)行如下操作;
21)使用JNI編寫解密庫decrytApp.so,將解密函數(shù)寫在動態(tài)鏈接庫中,用于解密所述加密后的dex文件;
22)使用JNI編寫底層核心邏輯庫Function.so,將程序的核心邏輯寫在動態(tài)鏈接庫中;
3)編寫偽smali文件,用于程序的完整性校驗和對加密后的dex文件class.dex.jar動態(tài)加載,由此達到混淆源程序的加載流程的目的,執(zhí)行如下操作:
31)進行源程序的完整性檢驗;
32)動態(tài)加載加密后的真正的dex文件class.dex.jar;
由此完成對Android應(yīng)用的加固,達到保護應(yīng)用程序的目的。
針對上述Android應(yīng)用加固方法,進一步地,步驟1)所述AES加密算法通過循環(huán)進行加密,包括多輪AES加密循環(huán)進行迭代運算;除最后一輪外,每輪AES加密循環(huán)都包含以下步驟:
11)輪密鑰加,明文區(qū)塊中的每一個字節(jié)與該次回合密鑰做異或運算;
12)字節(jié)代替,S盒變換,用查找表的方式對矩陣中的字節(jié)進行置換;
13)行移位,將矩陣中的行進行循環(huán)移位;
14)列混淆,對矩陣中的列進行混合變換。
在本發(fā)明一實施例中,所述AES加密算法的密鑰長度設(shè)定為128位,包括10輪迭代運算。
針對上述Android應(yīng)用加固方法,進一步地,步驟2)具體使用Android NDK編寫decrytApp.so和Function.so,包括如下步驟:
a)編寫java文件,聲明本地文件中的函數(shù);
b)新建decryptApp.c文件,編寫解密函數(shù);
c)編寫JNI入口函數(shù);
d)編寫Android.mk文件;
e)編譯生成so庫,包括decrytApp.so和Function.so。
針對上述Android應(yīng)用加固方法,進一步地,步驟21)用C或C++編寫解密函數(shù),將解密函數(shù)寫在動態(tài)鏈接庫中。
針對上述Android應(yīng)用加固方法,進一步地,步驟31)所述進行源程序的完整性檢驗,具體包括如下步驟:
311)計算Android應(yīng)用程序的校驗和,在所述應(yīng)用程序中加入哨兵函數(shù);
312)在所述應(yīng)用程序正式運行之前,先啟動哨兵函數(shù),重新計算應(yīng)用程序的校驗和,并與之前的校驗和進行對比;
313)當312)中重新計算得到校驗和和311)中的校驗和相同時,判斷得到軟件沒有被篡改,繼續(xù)運行;當二者不同時,判斷得到軟件被篡改,即所述Android應(yīng)用被二次打包,終止運行。
與現(xiàn)有技術(shù)相比,本發(fā)明的有益效果是:
本發(fā)明提供一種基于smali流程混淆技術(shù)的Android應(yīng)用加固方法,應(yīng)用加密算法、動態(tài)加載技術(shù)、JNI編程技術(shù)、完整性校驗技術(shù)和代碼混淆技術(shù)等,通過改變源程序的加載流程,使得加固后的應(yīng)用程序難以逆向,另外,需保證加固之后并不影響程序的正常執(zhí)行;以達到保護版權(quán),防止他人剽竊軟件中的智力成果或?qū)浖M行有目的的篡改的目的。因此,利用本發(fā)明提供的技術(shù)方案,能夠有效地對安卓應(yīng)用程序進行版權(quán)保護,防止其被逆向或篡改。
附圖說明
圖1是本發(fā)明提供的Android應(yīng)用加固方法的流程框圖。
圖2是本發(fā)明實施例中完整性檢驗的流程框圖。
具體實施方式
下面結(jié)合附圖,通過實施例進一步描述本發(fā)明,但不以任何方式限制本發(fā)明的范圍。
本發(fā)明提供一種基于smali流程混淆技術(shù)的Android應(yīng)用加固方法,應(yīng)用加密算法、動態(tài)加載技術(shù)、JNI編程技術(shù)、完整性校驗技術(shù)和代碼混淆技術(shù)等,通過改變源程序的加載流程,使得加固后的應(yīng)用程序難以逆向,另外,需保證加固之后并不影響程序的正常執(zhí)行;以達到保護版權(quán),防止他人剽竊軟件中的智力成果或?qū)浖M行有目的的篡改的目的。
如圖1所示,class.dex文件是真正的dex文件,用于實現(xiàn)程序的主要功能,加密成class.dex.jar文件。Function.so核心庫是動態(tài)鏈接庫,為程序提供底層核心邏輯功能;decrytApp.so主要用于實現(xiàn)對class.dex.jar的解密。
本發(fā)明方法編寫偽smali文件,編譯生成偽dex文件,主要負責(zé)程序的完整性校驗和對class.dex.jar動態(tài)加載。若程序沒有被篡改,則采用動態(tài)加載的方式加載解密后的dex文件,否則不能加載dex文件。
本發(fā)明的整體流程如圖1所示,包括如下步驟:
1)在動態(tài)加載真正的dex文件前,用經(jīng)典的加密算法AES(Advanced Encryption Standard,高級加密標準)算法加密dex文件,得到加密后的dex文件;
AES加密算法通過循環(huán)進行加密,包括多輪AES加密循環(huán)。除最后一輪外,每輪AES加密循環(huán)都包含以下四個步驟:
11)輪密鑰加(AddRoundKey),矩陣中的每一個字節(jié)與當前輪密鑰做異或運算;
12)字節(jié)代替(SubBytes),通過S盒變換的方法,用查找表的方式對矩陣中的字節(jié)進行置換;
13)行移位(ShiftRow),將矩陣中的行進行循環(huán)移位;
14)列混淆(MixColumns),對矩陣中的列進行混合變換。
因此,在實現(xiàn)AES算法時,可以抽象四個函數(shù),分別對應(yīng)上面四個步驟:AddRoundKey(state),SubBytes(state),ShiftRow(state)和MixColumns(state),其中State為矩陣。
2)編寫so函數(shù)庫,執(zhí)行如下操作:
Java程序經(jīng)過編譯會生成Java字節(jié)碼。而Java字節(jié)碼很容易被逆向工程,雖然可以利用代碼混淆技術(shù)增加逆向難度,但是,混潛技術(shù)并不能徹底阻止逆向工程。因此我們可以使用C/C++編寫程序中更重要的代碼,利用其難被逆向的特點來達到保護程序源碼的目的。除此之外,使用C/C++編寫的代碼不需要Dalvik虛擬機的解釋運行,具有更高的運行效率,可以加快程序執(zhí)行復(fù)雜任務(wù)時的運行速度。因此,本發(fā)明采用C/C++編寫程序的底層鏈接庫,并且利用JNI技術(shù)在上層代碼中調(diào)用這些底層動態(tài)鏈接庫。
21.使用JNI編寫解密庫decrytApp.so,用于解密加密過后的dex文件;
本發(fā)明中對dass.dex.jar進行解密的函數(shù)用C/C++編寫,最終編譯成decrytApp.so。之所以將解密函數(shù)寫在動態(tài)鏈接庫中,是因為如果直接將解密邏輯寫在Java文件中,攻擊者可以通過反編譯得到解密代碼,從而獲得解密的方法。
22.使用JNI編寫底層核心邏輯庫Function.so;
Function.so庫中是應(yīng)用程序的底層核心代碼,是整個應(yīng)用程序的關(guān)鍵所在。將程序的核心邏輯寫在動態(tài)鏈接庫中,可以保護諸如重要算法、核心技術(shù)等不被逆向工程所竊取。因此,在本發(fā)明加固方案中,將核心的邏輯寫在Function.so中,防御反編譯。
3)編寫偽smali文件,偽smali主要負責(zé)程序的完整性校驗和對class.dex.jar動態(tài)加載,達到混淆源程序的加載流程的目的,執(zhí)行如下操作:
31.進行源程序的完整性檢驗,具體步驟如圖2;
完整性校驗是一種防止軟件被篡改的技術(shù)。主要機制就是事先計算應(yīng)用程序的校驗和,然后在程序中加入哨兵函數(shù),在程序正式運行之前,先啟動哨兵函數(shù),重新計算應(yīng)用程序的校驗和,并與之前的校驗和進行對比。如果兩者一樣,就判斷軟件沒有被篡改,繼續(xù)運行;如果不一樣,就認為軟件被篡改,隨即終止運行。
完整性校驗可以很好地檢測Android應(yīng)用是否被二次打包。任何apk文件,哪怕只是受到一點篡改,計算出來的校驗和都會不一樣。因此利用完整性校驗,可以有效防止應(yīng)用被插入惡意代碼、植入廣告等攻擊手段。
32.動態(tài)加載class.dex.jar;
程序的主體代碼都在真正的dex文件中,而這個dex文件又不是程序啟動的時候需要運行的文件,所以可以對它進行加密,生成class.dex.jar,然后再動態(tài)地加載class.dex.jar。經(jīng)過加密的文件具有很高的安全性,如果攻擊者沒有密鑰的話,很難破解文件。這樣,攻擊者無法直接逆向分析經(jīng)過加密的代碼,從而達到保護應(yīng)用程序的目的。
下面通過實例對本發(fā)明做進一步說明。
實施例:
AES算法:
AES算法的密鑰長度很靈活,可以是128位、192位和256位中的任意一種。不同的密鑰長度對應(yīng)不同的加密算法的運算輪數(shù),通常128位密鑰需運算10輪,192位需運算12輪,256位需運算14輪。一般輪數(shù)越多,被破解的難度也就越大,但是運算的時間也越長。考慮到本發(fā)明是基于Android平臺,其內(nèi)存和處理能力都十分有限,因此密鑰長度不宜選得過長。而在AES算法確立之初,6輪迭代運算便可抵抗當時世界上已知的所有攻擊。因此在本發(fā)明中,AES算法的密鑰長度設(shè)定為128位。
AES算法一共需要經(jīng)過10輪迭代運算,而每輪運算都是由四個步驟組成,因此可以按如下方式把加/解密的過程用C語言代碼實現(xiàn),代碼如下。
(1)加密
(2)解密
Android系統(tǒng)支持NDK(Native Development Kit,安卓原生開發(fā)工具包)編程,本發(fā)明中使用JNI編程技術(shù)將解密函數(shù)寫在動態(tài)鏈接庫decryptApp.so中,在程序運行的時候,對dex文件進行動態(tài)解密。這樣既能提高解密的效率,又能利用C語言的特性,保證解密過程的安全。
so函數(shù)庫:
我們使用Android NDK開發(fā)decrytApp.so和Function.so的大概過程如下所示:
1.編寫java文件,聲明本地文件中的函數(shù)(以decrytApp.so庫為例)
System.loadLibrary(“decryApp”);//加載動態(tài)鏈接庫decrytApp.so
native boolean jnicheckApp();//聲明與decrytApp.c中相對應(yīng)的原生函數(shù)
2.新建decryptApp.c文件,編寫解密函數(shù)
在文件中聲明如下函數(shù),函數(shù)的作用是解密class.dex.jar,根據(jù)Android系統(tǒng)中JNI調(diào)用的規(guī)則,函數(shù)名需要與Java文件中的函數(shù)名相對應(yīng):JNIEXPORT jboolean NICALL Java_com_bupt_testjni_decrytAppJnicheckApp(JNIENV*env,jobject this){};
3.編寫JNI入口函數(shù)
JNI—OnLoad()與JNI_OnUnLoad()函數(shù)是動態(tài)鏈接庫的入口函數(shù),當Dalvik虛擬機執(zhí)行到System.loadLibrary()函數(shù)時,會首先執(zhí)行C文件中的入口函數(shù),它會告訴Dalvik虛擬機使用哪一個JNI版本,如果動態(tài)鏈接庫中沒有入口函數(shù),虛擬機會默認使用最老的JNI1.1版本。而且我們也需要在入口函數(shù)中進行一些一些初始化的操作。JNI—OnLoad函數(shù)的編寫格式如下:
4.編寫Android.mk文件
Android.mk文件向NDK編譯系統(tǒng)描述工程的本地源,主要是傳遞如下幾個參數(shù):
LOCAL_PATH:=$(call my-dir)/project//工程路徑
LOCAL_SRC_FILES:=decrytApp.c//本地C文件的名稱
LOCAL_MODULE:=decrytApp//要編譯的動態(tài)鏈接庫的名稱
5.編譯生成so庫
運行工程目錄下的ndk編譯腳本,編譯本地C/C++代碼。編譯腳本"ndk-build"文件位于NDK文件的根目錄下,運行的時候需指定路徑。運行完成后,就會在工程的libs目錄下生成decrytApp.so庫。
6.編譯打包應(yīng)用
最后像編譯普通應(yīng)用程序一樣,用SDK中的工具編譯工程。SDK編譯工具將會把so庫打包進應(yīng)用程序的apk文件中,然后就可以正常運行應(yīng)用程序了。
采用NDK編程的方式,可以把重要代碼保存在C/C++動態(tài)鏈接庫中,然后在Java代碼中采用JNI技術(shù)調(diào)用底層的so庫。這樣既可以實現(xiàn)應(yīng)用的功能,又能杜絕使用Java語言編寫的代碼容易被逆向的隱患,是一種變向的軟件保護措施。除此之外,把重要代碼寫到so庫中,還能增加代碼的重用性,下次在其他應(yīng)用中,可以直接引入現(xiàn)成的so庫,免去了二次幵發(fā)的麻煩。
完整性校驗:
Android系統(tǒng)中提供的應(yīng)用簽名機制,本質(zhì)上就是一種完整性校驗。而且,Android系統(tǒng)規(guī)定,所有安裝到Android設(shè)備中的應(yīng)用都必須經(jīng)過簽名。因此,我們可以利用應(yīng)用簽名機制,來實現(xiàn)完整性校驗的功能。
數(shù)字簽名需要一份數(shù)字證書,但這個數(shù)字證書并不需要權(quán)威的數(shù)字證書簽名機構(gòu)認證,它只是用來讓應(yīng)用程序包自我認證的,因此可以使用Android SDK自帶的簽名工具keytool和signapk為應(yīng)用程序簽名。Android將數(shù)字證書用來標識應(yīng)用程序的作者和在應(yīng)用程序之間建立信任關(guān)系。數(shù)字證書的私鋼保存在程序幵發(fā)者的手中,公鑰隨應(yīng)用一起打包進apk中。
鑒于數(shù)字證書是開發(fā)者自己生成的,因此,軟件發(fā)行者的身份只能由自己持有,是唯一的。其他人如果沒有開發(fā)者的密鑰,無法制造出與開發(fā)者一樣的數(shù)字證書。因此,開發(fā)者可以在程序中加入檢測發(fā)行者身份的方法,在程序真正運行之前,驗證程序的發(fā)行者,如果符合,則繼續(xù)運行;如果不符合,程序退出運行。
利用對比發(fā)行者身份的完整性校驗技術(shù),判斷一個應(yīng)用是否被篡改的步驟如圖2所示:
1)獲取應(yīng)用程序的簽名
Android提供了獲取應(yīng)用程序簽名信息的方法,通過調(diào)用PackageManager的getPackageInfo(packageName,PackageManager.GET_SIGNATURES)方法,就可以得到應(yīng)用的簽名信息。核心代碼如下:
Packagelnfo pinfo=this.getPackageManager().getPackageInfo(packageName,PackageManager.GET_SIGNATURES);
Signature[]s=pInfo.signatures;
Signature sign=signs[0];
sign中存儲的就是應(yīng)用程序的簽名信息。
2)獲得應(yīng)用的簽名證書的發(fā)行者的信息;
應(yīng)用的作者在發(fā)布應(yīng)用時,都會為應(yīng)用簽名。簽名所用的證書,是作者在本地利用keytool生成,因此證書的發(fā)行者是作者本人。如果程序被二次打包,經(jīng)過重新編譯,那么k次簽名所用的證書就是攻擊者的證書,證書的發(fā)行者就變成了攻擊者。因此我們可以通過比較證書的發(fā)行者,來判斷應(yīng)用是否被二次打包。
所以我們需要得到證書的發(fā)行者,核心代碼如下:
CertificateFactory certFactory=CertificateFactory.getInstance("X.509");
X509Certificate cert=(X509Certificate)certFactory.generateCertificate(new
ByteArrayInputStreain(sign.toByteArray());
String Issuer=cert.getIssuerDN().toString();
Issuer就是數(shù)字證書的發(fā)行者。
3)獲得應(yīng)用的原始證書的發(fā)行者的信息,對比兩個發(fā)行者的信息,判斷應(yīng)用是否被二次打包;
我們可以將原始證書的發(fā)行者信息以字符串的形式保存在文件中。但是這樣存在安全隱患,可能會被攻擊者“找到”,從而被修改成攻擊者自己構(gòu)造的信息。因此,我們從服務(wù)端實時獲取原始證書的發(fā)行者信息,或者把原始證書的發(fā)行者信息加密,將密文存儲在本地,當需要對比的時候,先進行解密。
對比兩個發(fā)行者的信息,根據(jù)當前應(yīng)用攜帶的證書的發(fā)行者與官方獲得發(fā)行者的比較結(jié)果,判斷應(yīng)用是否被二次打包。如果兩者不一樣,則判斷當前應(yīng)用已經(jīng)被修改,哨兵函數(shù)就可以中止程序的運行。
動態(tài)加載dex文件,得到其中的類:
Dalvik虛擬機不同于標準的Java虛擬機,標準Java虛擬機運行的是Java字節(jié)碼,而Dalvik虛擬機運行的經(jīng)過轉(zhuǎn)換的Dalvik字節(jié)碼,即.dex格式的文件。因此,不能像標準Java虛擬機那樣,直接通過Java中的ClassLoader來實現(xiàn)類的動態(tài)加載。Dalvik虛擬機通過DexClassLoader和PathClassLoader兩個類實現(xiàn)類的動態(tài)加載。
動態(tài)加載的實現(xiàn)過程大致如下:
1)拿到經(jīng)過加密的待載入的dex文件。dex文件可以存放在SD卡中,也可以從服務(wù)端下載,還可以以資源文件的形式打包在應(yīng)用中。
2)運行偽程序,對dex文件進行解密處理。
3)為程序通過DexClassLoader類動態(tài)加載解密后的dex文件。
4)通過Java反射機制,獲得類的實例。
動態(tài)加載類的實例代碼如下所示:
(1)動態(tài)加載代碼
(2)反射調(diào)用工具類
通過上面的步驟,就實現(xiàn)了程序在運行時動態(tài)加載dex文件,并得到其中的類,進而執(zhí)行程序的主體代碼。
需要注意的是,公布實施例的目的在于幫助進一步理解本發(fā)明,但是本領(lǐng)域的技術(shù)人員可以理解:在不脫離本發(fā)明及所附權(quán)利要求的精神和范圍內(nèi),各種替換和修改都是可能的。因此,本發(fā)明不應(yīng)局限于實施例所公開的內(nèi)容,本發(fā)明要求保護的范圍以權(quán)利要求書界定的范圍為準。