Android APK加壳技术方案

本文章由Jack_Jia编写,转载请注明出处。  
文章链接:
http://blog.csdn.net/jiazhijun/article/details/8678399
作者:Jack_Jia    邮箱: 309zhijun@163.com


一、什么是加壳?

       加壳是在二进制的程序中植入一段代码,在运行的时候优先取得程序的控制权,做一些额外的工作。大多数病毒就是基于此原理。PC EXE文件加壳的过程如下:


     


二、加壳作用

       加壳的程序可以有效阻止对程序的反汇编分析,以达到它不可告人的目的。这种技术也常用来保护软件版权,防止被软件破解。


三、Android Dex文件加壳原理

       PC平台现在已存在大量的标准的加壳和解壳工具,但是Android作为新兴平台还未出现APK加壳工具。Android Dex文件大量使用引用给加壳带来了一定的难度,但是从理论上讲,Android APK加壳也是可行的。

       在这个过程中,牵扯到三个角色:

           1、加壳程序:加密源程序为解壳数据、组装解壳程序和解壳数据

           2、解壳程序:解密解壳数据,并运行时通过DexClassLoader动态加载

           3、源程序:需要加壳处理的被保护代码

       阅读该文章,需要您对DEX文件结构有所了解,您可以通过以下网址了解相关信息:

        http://blog.csdn.net/jiazhijun/article/details/8664778


       根据解壳数据在解壳程序DEX文件中的不同分布,本文将提出两种Android Dex加壳的实现方案。


    (一)解壳数据位于解壳程序文件尾部


              该种方式简单实用,合并后的DEX文件结构如下。




          加壳程序工作流程:

                  1、加密源程序APK文件为解壳数据

                  2、把解壳数据写入解壳程序Dex文件末尾,并在文件尾部添加解壳数据的大小。

                  3、修改解壳程序DEX头中checksum、signature 和file_size头信息。

                  4、修改源程序AndroidMainfest.xml文件并覆盖解壳程序AndroidMainfest.xml文件。


          解壳DEX程序工作流程:

                  1、读取DEX文件末尾数据获取借壳数据长度。

                  2、从DEX文件读取解壳数据,解密解壳数据。以文件形式保存解密数据到a.APK文件

                  3、通过DexClassLoader动态加载a.apk。


