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

1、前言

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

 

2、原理解析

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

咱們在加固的過程當中須要三個對象:android

一、須要加密的Apk(源Apk)c++

二、殼程序Apk(負責解密Apk工做)算法

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

 

主要步驟:數據結構

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

 

在這個過程當中咱們可能須要瞭解的一個知識是:如何將源Apk和殼Apk進行合併成新的Dexide

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

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

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

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

二、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文件樣式以下:

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

一、源程序項目(須要加密的Apk)

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

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

 

3、項目案例

下面先來看一下源程序

一、須要加密的源程序Apk項目:ForceApkObj

須要一個Application類,這個到後面說爲何須要:

MyApplication.Java

 

[java]  view plain  copy
 
  1. package com.example.forceapkobj;  
  2.   
  3. import android.app.Application;  
  4. import android.util.Log;  
  5.   
  6. public class MyApplication extends Application{  
  7.       
  8.     @Override  
  9.     public void onCreate() {  
  10.         super.onCreate();  
  11.         Log.i("demo", "source apk onCreate:"+this);  
  12.     }  
  13.   
  14. }  

 

就是打印一下onCreate方法。

 

MainActivity.java

 

[java]  view plain  copy
 
  1. package com.example.forceapkobj;  
  2.   
  3. import android.app.Activity;  
  4. import android.content.Intent;  
  5. import android.os.Bundle;  
  6. import android.util.Log;  
  7. import android.view.View;  
  8. import android.view.View.OnClickListener;  
  9. import android.widget.TextView;  
  10.   
  11. public class MainActivity extends Activity {  
  12.   
  13.     @Override  
  14.     protected void onCreate(Bundle savedInstanceState) {  
  15.         super.onCreate(savedInstanceState);  
  16.           
  17.         TextView content = new TextView(this);  
  18.         content.setText("I am Source Apk");  
  19.         content.setOnClickListener(new OnClickListener(){  
  20.             @Override  
  21.             public void onClick(View arg0) {  
  22.                 Intent intent = new Intent(MainActivity.this, SubActivity.class);  
  23.                 startActivity(intent);  
  24.             }});  
  25.         setContentView(content);  
  26.           
  27.         Log.i("demo", "app:"+getApplicationContext());  
  28.           
  29.     }  
  30.   
  31. }  

也是打印一下內容。

 

 

二、加殼程序項目:DexShellTools

加殼程序其實就是一個Java工程,由於咱們從上面的分析能夠看到,他的工做就是加密源Apk,而後將其寫入到脫殼Dex文件中,修改文件頭,獲得一個新的Dex文件便可。

看一下代碼:

 

