APK的自我保護

      因爲Android應用程序中的大部分代碼使用Java語言編寫,而Java語言又比較容易進行逆向,因此Android應用程序的自我保護具備必定的意義。本文總結了Android中可使用的一些APK自我保護的技術,大部分都通過實際的代碼測試。

Dex

Dex文件結構

        classes.dex文件是Android系統運行於Dalvik Virtual Machine上的可執行文件,也是Android應用程序的核心所在,因此咱們首先來看下DEX文件的結構,這樣可以更好的理解後續的分析,須要更加詳細的信息,能夠參考Google關於Dex的技術文檔php

        從Java源文件(固然Android也支持JNI的調用方式)到生成Dex文件的基本映射關係如圖 1所示,Java源文件經過Java編譯器生成class文件,再經過dx工具轉換爲classes.dex文件。Dex文件從總體上來看是個索引的結構,類名、方法名、字段名等信息都存儲在常量池中,這樣可以充分減小存儲空間,一個Dex文件的基本結構如圖 2所示,相關結構聲明定義在DexFile.h中,在AOSP中的路徑爲/dalvik/libdex/DexFile.h。html

apk_protect_1 

圖 1 Java源文件生成Dex文件的映射關係java

Ÿ   header: Dex文件頭,包含magic字段、adler32校驗值、SHA-1哈希值、string_ids的個數android

 apk_protect_2

圖 2 Dex文件基本結構ios

以及偏移地址等。Dex文件頭結構固定,佔用0×70個字節,定義以下所示。git

 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
     struct DexHeader {
         u1 magic [ 8 ] ; /* includes version number */
         u4 checksum ; /* adler32 checksum */
         u1 signature [ kSHA1DigestLen ] ; /* SHA-1 hash */
         u4 fileSize ; /* length of entire file */
         u4 headerSize ; /* offset to start of next section */
         u4 endianTag ;
         u4 linkSize ;
         u4 linkOff ;
         u4 mapOff ;
         u4 stringIdsSize ;
         u4 stringIdsOff ;
         u4 typeIdsSize ;
         u4 typeIdsOff ;
         u4 protoIdsSize ;
         u4 protoIdsOff ;
         u4 fieldIdsSize ;
         u4 fieldIdsOff ;
         u4 methodIdsSize ;
         u4 methodIdsOff ;
         u4 classDefsSize ;
         u4 classDefsOff ;
         u4 dataSize ;
         u4 dataOff ;
} ;

Ÿ   DexStringId: 定義了字符串數據的偏移, stringDataOff指向字符串數據;github

 
1
2
3
     struct DexStringId {
         u4 stringDataOff ; /* file offset to string_data_item */
     } ;

Ÿ   DexTypeId: 表示應用程序代碼中使用到的具體類型,如整型、字符串等,在Dalvik字節碼中表示爲I、Ljava/lang/String;,descriptorIdx指向DexStringId列表的索引;服務器

 
1
2
3
     struct DexTypeId {
         u4 descriptorIdx ; /* index into stringIds list for type descriptor */
     } ;

Ÿ   DexProtoId:表示方法聲明的結構體,shortyIdx是方法聲明字符串,格式爲返回值類型後緊跟參數列表類型,如方法聲明爲VI,表示返回值爲V(空,無返回值),參數爲I(整型),全部的引用類型用L表示;returnTypeIdx指向DexTypeId列表的索引,表示返回值類型;parametersOff指向DexTypeList的偏移,表示參數列表類型;cookie

 
1
2
3
4
5
     struct DexProtoId {
         u4 shortyIdx ; /* index into stringIds for shorty descriptor */
         u4 returnTypeIdx ; /* index into typeIds list for return type */
         u4 parametersOff ; /* file offset to type_list for parameter types */
     } ;

Ÿ   DexFieldId: 表示代碼中的字段,classIdx指向DexTypeId列表索引,表示字段所屬的類;typeIdx表示字段類型,nameIdx指向DexStringId列表索引,表示字段名;網絡

 
1
2
3
4
5
     struct DexFieldId {
         u2 classIdx ; /* index into typeIds list for defining class */
         u2 typeIdx ; /* index into typeIds for field type */
         u4 nameIdx ; /* index into stringIds for field name */
     } ;