(二)解壳数据位于解壳程序文件头


          该种方式相对比较复杂, 合并后DEX文件结构如下:




          加壳程序工作流程:

                  1、加密源程序APK文件为解壳数据

                  2、计算解壳数据长度,并添加该长度到解壳DEX文件头末尾,并继续解壳数据到文件头末尾。

                       (插入数据的位置为0x70处

                  3、修改解壳程序DEX头中checksum、signature、file_size、header_size、string_ids_off、type_ids_off、proto_ids_off、field_ids_off、

              method_ids_off、class_defs_off和data_off相关项。  分析map_off 数据,修改相关的数据偏移量。  

                  4、修改源程序AndroidMainfest.xml文件并覆盖解壳程序AndroidMainfest.xml文件。


          解壳DEX程序工作流程:

                  1、从0x70处读取解壳数据长度。

                  2、从DEX文件读取解壳数据,解密解壳数据。以文件形式保存解密数据到a.APK

                  3、通过DexClassLoader动态加载a.APK。


   四、加壳及脱壳代码实现


          http://blog.csdn.net/jiazhijun/article/details/8809542


一、序言


        在上篇“Android APK加壳技术方案”(http://blog.csdn.net/jiazhijun/article/details/8678399)博文中,根据加壳数据在解壳程序Dex文件所处的位置,我提出了两种Android Dex加壳技术实现方案,本片博文将对方案1代码实现进行讲解。博友可以根据方案1的代码实现原理对方案2自行实现。

       在方案1的代码实现过程中,各种不同的问题接踵出现,最初的方案也在不同问题的出现、解决过程中不断的得到调整、优化。

       本文的代码实现了对整个APK包的加壳处理。加壳程序不会对源程序有任何的影响。


二、代码实现


     本程序基于Android2.3代码实现,因为牵扯到系统代码的反射修改,本程序不保证在其它android版本正常工作,博友可以根据实现原理,自行实现对其它Android版本的兼容性开发。


     1、 加壳程序流程及代码实现

                  1、加密源程序APK为解壳数据

                  2、把解壳数据写入解壳程序DEX文件末尾,并在文件尾部添加解壳数据的大小。

                  3、修改解壳程序DEX头中checksum、signature 和file_size头信息。


       代码实现如下:

[java] view plaincopy
  1. package com.android.dexshell;  
  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.   
  11. public class DexShellTool {  
  12.     /** 
  13.      * @param args 
  14.      */  
  15.     public static void main(String[] args) {  
  16.         // TODO Auto-generated method stub  
  17.         try {  
  18.             File payloadSrcFile = new File("g:/payload.apk");  
  19.             File unShellDexFile = new File("g:/unshell.dex");  
  20.             byte[] payloadArray = encrpt(readFileBytes(payloadSrcFile));  
  21.             byte[] unShellDexArray = readFileBytes(unShellDexFile);  
  22.             int payloadLen = payloadArray.length;  
  23.             int unShellDexLen = unShellDexArray.length;  
  24.             int totalLen = payloadLen + unShellDexLen +4;  
  25.             byte[] newdex = new byte[totalLen];  
  26.             //添加解壳代码  
  27.             System.arraycopy(unShellDexArray, 0, newdex, 0, unShellDexLen);  
  28.             //添加加密后的解壳数据  
  29.             System.arraycopy(payloadArray, 0, newdex, unShellDexLen,  
  30.                     payloadLen);  
  31.             //添加解壳数据长度  
  32.             System.arraycopy(intToByte(payloadLen), 0, newdex, totalLen-44);  
  33.                         //修改DEX file size文件头  
  34.             fixFileSizeHeader(newdex);  
  35.             //修改DEX SHA1 文件头  
  36.             fixSHA1Header(newdex);  
  37.             //修改DEX CheckSum文件头  
  38.             fixCheckSumHeader(newdex);  
  39.   
  40.   
  41.             String str = "g:/classes.dex";  
  42.             File file = new File(str);  
  43.             if (!file.exists()) {  
  44.                 file.createNewFile();  
  45.             }  
  46.               
  47.             FileOutputStream localFileOutputStream = new FileOutputStream(str);  
  48.             localFileOutputStream.write(newdex);  
  49.             localFileOutputStream.flush();  
  50.             localFileOutputStream.close();  
  51.   
  52.   
  53.         } catch (Exception e) {  
  54.             // TODO Auto-generated catch block  
  55.             e.printStackTrace();  
  56.         }  
  57.     }  
  58.       
  59.     //直接返回数据,读者可以添加自己加密方法  
  60.     private static byte[] encrpt(byte[] srcdata){  
  61.         return srcdata;  
  62.     }  
  63.   
  64.   
  65.     private static void fixCheckSumHeader(byte[] dexBytes) {  
  66.         Adler32 adler = new Adler32();  
  67.         adler.update(dexBytes, 12, dexBytes.length - 12);  
  68.         long value = adler.getValue();  
  69.         int va = (int) value;  
  70.         byte[] newcs = intToByte(va);  
  71.         byte[] recs = new byte[4];  
  72.         for (int i = 0; i < 4; i++) {  
  73.             recs[i] = newcs[newcs.length - 1 - i];  
  74.             System.out.println(Integer.toHexString(newcs[i]));  
  75.         }  
  76.         System.arraycopy(recs, 0, dexBytes, 84);  
  77.         System.out.println(Long.toHexString(value));  
  78.         System.out.println();  
  79.     }  
  80.   
  81.   
  82.     public static byte[] intToByte(int number) {  
  83.         byte[] b = new byte[4];  
  84.         for (int i = 3; i >= 0; i--) {  
  85.             b[i] = (byte) (number % 256);  
  86.             number >>= 8;  
  87.         }  
  88.         return b;  
  89.     }  
  90.   
  91.   
  92.     private static void fixSHA1Header(byte[] dexBytes)  
  93.             throws NoSuchAlgorithmException {  
  94.         MessageDigest md = MessageDigest.getInstance("SHA-1");  
  95.         md.update(dexBytes, 32, dexBytes.length - 32);  
  96.         byte[] newdt = md.digest();  
  97.         System.arraycopy(newdt, 0, dexBytes, 1220);  
  98.         String hexstr = "";  
  99.         for (int i = 0; i < newdt.length; i++) {  
  100.             hexstr += Integer.toString((newdt[i] & 0xff) + 0x10016)  
  101.                     .substring(1);  
  102.         }  
  103.         System.out.println(hexstr);  
  104.     }  
  105.   
  106.   
  107.     private static void fixFileSizeHeader(byte[] dexBytes) {  
  108.   
  109.   
  110.         byte[] newfs = intToByte(dexBytes.length);  
  111.         System.out.println(Integer.toHexString(dexBytes.length));  
  112.         byte[] refs = new byte[4];  
  113.         for (int i = 0; i < 4; i++) {  
  114.             refs[i] = newfs[newfs.length - 1 - i];  
  115.             System.out.println(Integer.toHexString(newfs[i]));  
  116.         }  
  117.         System.arraycopy(refs, 0, dexBytes, 324);  
  118.     }  
  119.   
  120.   
  121.     private static byte[] readFileBytes(File file) throws IOException {  
  122.         byte[] arrayOfByte = new byte[1024];  
  123.         ByteArrayOutputStream localByteArrayOutputStream = new ByteArrayOutputStream();  
  124.         FileInputStream fis = new FileInputStream(file);  
  125.         while (true) {  
  126.             int i = fis.read(arrayOfByte);  
  127.             if (i != -1) {  
  128.                 localByteArrayOutputStream.write(arrayOfByte, 0, i);  
  129.             } else {  
  130.                 return localByteArrayOutputStream.toByteArray();  
  131.             }  
  132.         }  
  133.     }  
  134.   
  135.   
  136. }  


    2、 解壳程序流程及代码实现

          在解壳程序的开发过程中需要解决如下几个关键的技术问题:

         (1)解壳代码如何能够第一时间执行?

                  Android程序由不同的组件构成,系统在有需要的时候启动程序组件。因此解壳程序必须在Android系统启动组件之前运行,完成对解壳数                据的解壳及APK文件的动态加载,否则会使程序出现加载类失败的异常。

                  Android开发者都知道Applicaiton做为整个应用的上下文,会被系统第一时间调用,这也是应用开发者程序代码的第一执行点。因此通过对              AndroidMainfest.xml的application的配置可以实现解壳代码第一时间运行。

[html] view plaincopy
  1. <application  
  2.     android:icon="@drawable/ic_launcher"  
  3.     android:label="@string/app_name"  
  4.     android:theme="@style/AppTheme" android:name="<span style="color: rgb(255, 0, 0);"><em><strong>com.android.dexunshell.ProxyApplication</strong></em></span>>  
  5. </application>  

         (2)如何替换回源程序原有的Application?

                  当在AndroidMainfest.xml文件配置为解壳代码的Application时。源程序原有的Applicaiton将被替换,为了不影响源程序代码逻辑,我们需要              在解壳代码运行完成后,替换回源程序原有的Application对象。我们通过在AndroidMainfest.xml文件中配置原有Applicaiton类信息来达到我们              的目的。解壳程序要在运行完毕后通过创建配置的Application对象,并通过反射修改回原Application。

[html] view plaincopy
  1. <application  
  2.     android:icon="@drawable/ic_launcher"  
  3.     android:label="@string/app_name"  
  4.     android:theme="@style/AppTheme" android:name="<em><strong><span style="color: rgb(255, 0, 0);">com.android.dexunshell.ProxyApplication</span></strong></em>>  
  5.     <span style="color: rgb(255, 0, 0);"><em><strong><meta-data android:name="APPLICATION_CLASS_NAME" android:value="com.***.Application"/></strong></em></span>  
  6. </application>  

          

          (3)如何通过DexClassLoader实现对apk代码的动态加载。

                  我们知道DexClassLoader加载的类是没有组件生命周期的,也就是说即使DexClassLoader通过对APK的动态加载完成了对组件类的加载,              当系统启动该组件时,还会出现加载类失败的异常。为什么组件类被动态加载入虚拟机,但系统却出现加载类失败呢?

                  通过查看Android源代码我们知道组件类的加载是由另一个ClassLoader来完成的,DexClassLoader和系统组件ClassLoader并不存在关                  系,系统组件ClassLoader当然找不到由DexClassLoader加载的类,如果把系统组件ClassLoader的parent修改成DexClassLoader,我们就可              以实现对apk代码的动态加载。


         (4)如何使解壳后的APK资源文件被代码动态引用。

                 代码默认引用的资源文件在最外层的解壳程序中,因此我们要增加系统的资源加载路径来实现对借壳后APK文件资源的加载。


        解壳实现代码:

[java] view plaincopy
  1. package com.android.dexunshell;  
  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.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.   
  18. import dalvik.system.DexClassLoader;  
  19. import android.app.Application;  
  20. import android.content.pm.ApplicationInfo;  
  21. import android.content.pm.PackageManager;  
  22. import android.content.pm.PackageManager.NameNotFoundException;  
  23. import android.os.Bundle;  
  24. public class ProxyApplication extends Application {  
  25.   
  26.   
  27.     private static final String appkey = "APPLICATION_CLASS_NAME";  
  28.     private String apkFileName;  
  29.     private String odexPath;  
  30.     private String libPath;  
  31.   
  32.   
  33.     protected void attachBaseContext(Context base) {  
  34.         super.attachBaseContext(base);  
  35.         try {  
  36.             File odex = this.getDir("payload_odex", MODE_PRIVATE);  
  37.             File libs = this.getDir("payload_lib", MODE_PRIVATE);  
  38.             odexPath = odex.getAbsolutePath();  
  39.             libPath = libs.getAbsolutePath();  
  40.             apkFileName = odex.getAbsolutePath() + "/payload.apk";  
  41.             File dexFile = new File(apkFileName);  
  42.             if (!dexFile.exists())  
  43.                 dexFile.createNewFile();  
  44.             // 读取程序classes.dex文件  
  45.             byte[] dexdata = this.readDexFileFromApk();  
  46.             // 分离出解壳后的apk文件已用于动态加载  
  47.             this.splitPayLoadFromDex(dexdata);  
  48.             // 配置动态加载环境  
  49.             Object currentActivityThread = RefInvoke.invokeStaticMethod(  
  50.                     "android.app.ActivityThread""currentActivityThread",  
  51.                     new Class[] {}, new Object[] {});  
  52.             String packageName = this.getPackageName();  
  53.             HashMap mPackages = (HashMap) RefInvoke.getFieldOjbect(  
  54.                     "android.app.ActivityThread", currentActivityThread,  
  55.                     "mPackages");  
  56.             WeakReference wr = (WeakReference) mPackages.get(packageName);  
  57.             DexClassLoader dLoader = new DexClassLoader(apkFileName, odexPath,  
  58.                     libPath, (ClassLoader) RefInvoke.getFieldOjbect(  
  59.                             "android.app.LoadedApk", wr.get(), "mClassLoader"));  
  60.             RefInvoke.setFieldOjbect("android.app.LoadedApk""mClassLoader",  
  61.                     wr.get(), dLoader);  
  62.   
  63.   
  64.         } catch (Exception e) {  
  65.             // TODO Auto-generated catch block  
  66.             e.printStackTrace();  
  67.         }  
  68.     }  
  69.   
  70.   
  71.     public void onCreate() {  
  72.         {  
  73.   
  74.   
  75.             // 如果源应用配置有Appliction对象,则替换为源应用Applicaiton,以便不影响源程序逻辑。  
  76.             String appClassName = null;  
  77.             try {  
  78.                 ApplicationInfo ai = this.getPackageManager()  
  79.                         .getApplicationInfo(this.getPackageName(),  
  80.                                 PackageManager.GET_META_DATA);  
  81.                 Bundle bundle = ai.metaData;  
  82.                 if (bundle != null  
  83.                         && bundle.containsKey("APPLICATION_CLASS_NAME")) {  
  84.                     appClassName = bundle.getString("APPLICATION_CLASS_NAME");  
  85.                 } else {  
  86.                     return;  
  87.                 }  
  88.             } catch (NameNotFoundException e) {  
  89.                 // TODO Auto-generated catch block  
  90.                 e.printStackTrace();  
  91.             }  
  92.   
  93.   
  94.             Object currentActivityThread = RefInvoke.invokeStaticMethod(  
  95.                     "android.app.ActivityThread""currentActivityThread",  
  96.                     new Class[] {}, new Object[] {});  
  97.             Object mBoundApplication = RefInvoke.getFieldOjbect(  
  98.                     "android.app.ActivityThread", currentActivityThread,  
  99.                     "mBoundApplication");  
  100.             Object loadedApkInfo = RefInvoke.getFieldOjbect(  
  101.                     "android.app.ActivityThread$AppBindData",  
  102.                     mBoundApplication, "info");  
  103.             RefInvoke.setFieldOjbect("android.app.LoadedApk""mApplication",  
  104.                     loadedApkInfo, null);  
  105.             Object oldApplication = RefInvoke.getFieldOjbect(  
  106.                     "android.app.ActivityThread", currentActivityThread,  
  107.                     "mInitialApplication");  
  108.             ArrayList<Application> mAllApplications = (ArrayList<Application>) RefInvoke  
  109.                     .getFieldOjbect("android.app.ActivityThread",  
  110.                             currentActivityThread, "mAllApplications");  
  111.             mAllApplications.remove(oldApplication);  
  112.             ApplicationInfo appinfo_In_LoadedApk = (ApplicationInfo) RefInvoke  
  113.                     .getFieldOjbect("android.app.LoadedApk", loadedApkInfo,  
  114.                             "mApplicationInfo");  
  115.             ApplicationInfo appinfo_In_AppBindData = (ApplicationInfo) RefInvoke  
  116.                     .getFieldOjbect("android.app.ActivityThread$AppBindData",  
  117.                             mBoundApplication, "appInfo");  
  118.             appinfo_In_LoadedApk.className = appClassName;  
  119.             appinfo_In_AppBindData.className = appClassName;  
  120.             Application app = (Application) RefInvoke.invokeMethod(  
  121.                     "android.app.LoadedApk""makeApplication", loadedApkInfo,  
  122.                     new Class[] { boolean.class, Instrumentation.class },  
  123.                     new Object[] { falsenull });  
  124.             RefInvoke.setFieldOjbect("android.app.ActivityThread",  
  125.                     "mInitialApplication", currentActivityThread, app);  
  126.   
  127.   
  128.             HashMap mProviderMap = (HashMap) RefInvoke.getFieldOjbect(  
  129.                     "android.app.ActivityThread", currentActivityThread,  
  130.                     "mProviderMap");  
  131.             Iterator it = mProviderMap.values().iterator();  
  132.             while (it.hasNext()) {  
  133.                 Object providerClientRecord = it.next();  
  134.                 Object localProvider = RefInvoke.getFieldOjbect(  
  135.                         "android.app.ActivityThread$ProviderClientRecord",  
  136.                         providerClientRecord, "mLocalProvider");  
  137.                 RefInvoke.setFieldOjbect("android.content.ContentProvider",  
  138.                         "mContext", localProvider, app);  
  139.             }  
  140.             app.onCreate();  
  141.         }  
  142.     }  
  143.   
  144.   
  145.     private void splitPayLoadFromDex(byte[] data) throws IOException {  
  146.         byte[] apkdata = decrypt(data);  
  147.         int ablen = apkdata.length;  
  148.         byte[] dexlen = new byte[4];  
  149.         System.arraycopy(apkdata, ablen - 4, dexlen, 04);  
  150.         ByteArrayInputStream bais = new ByteArrayInputStream(dexlen);  
  151.         DataInputStream in = new DataInputStream(bais);  
  152.         int readInt = in.readInt();  
  153.         System.out.println(Integer.toHexString(readInt));  
  154.         byte[] newdex = new byte[readInt];  
  155.         System.arraycopy(apkdata, ablen - 4 - readInt, newdex, 0, readInt);  
  156.         File file = new File(apkFileName);  
  157.         try {  
  158.             FileOutputStream localFileOutputStream = new FileOutputStream(file);  
  159.             localFileOutputStream.write(newdex);  
  160.             localFileOutputStream.close();  
  161.   
  162.   
  163.         } catch (IOException localIOException) {  
  164.             throw new RuntimeException(localIOException);  
  165.         }  
  166.   
  167.   
  168.         ZipInputStream localZipInputStream = new ZipInputStream(  
  169.                 new BufferedInputStream(new FileInputStream(file)));  
  170.         while (true) {  
  171.             ZipEntry localZipEntry = localZipInputStream.getNextEntry();  
  172.             if (localZipEntry == null) {  
  173.                 localZipInputStream.close();  
  174.                 break;  
  175.             }  
  176.             String name = localZipEntry.getName();  
  177.             if (name.startsWith("lib/") && name.endsWith(".so")) {  
  178.                 File storeFile = new File(libPath + "/"  
  179.                         + name.substring(name.lastIndexOf('/')));  
  180.                 storeFile.createNewFile();  
  181.                 FileOutputStream fos = new FileOutputStream(storeFile);  
  182.                 byte[] arrayOfByte = new byte[1024];  
  183.                 while (true) {  
  184.                     int i = localZipInputStream.read(arrayOfByte);  
  185.                     if (i == -1)  
  186.                         break;  
  187.                     fos.write(arrayOfByte, 0, i);  
  188.                 }  
  189.                 fos.flush();  
  190.                 fos.close();  
  191.             }  
  192.             localZipInputStream.closeEntry();  
  193.         }  
  194.         localZipInputStream.close();  
  195.   
  196.   
  197.     }  
  198.   
  199.   
  200.     private byte[] readDexFileFromApk() throws IOException {  
  201.         ByteArrayOutputStream dexByteArrayOutputStream = new ByteArrayOutputStream();  
  202.         ZipInputStream localZipInputStream = new ZipInputStream(  
  203.                 new BufferedInputStream(new FileInputStream(  
  204.                         this.getApplicationInfo().sourceDir)));  
  205.         while (true) {  
  206.             ZipEntry localZipEntry = localZipInputStream.getNextEntry();  
  207.             if (localZipEntry == null) {  
  208.                 localZipInputStream.close();  
  209.                 break;  
  210.             }  
  211.             if (localZipEntry.getName().equals("classes.dex")) {  
  212.                 byte[] arrayOfByte = new byte[1024];  
  213.                 while (true) {  
  214.                     int i = localZipInputStream.read(arrayOfByte);  
  215.                     if (i == -1)  
  216.                         break;  
  217.                     dexByteArrayOutputStream.write(arrayOfByte, 0, i);  
  218.                 }  
  219.             }  
  220.             localZipInputStream.closeEntry();  
  221.         }  
  222.         localZipInputStream.close();  
  223.         return dexByteArrayOutputStream.toByteArray();  
  224.     }  
  225.   
  226.   
  227.     // //直接返回数据,读者可以添加自己解密方法  
  228.     private byte[] decrypt(byte[] data) {  
  229.         return data;  
  230.     }  

[java] view plaincopy
  1. package com.android.dexunshell;  
  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.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.   
  18. import dalvik.system.DexClassLoader;  
  19. import android.app.Application;  
  20. import android.content.pm.ApplicationInfo;  
  21. import android.content.pm.PackageManager;  
  22. import android.content.pm.PackageManager.NameNotFoundException;  
  23. import android.os.Bundle;  
  24. public class ProxyApplication extends Application {  
  25.   
  26.   
  27. <span style="white-space:pre">  </span>private static final String appkey = "APPLICATION_CLASS_NAME";  
  28. <span style="white-space:pre">  </span>private String apkFileName;  
  29. <span style="white-space:pre">  </span>private String odexPath;  
  30. <span style="white-space:pre">  </span>private String libPath;  
  31.   
  32.   
  33. <span style="white-space:pre">  </span>protected void attachBaseContext(Context base) {  
  34. <span style="white-space:pre">      </span>super.attachBaseContext(base);  
  35. <span style="white-space:pre">      </span>try {  
  36. <span style="white-space:pre">          </span>File odex = this.getDir("payload_odex", MODE_PRIVATE);  
  37. <span style="white-space:pre">          </span>File libs = this.getDir("payload_lib", MODE_PRIVATE);  
  38. <span style="white-space:pre">          </span>odexPath = odex.getAbsolutePath();  
  39. <span style="white-space:pre">          </span>libPath = libs.getAbsolutePath();  
  40. <span style="white-space:pre">          </span>apkFileName = odex.getAbsolutePath() + "/payload.apk";  
  41. <span style="white-space:pre">          </span>File dexFile = new File(apkFileName);  
  42. <span style="white-space:pre">          </span>if (!dexFile.exists())  
  43. <span style="white-space:pre">              </span>dexFile.createNewFile();  
  44. <span style="white-space:pre">          </span>// 读取程序classes.dex文件  
  45. <span style="white-space:pre">          </span>byte[] dexdata = this.readDexFileFromApk();  
  46. <span style="white-space:pre">          </span>// 分离出解壳后的apk文件已用于动态加载  
  47. <span style="white-space:pre">          </span>this.splitPayLoadFromDex(dexdata);  
  48. <span style="white-space:pre">          </span>// 配置动态加载环境  
  49. <span style="white-space:pre">          </span>Object currentActivityThread = RefInvoke.invokeStaticMethod(  
  50. <span style="white-space:pre">                  </span>"android.app.ActivityThread""currentActivityThread",  
  51. <span style="white-space:pre">                  </span>new Class[] {}, new Object[] {});  
  52. <span style="white-space:pre">          </span>String packageName = this.getPackageName();  
  53. <span style="white-space:pre">          </span>HashMap mPackages = (HashMap) RefInvoke.getFieldOjbect(  
  54. <span style="white-space:pre">                  </span>"android.app.ActivityThread", currentActivityThread,  
  55. <span style="white-space:pre">                  </span>"mPackages");  
  56. <span style="white-space:pre">          </span>WeakReference wr = (WeakReference) mPackages.get(packageName);  
  57. <span style="white-space:pre">          </span>DexClassLoader dLoader = new DexClassLoader(apkFileName, odexPath,  
  58. <span style="white-space:pre">                  </span>libPath, (ClassLoader) RefInvoke.getFieldOjbect(  
  59. <span style="white-space:pre">                          </span>"android.app.LoadedApk", wr.get(), "mClassLoader"));  
  60. <span style="white-space:pre">          </span>RefInvoke.setFieldOjbect("android.app.LoadedApk""mClassLoader",  
  61. <span style="white-space:pre">                  </span>wr.get(), dLoader);  
  62.   
  63.   
  64. <span style="white-space:pre">      </span>} catch (Exception e) {  
  65. <span style="white-space:pre">          </span>// TODO Auto-generated catch block  
  66. <span style="white-space:pre">          </span>e.printStackTrace();  
  67. <span style="white-space:pre">      </span>}  
  68. <span style="white-space:pre">  </span>}  
  69.   
  70.   
  71. <span style="white-space:pre">  </span>public void onCreate() {  
  72. <span style="white-space:pre">      </span>{  
  73.   
  74.   
  75. <span style="white-space:pre">          </span>// 如果源应用配置有Appliction对象,则替换为源应用Applicaiton,以便不影响源程序逻辑。  
  76. <span style="white-space:pre">          </span>String appClassName = null;  
  77. <span style="white-space:pre">          </span>try {  
  78. <span style="white-space:pre">              </span>ApplicationInfo ai = this.getPackageManager()  
  79. <span style="white-space:pre">                      </span>.getApplicationInfo(this.getPackageName(),  
  80. <span style="white-space:pre">                              </span>PackageManager.GET_META_DATA);  
  81. <span style="white-space:pre">              </span>Bundle bundle = ai.metaData;  
  82. <span style="white-space:pre">              </span>if (bundle != null  
  83. <span style="white-space:pre">                      </span>&& bundle.containsKey("APPLICATION_CLASS_NAME")) {  
  84. <span style="white-space:pre">                  </span>appClassName = bundle.getString("APPLICATION_CLASS_NAME");  
  85. <span style="white-space:pre">              </span>} else {  
  86. <span style="white-space:pre">                  </span>return;  
  87. <span style="white-space:pre">              </span>}  
  88. <span style="white-space:pre">          </span>} catch (NameNotFoundException e) {  
  89. <span style="white-space:pre">              </span>// TODO Auto-generated catch block  
  90. <span style="white-space:pre">              </span>e.printStackTrace();  
  91. <span style="white-space:pre">          </span>}  
  92.   
  93.   
  94. <span style="white-space:pre">          </span>Object currentActivityThread = RefInvoke.invokeStaticMethod(  
  95. <span style="white-space:pre">                  </span>"android.app.ActivityThread""currentActivityThread",  
  96. <span style="white-space:pre">                  </span>new Class[] {}, new Object[] {});  
  97. <span style="white-space:pre">          </span>Object mBoundApplication = RefInvoke.getFieldOjbect(  
  98. <span style="white-space:pre">                  </span>"android.app.ActivityThread", currentActivityThread,  
  99. <span style="white-space:pre">                  </span>"mBoundApplication");  
  100. <span style="white-space:pre">          </span>Object loadedApkInfo = RefInvoke.getFieldOjbect(  
  101. <span style="white-space:pre">                  </span>"android.app.ActivityThread$AppBindData",  
  102. <span style="white-space:pre">                  </span>mBoundApplication, "info");  
  103. <span style="white-space:pre">          </span>RefInvoke.setFieldOjbect("android.app.LoadedApk""mApplication",  
  104. <span style="white-space:pre">                  </span>loadedApkInfo, null);  
  105. <span style="white-space:pre">          </span>Object oldApplication = RefInvoke.getFieldOjbect(  
  106. <span style="white-space:pre">                  </span>"android.app.ActivityThread", currentActivityThread,  
  107. <span style="white-space:pre">                  </span>"mInitialApplication");  
  108. <span style="white-space:pre">          </span>ArrayList<Application> mAllApplications = (ArrayList<Application>) RefInvoke  
  109. <span style="white-space:pre">                  </span>.getFieldOjbect("android.app.ActivityThread",  
  110. <span style="white-space:pre">                          </span>currentActivityThread, "mAllApplications");  
  111. <span style="white-space:pre">          </span>mAllApplications.remove(oldApplication);  
  112. <span style="white-space:pre">          </span>ApplicationInfo appinfo_In_LoadedApk = (ApplicationInfo) RefInvoke  
  113. <span style="white-space:pre">                  </span>.getFieldOjbect("android.app.LoadedApk", loadedApkInfo,  
  114. <span style="white-space:pre">                          </span>"mApplicationInfo");  
  115. <span style="white-space:pre">          </span>ApplicationInfo appinfo_In_AppBindData = (ApplicationInfo) RefInvoke  
  116. <span style="white-space:pre">                  </span>.getFieldOjbect("android.app.ActivityThread$AppBindData",  
  117. <span style="white-space:pre">                          </span>mBoundApplication, "appInfo");  
  118. <span style="white-space:pre">          </span>appinfo_In_LoadedApk.className = appClassName;  
  119. <span style="white-space:pre">          </span>appinfo_In_AppBindData.className = appClassName;  
  120. <span style="white-space:pre">          </span>Application app = (Application) RefInvoke.invokeMethod(  
  121. <span style="white-space:pre">                  </span>"android.app.LoadedApk""makeApplication", loadedApkInfo,  
  122. <span style="white-space:pre">                  </span>new Class[] { boolean.class, Instrumentation.class },  
  123. <span style="white-space:pre">                  </span>new Object[] { falsenull });  
  124. <span style="white-space:pre">          </span>RefInvoke.setFieldOjbect("android.app.ActivityThread",  
  125. <span style="white-space:pre">                  </span>"mInitialApplication", currentActivityThread, app);  
  126.   
  127.   
  128. <span style="white-space:pre">          </span>HashMap mProviderMap = (HashMap) RefInvoke.getFieldOjbect(  
  129. <span style="white-space:pre">                  </span>"android.app.ActivityThread", currentActivityThread,  
  130. <span style="white-space:pre">                  </span>"mProviderMap");  
  131. <span style="white-space:pre">          </span>Iterator it = mProviderMap.values().iterator();  
  132. <span style="white-space:pre">          </span>while (it.hasNext()) {  
  133. <span style="white-space:pre">              </span>Object providerClientRecord = it.next();  
  134. <span style="white-space:pre">              </span>Object localProvider = RefInvoke.getFieldOjbect(  
  135. <span style="white-space:pre">                      </span>"android.app.ActivityThread$ProviderClientRecord",  
  136. <span style="white-space:pre">                      </span>providerClientRecord, "mLocalProvider");  
  137. <span style="white-space:pre">              </span>RefInvoke.setFieldOjbect("android.content.ContentProvider",  
  138. <span style="white-space:pre">                      </span>"mContext", localProvider, app);  
  139. <span style="white-space:pre">          </span>}  
  140. <span style="white-space:pre">          </span>app.onCreate();  
  141. <span style="white-space:pre">      </span>}  
  142. <span style="white-space:pre">  </span>}  
  143.   
  144.   
  145. <span style="white-space:pre">  </span>private void splitPayLoadFromDex(byte[] data) throws IOException {  
  146. <span style="white-space:pre">      </span>byte[] apkdata = decrypt(data);  
  147. <span style="white-space:pre">      </span>int ablen = apkdata.length;  
  148. <span style="white-space:pre">      </span>byte[] dexlen = new byte[4];  
  149. <span style="white-space:pre">      </span>System.arraycopy(apkdata, ablen - 4, dexlen, 04);  
  150. <span style="white-space:pre">      </span>ByteArrayInputStream bais = new ByteArrayInputStream(dexlen);  
  151. <span style="white-space:pre">      </span>DataInputStream in = new DataInputStream(bais);  
  152. <span style="white-space:pre">      </span>int readInt = in.readInt();  
  153. <span style="white-space:pre">      </span>System.out.println(Integer.toHexString(readInt));  
  154. <span style="white-space:pre">      </span>byte[] newdex = new byte[readInt];  
  155. <span style="white-space:pre">      </span>System.arraycopy(apkdata, ablen - 4 - readInt, newdex, 0, readInt);  
  156. <span style="white-space:pre">      </span>File file = new File(apkFileName);  
  157. <span style="white-space:pre">      </span>try {  
  158. <span style="white-space:pre">          </span>FileOutputStream localFileOutputStream = new FileOutputStream(file);  
  159. <span style="white-space:pre">          </span>localFileOutputStream.write(newdex);  
  160. <span style="white-space:pre">          </span>localFileOutputStream.close();  
  161.   
  162.   
  163. <span style="white-space:pre">      </span>} catch (IOException localIOException) {  
  164. <span style="white-space:pre">          </span>throw new RuntimeException(localIOException);  
  165. <span style="white-space:pre">      </span>}  
  166.   
  167.   
  168. <span style="white-space:pre">      </span>ZipInputStream localZipInputStream = new ZipInputStream(  
  169. <span style="white-space:pre">              </span>new BufferedInputStream(new FileInputStream(file)));  
  170. <span style="white-space:pre">      </span>while (true) {  
  171. <span style="white-space:pre">          </span>ZipEntry localZipEntry = localZipInputStream.getNextEntry();  
  172. <span style="white-space:pre">          </span>if (localZipEntry == null) {  
  173. <span style="white-space:pre">              </span>localZipInputStream.close();  
  174. <span style="white-space:pre">              </span>break;  
  175. <span style="white-space:pre">          </span>}  
  176. <span style="white-space:pre">          </span>String name = localZipEntry.getName();  
  177. <span style="white-space:pre">          </span>if (name.startsWith("lib/") && name.endsWith(".so")) {  
  178. <span style="white-space:pre">              </span>File storeFile = new File(libPath + "/"  
  179. <span style="white-space:pre">                      </span>+ name.substring(name.lastIndexOf('/')));  
  180. <span style="white-space:pre">              </span>storeFile.createNewFile();  
  181. <span style="white-space:pre">              </span>FileOutputStream fos = new FileOutputStream(storeFile);  
  182. <span style="white-space:pre">              </span>byte[] arrayOfByte = new byte[1024];  
  183. <span style="white-space:pre">              </span>while (true) {  
  184. <span style="white-space:pre">                  </span>int i = localZipInputStream.read(arrayOfByte);  
  185. <span style="white-space:pre">                  </span>if (i == -1)  
  186. <span style="white-space:pre">                      </span>break;  
  187. <span style="white-space:pre">                  </span>fos.write(arrayOfByte, 0, i);  
  188. <span style="white-space:pre">              </span>}  
  189. <span style="white-space:pre">              </span>fos.flush();  
  190. <span style="white-space:pre">              </span>fos.close();  
  191. <span style="white-space:pre">          </span>}  
  192. <span style="white-space:pre">          </span>localZipInputStream.closeEntry();  
  193. <span style="white-space:pre">      </span>}  
  194. <span style="white-space:pre">      </span>localZipInputStream.close();  
  195.   
  196.   
  197. <span style="white-space:pre">  </span>}  
  198.   
  199.   
  200. <span style="white-space:pre">  </span>private byte[] readDexFileFromApk() throws IOException {  
  201. <span style="white-space:pre">      </span>ByteArrayOutputStream dexByteArrayOutputStream = new ByteArrayOutputStream();  
  202. <span style="white-space:pre">      </span>ZipInputStream localZipInputStream = new ZipInputStream(  
  203. <span style="white-space:pre">              </span>new BufferedInputStream(new FileInputStream(  
  204. <span style="white-space:pre">                      </span>this.getApplicationInfo().sourceDir)));  
  205. <span style="white-space:pre">      </span>while (true) {  
  206. <span style="white-space:pre">          </span>ZipEntry localZipEntry = localZipInputStream.getNextEntry();  
  207. <span style="white-space:pre">          </span>if (localZipEntry == null) {  
  208. <span style="white-space:pre">              </span>localZipInputStream.close();  
  209. <span style="white-space:pre">              </span>break;  
  210. <span style="white-space:pre">          </span>}  
  211. <span style="white-space:pre">          </span>if (localZipEntry.getName().equals("classes.dex")) {  
  212. <span style="white-space:pre">              </span>byte[] arrayOfByte = new byte[1024];  
  213. <span style="white-space:pre">              </span>while (true) {  
  214. <span style="white-space:pre">                  </span>int i = localZipInputStream.read(arrayOfByte);  
  215. <span style="white-space:pre">                  </span>if (i == -1)  
  216. <span style="white-space:pre">                      </span>break;  
  217. <span style="white-space:pre">                  </span>dexByteArrayOutputStream.write(arrayOfByte, 0, i);  
  218. <span style="white-space:pre">              </span>}  
  219. <span style="white-space:pre">          </span>}  
  220. <span style="white-space:pre">          </span>localZipInputStream.closeEntry();  
  221. <span style="white-space:pre">      </span>}  
  222. <span style="white-space:pre">      </span>localZipInputStream.close();  
  223. <span style="white-space:pre">      </span>return dexByteArrayOutputStream.toByteArray();  
  224. <span style="white-space:pre">  </span>}  
  225.   
  226.   
  227. <span style="white-space:pre">  </span>// //直接返回数据,读者可以添加自己解密方法  
  228. <span style="white-space:pre">  </span>private byte[] decrypt(byte[] data) {  
  229. <span style="white-space:pre">      </span>return data;  
  230. <span style="white-space:pre">  </span>}  


        RefInvoke为反射调用工具类:


[java] view plaincopy
  1. package com.android.dexunshell;  
  2.   
  3. import java.lang.reflect.Field;  
  4. import java.lang.reflect.InvocationTargetException;  
  5. import java.lang.reflect.Method;  
  6.   
  7. public class RefInvoke {  
  8.       
  9.     public static  Object invokeStaticMethod(String class_name, String method_name, Class[] pareTyple, Object[] pareVaules){  
  10.           
  11.         try {  
  12.             Class obj_class = Class.forName(class_name);  
  13.             Method method = obj_class.getMethod(method_name,pareTyple);  
  14.             return method.invoke(null, pareVaules);  
  15.         } catch (SecurityException e) {  
  16.             // TODO Auto-generated catch block  
  17.             e.printStackTrace();  
  18.         }  catch (IllegalArgumentException e) {  
  19.             // TODO Auto-generated catch block  
  20.             e.printStackTrace();  
  21.         } catch (IllegalAccessException e) {  
  22.             // TODO Auto-generated catch block  
  23.             e.printStackTrace();  
  24.         } catch (NoSuchMethodException e) {  
  25.             // TODO Auto-generated catch block  
  26.             e.printStackTrace();  
  27.         } catch (InvocationTargetException e) {  
  28.             // TODO Auto-generated catch block  
  29.             e.printStackTrace();  
  30.         } catch (ClassNotFoundException e) {  
  31.             // TODO Auto-generated catch block  
  32.             e.printStackTrace();  
  33.         }  
  34.         return null;  
  35.           
  36.     }  
  37.       
  38.     public static  Object invokeMethod(String class_name, String method_name, Object obj ,Class[] pareTyple, Object[] pareVaules){  
  39.           
  40.         try {  
  41.             Class obj_class = Class.forName(class_name);  
  42.             Method method = obj_class.getMethod(method_name,pareTyple);  
  43.             return method.invoke(obj, pareVaules);  
  44.         } catch (SecurityException e) {  
  45.             // TODO Auto-generated catch block  
  46.             e.printStackTrace();  
  47.         }  catch (IllegalArgumentException e) {  
  48.             // TODO Auto-generated catch block  
  49.             e.printStackTrace();  
  50.         } catch (IllegalAccessException e) {  
  51.             // TODO Auto-generated catch block  
  52.             e.printStackTrace();  
  53.         } catch (NoSuchMethodException e) {  
  54.             // TODO Auto-generated catch block  
  55.             e.printStackTrace();  
  56.         } catch (InvocationTargetException e) {  
  57.             // TODO Auto-generated catch block  
  58.             e.printStackTrace();  
  59.         } catch (ClassNotFoundException e) {  
  60.             // TODO Auto-generated catch block  
  61.             e.printStackTrace();  
  62.         }  
  63.         return null;  
  64.           
  65.     }  
  66.       
  67.     public static Object getFieldOjbect(String class_name,Object obj, String filedName){  
  68.         try {  
  69.             Class obj_class = Class.forName(class_name);  
  70.             Field field = obj_class.getDeclaredField(filedName);  
  71.             field.setAccessible(true);  
  72.             return field.get(obj);  
  73.         } catch (SecurityException e) {  
  74.             // TODO Auto-generated catch block  
  75.             e.printStackTrace();  
  76.         } catch (NoSuchFieldException e) {  
  77.             // TODO Auto-generated catch block  
  78.             e.printStackTrace();  
  79.         } catch (IllegalArgumentException e) {  
  80.             // TODO Auto-generated catch block  
  81.             e.printStackTrace();  
  82.         } catch (IllegalAccessException e) {  
  83.             // TODO Auto-generated catch block  
  84.             e.printStackTrace();  
  85.         } catch (ClassNotFoundException e) {  
  86.             // TODO Auto-generated catch block  
  87.             e.printStackTrace();  
  88.         }  
  89.         return null;  
  90.           
  91.     }  
  92.       
  93.     public static Object getStaticFieldOjbect(String class_name, String filedName){  
  94.           
  95.         try {  
  96.             Class obj_class = Class.forName(class_name);  
  97.             Field field = obj_class.getDeclaredField(filedName);  
  98.             field.setAccessible(true);  
  99.             return field.get(null);  
  100.         } catch (SecurityException e) {  
  101.             // TODO Auto-generated catch block  
  102.             e.printStackTrace();  
  103.         } catch (NoSuchFieldException e) {  
  104.             // TODO Auto-generated catch block  
  105.             e.printStackTrace();  
  106.         } catch (IllegalArgumentException e) {  
  107.             // TODO Auto-generated catch block  
  108.             e.printStackTrace();  
  109.         } catch (IllegalAccessException e) {  
  110.             // TODO Auto-generated catch block  
  111.             e.printStackTrace();  
  112.         } catch (ClassNotFoundException e) {  
  113.             // TODO Auto-generated catch block  
  114.             e.printStackTrace();  
  115.         }  
  116.         return null;  
  117.           
  118.     }  
  119.       
  120.     public static void setFieldOjbect(String classname, String filedName, Object obj, Object filedVaule){  
  121.         try {  
  122.             Class obj_class = Class.forName(classname);  
  123.             Field field = obj_class.getDeclaredField(filedName);  
  124.             field.setAccessible(true);  
  125.             field.set(obj, filedVaule);  
  126.         } catch (SecurityException e) {  
  127.             // TODO Auto-generated catch block  
  128.             e.printStackTrace();  
  129.         } catch (NoSuchFieldException e) {  
  130.             // TODO Auto-generated catch block  
  131.             e.printStackTrace();  
  132.         } catch (IllegalArgumentException e) {  
  133.             // TODO Auto-generated catch block  
  134.             e.printStackTrace();  
  135.         } catch (IllegalAccessException e) {  
  136.             // TODO Auto-generated catch block  
  137.             e.printStackTrace();  
  138.         } catch (ClassNotFoundException e) {  
  139.             // TODO Auto-generated catch block  
  140.             e.printStackTrace();  
  141.         }     
  142.     }  
  143.       
  144.     public static void setStaticOjbect(String class_name, String filedName, Object filedVaule){  
  145.         try {  
  146.             Class obj_class = Class.forName(class_name);  
  147.             Field field = obj_class.getDeclaredField(filedName);  
  148.             field.setAccessible(true);  
  149.             field.set(null, filedVaule);  
  150.         } catch (SecurityException e) {  
  151.             // TODO Auto-generated catch block  
  152.             e.printStackTrace();  
  153.         } catch (NoSuchFieldException e) {  
  154.             // TODO Auto-generated catch block  
  155.             e.printStackTrace();  
  156.         } catch (IllegalArgumentException e) {  
  157.             // TODO Auto-generated catch block  
  158.             e.printStackTrace();  
  159.         } catch (IllegalAccessException e) {  
  160.             // TODO Auto-generated catch block  
  161.             e.printStackTrace();  
  162.         } catch (ClassNotFoundException e) {  
  163.             // TODO Auto-generated catch block  
  164.             e.printStackTrace();  
  165.         }         
  166.     }  
  167.   
  168. }  


三、总结


       本文代码基本实现了APK文件的加壳及脱壳原理,该代码作为实验代码还有诸多地方需要改进。比如:

             1、加壳数据的加密算法的添加。

             2、脱壳代码由java语言实现,可通过C代码的实现对脱壳逻辑进行保护,以达到更好的反逆向分析效果。


阅读更多
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

关闭
关闭
关闭