[java]  view plain  copy
 
  1. package com.example.reforceapk;  
  2.   
  3. import java.io.ByteArrayOutputStream;  
  4. import java.io.File;  
  5. import java.io.FileInputStream;  
  6. import java.io.FileOutputStream;  
  7. import java.io.IOException;  
  8. import java.security.MessageDigest;  
  9. import java.security.NoSuchAlgorithmException;  
  10. import java.util.zip.Adler32;  
  11.   
  12.   
  13. public class mymain {  
  14.     /** 
  15.      * @param args 
  16.      */  
  17.     public static void main(String[] args) {  
  18.         // TODO Auto-generated method stub  
  19.         try {  
  20.             File payloadSrcFile = new File("force/ForceApkObj.apk");   //須要加殼的程序  
  21.             System.out.println("apk size:"+payloadSrcFile.length());  
  22.             File unShellDexFile = new File("force/ForceApkObj.dex");    //解客dex  
  23.             byte[] payloadArray = encrpt(readFileBytes(payloadSrcFile));//以二進制形式讀出apk,並進行加密處理//對源Apk進行加密操做  
  24.             byte[] unShellDexArray = readFileBytes(unShellDexFile);//以二進制形式讀出dex  
  25.             int payloadLen = payloadArray.length;  
  26.             int unShellDexLen = unShellDexArray.length;  
  27.             int totalLen = payloadLen + unShellDexLen +4;//多出4字節是存放長度的。  
  28.             byte[] newdex = new byte[totalLen]; // 申請了新的長度  
  29.             //添加解殼代碼  
  30.             System.arraycopy(unShellDexArray, 0, newdex, 0, unShellDexLen);//先拷貝dex內容  
  31.             //添加加密後的解殼數據  
  32.             System.arraycopy(payloadArray, 0, newdex, unShellDexLen, payloadLen);//再在dex內容後面拷貝apk的內容  
  33.             //添加解殼數據長度  
  34.             System.arraycopy(intToByte(payloadLen), 0, newdex, totalLen-4, 4);//最後4爲長度  
  35.             //修改DEX file size文件頭  
  36.             fixFileSizeHeader(newdex);  
  37.             //修改DEX SHA1 文件頭  
  38.             fixSHA1Header(newdex);  
  39.             //修改DEX CheckSum文件頭  
  40.             fixCheckSumHeader(newdex);  
  41.   
  42.             String str = "force/classes.dex";  
  43.             File file = new File(str);  
  44.             if (!file.exists()) {  
  45.                 file.createNewFile();  
  46.             }  
  47.               
  48.             FileOutputStream localFileOutputStream = new FileOutputStream(str);  
  49.             localFileOutputStream.write(newdex);  
  50.             localFileOutputStream.flush();  
  51.             localFileOutputStream.close();  
  52.   
  53.   
  54.         } catch (Exception e) {  
  55.             e.printStackTrace();  
  56.         }  
  57.     }  
  58.       
  59.     //直接返回數據,讀者能夠添加本身加密方法  
  60.     private static byte[] encrpt(byte[] srcdata){  
  61.         for(int i = 0;i<srcdata.length;i++){  
  62.             srcdata[i] = (byte)(0xFF ^ srcdata[i]);  
  63.         }  
  64.         return srcdata;  
  65.     }  
  66.   
  67.     /** 
  68.      * 修改dex頭,CheckSum 校驗碼 
  69.      * @param dexBytes 
  70.      */  
  71.     private static void fixCheckSumHeader(byte[] dexBytes) {  
  72.         Adler32 adler = new Adler32();  
  73.         adler.update(dexBytes, 12, dexBytes.length - 12);//從12到文件末尾計算校驗碼  
  74.         long value = adler.getValue();  
  75.         int va = (int) value;  
  76.         byte[] newcs = intToByte(va);  
  77.         //高位在前,低位在前掉個個  
  78.         byte[] recs = new byte[4];  
  79.         for (int i = 0; i < 4; i++) {  
  80.             recs[i] = newcs[newcs.length - 1 - i];  
  81.             System.out.println(Integer.toHexString(newcs[i]));  
  82.         }  
  83.         System.arraycopy(recs, 0, dexBytes, 8, 4);//效驗碼賦值(8-11)  
  84.         System.out.println(Long.toHexString(value));  
  85.         System.out.println();  
  86.     }  
  87.   
  88.   
  89.     /** 
  90.      * int 轉byte[] 
  91.      * @param number 
  92.      * @return 
  93.      */  
  94.     public static byte[] intToByte(int number) {  
  95.         byte[] b = new byte[4];  
  96.         for (int i = 3; i >= 0; i--) {  
  97.             b[i] = (byte) (number % 256);  
  98.             number >>= 8;  
  99.         }  
  100.         return b;  
  101.     }  
  102.   
  103.     /** 
  104.      * 修改dex頭 sha1值 
  105.      * @param dexBytes 
  106.      * @throws NoSuchAlgorithmException 
  107.      */  
  108.     private static void fixSHA1Header(byte[] dexBytes)  
  109.             throws NoSuchAlgorithmException {  
  110.         MessageDigest md = MessageDigest.getInstance("SHA-1");  
  111.         md.update(dexBytes, 32, dexBytes.length - 32);//從32爲到結束計算sha--1  
  112.         byte[] newdt = md.digest();  
  113.         System.arraycopy(newdt, 0, dexBytes, 12, 20);//修改sha-1值(12-31)  
  114.         //輸出sha-1值,無關緊要  
  115.         String hexstr = "";  
  116.         for (int i = 0; i < newdt.length; i++) {  
  117.             hexstr += Integer.toString((newdt[i] & 0xff) + 0x100, 16)  
  118.                     .substring(1);  
  119.         }  
  120.         System.out.println(hexstr);  
  121.     }  
  122.   
  123.     /** 
  124.      * 修改dex頭 file_size值 
  125.      * @param dexBytes 
  126.      */  
  127.     private static void fixFileSizeHeader(byte[] dexBytes) {  
  128.         //新文件長度  
  129.         byte[] newfs = intToByte(dexBytes.length);  
  130.         System.out.println(Integer.toHexString(dexBytes.length));  
  131.         byte[] refs = new byte[4];  
  132.         //高位在前,低位在前掉個個  
  133.         for (int i = 0; i < 4; i++) {  
  134.             refs[i] = newfs[newfs.length - 1 - i];  
  135.             System.out.println(Integer.toHexString(newfs[i]));  
  136.         }  
  137.         System.arraycopy(refs, 0, dexBytes, 32, 4);//修改(32-35)  
  138.     }  
  139.   
  140.   
  141.     /** 
  142.      * 以二進制讀出文件內容 
  143.      * @param file 
  144.      * @return 
  145.      * @throws IOException 
  146.      */  
  147.     private static byte[] readFileBytes(File file) throws IOException {  
  148.         byte[] arrayOfByte = new byte[1024];  
  149.         ByteArrayOutputStream localByteArrayOutputStream = new ByteArrayOutputStream();  
  150.         FileInputStream fis = new FileInputStream(file);  
  151.         while (true) {  
  152.             int i = fis.read(arrayOfByte);  
  153.             if (i != -1) {  
  154.                 localByteArrayOutputStream.write(arrayOfByte, 0, i);  
  155.             } else {  
  156.                 return localByteArrayOutputStream.toByteArray();  
  157.             }  
  158.         }  
  159.     }  
  160. }  

 