Ÿ   DexMethodId: 表示代碼中使用的方法,classIdx表示方法所屬的類,protoIdx指向DexProtoId列表索引,表示方法原型,nameIdx表示方法名;

 
1
2
3
4
5
     struct DexMethodId {
         u2 classIdx ; /* index into typeIds list for defining class */
         u2 protoIdx ; /* index into protoIds for method prototype */
         u4 nameIdx ; /* index into stringIds for method name */
     } ;

Ÿ   DexClassDef: 該結構相對要複雜一些,定義了代碼中的使用的類,以及相關的代碼指令。

 

 
1
2
3
4
5
6
7
8
9
10
     struct DexClassDef {
         u4 classIdx ; /* index into typeIds for this class */
         u4 accessFlags ;
         u4 superclassIdx ; /* index into typeIds for superclass */
         u4 interfacesOff ; /* file offset to DexTypeList */
         u4 sourceFileIdx ; /* index into stringIds for source file name */
         u4 annotationsOff ; /* file offset to annotations_directory_item */
         u4 classDataOff ; /* file offset to class_data_item */
         u4 staticValuesOff ; /* file offset to DexEncodedArray */
} ;

classIdx指向DexTypeId列表索引,表示該類的類型;accessFlags是類的訪問標誌,如public,private,static等;superclassIdx表示父類的類型;interfacesOff指向一個DexTypeList的偏移值,由於Java中能夠實現多個接口,這裏使用列表也就不難理解了;sourceFileIdx指向DexStringIdx列表的索引,表示類所在的源文件名稱;annotationsOff指向註解目錄結構;classDataOff指向DexClassData結構,表示類的數據部分;staticValuesOff表示類中的靜態數據。

 

Ÿ   DexClassData結構體定義在DexClass.h文件中,路徑爲/dalvik/libdex/DexClass.h,聲明以下,header中包含靜態字段個數,實例字段個數,直接方法(經過類直接訪問的方法)個數,虛方法(經過類實例訪問的方法)個數;

 
1
2
3
4
5
6
7
     struct DexClassData {
         DexClassDataHeader header ;
         DexField * staticFields ;
         DexField * instanceFields ;
         DexMethod * directMethods ;
         DexMethod * virtualMethods ;
     } ;

DexField表示字段的類型和訪問標誌, fieldIdx指向DexFieldId;

 
1
2
3
4
     struct DexField {
         u4 fieldIdx ; /* index to a field_id_item */
         u4 accessFlags ;
     } ;

DexMethod結構描述了方法的原型、名稱、訪問標誌以及代碼指令的偏移地址,methodIdx指向DexMethodId索引,須要注意的是在Google的Dex文件文檔中對此的定義:

index into the method_ids list for the identity of this method (includes the name and descriptor), represented as a difference from the index of previous element in the list. The index of the first element in a list is represented directly.

注意紅色字體部分,表示的是在Dex文件中,methodIdx是相對於前一個DexMethod中的methodIdx的增量,例如若是一個類中有兩個directMethods,第一個directMethod的methodIdx值爲0×13,表示指向索引爲0×13的methodIdx,那麼第二個directMethod的methodIdx的值是相對於前一個值的增量,例如0×01,表示指向索引爲0×14的methodIdx;accessFlags爲方法的訪問標誌,codeOff表示指令代碼的偏移地址;

 
1
2
3
4
5
     struct DexMethod {
         u4 methodIdx ; /* index to a method_id_item */
         u4 accessFlags ;
         u4 codeOff ; /* file offset to a code_item */
     } ;

DexCode的結構體聲明以下。

 
1
2
3
4
5
6
7
8
9
10
11
12
13
     struct DexCode {
         u2 registersSize ;
         u2 insSize ;
         u2 outsSize ;
         u2 triesSize ;
         u4 debugInfoOff ; /* file offset to debug info stream */
         u4 insnsSize ; /* size of the insns array, in u2 units */
         u2 insns [ 1 ] ;
         /* followed by optional u2 padding */
         /* followed by try_item[triesSize] */
         /* followed by uleb128 handlersSize */
         /* followed by catch_handler_item[handlersSize] */
} ;

