精华内容
下载资源
问答
  • Android加壳(加固)原理以及实现

    千次阅读 2017-11-17 09:56:56
    今天又到周末了,憋了好久又要出博客了,今天来介绍一下Android中的如何对Apk进行加固的原理。现阶段。我们知道android中的反编译工作越来越让人操作熟练,我们辛苦的开发出一个apk,结果被人反编译了,那心情真心不...

    一、前言

    今天又到周末了,憋了好久又要出博客了,今天来介绍一下Android中的如何对Apk进行加固的原理。现阶段。我们知道Android中的反编译工作越来越让人操作熟练,我们辛苦的开发出一个apk,结果被人反编译了,那心情真心不舒服。虽然我们混淆,做到native层,但是这都是治标不治本。反编译的技术在更新,那么保护Apk的技术就不能停止。现在网上有很多Apk加固的第三方平台,最有名的应当属于:爱加密和梆梆加固了。其实加固有些人认为很高深的技术,其实不然,说的简单点就是对源Apk进行加密,然后在套上一层壳即可,当然这里还有一些细节需要处理,这就是本文需要介绍的内容了。


    二、原理解析

    下面就来看一下Android中加壳的原理:


    我们在加固的过程中需要三个对象:

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

    2、壳程序Apk(负责解密Apk工作)

    3、加密工具(将源Apk进行加密和壳Dex合并成新的Dex)


    主要步骤:

    我们拿到需要加密的Apk和自己的壳程序Apk,然后用加密算法对源Apk进行加密在将壳Apk进行合并得到新的Dex文件,最后替换壳程序中的dex文件即可,得到新的Apk,那么这个新的Apk我们也叫作脱壳程序Apk.他已经不是一个完整意义上的Apk程序了,他的主要工作是:负责解密源Apk.然后加载Apk,让其正常运行起来。


    在这个过程中我们可能需要了解的一个知识是:如何将源Apk和壳Apk进行合并成新的Dex

    这里就需要了解Dex文件的格式了。下面就来简单介绍一下Dex文件的格式

    具体Dex文件格式的详细介绍可以查看这个文件:http://download.csdn.net/detail/jiangwei0910410003/9102599

    主要来看一下Dex文件的头部信息,其实Dex文件和Class文件的格式分析原理都是一样的,他们都是有固定的格式,我们知道现在反编译的一些工具:

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

    2、dex2jar:将dex文件转化成jar,原理也是一样的,只要知道Dex文件的格式,能够解析出dex文件中的类信息就可以了

    当然我们在分析这个文件的时候,最重要的还是头部信息,应该他是一个文件的开始部分,也是索引部分,内部信息很重要。


    我们今天只要关注上面红色标记的三个部分:

    1) checksum 

    文件校验码 ,使用alder32 算法校验文件除去 maigc ,checksum 外余下的所有文件区域 ,用于检查文件错误 。

    2) signature 

    使用 SHA-1 算法 hash 除去 magic ,checksum 和 signature 外余下的所有文件区域 ,用于唯一识别本文件 。

    3) file_size

    Dex 文件的大小 。

    为什么说我们只需要关注这三个字段呢?

    因为我们需要将一个文件(加密之后的源Apk)写入到Dex中,那么我们肯定需要修改文件校验码(checksum).因为他是检查文件是否有错误。那么signature也是一样,也是唯一识别文件的算法。还有就是需要修改dex文件的大小。

    不过这里还需要一个操作,就是标注一下我们加密的Apk的大小,因为我们在脱壳的时候,需要知道Apk的大小,才能正确的得到Apk。那么这个值放到哪呢?这个值直接放到文件的末尾就可以了。

    所以总结一下我们需要做:修改Dex的三个文件头,将源Apk的大小追加到壳dex的末尾就可以了。

    我们修改之后得到新的Dex文件样式如下:


    那么我们知道原理了,下面就是代码实现了。所以这里有三个工程:

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

    2、脱壳项目(解密源Apk和加载Apk)

    3、对源Apk进行加密和脱壳项目的Dex的合并


    三、项目案例

    下面先来看一下源程序

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


    需要一个Application类,这个到后面说为什么需要:

    MyApplication.java

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

    就是打印一下onCreate方法。


    MainActivity.java

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


    2、加壳程序项目:DexShellTools


    加壳程序其实就是一个Java工程,因为我们从上面的分析可以看到,他的工作就是加密源Apk,然后将其写入到脱壳Dex文件中,修改文件头,得到一个新的Dex文件即可。

    看一下代码:

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


    下面来分析一下:

    红色部分其实就是最核心的工作:

    1>、加密源程序Apk文件

    [java]  view plain  copy
    1. byte[] payloadArray = encrpt(readFileBytes(payloadSrcFile));//以二进制形式读出apk,并进行加密处理//对源Apk进行加密操作  
    加密算法很简单:

    [java]  view plain  copy
    1. //直接返回数据,读者可以添加自己加密方法  
    2. private static byte[] encrpt(byte[] srcdata){  
    3.     for(int i = 0;i<srcdata.length;i++){  
    4.         srcdata[i] = (byte)(0xFF ^ srcdata[i]);  
    5.     }  
    6.     return srcdata;  
    7. }  
    对每个字节进行异或一下即可。

    (说明:这里是为了简单,所以就用了很简单的加密算法了,其实为了增加破解难度,我们应该使用更高效的加密算法,同事最好将加密操作放到native层去做)


    2>、合并文件:将加密之后的Apk和原脱壳Dex进行合并

    [java]  view plain  copy
    1. int payloadLen = payloadArray.length;  
    2. int unShellDexLen = unShellDexArray.length;  
    3. int totalLen = payloadLen + unShellDexLen +4;//多出4字节是存放长度的。  
    4. byte[] newdex = new byte[totalLen]; // 申请了新的长度  
    5. //添加解壳代码  
    6. System.arraycopy(unShellDexArray, 0, newdex, 0, unShellDexLen);//先拷贝dex内容  
    7. //添加加密后的解壳数据  
    8. System.arraycopy(payloadArray, 0, newdex, unShellDexLen, payloadLen);//再在dex内容后面拷贝apk的内容  

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

    [java]  view plain  copy
    1. //添加解壳数据长度  
    2. System.arraycopy(intToByte(payloadLen), 0, newdex, totalLen-44);//最后4为长度  

    4>、修改新Dex文件的文件头信息:file_size; sha1; check_sum
    [java]  view plain  copy
    1. //修改DEX file size文件头  
    2. fixFileSizeHeader(newdex);  
    3. //修改DEX SHA1 文件头  
    4. fixSHA1Header(newdex);  
    5. //修改DEX CheckSum文件头  
    6. fixCheckSumHeader(newdex);  
    具体修改可以参照之前说的文件头格式,修改指定位置的字节值即可。


    这里我们还需要两个输入文件:

    1>、源Apk文件:ForceApkObj.apk

    2>、脱壳程序的Dex文件:ForceApkObj.dex

    那么第一个文件我们都知道,就是上面的源程序编译之后的Apk文件,那么第二个文件我们怎么得到呢?这个就是我们要讲到的第三个项目:脱壳程序项目,他是一个Android项目,我们在编译之后,能够得到他的classes.dex文件,然后修改一下名称就可。


    3、脱壳项目:ReforceApk


    在讲解这个项目之前,我们先来了解一下这个脱壳项目的工作:

    1>、通过反射置换android.app.ActivityThread 中的mClassLoader为加载解密出APK的DexClassLoader,该DexClassLoader一方面加载了源程序、另一方面以原mClassLoader为父节点,这就保证了即加载了源程序又没有放弃原先加载的资源与系统代码。

    关于这部分内容,不了解的同学可以看一下ActivityThread.java的源码:


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

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

    如何得到系统加载Apk的类加载器,然后我们怎么将加载进来的Apk运行起来等问题都在这篇文章中说到了。


    2>、找到源程序的Application,通过反射建立并运行。

    这里需要注意的是,我们现在是加载一个完整的Apk,让他运行起来,那么我们知道一个Apk运行的时候都是有一个Application对象的,这个也是一个程序运行之后的全局类。所以我们必须找到解密之后的源Apk的Application类,运行的他的onCreate方法,这样源Apk才开始他的运行生命周期。这里我们如何得到源Apk的Application的类呢?这个我们后面会说道。使用meta标签进行设置。


    下面来看一下整体的流程图:



    所以我们看到这里还需要一个核心的技术就是动态加载。关于动态加载技术,不了解的同学可以看这篇文章:

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


    下面来看一下代码:

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

    首先我们来看一下具体步骤的代码实现:

    1>、得到脱壳Apk中的dex文件,然后从这个文件中得到源程序Apk.进行解密,然后加载

    [java]  view plain  copy
    1. //这是context 赋值  
    2. @Override  
    3. protected void attachBaseContext(Context base) {  
    4.     super.attachBaseContext(base);  
    5.     try {  
    6.         //创建两个文件夹payload_odex,payload_lib 私有的,可写的文件目录  
    7.         File odex = this.getDir("payload_odex", MODE_PRIVATE);  
    8.         File libs = this.getDir("payload_lib", MODE_PRIVATE);  
    9.         odexPath = odex.getAbsolutePath();  
    10.         libPath = libs.getAbsolutePath();  
    11.         apkFileName = odex.getAbsolutePath() + "/payload.apk";  
    12.         File dexFile = new File(apkFileName);  
    13.         Log.i("demo""apk size:"+dexFile.length());  
    14.         if (!dexFile.exists())  
    15.         {  
    16.             dexFile.createNewFile();  //在payload_odex文件夹内,创建payload.apk  
    17.             // 读取程序classes.dex文件  
    18.             byte[] dexdata = this.readDexFileFromApk();  
    19.   
    20.             // 分离出解壳后的apk文件已用于动态加载  
    21.             this.splitPayLoadFromDex(dexdata);  
    22.         }  
    23.         // 配置动态加载环境  
    24.         Object currentActivityThread = RefInvoke.invokeStaticMethod(  
    25.                 "android.app.ActivityThread""currentActivityThread",  
    26.                 new Class[] {}, new Object[] {});//获取主线程对象 http://blog.csdn.net/myarrow/article/details/14223493  
    27.         String packageName = this.getPackageName();//当前apk的包名  
    28.         //下面两句不是太理解  
    29.         ArrayMap mPackages = (ArrayMap) RefInvoke.getFieldOjbect(  
    30.                 "android.app.ActivityThread", currentActivityThread,  
    31.                 "mPackages");  
    32.         WeakReference wr = (WeakReference) mPackages.get(packageName);  
    33.         //创建被加壳apk的DexClassLoader对象  加载apk内的类和本地代码(c/c++代码)  
    34.         DexClassLoader dLoader = new DexClassLoader(apkFileName, odexPath,  
    35.                 libPath, (ClassLoader) RefInvoke.getFieldOjbect(  
    36.                         "android.app.LoadedApk", wr.get(), "mClassLoader"));  
    37.         //base.getClassLoader(); 是不是就等同于 (ClassLoader) RefInvoke.getFieldOjbect()? 有空验证下//?  
    38.         //把当前进程的DexClassLoader 设置成了被加壳apk的DexClassLoader  ----有点c++中进程环境的意思~~  
    39.         RefInvoke.setFieldOjbect("android.app.LoadedApk""mClassLoader",  
    40.                 wr.get(), dLoader);  
    41.   
    42.         Log.i("demo","classloader:"+dLoader);  
    43.   
    44.         try{  
    45.             Object actObj = dLoader.loadClass("com.example.forceapkobj.MainActivity");  
    46.             Log.i("demo""actObj:"+actObj);  
    47.         }catch(Exception e){  
    48.             Log.i("demo""activity:"+Log.getStackTraceString(e));  
    49.         }  
    50.   
    51.   
    52.     } catch (Exception e) {  
    53.         Log.i("demo""error:"+Log.getStackTraceString(e));  
    54.         e.printStackTrace();  
    55.     }  
    56. }  
    这里需要注意的一个问题,就是我们需要找到一个时机,就是在脱壳程序还没有运行起来的时候,来加载源程序的Apk,执行他的onCreate方法,那么这个时机不能太晚,不然的话,就是运行脱壳程序,而不是源程序了。查看源码我们知道。Application中有一个方法:attachBaseContext这个方法,他在Application的onCreate方法执行前就会执行了,那么我们的工作就需要在这里进行

    1)、从脱壳程序Apk中找到源程序Apk,并且进行解密操作

    [java]  view plain  copy
    1. //创建两个文件夹payload_odex,payload_lib 私有的,可写的文件目录  
    2. File odex = this.getDir("payload_odex", MODE_PRIVATE);  
    3. File libs = this.getDir("payload_lib", MODE_PRIVATE);  
    4. odexPath = odex.getAbsolutePath();  
    5. libPath = libs.getAbsolutePath();  
    6. apkFileName = odex.getAbsolutePath() + "/payload.apk";  
    7. File dexFile = new File(apkFileName);  
    8. Log.i("demo""apk size:"+dexFile.length());  
    9. if (!dexFile.exists())  
    10. {  
    11.     dexFile.createNewFile();  //在payload_odex文件夹内,创建payload.apk  
    12.     // 读取程序classes.dex文件  
    13.     byte[] dexdata = this.readDexFileFromApk();  
    14.   
    15.     // 分离出解壳后的apk文件已用于动态加载  
    16.     this.splitPayLoadFromDex(dexdata);  
    17. }  
    这个脱壳解密操作一定要和我们之前的加壳以及加密操作对应,不然就会出现Dex加载错误问题

    A) 从Apk中获取到Dex文件

    [java]  view plain  copy
    1. /** 
    2.  * 从apk包里面获取dex文件内容(byte) 
    3.  * @return 
    4.  * @throws IOException 
    5.  */  
    6. private byte[] readDexFileFromApk() throws IOException {  
    7.     ByteArrayOutputStream dexByteArrayOutputStream = new ByteArrayOutputStream();  
    8.     ZipInputStream localZipInputStream = new ZipInputStream(  
    9.             new BufferedInputStream(new FileInputStream(  
    10.                     this.getApplicationInfo().sourceDir)));  
    11.     while (true) {  
    12.         ZipEntry localZipEntry = localZipInputStream.getNextEntry();  
    13.         if (localZipEntry == null) {  
    14.             localZipInputStream.close();  
    15.             break;  
    16.         }  
    17.         if (localZipEntry.getName().equals("classes.dex")) {  
    18.             byte[] arrayOfByte = new byte[1024];  
    19.             while (true) {  
    20.                 int i = localZipInputStream.read(arrayOfByte);  
    21.                 if (i == -1)  
    22.                     break;  
    23.                 dexByteArrayOutputStream.write(arrayOfByte, 0, i);  
    24.             }  
    25.         }  
    26.         localZipInputStream.closeEntry();  
    27.     }  
    28.     localZipInputStream.close();  
    29.     return dexByteArrayOutputStream.toByteArray();  
    30. }  
    其实就是解压Apk文件,直接得到dex文件即可


    B) 从脱壳Dex中得到源Apk文件

    [java]  view plain  copy
    1. /** 
    2.  * 释放被加壳的apk文件,so文件 
    3.  * @param data 
    4.  * @throws IOException 
    5.  */  
    6. private void splitPayLoadFromDex(byte[] apkdata) throws IOException {  
    7.     int ablen = apkdata.length;  
    8.     //取被加壳apk的长度   这里的长度取值,对应加壳时长度的赋值都可以做些简化  
    9.     byte[] dexlen = new byte[4];  
    10.     System.arraycopy(apkdata, ablen - 4, dexlen, 04);  
    11.     ByteArrayInputStream bais = new ByteArrayInputStream(dexlen);  
    12.     DataInputStream in = new DataInputStream(bais);  
    13.     int readInt = in.readInt();  
    14.     System.out.println(Integer.toHexString(readInt));  
    15.     byte[] newdex = new byte[readInt];  
    16.     //把被加壳apk内容拷贝到newdex中  
    17.     System.arraycopy(apkdata, ablen - 4 - readInt, newdex, 0, readInt);  
    18.     //这里应该加上对于apk的解密操作,若加壳是加密处理的话  
    19.     //?  
    20.       
    21.     //对源程序Apk进行解密  
    22.     newdex = decrypt(newdex);  
    23.       
    24.     //写入apk文件     
    25.     File file = new File(apkFileName);  
    26.     try {  
    27.         FileOutputStream localFileOutputStream = new FileOutputStream(file);  
    28.         localFileOutputStream.write(newdex);  
    29.         localFileOutputStream.close();  
    30.     } catch (IOException localIOException) {  
    31.         throw new RuntimeException(localIOException);  
    32.     }  
    33.       
    34.     //分析被加壳的apk文件  
    35.     ZipInputStream localZipInputStream = new ZipInputStream(  
    36.             new BufferedInputStream(new FileInputStream(file)));  
    37.     while (true) {  
    38.         ZipEntry localZipEntry = localZipInputStream.getNextEntry();//不了解这个是否也遍历子目录,看样子应该是遍历的  
    39.         if (localZipEntry == null) {  
    40.             localZipInputStream.close();  
    41.             break;  
    42.         }  
    43.         //取出被加壳apk用到的so文件,放到 libPath中(data/data/包名/payload_lib)  
    44.         String name = localZipEntry.getName();  
    45.         if (name.startsWith("lib/") && name.endsWith(".so")) {  
    46.             File storeFile = new File(libPath + "/"  
    47.                     + name.substring(name.lastIndexOf('/')));  
    48.             storeFile.createNewFile();  
    49.             FileOutputStream fos = new FileOutputStream(storeFile);  
    50.             byte[] arrayOfByte = new byte[1024];  
    51.             while (true) {  
    52.                 int i = localZipInputStream.read(arrayOfByte);  
    53.                 if (i == -1)  
    54.                     break;  
    55.                 fos.write(arrayOfByte, 0, i);  
    56.             }  
    57.             fos.flush();  
    58.             fos.close();  
    59.         }  
    60.         localZipInputStream.closeEntry();  
    61.     }  
    62.     localZipInputStream.close();  
    63.   
    64.   
    65. }  


    C) 解密源程序Apk
    [java]  view plain  copy
    1. 直接返回数据,读者可以添加自己解密方法  
    2. private byte[] decrypt(byte[] srcdata) {  
    3.     for(int i=0;i<srcdata.length;i++){  
    4.         srcdata[i] = (byte)(0xFF ^ srcdata[i]);  
    5.     }  
    6.     return srcdata;  
    7. }  
    这个解密算法和加密算法是一致的


    2>、加载解密之后的源程序Apk

    [java]  view plain  copy
    1. //配置动态加载环境  
    2. Object currentActivityThread = RefInvoke.invokeStaticMethod(  
    3.         "android.app.ActivityThread""currentActivityThread",  
    4.         new Class[] {}, new Object[] {});//获取主线程对象 http://blog.csdn.net/myarrow/article/details/14223493  
    5. String packageName = this.getPackageName();//当前apk的包名  
    6. //下面两句不是太理解  
    7. ArrayMap mPackages = (ArrayMap) RefInvoke.getFieldOjbect(  
    8.         "android.app.ActivityThread", currentActivityThread,  
    9.         "mPackages");  
    10. WeakReference wr = (WeakReference) mPackages.get(packageName);  
    11. //创建被加壳apk的DexClassLoader对象  加载apk内的类和本地代码(c/c++代码)  
    12. DexClassLoader dLoader = new DexClassLoader(apkFileName, odexPath,  
    13.         libPath, (ClassLoader) RefInvoke.getFieldOjbect(  
    14.                 "android.app.LoadedApk", wr.get(), "mClassLoader"));  
    15. //base.getClassLoader(); 是不是就等同于 (ClassLoader) RefInvoke.getFieldOjbect()? 有空验证下//?  
    16. //把当前进程的DexClassLoader 设置成了被加壳apk的DexClassLoader  ----有点c++中进程环境的意思~~  
    17. RefInvoke.setFieldOjbect("android.app.LoadedApk""mClassLoader",  
    18.         wr.get(), dLoader);  
    19.   
    20. Log.i("demo","classloader:"+dLoader);  
    21.   
    22. try{  
    23.     Object actObj = dLoader.loadClass("com.example.forceapkobj.MainActivity");  
    24.     Log.i("demo""actObj:"+actObj);  
    25. }catch(Exception e){  
    26.     Log.i("demo""activity:"+Log.getStackTraceString(e));  
    27. }  


    2)、找到源程序的Application程序,让其运行
    [java]  view plain  copy
    1. @Override  
    2. public void onCreate() {  
    3.     {  
    4.         //loadResources(apkFileName);  
    5.           
    6.         Log.i("demo""onCreate");  
    7.         // 如果源应用配置有Appliction对象,则替换为源应用Applicaiton,以便不影响源程序逻辑。  
    8.         String appClassName = null;  
    9.         try {  
    10.             ApplicationInfo ai = this.getPackageManager()  
    11.                     .getApplicationInfo(this.getPackageName(),  
    12.                             PackageManager.GET_META_DATA);  
    13.             Bundle bundle = ai.metaData;  
    14.             if (bundle != null && bundle.containsKey("APPLICATION_CLASS_NAME")) {  
    15.                 appClassName = bundle.getString("APPLICATION_CLASS_NAME");//className 是配置在xml文件中的。  
    16.             } else {  
    17.                 Log.i("demo""have no application class name");  
    18.                 return;  
    19.             }  
    20.         } catch (NameNotFoundException e) {  
    21.             Log.i("demo""error:"+Log.getStackTraceString(e));  
    22.             e.printStackTrace();  
    23.         }  
    24.         //有值的话调用该Applicaiton  
    25.         Object currentActivityThread = RefInvoke.invokeStaticMethod(  
    26.                 "android.app.ActivityThread""currentActivityThread",  
    27.                 new Class[] {}, new Object[] {});  
    28.         Object mBoundApplication = RefInvoke.getFieldOjbect(  
    29.                 "android.app.ActivityThread", currentActivityThread,  
    30.                 "mBoundApplication");  
    31.         Object loadedApkInfo = RefInvoke.getFieldOjbect(  
    32.                 "android.app.ActivityThread$AppBindData",  
    33.                 mBoundApplication, "info");  
    34.         //把当前进程的mApplication 设置成了null  
    35.         RefInvoke.setFieldOjbect("android.app.LoadedApk""mApplication",  
    36.                 loadedApkInfo, null);  
    37.         Object oldApplication = RefInvoke.getFieldOjbect(  
    38.                 "android.app.ActivityThread", currentActivityThread,  
    39.                 "mInitialApplication");  
    40.         //http://www.codeceo.com/article/android-context.html  
    41.         ArrayList<Application> mAllApplications = (ArrayList<Application>) RefInvoke  
    42.                 .getFieldOjbect("android.app.ActivityThread",  
    43.                         currentActivityThread, "mAllApplications");  
    44.         mAllApplications.remove(oldApplication);//删除oldApplication  
    45.           
    46.         ApplicationInfo appinfo_In_LoadedApk = (ApplicationInfo) RefInvoke  
    47.                 .getFieldOjbect("android.app.LoadedApk", loadedApkInfo,  
    48.                         "mApplicationInfo");  
    49.         ApplicationInfo appinfo_In_AppBindData = (ApplicationInfo) RefInvoke  
    50.                 .getFieldOjbect("android.app.ActivityThread$AppBindData",  
    51.                         mBoundApplication, "appInfo");  
    52.         appinfo_In_LoadedApk.className = appClassName;  
    53.         appinfo_In_AppBindData.className = appClassName;  
    54.         Application app = (Application) RefInvoke.invokeMethod(  
    55.                 "android.app.LoadedApk""makeApplication", loadedApkInfo,  
    56.                 new Class[] { boolean.class, Instrumentation.class },  
    57.                 new Object[] { falsenull });//执行 makeApplication(false,null)  
    58.         RefInvoke.setFieldOjbect("android.app.ActivityThread",  
    59.                 "mInitialApplication", currentActivityThread, app);  
    60.   
    61.   
    62.         ArrayMap mProviderMap = (ArrayMap) RefInvoke.getFieldOjbect(  
    63.                 "android.app.ActivityThread", currentActivityThread,  
    64.                 "mProviderMap");  
    65.         Iterator it = mProviderMap.values().iterator();  
    66.         while (it.hasNext()) {  
    67.             Object providerClientRecord = it.next();  
    68.             Object localProvider = RefInvoke.getFieldOjbect(  
    69.                     "android.app.ActivityThread$ProviderClientRecord",  
    70.                     providerClientRecord, "mLocalProvider");  
    71.             RefInvoke.setFieldOjbect("android.content.ContentProvider",  
    72.                     "mContext", localProvider, app);  
    73.         }  
    74.           
    75.         Log.i("demo""app:"+app);  
    76.           
    77.         app.onCreate();  
    78.     }  
    79. }  
    直接在脱壳的Application中的onCreate方法中进行就可以了。这里我们还可以看到是通过AndroidManifest.xml中的meta标签获取源程序Apk中的Application对象的。

    下面来看一下AndoridManifest.xml文件中的内容:

    在这里我们定义了源程序Apk的Application类名。


    项目下载:http://download.csdn.net/detail/jiangwei0910410003/9102741


    四、运行程序

    那么到这里我们就介绍完了,这三个项目的内容,下面就来看看如何运行吧:

    运行步骤:

    第一步:得到源程序Apk文件和脱壳程序的Dex文件

       

    运行源程序和脱壳程序项目,之后得到这两个文件(记得将classes.dex文件改名ForceApkObj.dex),然后使用加壳程序进行加壳:

    这里的ForceApkObj.apk文件和ForceApkObj.dex文件是输入文件,输出的是classes.dex文件。


    第二步:替换脱壳程序中的classes.dex文件

    我们在第一步中得到加壳之后的classes.dex文件之后,并且我们在第一步运行脱壳项目的时候得到一个ReforceApk.apk文件,这时候我们使用解压缩软件进行替换:



    第三步:我们在第二步的时候得到替换之后的ReforceApk.apk文件,这个文件因为被修改了,所以我们需要从新对他签名,不然运行也是报错的。


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

    下载之后的工具需要用ReforeceApk.apk文件替换ReforceApk_des.apk文件,然后运行run.bat就可以得到签名之后的文件了。

    run.bat文件的命令如下:

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

    这里最主要的命令就是中间的一条签名的命令,关于命令的参数说明如下:

    jarsigner -verbose -keystore 签名文件 -storepass 密码  -keypass alias的密码 -sigfile CERT -digestalg SHA1 -sigalg MD5withRSA  签名后的文件 签名前的apk alias名称

    eg:
    jarsigner -verbose -keystore forceapk -storepass 123456 -keypass 123456 -sigfile CERT -digestalg SHA1 -sigalg MD5withRSA -signedjar ReforceApk_des.apk ReforceApk_src.apk jiangwei

    签名文件的密码:123456
    alais的密码:123456


    所以这里我们在得到ReforceApk.apk文件的时候,需要签名,关于Eclipse中如何签名一个Apk的话,这里就不多说了,自己google一下吧:



    那么通过上面的三个步骤之后我们得到一个签名之后的最终文件:ReforceApk_des.apk

    我们安装这个Apk,然后运行,效果如下:


    看到运行结果的那一瞬间,我们是多么的开心,多么的有成就感,但是这个过程中遇到的问题,是可想而知的。

    我们这个时候再去反编译一下源程序Apk(这个文件是我们脱壳出来的payload.apk,看ReforeceApk中的代码,就知道他的位置了)


    发现dex文件格式是不正确的。说明我们的加固是成功的。


    五、遇到的问题

    1、研究的过程中遇到签名不正确的地方,开始的时候,直接替换dex文件之后,就直接运行了Apk,但是总是提示签名不正确。

    2、运行的过程中说找不到源程序中的Activity,这个问题其实我在动态加载的那篇文章中说道了,我们需要在脱壳程序中的AndroidManifest.xml中什么一下源程序中的Activiity:



    六、技术要点

    1、对Dex文件格式的了解

    2、动态加载技术的深入掌握

    3、Application的执行流程的了解

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

    5、如何从新签名一个Apk程序


    七、综合概述

    我们通过上面的过程可以看到,关于Apk加固的工作还是挺复杂的,涉及到的东西也挺多的,下面就在来总结一下吧:

    1、加壳程序

    任务:对源程序Apk进行加密,合并脱壳程序的Dex文件 ,然后输入一个加壳之后的Dex文件

    语言:任何语言都可以,不限于Java语言

    技术点:对Dex文件格式的解析


    2、脱壳程序

    任务:获取源程序Apk,进行解密,然后动态加载进来,运行程序

    语言:Android项目(Java)

    技术点:如何从Apk中获取Dex文件,动态加载Apk,使用反射运行Application


    八、总结

    Android中的Apk反编译可能是每个开发都会经历的事,但是在反编译的过程中,对于源程序的开发者来说那是不公平的,那么Apk加固也是应运而生,但是即使是这样,我们也还是做不到那么的安全,现在网上也是有很多文章在解析梆梆加固的原理了。而且有人破解成功了,那么加固还不是怎么安全。最后一句话:逆向和加固是一个永不停息的战争。

    展开全文
  • android apk加壳

    2018-01-08 14:29:03
    给apk加壳,运行时动态把壳去掉,让apk更加安全,适合研究android安全
  • Android的虚拟机ART和davilk都是JVM的一种实现,使用寄存器来实现。 JVM的类加载器包括3种: Bootstrap ClassLoader(引导类加载器) C/C++代码实现的加载器,用于加载指定的JDK的核心类库,比如java.lang.、java....
    #grep -Ril "IBM" /tmp
    

    递归列出/tmp目录下包含文本字符串"IBM"的文件
    有时要修复dex文件的前八个字节。

    ClassLoader和动态加载

    类加载器

    Android的虚拟机ART和davilk都是JVM的一种实现,使用寄存器来实现。
    JVM的类加载器包括3种:

    1. Bootstrap ClassLoader(引导类加载器)
      C/C++代码实现的加载器,用于加载指定的JDK的核心类库,比如java.lang.、java.uti.等这些系统类。Java虚拟机的启动就是通过Bootstrap ,该Classloader在java里无法获取,负责加载/lib下的类。
    2. Extensions ClassLoader(拓展类加载器)
      Java中的实现类为ExtClassLoader,提供了除了系统类之外的额外功能,可以在java里获取,负责加载/lib/ext下的类。
    3. Application ClassLoader(应用程序类加载器)
      Java中的实现类为AppClassLoader,是与我们接触对多的类加载器,开发人员写的代码默认就是由它来加载,ClassLoader.getSystemClassLoader返回的就是它。

    双亲委派

    双亲委派模式的工作原理的是;如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行,如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达顶层的启动类加载器,如果父类加载器可以完成类加载任务,就成功返回,倘若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载,这就是双亲委派模式,即每个儿子都不愿意干活,每次有活就丢给父亲去干,直到父亲说这件事我也干不了时,儿子自己想办法去完成,这个就是双亲委派。

    为什么要有双亲委派?

    1. 避免重复加载,如果已经加载过一次Class,可以直接读取已经加载的Class。
    2. 更加安全,无法自定义类来替代系统的类,可以防止核心API库被随意篡改。

    类加载的时机:

    隐式加载:

    • 创建类的实例
    • 访问类的静态变量,或者为静态变量赋值
    • 调用类的静态方法
    • 使用反射方式来强制创建某个类或接口对应的java.lang.Class对象
    • 初始化某个类的子类

    显式加载:两者又有所区别

    • 使用LoadClass()加载
    • 使用forName()加载
    • 通过源码分析Android下类加载的流程

    类加载

    1. 装载:查找和导入Class文件
    2. 链接:其中解析步骤是可以选择的
      (a)检查:检查载入的class文件数据的正确性
      (b)准备:给类的静态变量分配存储空间
      (c)解析:将符号引用转成直接引用
    3. 初始化:即调用函数,对静态变量,静态代码块执行初始化工作

    Android类加载器

    ClassLoader的继承关系,其中,InMemoryDexClassLoader为Android8.0新引入的ClassLoader.

    Android系统中与ClassLoader相关的一共有8个:
    ClassLoader为抽象类;
    BootClassLoader预加载常用类,单例模式。与Java中的BootClassLoader不同,它并不是由C/C++代码实现,而是由Java实现的;
    BaseDexClassLoader是PathClassLoader、DexClassLoader、InMemoryDexClassLoader的父类,类加载的主要逻辑都是在BaseDexClassLoader完成的。
    SecureClassLoader继承了抽象类ClassLoader,拓展了ClassLoader类加入了权限方面的功能,加强了安全性,其子类URLClassLoader是用URL路径从jar文件中加载类和资源。
    其中重点关注的是PathClassLoader和DexClassLoader。
    PathClassLoader是Android默认使用的类加载器,一个apk中的Activity等类便是在其中加载。
    DexClassLoader可以加载任意目录下的dex/jar/apk/zip文件,比PathClassLoader更灵活,是实现插件化、热修复以及dex加壳的重点。
    Android8.0新引入InMemoryDexClassLoader,从名字便可看出是用于直接从内存中加载dex。

    ClassLoader:

    实例

    写一个加载SD卡dex的app,原dex的源文件如下:

    加载app如下:
    首先要添加SD卡权限。

    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"></uses-permission>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"></uses-permission>
    
    package com.yrq.loaddex;
    
    import androidx.appcompat.app.AppCompatActivity;
    
    import android.content.Context;
    import android.os.Bundle;
    
    import java.io.File;
    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;
    
    import dalvik.system.DexClassLoader;
    
    public class MainActivity extends AppCompatActivity {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            Context appContext=this.getApplicationContext();
            testDexClassLoader(appContext,"/sdcard/3.dex");
        }
        //context用于获取APP私有目录
        public void testDexClassLoader(Context context,String dexFilePath){
            File opfile=context.getDir("opt_dex",0);//存放提出的dex文件
            File libfile=context.getDir("Lib_dex",0);//存放依赖的so文件
    
            DexClassLoader dexClassLoader=new DexClassLoader(dexFilePath,opfile.getAbsolutePath(),libfile.getAbsolutePath(),context.getClassLoader());
            //package com.yrq.test02;
            Class clazz=null;
            try {
                clazz=(Class)dexClassLoader.loadClass("com.yrq.test02.TestClass");
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
            if(clazz!=null){
                try {
                    Method testFun=clazz.getDeclaredMethod("testFun");
                    try {
                        Object obj=clazz.newInstance();
                        try {
                            testFun.invoke(obj);
                        } catch (InvocationTargetException e) {
                            e.printStackTrace();
                        }
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    } catch (InstantiationException e) {
                        e.printStackTrace();
                    }
    
    
                } catch (NoSuchMethodException e) {
                    e.printStackTrace();
                }
            }
        }
    
    
    }
    

    结果

    01-15 03:30:47.306 26185-26185/com.yrq.loaddex I/YRQ: i am from com.yrq.test02.TestClass.testFUnc
    

    加壳APP运行流程和ClassLoader修正

    App进程的创建流程:

    其中这个函数ActivityThread为单例模式。
    这个类中有一个静态函数currentActivityThread,可以通过其获取ActivityThread实例。
    其中有个ArrayMap的loadedApk变量中就有加载App的mClassloader即pathclassloader。

    关于app的执行。直到handlebindapplication函数里的makeApplication,才开始执行App的代码。

    private void handleBindApplication(AppBindData data) {
        //step 1: 创建LoadedApk对象
        data.info = getPackageInfoNoCheck(data.appInfo, data.compatInfo);
        ...
        //step 2: 创建ContextImpl对象;
        final ContextImpl appContext = ContextImpl.createAppContext(this, data.info);
     
        //step 3: 创建Instrumentation
        mInstrumentation = new Instrumentation();
     
        //step 4: 创建Application对象;在makeApplication函数中调用了newApplication,在该函数中又调用了app.attach(context),在attach函数中调用了Application.attachBaseContext函数
        Application app = data.info.makeApplication(data.restrictedBackupMode, null);
        mInitialApplication = app;
     
        //step 5: 安装providers
        List<ProviderInfo> providers = data.providers;
        installContentProviders(app, providers);
     
        //step 6: 执行Application.Create回调
        mInstrumentation.callApplicationOnCreate(app);
    

    APP运行流程

    • BootClassLoader加载系统核心库
    • PathClassLoader加载APP自身dex
    • 进入APP自身组件开始执行
    • 调用声明Application的attachBaseContext
    • 调用声明Application的onCreate

    加壳应用的运行流程


    在加壳程序中,不能直接跟加载插件一样使用DexClassLoader,因为系统的PathClassLoader要加载一些组件,这些组件都无法找到。就引出了一个问题:怎么解决动态加载dex中类的声明周期,怎么加载插件dex中的activity。

    生命周期类处理

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

    两种解决方案:
    1、替换系统组件类加载器为我们的DexClassLoader,同时设置DexClassLoader的parent为系统组件类加载器;
    2、打破原有的双亲关系,在系统组件类加载器和BootClassLoader的中间插入我们自己的DexClassLoader即可;


    代码如下:

    package com.yrq.loaddex;
    
    import androidx.appcompat.app.AppCompatActivity;
    
    import android.app.Activity;
    import android.content.Context;
    import android.content.Intent;
    import android.os.Bundle;
    import android.util.ArrayMap;
    
    import java.io.File;
    import java.lang.ref.WeakReference;
    import java.lang.reflect.Field;
    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;
    
    import dalvik.system.DexClassLoader;
    
    public class MainActivity extends AppCompatActivity {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            Context appContext=this.getApplicationContext();
            //testDexClassLoader(appContext,"/sdcard/3.dex");
            startTestActivity(this,"/sdcard/4.dex");
        }
        
        
      //第一种解决方案,自己实现的DexClassLoader替换APP组件ClassLoader:mClassLoader
        第一步是找到mclassloader
        public void replaceClassloader(ClassLoader classloader){
            //首先获得ActivityThread实例
            try {
                Class ActivityThreadClazz=classloader.loadClass("android.app.ActivityThread");
    
                Method CurrentActivityThreadMethod=ActivityThreadClazz.getDeclaredMethod("currentActivityThread");
                CurrentActivityThreadMethod.setAccessible(true);
    
                Object activityThreadObj=CurrentActivityThreadMethod.invoke(null);
    
                //final ArrayMap<String, WeakReference<LoadedApk>> mPackages = new ArrayMap<>();获取arrayMap
                //Class是对类的抽象,Field是对类的属性的抽象,可以通过Class获取到Field
                Field mPackageField=ActivityThreadClazz.getDeclaredField("mPackages");
                mPackageField.setAccessible(true);
                ArrayMap mPackagesObj= (ArrayMap) mPackageField.get(activityThreadObj);
                第一个参数是String类型,就是我们当前的包名
                WeakReference wr= (WeakReference) mPackagesObj.get(this.getPackageName());
                Object loadedApkObj=wr.get();
    
                //从loadedApk中取出mClassLoader
                Class LoaderApkClazz=classloader.loadClass("android.app.LoadedApk");
                Field mClassLoader=LoaderApkClazz.getDeclaredField("mClassLoader");
                mClassLoader.setAccessible(true);
                mClassLoader.set(loadedApkObj,classloader);
    
    
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            } catch (NoSuchFieldException e) {
                e.printStackTrace();
            }
    
    
        }
    
        //编写函数,加载插件中dex的activity
        public void startTestActivity(Context context,String dexFilePath){
            File opfile=context.getDir("opt_dex",0);//存放提出出的dex文件
            File libfile=context.getDir("Lib_dex",0);//存放依赖的so文件
    
    
    
    
            DexClassLoader dexClassLoader=new DexClassLoader(dexFilePath,opfile.getAbsolutePath(),libfile.getAbsolutePath(),context.getClassLoader());
    
            replaceClassloader(dexClassLoader);
    
            //package com.yrq.test02;
            Class clazz=null;
            try {
                clazz=(Class)dexClassLoader.loadClass("com.yrq.test02.TestActivity");
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
    
            context.startActivity(new Intent(context,clazz));
        }
    
    }   
        
    
    

    结果如下:

    01-15 19:17:32.768 29796-29796/com.yrq.loaddex I/YRQ: i am from TestActivity.onCreate
    

    package com.yrq.loaddex;
    
    import androidx.appcompat.app.AppCompatActivity;
    
    import android.app.Activity;
    import android.content.Context;
    import android.content.Intent;
    import android.os.Bundle;
    import android.util.ArrayMap;
    import android.util.Log;
    
    import java.io.File;
    import java.lang.ref.WeakReference;
    import java.lang.reflect.Field;
    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;
    
    import dalvik.system.DexClassLoader;
    
    public class MainActivity extends Activity {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            Context appContext=this.getApplicationContext();
            //testDexClassLoader(appContext,"/sdcard/3.dex");
            //startTestActivityFirstMethod(this,"/sdcard/4.dex");
            startTestActivitySecondMethod(this,"/sdcard/6.dex");
        }
        //context用于获取APP私有目录
        
    
        public void startTestActivitySecondMethod(Context context,String dexFilePath){
            File opfile=context.getDir("opt_dex",0);//存放提出出的dex文件
            File libfile=context.getDir("lib_dex",0);//存放依赖的so文件
    
            ClassLoader pathClassloader=MainActivity.class.getClassLoader();
            ClassLoader bootClassLoader=MainActivity.class.getClassLoader().getParent();
    
            //父节点设置成bootClassloader
            
            //1:dexPath,指目标类所在的APK或jar文件的路径.类装载器将从该路径中寻找指定的目标类,该类必须是APK或jar的全路径.如果要包含多个路径,路径之间必须使用特定的分割符分隔,特定的分割符可以使用System.getProperty(“path.separtor”)获得.
            //2:dexOutputDir,由于dex文件被包含在APK或者Jar文件中,因此在装载目标类之前需要先从APK或Jar文件中解压出dex文件,该参数就是制定解压出的dex 文件存放的路径.在Android系统中,一个应用程序一般对应一个Linux用户id,应用程序仅对属于自己的数据目录路径有写的权限,因此,该参数可以使用该程序的数据路径.
            //3:libPath,指目标类中所使用的C/C++库存放的路径
            //4:最后一个参数是指该装载器的父装载器,一般为当前执行类的装载器
    
    
            DexClassLoader dexClassLoader=new DexClassLoader(dexFilePath,opfile.getAbsolutePath(),libfile.getAbsolutePath(),bootClassLoader);
    
            //dexClassLoader子节点设置成app的PathClassloader
            try {
                Field parentField=ClassLoader.class.getDeclaredField("parent");
                parentField.setAccessible(true);
    
                parentField.set(pathClassloader,dexClassLoader);
    
            } catch (NoSuchFieldException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
    
    
            ClassLoader tmpClassloader=pathClassloader;
            ClassLoader parentClassloader=pathClassloader.getParent();
            while(parentClassloader!=null){
                Log.i("YRQ","this:"+tmpClassloader+"--parent:"+parentClassloader);
                tmpClassloader=parentClassloader;
                parentClassloader=parentClassloader.getParent();
            }
            Log.i("YRQ","root:"+tmpClassloader);
    
            //package com.yrq.test02;
            Class clazz=null;
            try {
                clazz=(Class)dexClassLoader.loadClass("com.yrq.test02.TestActivity");
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
    
            context.startActivity(new Intent(context,clazz));
        }
    
    
    }
    

    一二三代壳和加壳技术分类识别

    壳类型介绍
    第一代Dex字符串加密资源加密对抗反编译反调试自定义DexClassLoader,整体保护dex
    第二代对抗第一代常见的脱壳办法Dex Method代码抽取到外部,Dex动态加载So加密
    第三代Dex Method代码解密So代码膨胀混淆对抗之前的所有脱壳法
    第四代VMP

    Dalvik下一代壳通用解决方案

    因为Dalvik是4.4之前的版本,所有我们可以查看DexClassLoader 4.4的版本。

    public DexClassLoader(String dexPath, //dex的路径
                            String optimizedDirectory,//odex存放路径
                String libraryPath, //涉及的so路径
                ClassLoader parent  //双亲父节点
                ) {
            super(dexPath, new File(optimizedDirectory), libraryPath, parent);
        }
    

    可以看到父类是BaseDexClassLoader

    public class BaseDexClassLoader extends ClassLoader {
        private final DexPathList pathList;
    

    可以看到是BaseDexClassLoader父类ClassLoader
    可以看到4.4有art和davlik,我们要查看的是davlik

    可以看到只是设置父节点

    ClassLoader(ClassLoader parentLoader, boolean nullAllowed) {
            if (parentLoader == null && !nullAllowed) {
                throw new NullPointerException("parentLoader == null && !nullAllowed");
            }
            parent = parentLoader;
        }
    

    所以回到BaseDexClassLoader
    可以看到初始化DexPathList实例

    public class BaseDexClassLoader extends ClassLoader {
        private final DexPathList pathList;
    
        /**
         * Constructs an instance.
         *
         * @param dexPath the list of jar/apk files containing classes and
         * resources, delimited by {@code File.pathSeparator}, which
         * defaults to {@code ":"} on Android
         * @param optimizedDirectory directory where optimized dex files
         * should be written; may be {@code null}
         * @param libraryPath the list of directories containing native
         * libraries, delimited by {@code File.pathSeparator}; may be
         * {@code null}
         * @param parent the parent class loader
         */
        public BaseDexClassLoader(String dexPath, File optimizedDirectory,
                String libraryPath, ClassLoader parent) {
            super(parent);
            this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);
        }
    

    可以看到第二个参数是我们关心的dexPath,要加载的dex文件

    public DexPathList(ClassLoader definingContext, String dexPath,
               String libraryPath, File optimizedDirectory) {
           if (definingContext == null) {
               throw new NullPointerException("definingContext == null");
           }
    
           if (dexPath == null) {
               throw new NullPointerException("dexPath == null");
           }
    
           if (optimizedDirectory != null) {
               if (!optimizedDirectory.exists())  {
                   throw new IllegalArgumentException(
                           "optimizedDirectory doesn't exist: "
                           + optimizedDirectory);
                }
    
                if (!(optimizedDirectory.canRead()
                                && optimizedDirectory.canWrite())) {
                    throw new IllegalArgumentException(
                            "optimizedDirectory not readable/writable: "
                            + optimizedDirectory);
                }
            }
    
            this.definingContext = definingContext;
            ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();
            this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,
                                               suppressedExceptions);
            if (suppressedExceptions.size() > 0) {
                this.dexElementsSuppressedExceptions =
                    suppressedExceptions.toArray(new IOException[suppressedExceptions.size()]);
            } else {
                dexElementsSuppressedExceptions = null;
            }
            this.nativeLibraryDirectories = splitLibraryPath(libraryPath);
        }
    

    重要函数为makeDexElements,进入此函数查看

    private static Element[] makeDexElements(ArrayList<File> files, File optimizedDirectory,
                                                 ArrayList<IOException> suppressedExceptions) {
           ArrayList<Element> elements = new ArrayList<Element>();
    

    返回了一个Element[]数组,这个在DexPathList也有定义

     /**
         * Element of the dex/resource file path
         */
        /*package*/ static class Element {
            private final File file;
            private final boolean isDirectory;
            private final File zip;
            private final DexFile dexFile;
    
            private ZipFile zipFile;
            private boolean initialized;
    
            public Element(File file, boolean isDirectory, File zip, DexFile dexFile) {
                this.file = file;
                this.isDirectory = isDirectory;
                this.zip = zip;
                this.dexFile = dexFile;
            }
    

    可以看到就是loadDexFile,然后添加进Element[]数组

     private static Element[] makeDexElements(ArrayList<File> files, File optimizedDirectory,
                                                 ArrayList<IOException> suppressedExceptions) {
            ArrayList<Element> elements = new ArrayList<Element>();
            /*
             * Open all files and load the (direct or contained) dex files
             * up front.
             */
            for (File file : files) {
                File zip = null;
                DexFile dex = null;
                String name = file.getName();
    
                if (name.endsWith(DEX_SUFFIX)) {
                    // Raw dex file (not inside a zip/jar).
                    try {
                        dex = loadDexFile(file, optimizedDirectory);
                    } catch (IOException ex) {
                        System.logE("Unable to load dex file: " + file, ex);
                    }
                } else if (name.endsWith(APK_SUFFIX) || name.endsWith(JAR_SUFFIX)
                        || name.endsWith(ZIP_SUFFIX)) {
                    zip = file;
    
                    try {
                        dex = loadDexFile(file, optimizedDirectory);
                    } catch (IOException suppressed) {
                        /*
                         * IOException might get thrown "legitimately" by the DexFile constructor if the
                         * zip file turns out to be resource-only (that is, no classes.dex file in it).
                         * Let dex == null and hang on to the exception to add to the tea-leaves for
                         * when findClass returns null.
                         */
                        suppressedExceptions.add(suppressed);
                    }
                } else if (file.isDirectory()) {
                    // We support directories for looking up resources.
                    // This is only useful for running libcore tests.
                    elements.add(new Element(file, true, null, null));
                } else {
                    System.logW("Unknown file type for: " + file);
                }
    
                if ((zip != null) || (dex != null)) {
                    elements.add(new Element(file, false, zip, dex));
                }
            }
    
            return elements.toArray(new Element[elements.size()]);
    

    查看loadDexFile,第一个参数是dex文件,可以看到再次调用loadDex

    private static DexFile loadDexFile(File file, File optimizedDirectory)
                throws IOException {
            if (optimizedDirectory == null) {
                return new DexFile(file);
            } else {
                String optimizedPath = optimizedPathFor(file, optimizedDirectory);
                return DexFile.loadDex(file.getPath(), optimizedPath, 0);
            }
        }
    

    可以看到loadDex第一个参数是dex路径,返回是新建DexFile对象

    static public DexFile loadDex(String sourcePathName, String outputPathName,
            int flags) throws IOException {
    
            /*
             * TODO: we may want to cache previously-opened DexFile objects.
             * The cache would be synchronized with close().  This would help
             * us avoid mapping the same DEX more than once when an app
             * decided to open it multiple times.  In practice this may not
             * be a real issue.
             */
            return new DexFile(sourcePathName, outputPathName, flags);
        }
    

    DexFile对象中,调用了openDexFile,返回mCookie

    private DexFile(String sourceName, String outputName, int flags) throws IOException {
           if (outputName != null) {
               try {
                    String parent = new File(outputName).getParent();
                    if (Libcore.os.getuid() != Libcore.os.stat(parent).st_uid) {
                        throw new IllegalArgumentException("Optimized data directory " + parent
                                + " is not owned by the current user. Shared storage cannot protect"
                                + " your application from code injection attacks.");
                    }
                } catch (ErrnoException ignored) {
                    // assume we'll fail with a more contextual error later
                }
            }
    
            mCookie = openDexFile(sourceName, outputName, flags);
            mFileName = sourceName;
            guard.open("close");
            //System.out.println("DEX FILE cookie is " + mCookie);
        }
    

    可以看到此函数又调用了openDexFileNative。

    private static int openDexFile(String sourceName, String outputName,
            int flags) throws IOException {
            return openDexFileNative(new File(sourceName).getCanonicalPath(),
                                     (outputName == null) ? null : new File(outputName).getCanonicalPath(),
                                     flags);
        }
    

    在android中每个JNI实现有规律就是类名/改成_
    dalvik/system/DexFile变成dalvik_system_DexFile


    如下就是native层次函数代码

    static void Dalvik_dalvik_system_DexFile_openDexFileNative(const u4* args,
        JValue* pResult)
    {
        StringObject* sourceNameObj = (StringObject*) args[0];
        StringObject* outputNameObj = (StringObject*) args[1];
        DexOrJar* pDexOrJar = NULL;
        JarFile* pJarFile;
        RawDexFile* pRawDexFile;
        char* sourceName;
        char* outputName;
    
        if (sourceNameObj == NULL) {
            dvmThrowNullPointerException("sourceName == null");
            RETURN_VOID();
        }
        
        。。。。
        //后缀校验
        if (hasDexExtension(sourceName)
                && dvmRawDexFileOpen(sourceName, outputName, &pRawDexFile, false) == 0) {
            ALOGV("Opening DEX file '%s' (DEX)", sourceName);
    

    之后就是各种读文件,优化OPT
    通用脱壳点
    编译源码,对dexFileParse、dvmDexFileOpenPartial脱壳点的验证。第一个参数是地址,第二个参数一般是长度。
    分别添加如下代码


    添加头文件

    #include <sys/stat.h>
    #include <fcntl.h>
    #include <unistd.h>
    

    下面就是编译 -j就是多线程

    source build/envsetup.sh
    tom@ubuntu:~/SourceCode/android-4.4.4_r1$ lunch
    
    You're building on Linux
    
    Lunch menu... pick a combo:
         1. aosp_arm-eng
         2. aosp_x86-eng
         3. aosp_mips-eng
         4. vbox_x86-eng
         5. aosp_manta-userdebug
         6. aosp_mako-userdebug
         7. aosp_hammerhead-userdebug
         8. aosp_flo-userdebug
         9. aosp_tilapia-userdebug
         10. aosp_grouper-userdebug
         11. aosp_deb-userdebug
         12. mini_x86-userdebug
         13. mini_armv7a_neon-userdebug
         14. mini_mips-userdebug
    
    Which would you like? [aosp_arm-eng] 7     
                 
    
    ============================================
    PLATFORM_VERSION_CODENAME=REL
    PLATFORM_VERSION=4.4.4
    TARGET_PRODUCT=aosp_hammerhead
    TARGET_BUILD_VARIANT=userdebug
    TARGET_BUILD_TYPE=release
    TARGET_BUILD_APPS=
    TARGET_ARCH=arm
    TARGET_ARCH_VARIANT=armv7-a-neon
    TARGET_CPU_VARIANT=krait
    HOST_ARCH=x86
    HOST_OS=linux
    HOST_OS_EXTRA=Linux-4.15.0-88-generic-x86_64-with-Ubuntu-16.04-xenial
    HOST_BUILD_TYPE=release
    BUILD_ID=KTU84P
    OUT_DIR=out
    ============================================
    
    tom@ubuntu:~/SourceCode/android-4.4.4_r1$ time make -j4
    ============================================
    
    

    编译完成

    + OUTPUT_FILE=out/target/product/hammerhead/obj/PACKAGING/systemimage_intermediates/system.img
    + EXT_VARIANT=ext4
    + MOUNT_POINT=system
    + SIZE=1073741824
    + FC=out/target/product/hammerhead/root/file_contexts
    + case $EXT_VARIANT in
    + '[' -z system ']'
    + '[' -z 1073741824 ']'
    + '[' -n out/target/product/hammerhead/root/file_contexts ']'
    + FCOPT='-S out/target/product/hammerhead/root/file_contexts'
    + MAKE_EXT4FS_CMD='make_ext4fs -s -S out/target/product/hammerhead/root/file_contexts -l 1073741824 -a system out/target/product/hammerhead/obj/PACKAGING/systemimage_intermediates/system.img out/target/product/hammerhead/system'
    + echo make_ext4fs -s -S out/target/product/hammerhead/root/file_contexts -l 1073741824 -a system out/target/product/hammerhead/obj/PACKAGING/systemimage_intermediates/system.img out/target/product/hammerhead/system
    make_ext4fs -s -S out/target/product/hammerhead/root/file_contexts -l 1073741824 -a system out/target/product/hammerhead/obj/PACKAGING/systemimage_intermediates/system.img out/target/product/hammerhead/system
    + make_ext4fs -s -S out/target/product/hammerhead/root/file_contexts -l 1073741824 -a system out/target/product/hammerhead/obj/PACKAGING/systemimage_intermediates/system.img out/target/product/hammerhead/system
    Creating filesystem with parameters:
        Size: 1073741824
        Block size: 4096
        Blocks per group: 32768
        Inodes per group: 8192
        Inode size: 256
        Journal blocks: 4096
        Label: 
        Blocks: 262144
        Block groups: 8
        Reserved block group size: 63
    Created filesystem with 1429/65536 inodes and 76583/262144 blocks
    + '[' 0 -ne 0 ']'
    Install system fs image: out/target/product/hammerhead/system.img
    out/target/product/hammerhead/system.img+out/target/product/hammerhead/obj/PACKAGING/recovery_patch_intermediates/recovery_from_boot.p maxsize=1096212480 blocksize=135168 total=298434830 reserve=11083776
    
    real	2m9.789s
    user	1m50.879s
    sys	0m16.426s
    
    

    将编译好的文件拷出

    然后就可以脱壳操作了。加载apk时,会再如代码缩写,sdcard生成dex

    ART下一代壳通用解决方案

    8.0之后引入InMemoryClassLoader。
    可以看到InMemoryClassLoader调用了BaseDexClassloader。
    又调用了DexPathlist,调用makeInMemoryDexElements
    然后遍历dexFiles对象,创建element的数组保存DexFile。
    DexFile中openInMemoryDexFile被调用。
    这个函数中create函数,调用了CreateSingleDexfileCookie
    此函数里创建了dex_file
    里面CreateDexFiles
    这些很多函数都涉及了起始地址与长度,所以可以在这些点脱壳。

    这些函数有

    1.static jobject CreateSingleDexFileCookie(JNIEnv* env, std::unique_ptr<MemMap> data) 
    2、static const DexFile* CreateDexFile(JNIEnv* env, std::unique_ptr<MemMap> dex_mem_map) 
    3、DexFile::Open(location,
    4、OpenCommon(map->Begin() 
    5、DexFile::DexFile(const uint8_t* base,
    

    art下函数抽取的方案思路就是阻断dex2oat流程。
    阻断之后,就会进入加载dex文件流程,可以在以下函数点进行脱壳(就是有dex路径作为参数的函数)

    1、OpenAndReadMagic(filename, &magic, error_msg); 2、DexFile::OpenCommon(const uint8_t* base, 3、DexFile::DexFile(const uint8_t* base,
    

    FART

    脱壳组件

    apk安装时进行的dex2oat编译流程

    DexClassLoader再最后回调用dex2oat进行编译,dex2oat是一个二进制文件,在dex2oat中按函数粒度编译,不编译类的初始化函数,类的初始化函数运行在解释interpreter模式下,所以如果禁用dex2oat,所有函数都运行在解释模式下。

    关于解释器实现可以看到2种,switch和汇编

    我们在脱壳时,比如初始化函数也对应着可以通过ArtMethod对象,ArtMethod对象提供了一个函数GetDexFile(),用来获取所属的DexFile。DexFile中提供了Begin()和Size()函数,获取dexfile的开始和大小,就可以得到dex文件进行dump。
    FART的脱壳点是在解释器下的Execute()。 路径为art中runtime目录

    //首先获取当前执行Artmethod
    ArtMethod* artmethod=shadow_frame.GetMethod();
    	//防止多次dump引起卡顿,只在初始化函数dump
    	if(strstr(artmethod->PrettyMethod().c_str(),"<clinit>"))
    	{
    		
    				
    		//inline const DexFile* ArtMethod::GetDexFile() 
    		const DexFile* dexfile=artmethod->GetDexFile();
    		/*  const uint8_t* Begin() const {
    	1050      return begin_;
    	1051    }
    	1052  
    	1053    size_t Size() const {
    	1054      return size_;
    	1055    }*/
    		const uint8_t* begin=dexfile->Begin();
    		size_t size=dexfile->Size();
    		//拼接存储路径
    		char dexfilepath[100]={0};
    		sprintf(dexfilepath,"/sdcard/%d_%d_Execute.dex",(int)size,getpid());
    		int fd=open(dexfilepath,O_CREAT|O_RDWR,0666);
    		if(fd>0)
    		{
    			int number=write(fd,begin,size);
    			if(number>0){
    				}
    			close(fd);
    			
    			}
    		
    		}
    
    记得如下头文件
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <unistd.h>
    

    主动调用组件

    相关概念


    关于dex,如果没有异常处理,长度即头部长度加指令长度,有异常处理还要加异常处理部分。

    FART脱壳结束得到的文件列表。

    手动修复教程

    首先安装fart镜像,安装运行APK,将Sdcard卡目录下dump文件夹保存脱下来

    打开GDA观察,函数都被抽空。

    然后就用dump下了的codeItem进行修复
    使用脚本fart.py脚本,格式为
    -d xxx.dex -i xxx.bin >> result
    结果如下:

    参考资料

    看雪视频

    展开全文
  • 安卓逆向-加壳检测

    2019-12-10 11:09:43
    安卓逆向-加壳检测 环境 Win10(虚拟机)、Java1.8 工具 ApkScan-PKID 步骤(已有java环境的省略一、二、三步) 一、下载Java1.8 链接: https://www.java.com/zh_CN/download/windows-64bit.jsp 二、安装java 记得修改...

    安卓逆向-加壳检测

    环境

    Win10(虚拟机)、Java1.8

    工具

    ApkScan-PKID

    步骤(已有java环境的省略一、二、三步)

    一、下载Java1.8

    链接: https://www.java.com/zh_CN/download/windows-64bit.jsp

    二、安装java

    记得修改保存路径

    三、配置Java环境

    1、
    在这里插入图片描述
    2、
    在这里插入图片描述
    3、
    在这里插入图片描述
    4、
    在这里插入图片描述
    5、
    在这里插入图片描述
    6、
    在这里插入图片描述
    在这里插入图片描述
    JAVA_HOME的值是第2步安装的java文件路径.

    四、下载ApkScan-PKID

    链接: 链接:https://pan.baidu.com/s/17Khi8m6K6HiVOk6IaKrSCw 密码:18wg
    (注: 如果链接失效,就去网上搜一搜)

    五、ApkScan-PKID使用

    解压完进入文件
    1、打开cmd
    2、进入ps:

    powershell
    

    3、检测java环境:

    java version
    

    4、启动pkid:

    java -jar .\ApkScan-PKID.jar
    

    在这里插入图片描述

    六、查壳

    在这里插入图片描述
    结束语:
    虽然有些软件没有做加壳,但是个人感觉这一步还是很有必要做的.

    展开全文
  • APK加壳工具APKProtect

    热门讨论 2013-10-31 10:04:56
    APK加壳工具APKProtect,对apk进行加密
  • 一个是加壳安卓工程; 一个是apkdemo源程序; 直接运行Packers工程下面的build.xml文件后,在Packers的bin目录下查看Packers-release.apk即可安装测试。 其中:签名key文件请自己提供,在ant.properties中配置即可。
  • android 加壳

    2015-05-25 14:26:03
    转载自:http://my.oschina.net/u/2323218/blog/393372
    转载自:http://my.oschina.net/u/2323218/blog/393372
    展开全文
  • Web应用加壳让它变成Android APP,基于WebView开发安卓应用
  • 今天来介绍一下Android中的如何对Apk进行加固的原理。现阶段。我们知道Android中的反编译工作越来越让人操作熟练,我们辛苦的开发出一个apk,结果被人反编译了,那心情真心不舒服。虽然我们混淆,做到native层,但是...
  • Android 加壳与脱壳方式总结

    千次阅读 2019-04-29 16:18:40
    说到加壳,之前接触的主要都是pc端的壳,当时的脱壳就是用的esp脱壳法,大概意思就是程序运行到将当前环境保存之后,在当前的esp指向的地址下硬件断点,然后再次运行到该点时,进行dump内存,即可(大概意思,好多年...
  • Android中的Apk的加固(加壳)原理解析和实现Android中的Apk的加固(加壳)原理解析和实现
  • Android Apk加壳原理分析

    千次阅读 2016-01-19 17:43:19
    0x00 阅读本文前,建议读者首先阅读Android加壳原理,参考文章Android中的Apk的加固(加壳)原理解析和实现。如果没有看过这篇文章,本文理解起来比较困难。 0x01 下面我们来分析脱壳代码为什么要这样写,核心脱壳...
  • Android Apk加壳技术实战详解

    千次阅读 热门讨论 2017-10-18 09:23:21
    前言前几天面试了一家信息加密相关的公司,经过...Android的实践:APK加壳【1】初步方案实现详解 嗯…不会,没接触过!果然是信息加密的公司/(ㄒoㄒ)/~~ 此处省略∞个字。 结合文章内容,自己又百度了一些相关文章,
  • 版权声明:尊重原创,转载请附本文链接。谢谢合作! https://blog.csdn.net/DeMonliuhui/article/details/78269234前言前几天面试了一家信息加密相关的公司,经过两轮... 面试官加我QQ后,扔来了链接: Android的...
  • #0x00 前言 安卓应用加固技术是伴随安卓...市面上成熟的加固厂商一般会使用加壳保护技术配合反反汇编技术对安卓应用进行加固。 安卓反反汇编技术的总结在我以下博客中将进行详细探讨(未写完),链接: 反反汇编: ...
  • Android 加壳尝试(一)

    千次阅读 2017-09-11 21:00:15
    Android 加壳尝试(一)
  • android 加壳技术文档

    2014-10-30 16:00:43
    根据加壳数据在解壳程序Dex文件所处的位置,Android Dex加壳技术实现方案
  • 1.工具介绍 IDA6.5,静态分析so文件用 ...项目是Android jni的关键代码 可以用AndroidKiller 打开app,找出相应的so库,获取当前程序的包名 例:cn.com.location.hshelldemo 源码: Java_cn_com...
  • APK加壳原理简述

    千次阅读 2018-01-19 09:52:36
    PRE、dex文件结构知识和加壳原理 先看下dex文件的基本结构 对于加壳主要关注3个关键字: 1)checksum 文件校验码,使用alder32算法,校验文件除了maigc和checksum外余下的所有文件区域,用于检查文件错误。 2...
  • Android加壳

    2015-12-14 16:06:00
    android应用加壳 1、需求背景 在某些特殊场景下,我们需要在不修改原应用apk的情况下对其做些额外的事情,比方集成广告、增加鉴权、增加防止反编译的逻辑等。那么我们需要在应用外嵌套一层壳,该壳集成我们想...
  • 本文章由Jack_Jia编写,转载请注明出处。  文章链接:http://blog.csdn.net/jiazhijun/article/details/8809542 作者:Jack_Jia 邮箱: 309zhijun@... 在上篇“Android APK加壳技术方案”(http://blog.cs
  • 1. 加密目标 隐藏原始 dex 数据,保护程序。 2. 实现步骤 1) 反编译原始 apk 文件(不反编译 dex 文件...获取完 classLoader 之后开始创建数据加壳时需要对程序本身的 Application 方法通过反射方法来覆盖掉。
  • Android之Apk加壳

    千次阅读 2018-12-11 16:12:58
    基于ADT环境开发的的实现,请参考: Android中的Apk的加固(加壳)原理解析和实现  类加载和dex文件相关的内容,如:Android动态加载Dex机制解析  一、什么是加壳加壳是在二进制的程序中植入一段代码,在运行...
  • apk在开发时,开发者会在工程项目proguard-rules.pro文件内加入google常用的混淆,并用加密秘钥(key alias)加密打包之后,也会用一些第三方加固方式(加固、一定程度的压缩、安全性增强)或者apk加壳技术(文件会...
  • android防止代码被hack 就需要加壳加密防止反编译 通常360/乐固等等 一键加壳 as自带混淆搞定 那么android木马病毒怎么加壳加密免杀呢 360 不让加固 功能开发已经很费时费事了吧如果是kali linux 中的msfvenom生成的...
  • Android APK加固(加壳)工具

    千次阅读 2017-08-23 16:05:33
    之前一篇文章Android proguard代码混淆,我们讲解了如何实现APK的代码混淆,让反编译者不那么容易阅读我们的源代码。虽然我们混淆,做到native层,但是这都是治标不治本的。反编译的技术在更新,那么保护Apk的技术就...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 3,007
精华内容 1,202
关键字:

安卓加壳