下面來分析一下:

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

1>、加密源程序Apk文件

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

加密算法很簡單:

 

[java]  view plain  copy
 
  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;  
  7. }  

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

 

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

 

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

[java]  view plain  copy
 
  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 plain  copy
 
  1. //添加解殼數據長度  
  2. System.arraycopy(intToByte(payloadLen), 0, newdex, totalLen-4, 4);//最後4爲長度  


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

[java]  view plain  copy
 
  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文件,而後修改一下名稱就可。

 

三、脫殼項目: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 plain  copy
 
  1. package com.example.reforceapk;  
  2.   
  3. import java.io.BufferedInputStream;  
  4. import java.io.ByteArrayInputStream;  
  5. import java.io.ByteArrayOutputStream;  
  6. import java.io.DataInputStream;  
  7. import java.io.File;  
  8. import java.io.FileInputStream;  
  9. import java.io.FileOutputStream;  
  10. import java.io.IOException;  
  11. import java.lang.ref.WeakReference;  
  12. import java.lang.reflect.Method;  
  13. import java.util.ArrayList;  
  14. import java.util.HashMap;  
  15. import java.util.Iterator;  
  16. import java.util.zip.ZipEntry;  
  17. import java.util.zip.ZipInputStream;  
  18.   
  19. import android.app.Application;  
  20. import android.app.Instrumentation;  
  21. import android.content.Context;  
  22. import android.content.pm.ApplicationInfo;  
  23. import android.content.pm.PackageManager;  
  24. import android.content.pm.PackageManager.NameNotFoundException;  
  25. import android.content.res.AssetManager;  
  26. import android.content.res.Resources;  
  27. import android.content.res.Resources.Theme;  
  28. import android.os.Bundle;  
  29. import android.util.ArrayMap;  
  30. import android.util.Log;  
  31. import dalvik.system.DexClassLoader;  
  32.   
  33. public class ProxyApplication extends Application{  
  34.     private static final String appkey = "APPLICATION_CLASS_NAME";  
  35.     private String apkFileName;  
  36.     private String odexPath;  
  37.     private String libPath;  
  38.   
  39.     //這是context 賦值  
  40.     @Override  
  41.     protected void attachBaseContext(Context base) {  
  42.         super.attachBaseContext(base);  
  43.         try {  
  44.             //建立兩個文件夾payload_odex,payload_lib 私有的,可寫的文件目錄  
  45.             File odex = this.getDir("payload_odex", MODE_PRIVATE);  
  46.             File libs = this.getDir("payload_lib", MODE_PRIVATE);  
  47.             odexPath = odex.getAbsolutePath();  
  48.             libPath = libs.getAbsolutePath();  
  49.             apkFileName = odex.getAbsolutePath() + "/payload.apk";  
  50.             File dexFile = new File(apkFileName);  
  51.             Log.i("demo", "apk size:"+dexFile.length());  
  52.             if (!dexFile.exists())  
  53.             {  
  54.                 dexFile.createNewFile();  //在payload_odex文件夾內,建立payload.apk  
  55.                 // 讀取程序classes.dex文件  
  56.                 byte[] dexdata = this.readDexFileFromApk();  
  57.                   
  58.                 // 分離出解殼後的apk文件已用於動態加載  
  59.                 this.splitPayLoadFromDex(dexdata);  
  60.             }  
  61.             // 配置動態加載環境  
  62.             Object currentActivityThread = RefInvoke.invokeStaticMethod(  
  63.                     "android.app.ActivityThread", "currentActivityThread",  
  64.                     new Class[] {}, new Object[] {});//獲取主線程對象 http://blog.csdn.net/myarrow/article/details/14223493  
  65.             String packageName = this.getPackageName();//當前apk的包名  
  66.             //下面兩句不是太理解  
  67.             ArrayMap mPackages = (ArrayMap) RefInvoke.getFieldOjbect(  
  68.                     "android.app.ActivityThread", currentActivityThread,  
  69.                     "mPackages");  
  70.             WeakReference wr = (WeakReference) mPackages.get(packageName);  
  71.             //建立被加殼apk的DexClassLoader對象  加載apk內的類和本地代碼(c/c++代碼)  
  72.             DexClassLoader dLoader = new DexClassLoader(apkFileName, odexPath,  
  73.                     libPath, (ClassLoader) RefInvoke.getFieldOjbect(  
  74.                             "android.app.LoadedApk", wr.get(), "mClassLoader"));  
  75.             //base.getClassLoader(); 是否是就等同於 (ClassLoader) RefInvoke.getFieldOjbect()? 有空驗證下//?  
  76.             //把當前進程的DexClassLoader 設置成了被加殼apk的DexClassLoader  ----有點c++中進程環境的意思~~  
  77.             RefInvoke.setFieldOjbect("android.app.LoadedApk", "mClassLoader",  
  78.                     wr.get(), dLoader);  
  79.               
  80.             Log.i("demo","classloader:"+dLoader);  
  81.               
  82.             try{  
  83.                 Object actObj = dLoader.loadClass("com.example.forceapkobj.MainActivity");  
  84.                 Log.i("demo", "actObj:"+actObj);  
  85.             }catch(Exception e){  
  86.                 Log.i("demo", "activity:"+Log.getStackTraceString(e));  
  87.             }  
  88.               
  89.   
  90.         } catch (Exception e) {  
  91.             Log.i("demo", "error:"+Log.getStackTraceString(e));  
  92.             e.printStackTrace();  
  93.         }  
  94.     }  
  95.   
  96.     @Override  
  97.     public void onCreate() {  
  98.         {  
  99.             //loadResources(apkFileName);  
  100.               
  101.             Log.i("demo", "onCreate");  
  102.             // 若是源應用配置有Appliction對象,則替換爲源應用Applicaiton,以便不影響源程序邏輯。  
  103.             String appClassName = null;  
  104.             try {  
  105.                 ApplicationInfo ai = this.getPackageManager()  
  106.                         .getApplicationInfo(this.getPackageName(),  
  107.                                 PackageManager.GET_META_DATA);  
  108.                 Bundle bundle = ai.metaData;  
  109.                 if (bundle != null && bundle.containsKey("APPLICATION_CLASS_NAME")) {  
  110.                     appClassName = bundle.getString("APPLICATION_CLASS_NAME");//className 是配置在xml文件中的。  
  111.                 } else {  
  112.                     Log.i("demo", "have no application class name");  
  113.                     return;  
  114.                 }  
  115.             } catch (NameNotFoundException e) {  
  116.                 Log.i("demo", "error:"+Log.getStackTraceString(e));  
  117.                 e.printStackTrace();  
  118.             }  
  119.             //有值的話調用該Applicaiton  
  120.             Object currentActivityThread = RefInvoke.invokeStaticMethod(  
  121.                     "android.app.ActivityThread", "currentActivityThread",  
  122.                     new Class[] {}, new Object[] {});  
  123.             Object mBoundApplication = RefInvoke.getFieldOjbect(  
  124.                     "android.app.ActivityThread", currentActivityThread,  
  125.                     "mBoundApplication");  
  126.             Object loadedApkInfo = RefInvoke.getFieldOjbect(  
  127.                     "android.app.ActivityThread$AppBindData",  
  128.                     mBoundApplication, "info");  
  129.             //把當前進程的mApplication 設置成了null  
  130.             RefInvoke.setFieldOjbect("android.app.LoadedApk", "mApplication",  
  131.                     loadedApkInfo, null);  
  132.             Object oldApplication = RefInvoke.getFieldOjbect(  
  133.                     "android.app.ActivityThread", currentActivityThread,  
  134.                     "mInitialApplication");  
  135.             //http://www.codeceo.com/article/android-context.html  
  136.             ArrayList<Application> mAllApplications = (ArrayList<Application>) RefInvoke  
  137.                     .getFieldOjbect("android.app.ActivityThread",  
  138.                             currentActivityThread, "mAllApplications");  
  139.             mAllApplications.remove(oldApplication);//刪除oldApplication  
  140.               
  141.             ApplicationInfo appinfo_In_LoadedApk = (ApplicationInfo) RefInvoke  
  142.                     .getFieldOjbect("android.app.LoadedApk", loadedApkInfo,  
  143.                             "mApplicationInfo");  
  144.             ApplicationInfo appinfo_In_AppBindData = (ApplicationInfo) RefInvoke  
  145.                     .getFieldOjbect("android.app.ActivityThread$AppBindData",  
  146.                             mBoundApplication, "appInfo");  
  147.             appinfo_In_LoadedApk.className = appClassName;  
  148.             appinfo_In_AppBindData.className = appClassName;  
  149.             Application app = (Application) RefInvoke.invokeMethod(  
  150.                     "android.app.LoadedApk", "makeApplication", loadedApkInfo,  
  151.                     new Class[] { boolean.class, Instrumentation.class },  
  152.                     new Object[] { false, null });//執行 makeApplication(false,null)  
  153.             RefInvoke.setFieldOjbect("android.app.ActivityThread",  
  154.                     "mInitialApplication", currentActivityThread, app);  
  155.   
  156.   
  157.             ArrayMap mProviderMap = (ArrayMap) RefInvoke.getFieldOjbect(  
  158.                     "android.app.ActivityThread", currentActivityThread,  
  159.                     "mProviderMap");  
  160.             Iterator it = mProviderMap.values().iterator();  
  161.             while (it.hasNext()) {  
  162.                 Object providerClientRecord = it.next();  
  163.                 Object localProvider = RefInvoke.getFieldOjbect(  
  164.                         "android.app.ActivityThread$ProviderClientRecord",  
  165.                         providerClientRecord, "mLocalProvider");  
  166.                 RefInvoke.setFieldOjbect("android.content.ContentProvider",  
  167.                         "mContext", localProvider, app);  
  168.             }  
  169.               
  170.             Log.i("demo", "app:"+app);  
  171.               
  172.             app.onCreate();  
  173.         }  
  174.     }  
  175.   
  176.     /** 
  177.      * 釋放被加殼的apk文件,so文件 
  178.      * @param data 
  179.      * @throws IOException 
  180.      */  
  181.     private void splitPayLoadFromDex(byte[] apkdata) throws IOException {  
  182.         int ablen = apkdata.length;  
  183.         //取被加殼apk的長度   這裏的長度取值,對應加殼時長度的賦值均可以作些簡化  
  184.         byte[] dexlen = new byte[4];  
  185.         System.arraycopy(apkdata, ablen - 4, dexlen, 0, 4);  
  186.         ByteArrayInputStream bais = new ByteArrayInputStream(dexlen);  
  187.         DataInputStream in = new DataInputStream(bais);  
  188.         int readInt = in.readInt();  
  189.         System.out.println(Integer.toHexString(readInt));  
  190.         byte[] newdex = new byte[readInt];  
  191.         //把被加殼apk內容拷貝到newdex中  
  192.         System.arraycopy(apkdata, ablen - 4 - readInt, newdex, 0, readInt);  
  193.         //這裏應該加上對於apk的解密操做,若加殼是加密處理的話  
  194.         //?  
  195.           
  196.         //對源程序Apk進行解密  
  197.         newdex = decrypt(newdex);  
  198.           
  199.         //寫入apk文件     
  200.         File file = new File(apkFileName);  
  201.         try {  
  202.             FileOutputStream localFileOutputStream = new FileOutputStream(file);  
  203.             localFileOutputStream.write(newdex);  
  204.             localFileOutputStream.close();  
  205.         } catch (IOException localIOException) {  
  206.             throw new RuntimeException(localIOException);  
  207.         }  
  208.           
  209.         //分析被加殼的apk文件  
  210.         ZipInputStream localZipInputStream = new ZipInputStream(  
  211.                 new BufferedInputStream(new FileInputStream(file)));  
  212.         while (true) {  
  213.             ZipEntry localZipEntry = localZipInputStream.getNextEntry();//不瞭解這個是否也遍歷子目錄,看樣子應該是遍歷的  
  214.             if (localZipEntry == null) {  
  215.                 localZipInputStream.close();  
  216.                 break;  
  217.             }  
  218.             //取出被加殼apk用到的so文件,放到 libPath中(data/data/包名/payload_lib)  
  219.             String name = localZipEntry.getName();  
  220.             if (name.startsWith("lib/") && name.endsWith(".so")) {  
  221.                 File storeFile = new File(libPath + "/"  
  222.                         + name.substring(name.lastIndexOf('/')));  
  223.                 storeFile.createNewFile();  
  224.                 FileOutputStream fos = new FileOutputStream(storeFile);  
  225.                 byte[] arrayOfByte = new byte[1024];  
  226.                 while (true) {  
  227.                     int i = localZipInputStream.read(arrayOfByte);  
  228.                     if (i == -1)  
  229.                         break;  
  230.                     fos.write(arrayOfByte, 0, i);  
  231.                 }  
  232.                 fos.flush();  
  233.                 fos.close();  
  234.             }  
  235.             localZipInputStream.closeEntry();  
  236.         }  
  237.         localZipInputStream.close();  
  238.   
  239.   
  240.     }  
  241.   
  242.     /** 
  243.      * 從apk包裏面獲取dex文件內容(byte) 
  244.      * @return 
  245.      * @throws IOException 
  246.      */  
  247.     private byte[] readDexFileFromApk() throws IOException {  
  248.         ByteArrayOutputStream dexByteArrayOutputStream = new ByteArrayOutputStream();  
  249.         ZipInputStream localZipInputStream = new ZipInputStream(  
  250.                 new BufferedInputStream(new FileInputStream(  
  251.                         this.getApplicationInfo().sourceDir)));  
  252.         while (true) {  
  253.             ZipEntry localZipEntry = localZipInputStream.getNextEntry();  
  254.             if (localZipEntry == null) {  
  255.                 localZipInputStream.close();  
  256.                 break;  
  257.             }  
  258.             if (localZipEntry.getName().equals("classes.dex")) {  
  259.                 byte[] arrayOfByte = new byte[1024];  
  260.                 while (true) {  
  261.                     int i = localZipInputStream.read(arrayOfByte);  
  262.                     if (i == -1)  
  263.                         break;  
  264.                     dexByteArrayOutputStream.write(arrayOfByte, 0, i);  
  265.                 }  
  266.             }  
  267.             localZipInputStream.closeEntry();  
  268.         }  
  269.         localZipInputStream.close();  
  270.         return dexByteArrayOutputStream.toByteArray();  
  271.     }  
  272.   
  273.   
  274.     // //直接返回數據,讀者能夠添加本身解密方法  
  275.     private byte[] decrypt(byte[] srcdata) {  
  276.         for(int i=0;i<srcdata.length;i++){  
  277.             srcdata[i] = (byte)(0xFF ^ srcdata[i]);  
  278.         }  
  279.         return srcdata;  
  280.     }  
  281.       
  282.       
  283.     //如下是加載資源  
  284.     protected AssetManager mAssetManager;//資源管理器    
  285.     protected Resources mResources;//資源    
  286.     protected Theme mTheme;//主題    
  287.       
  288.     protected void loadResources(String dexPath) {    
  289.         try {    
  290.             AssetManager assetManager = AssetManager.class.newInstance();    
  291.             Method addAssetPath = assetManager.getClass().getMethod("addAssetPath", String.class);    
  292.             addAssetPath.invoke(assetManager, dexPath);    
  293.             mAssetManager = assetManager;    
  294.         } catch (Exception e) {    
  295.             Log.i("inject", "loadResource error:"+Log.getStackTraceString(e));  
  296.             e.printStackTrace();    
  297.         }    
  298.         Resources superRes = super.getResources();    
  299.         superRes.getDisplayMetrics();    
  300.         superRes.getConfiguration();    
  301.         mResources = new Resources(mAssetManager, superRes.getDisplayMetrics(),superRes.getConfiguration());    
  302.         mTheme = mResources.newTheme();    
  303.         mTheme.setTo(super.getTheme());  
  304.     }    
  305.       
  306.     @Override    
  307.     public AssetManager getAssets() {    
  308.         return mAssetManager == null ? super.getAssets() : mAssetManager;    
  309.     }    
  310.       
  311.     @Override    
  312.     public Resources getResources() {    
  313.         return mResources == null ? super.getResources() : mResources;    
  314.     }    
  315.       
  316.     @Override    
  317.     public Theme getTheme() {    
  318.         return mTheme == null ? super.getTheme() : mTheme;    
  319.     }   
  320.       
  321. }  


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

 

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