須要注意的是,在DexClass.h中,全部的u4類型,其實是uleb128類型。每一個uleb128類型是leb128的無符號類型,每一個leb128類型的數據包含1-5個字節,表示一個32bit的數值。每一個字節只有7位有效,最高一位用來表示是否須要使用到下一個字節,好比若是第一個字節最高位爲1,表示還須要使用到第2個字節,若是第二個字節的最高位爲1,表示會使用到第3個字節,以此類推,最多5個字節。對於一個2個字節的leb128類型數據,其結構如圖 3所示。

 

apk_protect_3

圖 3 兩字節的leb128類型數據格式

Dex中方法的隱藏

         此部份內容可參考Playing Hide and Seek with Dalvik Executables

        前文分析了Dex文件的結構,根據Dex的文件結構,能夠實現對Dex中特定方法的隱藏,這樣在使用baksamli或者apktool工具對classes.dex文件進行反彙編時,沒法發現隱藏的方法,不過會有特定的現象發生,其實也是比較容易檢測出來的。

       在Dex文件格式分析中關於method的結構體是DexMethod,若是將methodIdx的值指向另外一個method,同時修改相應的代碼偏移量codeOff(accessFlags通常不須要修改),修改後續相應的methodIdx,則能夠實現特定方法的隱藏。對Dex文件修改後須要從新計算Dex文件的SHA1值以及校驗值,用來更新Dex文件。

       隱藏方法的步驟以下:

Ÿ       一、修改Dex文件中須要隱藏方法的DexMethod結構體,如圖 4所示,圖中隱藏了方法B。具體包括:

  • Ÿ   將DexMethod的methodIdx值設爲0×0,至關於將原先的方法指向了前一個方法;
  • Ÿ   訪問標誌符accessFlags通常不須要修改,在Dex文件格式裏,directMethods和virtualMethods是分開的;
  • Ÿ   將codeOffset設置爲前一個方法的代碼偏移地址。
  • Ÿ   更新需隱藏方法的下一個方法的methodIdx,可使用公式:next_method_idx=original_next_method_idx + original_method_idx

 apk_protect_4

圖 4 Dex方法隱藏

Ÿ   二、從新計算Dex的SHA1哈希值和Adler校驗值,並用以更新DexHeader,可使用DexFixer修復classes.dex文件;

Ÿ   三、從新打包生成APK文件:

  • Ÿ   將APK解壓縮,提取其中出META-INF文件夾以外的全部文件;
  • Ÿ   壓縮成Zip格式文件;
  • Ÿ   使用jarsigner或者其餘工具對生成的Zip文件簽名,後綴名修改爲.apk

    隱藏的方法仍然須要在程序中進行調用,調用隱藏方法的步驟以下:

  • Ÿ   使用反射調用android.content.res.AssetManager.openNonAsset方法打開當前應用程序的classes.dex文件,將數據保存到內存中;還能夠經過調用Context.getPackageCodePath()來得到當前應用程序對應的apk文件的路徑,利用此路徑構造ZipFile對象,進而獲取classes.dex的ZipEntry,利用ZipFile的getInputStream(ZipEntry)方法獲取classes.dex的數據流,核心代碼以下所示;

 

 
1
2
3
4
String apkPath = this . getPackageCodePath ( ) ;
ZipFile apkfile = new ZipFile ( apkPath ) ;
ZipEntry dexentry = zipfile . getEntry ( 「classes.dex」 ) ;
InputStream dexstream = zipfile . getInputStream ( dexentry ) ;

 

  •  Ÿ   修復Dex文件,將以前隱藏方法的DexMethod結構體恢復;
  • Ÿ   將修復後的Dex數據使用類加載器從新加載;
  • Ÿ   搜索被隱藏的方法;
  • Ÿ   調用被隱藏的方法。

        須要注意的是,方法在Dex文件中是按方法名的字典序排序的,因此須要隱藏的方法若是是該類中全部方法排序第一個的話,那麼methodIdx值是個絕對值,若是要隱藏的話就不是很方便,因此建議能夠寫個無用的方法,其方法名排序爲第一個,讓須要隱藏的方法從新指向該方法。

        使用修改methodIdx的方法,讓其指向另外一個DexMethodId的結構體,若是使用baksmali進行反彙編,則會發如今一個類中有兩個徹底相同的函數。

        那有沒有更加隱蔽的手段來隱藏一個方法了?考慮到在DexClassData結構體中的DexClassDataHeader頭部,其中directMethodsSize和virtualMethodsSize分別表示直接方法個數和虛方法個數,所以若是但願隱藏某個方法,能夠經過將相應的directMethodsSize或virtualMethodsSize減1,同時將表示該須要隱藏方法的DexMethod結構體中的數據所有修改成0,這樣就能夠將該方法隱藏起來,使用baksmali反彙編時,不會顯示出該方法的反彙編代碼,具體能夠參考Hashdays 2012 Android Chanllenge

        固然,上述這兩種隱藏方法,都沒能隱藏掉DexMethodId結構體,這個結構體中包含了方法所屬的類名、原型聲明以及方法名,因此能夠經過對比DexMethodId的個數和DexMethod結構體的個數來判斷是否存在方法隱藏的問題。

