Android APK加壳技术方案

分类:            Android 破解Android 安全 4624人阅读 评论(4) 收藏 举报

本文章由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头信息。


       代码实现如下:

  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-4, 4); 
  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, 8, 4); 
  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, 12, 20); 
  98.         String hexstr = ""
  99.         for (int i = 0; i < newdt.length; i++) { 
  100.             hexstr += Integer.toString((newdt[i] & 0xff) + 0x100, 16
  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, 32, 4); 
  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.  


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

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

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

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

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

  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。

  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文件资源的加载。


        解壳实现代码:

  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[] { false, null }); 
  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, 0, 4); 
  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.     } 

  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[] { false, null }); 
  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, 0, 4); 
  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为反射调用工具类:


  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.  


三、总结


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

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

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值