[java]  view plain  copy
 
  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.   
  20.             // 分離出解殼後的apk文件已用於動態加載  
  21.             this.splitPayLoadFromDex(dexdata);  
  22.         }  
  23.         // 配置動態加載環境  
  24.         Object currentActivityThread = RefInvoke.invokeStaticMethod(  
  25.                 "android.app.ActivityThread", "currentActivityThread",  
  26.                 new Class[] {}, new Object[] {});//獲取主線程對象 http://blog.csdn.net/myarrow/article/details/14223493  
  27.         String packageName = this.getPackageName();//當前apk的包名  
  28.         //下面兩句不是太理解  
  29.         ArrayMap mPackages = (ArrayMap) RefInvoke.getFieldOjbect(  
  30.                 "android.app.ActivityThread", currentActivityThread,  
  31.                 "mPackages");  
  32.         WeakReference wr = (WeakReference) mPackages.get(packageName);  
  33.         //建立被加殼apk的DexClassLoader對象  加載apk內的類和本地代碼(c/c++代碼)  
  34.         DexClassLoader dLoader = new DexClassLoader(apkFileName, odexPath,  
  35.                 libPath, (ClassLoader) RefInvoke.getFieldOjbect(  
  36.                         "android.app.LoadedApk", wr.get(), "mClassLoader"));  
  37.         //base.getClassLoader(); 是否是就等同於 (ClassLoader) RefInvoke.getFieldOjbect()? 有空驗證下//?  
  38.         //把當前進程的DexClassLoader 設置成了被加殼apk的DexClassLoader  ----有點c++中進程環境的意思~~  
  39.         RefInvoke.setFieldOjbect("android.app.LoadedApk", "mClassLoader",  
  40.                 wr.get(), dLoader);  
  41.   
  42.         Log.i("demo","classloader:"+dLoader);  
  43.   
  44.         try{  
  45.             Object actObj = dLoader.loadClass("com.example.forceapkobj.MainActivity");  
  46.             Log.i("demo", "actObj:"+actObj);  
  47.         }catch(Exception e){  
  48.             Log.i("demo", "activity:"+Log.getStackTraceString(e));  
  49.         }  
  50.   
  51.   
  52.     } catch (Exception e) {  
  53.         Log.i("demo", "error:"+Log.getStackTraceString(e));  
  54.         e.printStackTrace();  
  55.     }  
  56. }  

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

 

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