Dex完整性校驗

        classes.dex在Android系統上基本負責完成全部的邏輯業務,所以不少針對Android應用程序的篡改都是針對classes.dex文件的。在APK的自我保護上,也能夠考慮對classes.dex文件進行完整性校驗,簡單的能夠經過CRC校驗完成,也能夠檢查Hash值。因爲只是檢查classes.dex,因此能夠將CRC值存儲在string資源文件中,固然也能夠放在本身的服務器上,經過運行時從服務器獲取校驗值。基本步驟以下:

  • Ÿ   首先在代碼中完成校驗值比對的邏輯,此部分代碼後續不能再改變,不然CRC值會發生變化;
  • Ÿ   從生成的APK文件中提取出classes.dex文件,計算其CRC值,其餘hash值相似;
  • Ÿ   將計算出的值放入strings.xml文件中。

    核心代碼以下:

 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
String apkPath = this . getPackageCodePath ( ) ;
Long dexCrc = Long . parseLong ( this . getString ( R . string . dex_crc ) ) ;
try
{
     ZipFile zipfile = new ZipFile ( apkPath ) ;
     ZipEntry dexentry = zipfile . getEntry ( 「classes.dex」 ) ;
     if ( dexentry . getCrc ( ) != dexCrc ) {
         System . out . println ( 「Dex has been modified!」 ) ;
     } else {
         System . out . println ( 「Dex hasn’t been modified!」 ) ;
     }
} catch ( IOException e ) {
             // TODO Auto-generated catch block
     e . printStackTrace ( ) ;
}

       可是上述的保護方式容易被暴力破解, 完整性檢查最終仍是經過返回true/false來控制後續代碼邏輯的走向,若是攻擊者直接修改代碼邏輯,完整性檢查始終返回true,那這種方法就無效了,因此相似文件完整性校驗須要配合一些其餘方法,或者有其餘更爲巧妙的方式實現?

APK完整性校驗

         雖然Android程序的主要邏輯經過classes.dex文件執行,可是其餘文件也會影響到整個程序的邏輯走向,以上述Dex文件校驗爲例,若是程序依賴strings.xml文件中的某些值,則修改這些值就會影響程序的運行,因此進一步能夠整個APK文件進行完整性校驗。可是若是對整個APK文件進行完整性校驗,因爲在開發Android應用程序時,沒法知道完整APK文件的Hash值,因此這個Hash值的存儲沒法像Dex完整性校驗那樣放在strings.xml文件中,因此能夠考慮將值放在服務器端。核心代碼以下:

 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
     MessageDigest msgDigest = null ;
     try {
       msgDigest = MessageDigest . getInstance ( 「MD5″ )
       byte [ ] bytes = new byte [ 8192 ] ;
       int byteCount ;
       FileInputStream fis = null ;
       fis = new FileInputStream ( new File ( apkPath ) ) ;
       while ( ( byteCount = fis . read ( bytes ) ) > 0 )
         msgDigest . update ( bytes , 0 , byteCount ) ;
       BigInteger bi = new BigInteger ( 1 , msgDigest . digest ( ) ) ;
       String md5 = bi . toString ( 16 ) ;
       fis . close ( ) ;
       /*
       從服務器獲取存儲的Hash值,並進行比較
      */
 
       } catch ( Exception e ) {
         e . printStackTrace ( ) ;
     }

 

 

