Android中的Apk的加固(加殼)原理解析和實現(轉)

一、前言

今天又到週末了,憋了好久又要出博客了,今天來介紹一下Android中的如何對Apk進行加固的原理。現階段。我們知道Android中的反編譯工作越來越讓人操作熟練,我們辛苦的開發出一個apk,結果被人反編譯了,那心情真心不舒服。雖然我們混淆,做到native層,但是這都是治標不治本。反編譯的技術在更新,那麼保護Apk的技術就不能停止。現在網上有很多Apk加固的第三方平臺,最有名的應當屬於:愛加密和梆梆加固了。其實加固有些人認爲很高深的技術,其實不然,說的簡單點就是對源Apk進行加密,然後在套上一層殼即可,當然這裏還有一些細節需要處理,這就是本文需要介紹的內容了。

二、原理解析

下面就來看一下Android中加殼的原理:

我們在加固的過程中需要三個對象:

1、需要加密的Apk(源Apk)

2、殼程序Apk(負責解密Apk工作)

3、加密工具(將源Apk進行加密和殼Dex合併成新的Dex)


主要步驟:

我們拿到需要加密的Apk和自己的殼程序Apk,然後用加密算法對源Apk進行加密在將殼Apk進行合併得到新的Dex文件,最後替換殼程序中的dex文件即可,得到新的Apk,那麼這個新的Apk我們也叫作脫殼程序Apk.他已經不是一個完整意義上的Apk程序了,他的主要工作是:負責解密源Apk.然後加載Apk,讓其正常運行起來。

在這個過程中我們可能需要了解的一個知識是:如何將源Apk和殼Apk進行合併成新的Dex

這裏就需要了解Dex文件的格式了。下面就來簡單介紹一下Dex文件的格式

具體Dex文件格式的詳細介紹可以查看這個文件:http://download.csdn.net/detail/jiangwei0910410003/9102599

主要來看一下Dex文件的頭部信息,其實Dex文件和Class文件的格式分析原理都是一樣的,他們都是有固定的格式,我們知道現在反編譯的一些工具:

1、jd-gui:可以查看jar中的類,其實他就是解析class文件,只要瞭解class文件的格式就可以

2、dex2jar:將dex文件轉化成jar,原理也是一樣的,只要知道Dex文件的格式,能夠解析出dex文件中的類信息就可以了

當然我們在分析這個文件的時候,最重要的還是頭部信息,應該他是一個文件的開始部分,也是索引部分,內部信息很重要。

我們今天只要關注上面紅色標記的三個部分:

1) checksum

文件校驗碼 ,使用alder32 算法校驗文件除去 maigc ,checksum 外餘下的所有文件區域 ,用於檢查文件錯誤 。

2) signature

使用 SHA-1 算法 hash 除去 magic ,checksum 和 signature 外餘下的所有文件區域 ,用於唯一識別本文件 。

3) file_size

Dex 文件的大小 。

爲什麼說我們只需要關注這三個字段呢?

因爲我們需要將一個文件(加密之後的源Apk)寫入到Dex中,那麼我們肯定需要修改文件校驗碼(checksum).因爲他是檢查文件是否有錯誤。那麼signature也是一樣,也是唯一識別文件的算法。還有就是需要修改dex文件的大小。

不過這裏還需要一個操作,就是標註一下我們加密的Apk的大小,因爲我們在脫殼的時候,需要知道Apk的大小,才能正確的得到Apk。那麼這個值放到哪呢?這個值直接放到文件的末尾就可以了。

所以總結一下我們需要做:修改Dex的三個文件頭,將源Apk的大小追加到殼dex的末尾就可以了。

我們修改之後得到新的Dex文件樣式如下:

那麼我們知道原理了,下面就是代碼實現了。所以這裏有三個工程:

1、源程序項目(需要加密的Apk)

2、脫殼項目(解密源Apk和加載Apk)

3、對源Apk進行加密和脫殼項目的Dex的合併

三、項目案例

下面先來看一下源程序

1、需要加密的源程序Apk項目:ForceApkObj

需要一個Application類,這個到後面說爲什麼需要:

MyApplication.java