[java]  view plain  copy
 
  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. {  
  11.     dexFile.createNewFile();  //在payload_odex文件夾內,建立payload.apk  
  12.     // 讀取程序classes.dex文件  
  13.     byte[] dexdata = this.readDexFileFromApk();  
  14.   
  15.     // 分離出解殼後的apk文件已用於動態加載  
  16.     this.splitPayLoadFromDex(dexdata);  
  17. }  

這個脫殼解密操做必定要和咱們以前的加殼以及加密操做對應,否則就會出現Dex加載錯誤問題

 

A) 從Apk中獲取到Dex文件

 

[java]  view plain  copy
 
  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();  
  30. }  

其實就是解壓Apk文件,直接獲得dex文件便可

 

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

[java]  view plain  copy
 
  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.       
  21.     //對源程序Apk進行解密  
  22.     newdex = decrypt(newdex);  
  23.       
  24.     //寫入apk文件     
  25.     File file = new File(apkFileName);  
  26.     try {  
  27.         FileOutputStream localFileOutputStream = new FileOutputStream(file);  
  28.         localFileOutputStream.write(newdex);  
  29.         localFileOutputStream.close();  
  30.     } catch (IOException localIOException) {  
  31.         throw new RuntimeException(localIOException);  
  32.     }  
  33.       
  34.     //分析被加殼的apk文件  
  35.     ZipInputStream localZipInputStream = new ZipInputStream(  
  36.             new BufferedInputStream(new FileInputStream(file)));  
  37.     while (true) {  
  38.         ZipEntry localZipEntry = localZipInputStream.getNextEntry();//不瞭解這個是否也遍歷子目錄,看樣子應該是遍歷的  
  39.         if (localZipEntry == null) {  
  40.             localZipInputStream.close();  
  41.             break;  
  42.         }  
  43.         //取出被加殼apk用到的so文件,放到 libPath中(data/data/包名/payload_lib)  
  44.         String name = localZipEntry.getName();  
  45.         if (name.startsWith("lib/") && name.endsWith(".so")) {  
  46.             File storeFile = new File(libPath + "/"  
  47.                     + name.substring(name.lastIndexOf('/')));  
  48.             storeFile.createNewFile();  
  49.             FileOutputStream fos = new FileOutputStream(storeFile);  
  50.             byte[] arrayOfByte = new byte[1024];  
  51.             while (true) {  
  52.                 int i = localZipInputStream.read(arrayOfByte);  
  53.                 if (i == -1)  
  54.                     break;  
  55.                 fos.write(arrayOfByte, 0, i);  
  56.             }  
  57.             fos.flush();  
  58.             fos.close();  
  59.         }  
  60.         localZipInputStream.closeEntry();  
  61.     }  
  62.     localZipInputStream.close();  
  63.   
  64.   
  65. }  

 

 