動態加載

         Java中可使用反射技術來更加靈活地控制程序的運行,爲Java運行時的行爲提供了強大的支持。Android系統提供了DexClassLoader來支持在程序運行過程當中動態加載包含classes.dex的.jar或者.apk文件,若是再結合Java反射技術,能夠實現執行非應用程序部分的代碼。利用動態加載技術,能夠提供逆向分析的難度,在必定程度上能夠保護APK自身的業務邏輯防止被破解。

       DexClassLoader的構造函數原型以下:

 
1
public DexClassLoader ( String dexPath , String optimizedDirectory , String libraryPath , ClassLoader parent )

其中,dexPath爲包含dex文件的.apk或者.jar路徑,optimizedDirectory是優化後的dex文件的路徑,libraryPath表示Native庫的路徑,parent是父類加載器。經過DexClassLoader實例化對象,調用loadClass加載須要調用的類,得到Class對象後,就能夠進一步使用Java反射技術來調用相應的方法。以下:

 
1
2
3
4
5
6
7
8
9
10
11
12
     DexClassLoader classLoader = new DexClassLoader ( apkPath , dexPath , null , getClassLoader ( ) ) ;
     try {
     Class <? > mLoadClass = classLoader . loadClass ( 「com.example.dexclassloaderslave.DexSlave」 ) ;
     Constructor <? > constructor = mLoadClass . getConstructor ( new Class [ ] { } ) ;
     Object dexSlave = constructor . newInstance ( new Object [ ] { } ) ;
     Method sayHello = mLoadClass . getDeclaredMethod ( 「sayHello」 , new Class [ ] { } ) ;
     sayHello . setAccessible ( true ) ;
     sayHello . invoke ( dexSlave , new Object [ ] { } ) ;
     } catch ( Exception e )
   {
       e . printStackTrace ( ) ;
     }

上述代碼實現調用com.example.dexclassloaderslave.DexSlave類中的sayHello方法。

 

       對於須要經過DexClassLoader被調用的.apk或者.jar文件的分發,能夠將其放入Android項目的assets或者res目錄下,也能夠將其放在服務器端,在實際須要調用時經過網絡獲取文件。爲了提升逆向的難度,能夠對被調用的.apk或者.jar文件採起如下措施進行進一步的保護:

  • Ÿ   進行完整性校驗,防止文件被篡改;
  • Ÿ   進行加密處理,在調用加載前進行解密;
  • Ÿ   對須要調用的函數相關信息使用經過網絡獲取的方式,而不是硬編碼在代碼中,能夠真正實現動態調用,提升靜態分析的難度;
  • Ÿ   對於使用網絡服務器分發的方式,注意對網絡服務器地址的保護,不要以字符串硬編碼的方式寫在代碼中,對下載請求也須要使用cookie等輔助識別的技術。

        除了使用DexClassLoader類實現動態加載外,還可使用dalvik.system.DexFile類實現Dex文件的加載,可是DexFile類提供的構造方法在實例化過程當中須要在/data/davik-cache目錄下生成相應的Dex文件,而/data/davik-cache目錄對於通常應用程序是沒有寫權限的,因此在程序中沒法實例化DexFile對象,也就沒法調用DexFile.loadClass方法。因此須要經過反射調用DexFile類的openDex方法,具體能夠參考該代碼中invokeHidden函數。

APK僞加密

        APK其實是Zip壓縮文件,可是Android系統在解析APK文件時,和傳統的解壓縮軟件在解析Zip文件時仍是有所差別的,利用這種差別能夠實現給APK文件加密的功能。Zip文件格式能夠參考MasterKey漏洞分析的一篇文章。在Central Directory部分的File Header頭文件中,有一個2字節長的名爲General purpose bit flags的字段,這個字段中每一位的做用能夠參考Zip文件格式規範的4.4.4部分,其中若是第0位置1,則表示Zip文件的該Central Directory是加密的,若是使用傳統的解壓縮軟件打開這個Zip文件,在解壓該部分Central Directory文件時,是須要輸入密碼的,如圖 5所示。可是Android系統在解析Zip文件時並無使用這一位,也就是說這一位是否置位對APK文件在Android系統的運行沒有任何影響。通常在逆向APK文件時,會首先使用apktool來完成資源文件的解析,dex文件的反彙編工做,但若是將Zip文件中Central Directory的General purpose bit flags第0位置1的話,apktool(version:1.5.2)將沒法完成正常的解析工做,如圖 6所示,可是又不會影響到APK在Android系統上的正常運行,如圖 7所示。

 apk_protect_5