[java] view plaincopy

  1. package com.example.forceapkobj; 

  2. import android.app.Application; 
  3. import android.util.Log; 

  4. public class MyApplication extends Application{ 

  5. @Override
  6. public void onCreate() { 
  7. super.onCreate(); 
  8.         Log.i("demo", "source apk onCreate:"+this); 
  9.     } 

就是打印一下onCreate方法。

MainActivity.java

[java] view plaincopy

  1. package com.example.forceapkobj; 

  2. import android.app.Activity; 
  3. import android.content.Intent; 
  4. import android.os.Bundle; 
  5. import android.util.Log; 
  6. import android.view.View; 
  7. import android.view.View.OnClickListener; 
  8. import android.widget.TextView; 

  9. public class MainActivity extends Activity { 

  10. @Override
  11. protected void onCreate(Bundle savedInstanceState) { 
  12. super.onCreate(savedInstanceState); 

  13.         TextView content = new TextView(this); 
  14.         content.setText("I am Source Apk"); 
  15.         content.setOnClickListener(new OnClickListener(){ 
  16. @Override
  17. public void onClick(View arg0) { 
  18.                 Intent intent = new Intent(MainActivity.this, SubActivity.class); 
  19.                 startActivity(intent); 
  20.             }}); 
  21.         setContentView(content); 

  22.         Log.i("demo", "app:"+getApplicationContext()); 

  23.     } 

也是打印一下內容。

2、加殼程序項目:DexShellTools

加殼程序其實就是一個Java工程,因爲我們從上面的分析可以看到,他的工作就是加密源Apk,然後將其寫入到脫殼Dex文件中,修改文件頭,得到一個新的Dex文件即可。

看一下代碼:

[java] view plaincopy

  1. package com.example.reforceapk; 

  2. import java.io.ByteArrayOutputStream; 
  3. import java.io.File; 
  4. import java.io.FileInputStream; 
  5. import java.io.FileOutputStream; 
  6. import java.io.IOException; 
  7. import java.security.MessageDigest; 
  8. import java.security.NoSuchAlgorithmException; 
  9. import java.util.zip.Adler32; 


  10. public class mymain { 
  11. /**
  12.      * @param args
  13.      */
  14. public static void main(String[] args) { 
  15. // TODO Auto-generated method stub
  16. try { 
  17.             File payloadSrcFile = new File("force/ForceApkObj.apk");   //需要加殼的程序
  18.             System.out.println("apk size:"+payloadSrcFile.length()); 
  19.             File unShellDexFile = new File("force/ForceApkObj.dex");    //解客dex
  20. byte[] payloadArray = encrpt(readFileBytes(payloadSrcFile));//以二進制形式讀出apk,並進行加密處理//對源Apk進行加密操作
  21. byte[] unShellDexArray = readFileBytes(unShellDexFile);//以二進制形式讀出dex
  22. int payloadLen = payloadArray.length; 
  23. int unShellDexLen = unShellDexArray.length; 
  24. int totalLen = payloadLen + unShellDexLen +4;//多出4字節是存放長度的。
  25. byte[] newdex = new byte[totalLen]; // 申請了新的長度
  26. //添加解殼代碼
  27.             System.arraycopy(unShellDexArray, 0, newdex, 0, unShellDexLen);//先拷貝dex內容
  28. //添加加密後的解殼數據
  29.             System.arraycopy(payloadArray, 0, newdex, unShellDexLen, payloadLen);//再在dex內容後面拷貝apk的內容
  30. //添加解殼數據長度
  31.             System.arraycopy(intToByte(payloadLen), 0, newdex, totalLen-4, 4);//最後4爲長度
  32. //修改DEX file size文件頭
  33.             fixFileSizeHeader(newdex); 
  34. //修改DEX SHA1 文件頭
  35.             fixSHA1Header(newdex); 
  36. //修改DEX CheckSum文件頭
  37.             fixCheckSumHeader(newdex); 

  38.             String str = "force/classes.dex"; 
  39.             File file = new File(str); 
  40. if (!file.exists()) { 
  41.                 file.createNewFile(); 
  42.             } 

  43.             FileOutputStream localFileOutputStream = new FileOutputStream(str); 
  44.             localFileOutputStream.write(newdex); 
  45.             localFileOutputStream.flush(); 
  46.             localFileOutputStream.close(); 


  47.         } catch (Exception e) { 
  48.             e.printStackTrace(); 
  49.         } 
  50.     } 

  51. //直接返回數據,讀者可以添加自己加密方法
  52. private static byte[] encrpt(byte[] srcdata){ 
  53. for(int i = 0;i<srcdata.length;i++){ 
  54.             srcdata[i] = (byte)(0xFF ^ srcdata[i]); 
  55.         } 
  56. return srcdata; 
  57.     } 

  58. /**
  59.      * 修改dex頭,CheckSum 校驗碼
  60.      * @param dexBytes
  61.      */
  62. private static void fixCheckSumHeader(byte[] dexBytes) { 
  63.         Adler32 adler = new Adler32(); 
  64.         adler.update(dexBytes, 12, dexBytes.length - 12);//從12到文件末尾計算校驗碼
  65. long value = adler.getValue(); 
  66. int va = (int) value; 
  67. byte[] newcs = intToByte(va); 
  68. //高位在前,低位在前掉個個
  69. byte[] recs = new byte[4]; 
  70. for (int i = 0; i < 4; i++) { 
  71.             recs[i] = newcs[newcs.length - 1 - i]; 
  72.             System.out.println(Integer.toHexString(newcs[i])); 
  73.         } 
  74.         System.arraycopy(recs, 0, dexBytes, 8, 4);//效驗碼賦值(8-11)
  75.         System.out.println(Long.toHexString(value)); 
  76.         System.out.println(); 
  77.     } 


  78. /**
  79.      * int 轉byte[]
  80.      * @param number
  81.      * @return
  82.      */
  83. public static byte[] intToByte(int number) { 
  84. byte[] b = new byte[4]; 
  85. for (int i = 3; i >= 0; i--) { 
  86.             b[i] = (byte) (number % 256); 
  87.             number >>= 8; 
  88.         } 
  89. return b; 
  90.     } 

  91. /**
  92.      * 修改dex頭 sha1值
  93.      * @param dexBytes
  94.      * @throws NoSuchAlgorithmException
  95.      */
  96. private static void fixSHA1Header(byte[] dexBytes) 
  97. throws NoSuchAlgorithmException { 
  98.         MessageDigest md = MessageDigest.getInstance("SHA-1"); 
  99.         md.update(dexBytes, 32, dexBytes.length - 32);//從32爲到結束計算sha--1
  100. byte[] newdt = md.digest(); 
  101.         System.arraycopy(newdt, 0, dexBytes, 12, 20);//修改sha-1值(12-31)
  102. //輸出sha-1值,可有可無
  103.         String hexstr = ""; 
  104. for (int i = 0; i < newdt.length; i++) { 
  105.             hexstr += Integer.toString((newdt[i] & 0xff) + 0x100, 16) 
  106.                     .substring(1); 
  107.         } 
  108.         System.out.println(hexstr); 
  109.     } 

  110. /**
  111.      * 修改dex頭 file_size值
  112.      * @param dexBytes
  113.      */
  114. private static void fixFileSizeHeader(byte[] dexBytes) { 
  115. //新文件長度
  116. byte[] newfs = intToByte(dexBytes.length); 
  117.         System.out.println(Integer.toHexString(dexBytes.length)); 
  118. byte[] refs = new byte[4]; 
  119. //高位在前,低位在前掉個個
  120. for (int i = 0; i < 4; i++) { 
  121.             refs[i] = newfs[newfs.length - 1 - i]; 
  122.             System.out.println(Integer.toHexString(newfs[i])); 
  123.         } 
  124.         System.arraycopy(refs, 0, dexBytes, 32, 4);//修改(32-35)
  125.     } 


  126. /**
  127.      * 以二進制讀出文件內容
  128.      * @param file
  129.      * @return
  130.      * @throws IOException
  131.      */
  132. private static byte[] readFileBytes(File file) throws IOException { 
  133. byte[] arrayOfByte = new byte[1024]; 
  134.         ByteArrayOutputStream localByteArrayOutputStream = new ByteArrayOutputStream(); 
  135.         FileInputStream fis = new FileInputStream(file); 
  136. while (true) { 
  137. int i = fis.read(arrayOfByte); 
  138. if (i != -1) { 
  139.                 localByteArrayOutputStream.write(arrayOfByte, 0, i); 
  140.             } else { 
  141. return localByteArrayOutputStream.toByteArray(); 
  142.             } 
  143.         } 
  144.     } 

下面來分析一下:

紅色部分其實就是最核心的工作:

1>、加密源程序Apk文件

[java] view plaincopy

  1. byte[] payloadArray = encrpt(readFileBytes(payloadSrcFile));//以二進制形式讀出apk,並進行加密處理//對源Apk進行加密操作

加密算法很簡單:

[java] view plaincopy

  1. //直接返回數據,讀者可以添加自己加密方法
  2. private static byte[] encrpt(byte[] srcdata){ 
  3. for(int i = 0;i<srcdata.length;i++){ 
  4.         srcdata[i] = (byte)(0xFF ^ srcdata[i]); 
  5.     } 
  6. return srcdata; 

對每個字節進行異或一下即可。

(說明:這裏是爲了簡單,所以就用了很簡單的加密算法了,其實爲了增加破解難度,我們應該使用更高效的加密算法,同事最好將加密操作放到native層去做)

2>、合併文件:將加密之後的Apk和原脫殼Dex進行合併

[java] view plaincopy

  1. int payloadLen = payloadArray.length; 
  2. int unShellDexLen = unShellDexArray.length; 
  3. int totalLen = payloadLen + unShellDexLen +4;//多出4字節是存放長度的。
  4. byte[] newdex = new byte[totalLen]; // 申請了新的長度
  5. //添加解殼代碼
  6. System.arraycopy(unShellDexArray, 0, newdex, 0, unShellDexLen);//先拷貝dex內容
  7. //添加加密後的解殼數據
  8. System.arraycopy(payloadArray, 0, newdex, unShellDexLen, payloadLen);//再在dex內容後面拷貝apk的內容

3>、在文件的末尾追加源程序Apk的長度

[java] view plaincopy

  1. //添加解殼數據長度
  2. System.arraycopy(intToByte(payloadLen), 0, newdex, totalLen-4, 4);//最後4爲長度

4>、修改新Dex文件的文件頭信息:file_size; sha1; check_sum

[java] view plaincopy

  1. //修改DEX file size文件頭
  2. fixFileSizeHeader(newdex); 
  3. //修改DEX SHA1 文件頭
  4. fixSHA1Header(newdex); 
  5. //修改DEX CheckSum文件頭
  6. fixCheckSumHeader(newdex); 

具體修改可以參照之前說的文件頭格式,修改指定位置的字節值即可。

這裏我們還需要兩個輸入文件:

1>、源Apk文件:ForceApkObj.apk

2>、脫殼程序的Dex文件:ForceApkObj.dex

那麼第一個文件我們都知道,就是上面的源程序編譯之後的Apk文件,那麼第二個文件我們怎麼得到呢?這個就是我們要講到的第三個項目:脫殼程序項目,他是一個Android項目,我們在編譯之後,能夠得到他的classes.dex文件,然後修改一下名稱就可。

3、脫殼項目:ReforceApk

在講解這個項目之前,我們先來了解一下這個脫殼項目的工作:

1>、通過反射置換android.app.ActivityThread 中的mClassLoader爲加載解密出APK的DexClassLoader,該DexClassLoader一方面加載了源程序、另一方面以原mClassLoader爲父節點,這就保證了即加載了源程序又沒有放棄原先加載的資源與系統代碼。

關於這部分內容,不瞭解的同學可以看一下ActivityThread.java的源碼:

或者直接看一下這篇文章:

http://blog.csdn.net/jiangwei0910410003/article/details/48104455

如何得到系統加載Apk的類加載器,然後我們怎麼將加載進來的Apk運行起來等問題都在這篇文章中說到了。

2>、找到源程序的Application,通過反射建立並運行。

這裏需要注意的是,我們現在是加載一個完整的Apk,讓他運行起來,那麼我們知道一個Apk運行的時候都是有一個Application對象的,這個也是一個程序運行之後的全局類。所以我們必須找到解密之後的源Apk的Application類,運行的他的onCreate方法,這樣源Apk纔開始他的運行生命週期。這裏我們如何得到源Apk的Application的類呢?這個我們後面會說道。使用meta標籤進行設置。

下面來看一下整體的流程圖:

所以我們看到這裏還需要一個核心的技術就是動態加載。關於動態加載技術,不瞭解的同學可以看這篇文章:

http://blog.csdn.net/jiangwei0910410003/article/details/48104581

下面來看一下代碼:

[java] view plaincopy

  1. package com.example.reforceapk; 

  2. import java.io.BufferedInputStream; 
  3. import java.io.ByteArrayInputStream; 
  4. import java.io.ByteArrayOutputStream; 
  5. import java.io.DataInputStream; 
  6. import java.io.File; 
  7. import java.io.FileInputStream; 
  8. import java.io.FileOutputStream; 
  9. import java.io.IOException; 
  10. import java.lang.ref.WeakReference; 
  11. import java.lang.reflect.Method; 
  12. import java.util.ArrayList; 
  13. import java.util.HashMap; 
  14. import java.util.Iterator; 
  15. import java.util.zip.ZipEntry; 
  16. import java.util.zip.ZipInputStream; 

  17. import android.app.Application; 
  18. import android.app.Instrumentation; 
  19. import android.content.Context; 
  20. import android.content.pm.ApplicationInfo; 
  21. import android.content.pm.PackageManager; 
  22. import android.content.pm.PackageManager.NameNotFoundException; 
  23. import android.content.res.AssetManager; 
  24. import android.content.res.Resources; 
  25. import android.content.res.Resources.Theme; 
  26. import android.os.Bundle; 
  27. import android.util.ArrayMap; 
  28. import android.util.Log; 
  29. import dalvik.system.DexClassLoader; 

  30. public class ProxyApplication extends Application{ 
  31. private static final String appkey = "APPLICATION_CLASS_NAME"; 
  32. private String apkFileName; 
  33. private String odexPath; 
  34. private String libPath; 

  35. //這是context 賦值
  36. @Override
  37. protected void attachBaseContext(Context base) { 
  38. super.attachBaseContext(base); 
  39. try { 
  40. //創建兩個文件夾payload_odex,payload_lib 私有的,可寫的文件目錄
  41.             File odex = this.getDir("payload_odex", MODE_PRIVATE); 
  42.             File libs = this.getDir("payload_lib", MODE_PRIVATE); 
  43.             odexPath = odex.getAbsolutePath(); 
  44.             libPath = libs.getAbsolutePath(); 
  45.             apkFileName = odex.getAbsolutePath() + "/payload.apk"; 
  46.             File dexFile = new File(apkFileName); 
  47.             Log.i("demo", "apk size:"+dexFile.length()); 
  48. if (!dexFile.exists()) 
  49.             { 
  50.                 dexFile.createNewFile();  //在payload_odex文件夾內,創建payload.apk
  51. // 讀取程序classes.dex文件
  52. byte[] dexdata = this.readDexFileFromApk(); 

  53. // 分離出解殼後的apk文件已用於動態加載
  54. this.splitPayLoadFromDex(dexdata); 
  55.             } 
  56. // 配置動態加載環境
  57.             Object currentActivityThread = RefInvoke.invokeStaticMethod( 
  58. "android.app.ActivityThread", "currentActivityThread", 
  59. new Class[] {}, new Object[] {});//獲取主線程對象 http://blog.csdn.net/myarrow/article/details/14223493
  60.             String packageName = this.getPackageName();//當前apk的包名
  61. //下面兩句不是太理解
  62.             ArrayMap mPackages = (ArrayMap) RefInvoke.getFieldOjbect( 
  63. "android.app.ActivityThread", currentActivityThread, 
  64. "mPackages"); 
  65.             WeakReference wr = (WeakReference) mPackages.get(packageName); 
  66. //創建被加殼apk的DexClassLoader對象  加載apk內的類和本地代碼(c/c++代碼)
  67.             DexClassLoader dLoader = new DexClassLoader(apkFileName, odexPath, 
  68.                     libPath, (ClassLoader) RefInvoke.getFieldOjbect( 
  69. "android.app.LoadedApk", wr.get(), "mClassLoader")); 
  70. //base.getClassLoader(); 是不是就等同於 (ClassLoader) RefInvoke.getFieldOjbect()? 有空驗證下//?
  71. //把當前進程的DexClassLoader 設置成了被加殼apk的DexClassLoader  ----有點c++中進程環境的意思~~
  72.             RefInvoke.setFieldOjbect("android.app.LoadedApk", "mClassLoader", 
  73.                     wr.get(), dLoader); 

  74.             Log.i("demo","classloader:"+dLoader); 

  75. try{ 
  76.                 Object actObj = dLoader.loadClass("com.example.forceapkobj.MainActivity"); 
  77.                 Log.i("demo", "actObj:"+actObj); 
  78.             }catch(Exception e){ 
  79.                 Log.i("demo", "activity:"+Log.getStackTraceString(e)); 
  80.             } 


  81.         } catch (Exception e) { 
  82.             Log.i("demo", "error:"+Log.getStackTraceString(e)); 
  83.             e.printStackTrace(); 
  84.         } 
  85.     } 

  86. @Override
  87. public void onCreate() { 
  88.         { 
  89. //loadResources(apkFileName);

  90.             Log.i("demo", "onCreate"); 
  91. // 如果源應用配置有Appliction對象,則替換爲源應用Applicaiton,以便不影響源程序邏輯。
  92.             String appClassName = null; 
  93. try { 
  94.                 ApplicationInfo ai = this.getPackageManager() 
  95.                         .getApplicationInfo(this.getPackageName(), 
  96.                                 PackageManager.GET_META_DATA); 
  97.                 Bundle bundle = ai.metaData; 
  98. if (bundle != null && bundle.containsKey("APPLICATION_CLASS_NAME")) { 
  99.                     appClassName = bundle.getString("APPLICATION_CLASS_NAME");//className 是配置在xml文件中的。
  100.                 } else { 
  101.                     Log.i("demo", "have no application class name"); 
  102. return; 
  103.                 } 
  104.             } catch (NameNotFoundException e) { 
  105.                 Log.i("demo", "error:"+Log.getStackTraceString(e)); 
  106.                 e.printStackTrace(); 
  107.             } 
  108. //有值的話調用該Applicaiton
  109.             Object currentActivityThread = RefInvoke.invokeStaticMethod( 
  110. "android.app.ActivityThread", "currentActivityThread", 
  111. new Class[] {}, new Object[] {}); 
  112.             Object mBoundApplication = RefInvoke.getFieldOjbect( 
  113. "android.app.ActivityThread", currentActivityThread, 
  114. "mBoundApplication"); 
  115.             Object loadedApkInfo = RefInvoke.getFieldOjbect( 
  116. "android.app.ActivityThread$AppBindData", 
  117.                     mBoundApplication, "info"); 
  118. //把當前進程的mApplication 設置成了null
  119.             RefInvoke.setFieldOjbect("android.app.LoadedApk", "mApplication", 
  120.                     loadedApkInfo, null); 
  121.             Object oldApplication = RefInvoke.getFieldOjbect( 
  122. "android.app.ActivityThread", currentActivityThread, 
  123. "mInitialApplication"); 
  124. //http://www.codeceo.com/article/android-context.html
  125.             ArrayList<Application> mAllApplications = (ArrayList<Application>) RefInvoke 
  126.                     .getFieldOjbect("android.app.ActivityThread", 
  127.                             currentActivityThread, "mAllApplications"); 
  128.             mAllApplications.remove(oldApplication);//刪除oldApplication

  129.             ApplicationInfo appinfo_In_LoadedApk = (ApplicationInfo) RefInvoke 
  130.                     .getFieldOjbect("android.app.LoadedApk", loadedApkInfo, 
  131. "mApplicationInfo"); 
  132.             ApplicationInfo appinfo_In_AppBindData = (ApplicationInfo) RefInvoke 
  133.                     .getFieldOjbect("android.app.ActivityThread$AppBindData", 
  134.                             mBoundApplication, "appInfo"); 
  135.             appinfo_In_LoadedApk.className = appClassName; 
  136.             appinfo_In_AppBindData.className = appClassName; 
  137.             Application app = (Application) RefInvoke.invokeMethod( 
  138. "android.app.LoadedApk", "makeApplication", loadedApkInfo, 
  139. new Class[] { boolean.class, Instrumentation.class }, 
  140. new Object[] { false, null });//執行 makeApplication(false,null)
  141.             RefInvoke.setFieldOjbect("android.app.ActivityThread", 
  142. "mInitialApplication", currentActivityThread, app); 


  143.             ArrayMap mProviderMap = (ArrayMap) RefInvoke.getFieldOjbect( 
  144. "android.app.ActivityThread", currentActivityThread, 
  145. "mProviderMap"); 
  146.             Iterator it = mProviderMap.values().iterator(); 
  147. while (it.hasNext()) { 
  148.                 Object providerClientRecord = it.next(); 
  149.                 Object localProvider = RefInvoke.getFieldOjbect( 
  150. "android.app.ActivityThread$ProviderClientRecord", 
  151.                         providerClientRecord, "mLocalProvider"); 
  152.                 RefInvoke.setFieldOjbect("android.content.ContentProvider", 
  153. "mContext", localProvider, app); 
  154.             } 

  155.             Log.i("demo", "app:"+app); 

  156.             app.onCreate(); 
  157.         } 
  158.     } 

  159. /**
  160.      * 釋放被加殼的apk文件,so文件
  161.      * @param data
  162.      * @throws IOException
  163.      */
  164. private void splitPayLoadFromDex(byte[] apkdata) throws IOException { 
  165. int ablen = apkdata.length; 
  166. //取被加殼apk的長度   這裏的長度取值,對應加殼時長度的賦值都可以做些簡化
  167. byte[] dexlen = new byte[4]; 
  168.         System.arraycopy(apkdata, ablen - 4, dexlen, 0, 4); 
  169.         ByteArrayInputStream bais = new ByteArrayInputStream(dexlen); 
  170.         DataInputStream in = new DataInputStream(bais); 
  171. int readInt = in.readInt(); 
  172.         System.out.println(Integer.toHexString(readInt)); 
  173. byte[] newdex = new byte[readInt]; 
  174. //把被加殼apk內容拷貝到newdex中
  175.         System.arraycopy(apkdata, ablen - 4 - readInt, newdex, 0, readInt); 
  176. //這裏應該加上對於apk的解密操作,若加殼是加密處理的話
  177. //?

  178. //對源程序Apk進行解密
  179.         newdex = decrypt(newdex); 

  180. //寫入apk文件  
  181.         File file = new File(apkFileName); 
  182. try { 
  183.             FileOutputStream localFileOutputStream = new FileOutputStream(file); 
  184.             localFileOutputStream.write(newdex); 
  185.             localFileOutputStream.close(); 
  186.         } catch (IOException localIOException) { 
  187. throw new RuntimeException(localIOException); 
  188.         } 

  189. //分析被加殼的apk文件
  190.         ZipInputStream localZipInputStream = new ZipInputStream( 
  191. new BufferedInputStream(new FileInputStream(file))); 
  192. while (true) { 
  193.             ZipEntry localZipEntry = localZipInputStream.getNextEntry();//不瞭解這個是否也遍歷子目錄,看樣子應該是遍歷的
  194. if (localZipEntry == null) { 
  195.                 localZipInputStream.close(); 
  196. break; 
  197.             } 
  198. //取出被加殼apk用到的so文件,放到 libPath中(data/data/包名/payload_lib)
  199.             String name = localZipEntry.getName(); 
  200. if (name.startsWith("lib/") && name.endsWith(".so")) { 
  201.                 File storeFile = new File(libPath + "/"
  202.                         + name.substring(name.lastIndexOf('/'))); 
  203.                 storeFile.createNewFile(); 
  204.                 FileOutputStream fos = new FileOutputStream(storeFile); 
  205. byte[] arrayOfByte = new byte[1024]; 
  206. while (true) { 
  207. int i = localZipInputStream.read(arrayOfByte); 
  208. if (i == -1) 
  209. break; 
  210.                     fos.write(arrayOfByte, 0, i); 
  211.                 } 
  212.                 fos.flush(); 
  213.                 fos.close(); 
  214.             } 
  215.             localZipInputStream.closeEntry(); 
  216.         } 
  217.         localZipInputStream.close(); 


  218.     } 

  219. /**
  220.      * 從apk包裏面獲取dex文件內容(byte)
  221.      * @return
  222.      * @throws IOException
  223.      */
  224. private byte[] readDexFileFromApk() throws IOException { 
  225.         ByteArrayOutputStream dexByteArrayOutputStream = new ByteArrayOutputStream(); 
  226.         ZipInputStream localZipInputStream = new ZipInputStream( 
  227. new BufferedInputStream(new FileInputStream( 
  228. this.getApplicationInfo().sourceDir))); 
  229. while (true) { 
  230.             ZipEntry localZipEntry = localZipInputStream.getNextEntry(); 
  231. if (localZipEntry == null) { 
  232.                 localZipInputStream.close(); 
  233. break; 
  234.             } 
  235. if (localZipEntry.getName().equals("classes.dex")) { 
  236. byte[] arrayOfByte = new byte[1024]; 
  237. while (true) { 
  238. int i = localZipInputStream.read(arrayOfByte); 
  239. if (i == -1) 
  240. break; 
  241.                     dexByteArrayOutputStream.write(arrayOfByte, 0, i); 
  242.                 } 
  243.             } 
  244.             localZipInputStream.closeEntry(); 
  245.         } 
  246.         localZipInputStream.close(); 
  247. return dexByteArrayOutputStream.toByteArray(); 
  248.     } 


  249. // //直接返回數據,讀者可以添加自己解密方法
  250. private byte[] decrypt(byte[] srcdata) { 
  251. for(int i=0;i<srcdata.length;i++){ 
  252.             srcdata[i] = (byte)(0xFF ^ srcdata[i]); 
  253.         } 
  254. return srcdata; 
  255.     } 


  256. //以下是加載資源
  257. protected AssetManager mAssetManager;//資源管理器 
  258. protected Resources mResources;//資源 
  259. protected Theme mTheme;//主題 

  260. protected void loadResources(String dexPath) {   
  261. try {   
  262.             AssetManager assetManager = AssetManager.class.newInstance();   
  263.             Method addAssetPath = assetManager.getClass().getMethod("addAssetPath", String.class);   
  264.             addAssetPath.invoke(assetManager, dexPath);   
  265.             mAssetManager = assetManager;   
  266.         } catch (Exception e) {   
  267.             Log.i("inject", "loadResource error:"+Log.getStackTraceString(e)); 
  268.             e.printStackTrace();   
  269.         }   
  270.         Resources superRes = super.getResources();   
  271.         superRes.getDisplayMetrics();   
  272.         superRes.getConfiguration();   
  273.         mResources = new Resources(mAssetManager, superRes.getDisplayMetrics(),superRes.getConfiguration());   
  274.         mTheme = mResources.newTheme();   
  275.         mTheme.setTo(super.getTheme()); 
  276.     }   

  277. @Override
  278. public AssetManager getAssets() {   
  279. return mAssetManager == null ? super.getAssets() : mAssetManager;   
  280.     }   

  281. @Override
  282. public Resources getResources() {   
  283. return mResources == null ? super.getResources() : mResources;   
  284.     }   

  285. @Override
  286. public Theme getTheme() {   
  287. return mTheme == null ? super.getTheme() : mTheme;   
  288.     }  

首先我們來看一下具體步驟的代碼實現:

1>、得到脫殼Apk中的dex文件,然後從這個文件中得到源程序Apk.進行解密,然後加載

[java] view plaincopy

  1. //這是context 賦值
  2. @Override
  3. protected void attachBaseContext(Context base) { 
  4. super.attachBaseContext(base); 
  5. try { 
  6. //創建兩個文件夾payload_odex,payload_lib 私有的,可寫的文件目錄
  7.         File odex = this.getDir("payload_odex", MODE_PRIVATE); 
  8.         File libs = this.getDir("payload_lib", MODE_PRIVATE); 
  9.         odexPath = odex.getAbsolutePath(); 
  10.         libPath = libs.getAbsolutePath(); 
  11.         apkFileName = odex.getAbsolutePath() + "/payload.apk"; 
  12.         File dexFile = new File(apkFileName); 
  13.         Log.i("demo", "apk size:"+dexFile.length()); 
  14. if (!dexFile.exists()) 
  15.         { 
  16.             dexFile.createNewFile();  //在payload_odex文件夾內,創建payload.apk
  17. // 讀取程序classes.dex文件
  18. byte[] dexdata = this.readDexFileFromApk(); 

  19. // 分離出解殼後的apk文件已用於動態加載
  20. this.splitPayLoadFromDex(dexdata); 
  21.         } 
  22. // 配置動態加載環境
  23.         Object currentActivityThread = RefInvoke.invokeStaticMethod( 
  24. "android.app.ActivityThread", "currentActivityThread", 
  25. new Class[] {}, new Object[] {});//獲取主線程對象 http://blog.csdn.net/myarrow/article/details/14223493
  26.         String packageName = this.getPackageName();//當前apk的包名
  27. //下面兩句不是太理解
  28.         ArrayMap mPackages = (ArrayMap) RefInvoke.getFieldOjbect( 
  29. "android.app.ActivityThread", currentActivityThread, 
  30. "mPackages"); 
  31.         WeakReference wr = (WeakReference) mPackages.get(packageName); 
  32. //創建被加殼apk的DexClassLoader對象  加載apk內的類和本地代碼(c/c++代碼)
  33.         DexClassLoader dLoader = new DexClassLoader(apkFileName, odexPath, 
  34.                 libPath, (ClassLoader) RefInvoke.getFieldOjbect( 
  35. "android.app.LoadedApk", wr.get(), "mClassLoader")); 
  36. //base.getClassLoader(); 是不是就等同於 (ClassLoader) RefInvoke.getFieldOjbect()? 有空驗證下//?
  37. //把當前進程的DexClassLoader 設置成了被加殼apk的DexClassLoader  ----有點c++中進程環境的意思~~
  38.         RefInvoke.setFieldOjbect("android.app.LoadedApk", "mClassLoader", 
  39.                 wr.get(), dLoader); 

  40.         Log.i("demo","classloader:"+dLoader); 

  41. try{ 
  42.             Object actObj = dLoader.loadClass("com.example.forceapkobj.MainActivity"); 
  43.             Log.i("demo", "actObj:"+actObj); 
  44.         }catch(Exception e){ 
  45.             Log.i("demo", "activity:"+Log.getStackTraceString(e)); 
  46.         } 


  47.     } catch (Exception e) { 
  48.         Log.i("demo", "error:"+Log.getStackTraceString(e)); 
  49.         e.printStackTrace(); 
  50.     } 

這裏需要注意的一個問題,就是我們需要找到一個時機,就是在脫殼程序還沒有運行起來的時候,來加載源程序的Apk,執行他的onCreate方法,那麼這個時機不能太晚,不然的話,就是運行脫殼程序,而不是源程序了。查看源碼我們知道。Application中有一個方法:attachBaseContext這個方法,他在Application的onCreate方法執行前就會執行了,那麼我們的工作就需要在這裏進行

1)、從脫殼程序Apk中找到源程序Apk,並且進行解密操作

[java] view plaincopy

  1. //創建兩個文件夾payload_odex,payload_lib 私有的,可寫的文件目錄
  2. File odex = this.getDir("payload_odex", MODE_PRIVATE); 
  3. File libs = this.getDir("payload_lib", MODE_PRIVATE); 
  4. odexPath = odex.getAbsolutePath(); 
  5. libPath = libs.getAbsolutePath(); 
  6. apkFileName = odex.getAbsolutePath() + "/payload.apk"; 
  7. File dexFile = new File(apkFileName); 
  8. Log.i("demo", "apk size:"+dexFile.length()); 
  9. if (!dexFile.exists()) 
  10.     dexFile.createNewFile();  //在payload_odex文件夾內,創建payload.apk
  11. // 讀取程序classes.dex文件
  12. byte[] dexdata = this.readDexFileFromApk(); 

  13. // 分離出解殼後的apk文件已用於動態加載
  14. this.splitPayLoadFromDex(dexdata); 

這個脫殼解密操作一定要和我們之前的加殼以及加密操作對應,不然就會出現Dex加載錯誤問題

A) 從Apk中獲取到Dex文件

[java] view plaincopy

  1. /**
  2. * 從apk包裏面獲取dex文件內容(byte)
  3. * @return
  4. * @throws IOException
  5. */
  6. private byte[] readDexFileFromApk() throws IOException { 
  7.     ByteArrayOutputStream dexByteArrayOutputStream = new ByteArrayOutputStream(); 
  8.     ZipInputStream localZipInputStream = new ZipInputStream( 
  9. new BufferedInputStream(new FileInputStream( 
  10. this.getApplicationInfo().sourceDir))); 
  11. while (true) { 
  12.         ZipEntry localZipEntry = localZipInputStream.getNextEntry(); 
  13. if (localZipEntry == null) { 
  14.             localZipInputStream.close(); 
  15. break; 
  16.         } 
  17. if (localZipEntry.getName().equals("classes.dex")) { 
  18. byte[] arrayOfByte = new byte[1024]; 
  19. while (true) { 
  20. int i = localZipInputStream.read(arrayOfByte); 
  21. if (i == -1) 
  22. break; 
  23.                 dexByteArrayOutputStream.write(arrayOfByte, 0, i); 
  24.             } 
  25.         } 
  26.         localZipInputStream.closeEntry(); 
  27.     } 
  28.     localZipInputStream.close(); 
  29. return dexByteArrayOutputStream.toByteArray(); 

其實就是解壓Apk文件,直接得到dex文件即可

B) 從脫殼Dex中得到源Apk文件

[java] view plaincopy

  1. /**
  2. * 釋放被加殼的apk文件,so文件
  3. * @param data
  4. * @throws IOException
  5. */
  6. private void splitPayLoadFromDex(byte[] apkdata) throws IOException { 
  7. int ablen = apkdata.length; 
  8. //取被加殼apk的長度   這裏的長度取值,對應加殼時長度的賦值都可以做些簡化
  9. byte[] dexlen = new byte[4]; 
  10.     System.arraycopy(apkdata, ablen - 4, dexlen, 0, 4); 
  11.     ByteArrayInputStream bais = new ByteArrayInputStream(dexlen); 
  12.     DataInputStream in = new DataInputStream(bais); 
  13. int readInt = in.readInt(); 
  14.     System.out.println(Integer.toHexString(readInt)); 
  15. byte[] newdex = new byte[readInt]; 
  16. //把被加殼apk內容拷貝到newdex中
  17.     System.arraycopy(apkdata, ablen - 4 - readInt, newdex, 0, readInt); 
  18. //這裏應該加上對於apk的解密操作,若加殼是加密處理的話
  19. //?

  20. //對源程序Apk進行解密
  21.     newdex = decrypt(newdex); 

  22. //寫入apk文件  
  23.     File file = new File(apkFileName); 
  24. try { 
  25.         FileOutputStream localFileOutputStream = new FileOutputStream(file); 
  26.         localFileOutputStream.write(newdex); 
  27.         localFileOutputStream.close(); 
  28.     } catch (IOException localIOException) { 
  29. throw new RuntimeException(localIOException); 
  30.     } 

  31. //分析被加殼的apk文件
  32.     ZipInputStream localZipInputStream = new ZipInputStream( 
  33. new BufferedInputStream(new FileInputStream(file))); 
  34. while (true) { 
  35.         ZipEntry localZipEntry = localZipInputStream.getNextEntry();//不瞭解這個是否也遍歷子目錄,看樣子應該是遍歷的
  36. if (localZipEntry == null) { 
  37.             localZipInputStream.close(); 
  38. break; 
  39.         } 
  40. //取出被加殼apk用到的so文件,放到 libPath中(data/data/包名/payload_lib)
  41.         String name = localZipEntry.getName(); 
  42. if (name.startsWith("lib/") && name.endsWith(".so")) { 
  43.             File storeFile = new File(libPath + "/"
  44.                     + name.substring(name.lastIndexOf('/'))); 
  45.             storeFile.createNewFile(); 
  46.             FileOutputStream fos = new FileOutputStream(storeFile); 
  47. byte[] arrayOfByte = new byte[1024]; 
  48. while (true) { 
  49. int i = localZipInputStream.read(arrayOfByte); 
  50. if (i == -1) 
  51. break; 
  52.                 fos.write(arrayOfByte, 0, i); 
  53.             } 
  54.             fos.flush(); 
  55.             fos.close(); 
  56.         } 
  57.         localZipInputStream.closeEntry(); 
  58.     } 
  59.     localZipInputStream.close(); 


C) 解密源程序Apk

[java] view plaincopy

  1. ////直接返回數據,讀者可以添加自己解密方法
  2. private byte[] decrypt(byte[] srcdata) { 
  3. for(int i=0;i<srcdata.length;i++){ 
  4.         srcdata[i] = (byte)(0xFF ^ srcdata[i]); 
  5.     } 
  6. return srcdata; 

這個解密算法和加密算法是一致的

2>、加載解密之後的源程序Apk

[java] view plaincopy

  1. //配置動態加載環境
  2. Object currentActivityThread = RefInvoke.invokeStaticMethod( 
  3. "android.app.ActivityThread", "currentActivityThread", 
  4. new Class[] {}, new Object[] {});//獲取主線程對象 http://blog.csdn.net/myarrow/article/details/14223493
  5. String packageName = this.getPackageName();//當前apk的包名
  6. //下面兩句不是太理解
  7. ArrayMap mPackages = (ArrayMap) RefInvoke.getFieldOjbect( 
  8. "android.app.ActivityThread", currentActivityThread, 
  9. "mPackages"); 
  10. WeakReference wr = (WeakReference) mPackages.get(packageName); 
  11. //創建被加殼apk的DexClassLoader對象  加載apk內的類和本地代碼(c/c++代碼)
  12. DexClassLoader dLoader = new DexClassLoader(apkFileName, odexPath, 
  13.         libPath, (ClassLoader) RefInvoke.getFieldOjbect( 
  14. "android.app.LoadedApk", wr.get(), "mClassLoader")); 
  15. //base.getClassLoader(); 是不是就等同於 (ClassLoader) RefInvoke.getFieldOjbect()? 有空驗證下//?
  16. //把當前進程的DexClassLoader 設置成了被加殼apk的DexClassLoader  ----有點c++中進程環境的意思~~
  17. RefInvoke.setFieldOjbect("android.app.LoadedApk", "mClassLoader", 
  18.         wr.get(), dLoader); 

  19. Log.i("demo","classloader:"+dLoader); 

  20. try{ 
  21.     Object actObj = dLoader.loadClass("com.example.forceapkobj.MainActivity"); 
  22.     Log.i("demo", "actObj:"+actObj); 
  23. }catch(Exception e){ 
  24.     Log.i("demo", "activity:"+Log.getStackTraceString(e)); 

2)、找到源程序的Application程序,讓其運行

[java] view plaincopy

  1. @Override
  2. public void onCreate() { 
  3.     { 
  4. //loadResources(apkFileName);

  5.         Log.i("demo", "onCreate"); 
  6. // 如果源應用配置有Appliction對象,則替換爲源應用Applicaiton,以便不影響源程序邏輯。
  7.         String appClassName = null; 
  8. try { 
  9.             ApplicationInfo ai = this.getPackageManager() 
  10.                     .getApplicationInfo(this.getPackageName(), 
  11.                             PackageManager.GET_META_DATA); 
  12.             Bundle bundle = ai.metaData; 
  13. if (bundle != null && bundle.containsKey("APPLICATION_CLASS_NAME")) { 
  14.                 appClassName = bundle.getString("APPLICATION_CLASS_NAME");//className 是配置在xml文件中的。
  15.             } else { 
  16.                 Log.i("demo", "have no application class name"); 
  17. return; 
  18.             } 
  19.         } catch (NameNotFoundException e) { 
  20.             Log.i("demo", "error:"+Log.getStackTraceString(e)); 
  21.             e.printStackTrace(); 
  22.         } 
  23. //有值的話調用該Applicaiton
  24.         Object currentActivityThread = RefInvoke.invokeStaticMethod( 
  25. "android.app.ActivityThread", "currentActivityThread", 
  26. new Class[] {}, new Object[] {}); 
  27.         Object mBoundApplication = RefInvoke.getFieldOjbect( 
  28. "android.app.ActivityThread", currentActivityThread, 
  29. "mBoundApplication"); 
  30.         Object loadedApkInfo = RefInvoke.getFieldOjbect( 
  31. "android.app.ActivityThread$AppBindData", 
  32.                 mBoundApplication, "info"); 
  33. //把當前進程的mApplication 設置成了null
  34.         RefInvoke.setFieldOjbect("android.app.LoadedApk", "mApplication", 
  35.                 loadedApkInfo, null); 
  36.         Object oldApplication = RefInvoke.getFieldOjbect( 
  37. "android.app.ActivityThread", currentActivityThread, 
  38. "mInitialApplication"); 
  39. //http://www.codeceo.com/article/android-context.html
  40.         ArrayList<Application> mAllApplications = (ArrayList<Application>) RefInvoke 
  41.                 .getFieldOjbect("android.app.ActivityThread", 
  42.                         currentActivityThread, "mAllApplications"); 
  43.         mAllApplications.remove(oldApplication);//刪除oldApplication

  44.         ApplicationInfo appinfo_In_LoadedApk = (ApplicationInfo) RefInvoke 
  45.                 .getFieldOjbect("android.app.LoadedApk", loadedApkInfo, 
  46. "mApplicationInfo"); 
  47.         ApplicationInfo appinfo_In_AppBindData = (ApplicationInfo) RefInvoke 
  48.                 .getFieldOjbect("android.app.ActivityThread$AppBindData", 
  49.                         mBoundApplication, "appInfo"); 
  50.         appinfo_In_LoadedApk.className = appClassName; 
  51.         appinfo_In_AppBindData.className = appClassName; 
  52.         Application app = (Application) RefInvoke.invokeMethod( 
  53. "android.app.LoadedApk", "makeApplication", loadedApkInfo, 
  54. new Class[] { boolean.class, Instrumentation.class }, 
  55. new Object[] { false, null });//執行 makeApplication(false,null)
  56.         RefInvoke.setFieldOjbect("android.app.ActivityThread", 
  57. "mInitialApplication", currentActivityThread, app); 


  58.         ArrayMap mProviderMap = (ArrayMap) RefInvoke.getFieldOjbect( 
  59. "android.app.ActivityThread", currentActivityThread, 
  60. "mProviderMap"); 
  61.         Iterator it = mProviderMap.values().iterator(); 
  62. while (it.hasNext()) { 
  63.             Object providerClientRecord = it.next(); 
  64.             Object localProvider = RefInvoke.getFieldOjbect( 
  65. "android.app.ActivityThread$ProviderClientRecord", 
  66.                     providerClientRecord, "mLocalProvider"); 
  67.             RefInvoke.setFieldOjbect("android.content.ContentProvider", 
  68. "mContext", localProvider, app); 
  69.         } 

  70.         Log.i("demo", "app:"+app); 

  71.         app.onCreate(); 
  72.     } 

直接在脫殼的Application中的onCreate方法中進行就可以了。這裏我們還可以看到是通過AndroidManifest.xml中的meta標籤獲取源程序Apk中的Application對象的。

下面來看一下AndoridManifest.xml文件中的內容:

在這裏我們定義了源程序Apk的Application類名。

項目下載:http://download.csdn.net/detail/jiangwei0910410003/9102741

四、運行程序

那麼到這裏我們就介紹完了,這三個項目的內容,下面就來看看如何運行吧:

運行步驟:

第一步:得到源程序Apk文件和脫殼程序的Dex文件

運行源程序和脫殼程序項目,之後得到這兩個文件(記得將classes.dex文件改名ForceApkObj.dex),然後使用加殼程序進行加殼:

這裏的ForceApkObj.apk文件和ForceApkObj.dex文件是輸入文件,輸出的是classes.dex文件。

第二步:替換脫殼程序中的classes.dex文件

我們在第一步中得到加殼之後的classes.dex文件之後,並且我們在第一步運行脫殼項目的時候得到一個ReforceApk.apk文件,這時候我們使用解壓縮軟件進行替換:

第三步:我們在第二步的時候得到替換之後的ReforceApk.apk文件,這個文件因爲被修改了,所以我們需要從新對他簽名,不然運行也是報錯的。

工具下載:http://download.csdn.net/detail/jiangwei0910410003/9102767

下載之後的工具需要用ReforeceApk.apk文件替換ReforceApk_des.apk文件,然後運行run.bat就可以得到簽名之後的文件了。

run.bat文件的命令如下:

cd C:\Users\i\Desktop\forceapks
jarsigner -verbose -keystore forceapk -storepass 123456 -keypass 123456 -sigfile CERT -digestalg SHA1 -sigalg MD5withRSA -signedjar ReforceApk_des.apk ReforceApk.apk jiangwei
del ReforceApk.apk

這裏最主要的命令就是中間的一條簽名的命令,關於命令的參數說明如下:

jarsigner -verbose -keystore 簽名文件 -storepass 密碼  -keypass alias的密碼 -sigfile CERT -digestalg SHA1 -sigalg MD5withRSA  簽名後的文件 簽名前的apk alias名稱
eg:
jarsigner -verbose -keystore forceapk -storepass 123456 -keypass 123456 -sigfile CERT -digestalg SHA1 -sigalg MD5withRSA -signedjar ReforceApk_des.apk ReforceApk_src.apk jiangwei
簽名文件的密碼:123456
alais的密碼:123456

所以這裏我們在得到ReforceApk.apk文件的時候,需要簽名,關於Eclipse中如何簽名一個Apk的話,這裏就不多說了,自己google一下吧:

那麼通過上面的三個步驟之後我們得到一個簽名之後的最終文件:ReforceApk_des.apk

我們安裝這個Apk,然後運行,效果如下:

看到運行結果的那一瞬間,我們是多麼的開心,多麼的有成就感,但是這個過程中遇到的問題,是可想而知的。

我們這個時候再去反編譯一下源程序Apk(這個文件是我們脫殼出來的payload.apk,看ReforeceApk中的代碼,就知道他的位置了)

發現dex文件格式是不正確的。說明我們的加固是成功的。

五、遇到的問題

1、研究的過程中遇到簽名不正確的地方,開始的時候,直接替換dex文件之後,就直接運行了Apk,但是總是提示簽名不正確。

2、運行的過程中說找不到源程序中的Activity,這個問題其實我在動態加載的那篇文章中說道了,我們需要在脫殼程序中的AndroidManifest.xml中什麼一下源程序中的Activiity:

六、技術要點

1、對Dex文件格式的瞭解

2、動態加載技術的深入掌握

3、Application的執行流程的瞭解

4、如何從Apk中得到Dex文件

5、如何從新簽名一個Apk程序

七、綜合概述

我們通過上面的過程可以看到,關於Apk加固的工作還是挺複雜的,涉及到的東西也挺多的,下面就在來總結一下吧:

1、加殼程序

任務:對源程序Apk進行加密,合併脫殼程序的Dex文件 ,然後輸入一個加殼之後的Dex文件

語言:任何語言都可以,不限於Java語言

技術點:對Dex文件格式的解析

2、脫殼程序

任務:獲取源程序Apk,進行解密,然後動態加載進來,運行程序

語言:Android項目(Java)

技術點:如何從Apk中獲取Dex文件,動態加載Apk,使用反射運行Application

八、總結

Android中的Apk反編譯可能是每個開發都會經歷的事,但是在反編譯的過程中,對於源程序的開發者來說那是不公平的,那麼Apk加固也是應運而生,但是即使是這樣,我們也還是做不到那麼的安全,現在網上也是有很多文章在解析梆梆加固的原理了。而且有人破解成功了,那麼加固還不是怎麼安全。最後一句話:逆向和加固是一個永不停息的戰爭。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章