C) 解密源程序Apk

[java]  view plain  copy
 
  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;  
  7. }  

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

 

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

[java]  view plain  copy
 
  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.   
  20. Log.i("demo","classloader:"+dLoader);  
  21.   
  22. try{  
  23.     Object actObj = dLoader.loadClass("com.example.forceapkobj.MainActivity");  
  24.     Log.i("demo", "actObj:"+actObj);  
  25. }catch(Exception e){  
  26.     Log.i("demo", "activity:"+Log.getStackTraceString(e));  
  27. }  

 

 

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

[java]  view plain  copy
 
  1. @Override  
  2. public void onCreate() {  
  3.     {  
  4.         //loadResources(apkFileName);  
  5.           
  6.         Log.i("demo", "onCreate");  
  7.         // 若是源應用配置有Appliction對象,則替換爲源應用Applicaiton,以便不影響源程序邏輯。  
  8.         String appClassName = null;  
  9.         try {  
  10.             ApplicationInfo ai = this.getPackageManager()  
  11.                     .getApplicationInfo(this.getPackageName(),  
  12.                             PackageManager.GET_META_DATA);  
  13.             Bundle bundle = ai.metaData;  
  14.             if (bundle != null && bundle.containsKey("APPLICATION_CLASS_NAME")) {  
  15.                 appClassName = bundle.getString("APPLICATION_CLASS_NAME");//className 是配置在xml文件中的。  
  16.             } else {  
  17.                 Log.i("demo", "have no application class name");  
  18.                 return;  
  19.             }  
  20.         } catch (NameNotFoundException e) {  
  21.             Log.i("demo", "error:"+Log.getStackTraceString(e));  
  22.             e.printStackTrace();  
  23.         }  
  24.         //有值的話調用該Applicaiton  
  25.         Object currentActivityThread = RefInvoke.invokeStaticMethod(  
  26.                 "android.app.ActivityThread", "currentActivityThread",  
  27.                 new Class[] {}, new Object[] {});  
  28.         Object mBoundApplication = RefInvoke.getFieldOjbect(  
  29.                 "android.app.ActivityThread", currentActivityThread,  
  30.                 "mBoundApplication");  
  31.         Object loadedApkInfo = RefInvoke.getFieldOjbect(  
  32.                 "android.app.ActivityThread$AppBindData",  
  33.                 mBoundApplication, "info");  
  34.         //把當前進程的mApplication 設置成了null  
  35.         RefInvoke.setFieldOjbect("android.app.LoadedApk", "mApplication",  
  36.                 loadedApkInfo, null);  
  37.         Object oldApplication = RefInvoke.getFieldOjbect(  
  38.                 "android.app.ActivityThread", currentActivityThread,  
  39.                 "mInitialApplication");  
  40.         //http://www.codeceo.com/article/android-context.html  
  41.         ArrayList<Application> mAllApplications = (ArrayList<Application>) RefInvoke  
  42.                 .getFieldOjbect("android.app.ActivityThread",  
  43.                         currentActivityThread, "mAllApplications");  
  44.         mAllApplications.remove(oldApplication);//刪除oldApplication  
  45.           
  46.         ApplicationInfo appinfo_In_LoadedApk = (ApplicationInfo) RefInvoke  
  47.                 .getFieldOjbect("android.app.LoadedApk", loadedApkInfo,  
  48.                         "mApplicationInfo");  
  49.         ApplicationInfo appinfo_In_AppBindData = (ApplicationInfo) RefInvoke  
  50.                 .getFieldOjbect("android.app.ActivityThread$AppBindData",  
  51.                         mBoundApplication, "appInfo");  
  52.         appinfo_In_LoadedApk.className = appClassName;  
  53.         appinfo_In_AppBindData.className = appClassName;  
  54.         Application app = (Application) RefInvoke.invokeMethod(  
  55.                 "android.app.LoadedApk", "makeApplication", loadedApkInfo,  
  56.                 new Class[] { boolean.class, Instrumentation.class },  
  57.                 new Object[] { false, null });//執行 makeApplication(false,null)  
  58.         RefInvoke.setFieldOjbect("android.app.ActivityThread",  
  59.                 "mInitialApplication", currentActivityThread, app);  
  60.   
  61.   
  62.         ArrayMap mProviderMap = (ArrayMap) RefInvoke.getFieldOjbect(  
  63.                 "android.app.ActivityThread", currentActivityThread,  
  64.                 "mProviderMap");  
  65.         Iterator it = mProviderMap.values().iterator();  
  66.         while (it.hasNext()) {  
  67.             Object providerClientRecord = it.next();  
  68.             Object localProvider = RefInvoke.getFieldOjbect(  
  69.                     "android.app.ActivityThread$ProviderClientRecord",  
  70.                     providerClientRecord, "mLocalProvider");  
  71.             RefInvoke.setFieldOjbect("android.content.ContentProvider",  
  72.                     "mContext", localProvider, app);  
  73.         }  
  74.           
  75.         Log.i("demo", "app:"+app);  
  76.           
  77.         app.onCreate();  
  78.     }  
  79. }  

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

 

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

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

 

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

 

4、運行程序

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

運行步驟:

第一步:獲得源程序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文件格式是不正確的。說明咱們的加固是成功的。

 

5、遇到的問題

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

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

 

6、技術要點

一、對Dex文件格式的瞭解

二、動態加載技術的深刻掌握

三、Application的執行流程的瞭解

四、如何從Apk中獲得Dex文件

五、如何重新簽名一個Apk程序

 

7、綜合概述

咱們經過上面的過程能夠看到,關於Apk加固的工做仍是挺複雜的,涉及到的東西也挺多的,下面就在來總結一下吧:

一、加殼程序

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

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

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

 

二、脫殼程序

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

語言:Android項目(Java)

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

 

8、總結

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

from:https://www.cnblogs.com/yanzheng216/articles/6831689.html

相關文章
相關標籤/搜索