圖 5 傳統解壓縮軟件須要輸入密碼進行解壓縮

 apk_protect_6

圖 6 apktool解析僞加密的APK文件失敗

       對APK文件進行僞加密可使用這個腳本,在Python的zipfile模塊中,ZipInfo類中記錄了Zip文件中相應的Central Directory的相關信息,包括General purpose bit flags,在ZipInfo類中屬性爲flag_bits,所以上述腳本中將需加密的APK文件的每一個ZipInfo的flag_bits和1作或操做,實如今General purpose bit flags的第0位置1.

       而須要去除這些僞加密的標誌的話,可使用這個腳本。相關內容能夠參考BlueBox以前提出的一個Android Security Analysis Chanllenge.

apk_protect_7 

圖 7 僞加密的APK能夠正常運行

 Manifest Cheating

         AndroidManifest.xml是Android應用程序的配置文件,包含了包名、應用程序名稱、申請的權限信息以及組件信息等。在Android應用程序開發,生成APK時,aapt會負責完成資源的打包,打包會將文本格式的XML資源文件編譯成二進制格式的XML資源文件。將文本格式的XML文件轉換成二進制格式,一方面經過字符串資源池的統一管理,減小文件體積;另外一方面二進制格式的XML文件解析速度也會更快。在Android開發過程當中,生成的R.java文件中包含了相應的資源類型、名稱以及對應的id值。資源id是32bit的整型值,格式爲:0xPPTTNNNN。其中PP表示使用該資源的包,TT表明該資源的類型,而NNNN是該類型中資源的名稱。對於應用程序資源,PP值固定爲7f,而對於被引用的系統資源包,其PP值爲01。TT和NNNN通常是aapt按照資源出現的順序生成的。更多分析能夠參考羅昇陽的Android應用程序資源的編譯和打包過程分析

       Manifest Cheating的基本原理是,在AndroidManifest的<application>節點中插入一個未知id(如0×0),名稱爲name的屬性,其值能夠是一個從未定義實現的Java類文件名。而對AndroidManifest的修改須要在二進制格式下進行,這樣才能不會破壞以前aapt對資源文件的處理。因爲是未知的資源id,在應用程序運行過程當中,Android會忽略此屬性。可是在使用apktool進行重打包時,首先會將AndroidManifest.xml轉換爲明文,進而會包含名稱爲name的屬性,而相應的id信息會丟失,apktool重打包會從新進行資源打包處理,因爲該name屬性值是一個未實現的Java類,重打包後的應用程序在運行過程當中,因爲application節點中定義的類是先於全部其餘組件運行的,若系統找不到對應的類,會出現運行時錯誤,Dalvik虛擬機會直接關閉。另外,也能夠實現name屬性值對應的Java類,若此類被調用,則代表被重打包了,能夠採起進一步的措施。這樣就能夠起到保護自身APK的做用,防止被重打包。可是這種方法也很容易被繞過,只須要在通過apktool解碼的AndroidManifest文件中,去掉在application節點中添加的name屬性便可。整個過程以下:

  • Ÿ   將APK解壓縮,提取其中的AndroidManifest.xml文件;
  • Ÿ   使用axml工具,修改二進制的AndroidManifest.xml文件,在application節點下插入id未知(如0×0),名爲name的屬性(值能夠任意,只要不對應到項目中的類文件名便可,如some.class);
  • Ÿ   將除META-INF文件夾以外的文件壓縮成zip文件,簽名後生成.apk文件。

如果攻擊者使用apktool重打包,運行重打包後的文件會出現以下運行時錯誤: apk_protect_8

圖 8 使用Manifest Cheating重打包後APK文件運行時錯誤

調試器檢測

         在對APK逆向分析時,每每會採起動態調試技術,可使用netbeans+apktool對反彙編生成的smali代碼進行動態調試。爲了防止APK被動態調試,能夠檢測是否有調試器鏈接。Android系統在android.os.Debug類中提供了isDebuggerConnected()方法,用於檢測是否有調試器鏈接。能夠在Application類中調用isDebuggerConnected()方法,判斷是否有調試器鏈接,若是有,直接退出程序。

       除了isDebuggerConnected方法,還能夠經過在AndroidManifest文件的application節點中加入android:debuggable=」false」使得程序不可被調試,這樣若是但願調試代碼,則須要修改該值爲true,所以能夠在代碼中檢查這個屬性的值,判斷程序是否被修改過,代碼以下:

 
1
2
3
4
     if ( getApplicationInfo ( ) . flags &= ApplicationInfo . FLAG_DEBUGGABLE != 0 ) {
       System . out . println ( 「Debug」 ) ;
       android . os . Process . killProcess ( android . os . Process . myPid ( ) ) ;
     }

代碼混淆

         使用Java編寫的代碼很容易被反編譯,所以可使用代碼混淆的方法增長反編譯代碼閱讀的難度。ProGuard是一款免費的Java代碼混淆工具,提供了文件壓縮、優化、混淆和審覈功能。在Eclipse+ADT開發環境下,每一個Android應用程序項目目錄下會默認生成project.properties和proguard-project.txt文件。若是須要使用ProGuard進行壓縮以及混淆,首先須要在project.properties文件中去掉對以下語句的註釋:

 
1
proguard . config = $ { sdk . dir } / tools / proguard / proguard - android . txt : proguard - project . txt

ProGuard的相關配置信息須要在proguard-project.txt文件中聲明,在其中能夠設置須要混淆和保留的類或方法。因爲在某些狀況下,ProGuard會錯誤地認爲某些代碼沒有被使用,如在只在AndroidManifest文件中引用的類,從JNI中調用的方法等。對於這些狀況,須要在proguard-project.txt文件中添加-keep命令,用來保留類或方法。關於ProGuard更加詳細的配置項能夠參考ProGuard Manual

       除了使用ProGuard對Android代碼進行混淆外,還可使用DexGuard。DexGuard是特別針對Android的一款代碼優化混淆的收費軟件,提供代碼優化混淆、字符串加密、類加密、Assets資源加密、隱藏對敏感API的調用、篡改檢測以及移除Log代碼。

       關於代碼混淆,還能夠參考Android:Game of Obfuscation

NDK

         Android軟件的開發主要使用Java語言,可是Android也提供了對本地語言C、C++的支持。藉助JNI,能夠在Java類中使用C語言庫中的特定函數,或在C語言程序中使用Java類庫。通常來講,若是代碼中對處理速度有較高要求或者爲了更好地控制硬件,抑或者爲了複用既有的C/C++代碼,均可以考慮經過JNI來實現對Native代碼的調用。

       因爲逆向Native程序的彙編代碼要比逆向Java彙編代碼困難,所以能夠考慮在關鍵代碼部位使用Native代碼,如註冊驗證,加解密操做等。一個可能的藉助Native代碼保護APK的方法是:將核心業務邏輯代碼放入加密的.jar或者.apk文件中,在須要調用時使用Native代碼進行解密,同時完成對解密後文件的完整性校驗,不過不論是.jar仍是.apk文件,解密後都會留在物理存儲上,爲了不這種狀況,可使用反射技術直接調用dalvik.system.DexFile.openDex()方法,該方法接受classes.dex文件字節流返回DexFile對象。關於Native代碼的編寫,能夠參考Google官方文檔的Android NDK

逆向工具對抗

         在逆向分析Android應用程序時,通常會使用apktool,baksmali/smali,dex2jar,androguard,jdGUI以及IDA Pro等。所以能夠考慮使得這些工具在反編譯APK時出錯來保護APK,這些工具大部分都是開源的,能夠經過閱讀其源代碼,分析其在解析APK、dex等文件存在的缺陷,在開發Android應用程序時加以利用。能夠參考Tim Strazzere的Dex Education:Practicing Safe Dex,相應的Demo,看雪上的中文翻譯,不過其中的不少技巧已經失效了。DexLabs的Dalvik Bytecode Obfuscation on Android介紹了垃圾字節碼插入的技術。

總結

         以上APK自我保護的技術並不能作到徹底的保護做用,只是提升了逆向分析的難度,在實際運用中應該根據狀況多種技術結合使用。這些技術其實不少來源於Android惡意代碼,因此能夠關注Android惡意代碼中使用的一些技術來應用到本身開發的Android應用程序中。

來源:http://www.sanwho.com/445.html

相關文章
相關標籤/搜索