关于apk加壳之动态加载dex文件

由于自己之前做了一个关于手机令牌的APK软件,在实现的过程中尽管使用了native so进行一定的逻辑算法保护,但是在自己逆向破解的过程中发现我的手机令牌关键数据能够“轻易地”暴露出来,所以我就想进一步的对其进行加固。于是,我使用的网上常用的梆梆加固、爱加密和阿里的聚安全应用来对我的apk进行一个加固保护。加固后,出于好奇心,我想对这些加固的原理进行一个了解,便于我自己能够实现这个加固的方法。于是开始了网上关于这方面的学习,我将这些加固的大致原理进行了一个总结,发现它们实现的最主要的方法就是利用了dex文件动态加载,将主逻辑的dex文件经过加密隐藏在壳程序的dex中,并在运行时通过so进行解密,并从内存读取dex数据,直接在native层进行一个动态加载。这样的实现有几个关键点:

  1. dex文件不存储在设备的物理存储区域而是将文件的数据加密存储在壳程序的dex数据区域(关于dex的结构就在此不再解释);
  2. 从内存中获取dex数据,动态加载到进程空间中;
  3. 壳程序的application重定向加载到原程序的application对象;

下面我就对这几个问题进行一一的学习之旅。

关于第一个问题,其实经历了我很长时间的学习,主要是我在最开始学习的过程中,一直在dex的动态加载上面打转,由于关于dex的加载问题主要涉及到一个

DexClassLoder(String dexPath, String optimizedDirectory,String libraryPath ,ClassLoader parent)方法,所以我必须得有个dex的路径方法啊,这点让我真的很抓狂,所以只能硬着头皮写咯。

于是在http://blog.csdn.net/androidsecurity/article/details/8809542的帮助下完成了壳程序加载dex数据的方法。

DexClassLoder-> BaseDexClassLoader->DexPathList->makeDexElements-> loadDexFile-> loadDex->DexFile(String fileName)

  1 package com.unshell.test;
  2
  3 import android.app.Application;
  4 import java.io.BufferedInputStream;
  5 import java.io.ByteArrayInputStream;
  6 import java.io.ByteArrayOutputStream;
  7 import java.io.DataInputStream;
  8 import java.io.File;
  9 import java.io.FileInputStream;
 10 import java.io.FileOutputStream;
 11 import java.io.IOException;
 12 import java.lang.ref.WeakReference;
 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 dalvik.system.DexClassLoader;
 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.os.Bundle;
 26
 27 public class ProxyApplication extends Application{
 28     private static final String appkey = "APPLICATION_CLASS_NAME";
 29     private String apkFileName;
 30     private String odexPath;
 31     private String libPath;
 32
 33     //这是context 赋值
 34     @Override
 35     protected void attachBaseContext(Context base) {
 36         super.attachBaseContext(base);
 37         try {
 38             //创建两个文件夹payload_odex,payload_lib 私有的,可写的文件目录
 39             File odex = this.getDir("payload_odex", MODE_PRIVATE);
 40             File libs = this.getDir("payload_lib", MODE_PRIVATE);
 41             odexPath = odex.getAbsolutePath();
 42             libPath = libs.getAbsolutePath();
 43             apkFileName = odex.getAbsolutePath() + "/payload.apk";
 44             File dexFile = new File(apkFileName);
 45             if (!dexFile.exists())
 46             {
 47                 dexFile.createNewFile();  //在payload_odex文件夹内,创建payload.apk
 48                 // 读取程序classes.dex文件
 49                 byte[] dexdata = this.readDexFileFromApk();
 50                 // 分离出解壳后的apk文件已用于动态加载
 51                 this.splitPayLoadFromDex(dexdata);
 52             }
 53             // 配置动态加载环境
 54             Object currentActivityThread = RefInvoke.invokeStaticMethod(
 55                     "android.app.ActivityThread", "currentActivityThread",
 56                     new Class[] {}, new Object[] {});//获取主线程对象 http://blog.csdn.net/myarrow/article/details/14223493
 57             String packageName = this.getPackageName();//当前apk的包名
 58             //下面两句不是太理解
 59             HashMap mPackages = (HashMap) RefInvoke.getFieldOjbect(
 60                     "android.app.ActivityThread", currentActivityThread,
 61                     "mPackages");
 62             WeakReference wr = (WeakReference) mPackages.get(packageName);
 63             //创建被加壳apk的DexClassLoader对象   加载apk内的类和本地代码(c/c++代码)
 64             DexClassLoader dLoader = new DexClassLoader(apkFileName, odexPath,
 65                     libPath, (ClassLoader) RefInvoke.getFieldOjbect(
 66                             "android.app.LoadedApk", wr.get(), "mClassLoader"));
 67             //base.getClassLoader(); 是不是就等同于 (ClassLoader) RefInvoke.getFieldOjbect()? 有空验证下//?
 68             //把当前进程的DexClassLoader 设置成了被加壳apk的DexClassLoader  ----有点c++中进程环境的意思~~
 69             RefInvoke.setFieldOjbect("android.app.LoadedApk", "mClassLoader",
 70                     wr.get(), dLoader);
 71
 72
 73         } catch (Exception e) {
 74             // TODO Auto-generated catch block
 75             e.printStackTrace();
 76         }
 77     }
 78
 79     @Override
 80     public void onCreate() {
 81         {
 82             // 如果源应用配置有Appliction对象,则替换为源应用Applicaiton,以便不影响源程序逻辑。
 83             String appClassName = null;
 84             //获取xml文件里配置的被加壳apk的Applicaiton
 85             try {
 86                 ApplicationInfo ai = this.getPackageManager()
 87                         .getApplicationInfo(this.getPackageName(),
 88                                 PackageManager.GET_META_DATA);
 89                 Bundle bundle = ai.metaData;
 90                 if (bundle != null
 91                         && bundle.containsKey("APPLICATION_CLASS_NAME")) {
 92                     appClassName = bundle.getString("APPLICATION_CLASS_NAME");//className 是配置在xml文件中的。
 93                 } else {
 94                     return;
 95                 }
 96             } catch (NameNotFoundException e) {
 97                 // TODO Auto-generated catch block
 98                 e.printStackTrace();
 99             }
100             //有值的话调用该Applicaiton
101             Object currentActivityThread = RefInvoke.invokeStaticMethod(
102                     "android.app.ActivityThread", "currentActivityThread",
103                     new Class[] {}, new Object[] {});
104             Object mBoundApplication = RefInvoke.getFieldOjbect(
105                     "android.app.ActivityThread", currentActivityThread,
106                     "mBoundApplication");
107             Object loadedApkInfo = RefInvoke.getFieldOjbect(
108                     "android.app.ActivityThread$AppBindData",
109                     mBoundApplication, "info");
110             //把当前进程的mApplication 设置成了null
111             RefInvoke.setFieldOjbect("android.app.LoadedApk", "mApplication",
112                     loadedApkInfo, null);
113             Object oldApplication = RefInvoke.getFieldOjbect(
114                     "android.app.ActivityThread", currentActivityThread,
115                     "mInitialApplication");
117             ArrayList<Application> mAllApplications = (ArrayList<Application>) RefInvoke
118                     .getFieldOjbect("android.app.ActivityThread",
119                             currentActivityThread, "mAllApplications");
120             mAllApplications.remove(oldApplication);//删除oldApplication
121
122             ApplicationInfo appinfo_In_LoadedApk = (ApplicationInfo) RefInvoke
123                     .getFieldOjbect("android.app.LoadedApk", loadedApkInfo,
124                             "mApplicationInfo");
125             ApplicationInfo appinfo_In_AppBindData = (ApplicationInfo) RefInvoke
126                     .getFieldOjbect("android.app.ActivityThread$AppBindData",
127                             mBoundApplication, "appInfo");
128             appinfo_In_LoadedApk.className = appClassName;
129             appinfo_In_AppBindData.className = appClassName;
130             Application app = (Application) RefInvoke.invokeMethod(
131                     "android.app.LoadedApk", "makeApplication", loadedApkInfo,
132                     new Class[] { boolean.class, Instrumentation.class },
133                     new Object[] { false, null });//执行 makeApplication(false,null)
134             RefInvoke.setFieldOjbect("android.app.ActivityThread",
135                     "mInitialApplication", currentActivityThread, app);
136
137
138             HashMap mProviderMap = (HashMap) RefInvoke.getFieldOjbect(
139                     "android.app.ActivityThread", currentActivityThread,
140                     "mProviderMap");
141             Iterator it = mProviderMap.values().iterator();
142             while (it.hasNext()) {
143                 Object providerClientRecord = it.next();
144                 Object localProvider = RefInvoke.getFieldOjbect(
145                         "android.app.ActivityThread$ProviderClientRecord",
146                         providerClientRecord, "mLocalProvider");
147                 RefInvoke.setFieldOjbect("android.content.ContentProvider",
148                         "mContext", localProvider, app);
149             }
150             app.onCreate();
151         }
152     }
153
154     /**
155      * 释放被加壳的apk文件,so文件
156      * @param data
157      * @throws IOException
158      */
159     private void splitPayLoadFromDex(byte[] data) throws IOException {
160
161         int ablen = apkdata.length;
162         //取被加壳apk的长度   这里的长度取值,对应加壳时长度的赋值都可以做些简化
163         byte[] dexlen = new byte[4];
164         System.arraycopy(apkdata, ablen - 4, dexlen, 0, 4);
165         ByteArrayInputStream bais = new ByteArrayInputStream(dexlen);
166         DataInputStream in = new DataInputStream(bais);
167         int readInt = in.readInt();
168         System.out.println(Integer.toHexString(readInt));
169         byte[] newdex = new byte[readInt];
170         //把被加壳apk内容拷贝到newdex中
171         System.arraycopy(apkdata, ablen - 4 - readInt, newdex, 0, readInt);
172         //这里应该加上对于apk的解密操作,若加壳是加密处理的话
173         //?byte[] apkdata = decrypt(newdex);  //解壳程序的dex并没有加密,所以也不需要解密
174         //写入apk文件
175         File file = new File(apkFileName);
176         try {
177             FileOutputStream localFileOutputStream = new FileOutputStream(file);
178             localFileOutputStream.write(newdex);
179             localFileOutputStream.close();
180
181
182         } catch (IOException localIOException) {
183             throw new RuntimeException(localIOException);
184         }
185
186         //分析被加壳的apk文件
187         ZipInputStream localZipInputStream = new ZipInputStream(
188                 new BufferedInputStream(new FileInputStream(file)));
189         while (true) {
190             ZipEntry localZipEntry = localZipInputStream.getNextEntry();//不了解这个是否也遍历子目录,看样子应该是遍历的
191             if (localZipEntry == null) {
192                 localZipInputStream.close();
193                 break;
194             }
195             //取出被加壳apk用到的so文件,放到 libPath中(data/data/包名/payload_lib)
196             String name = localZipEntry.getName();
197             if (name.startsWith("lib/") && name.endsWith(".so")) {
198                 File storeFile = new File(libPath + "/"
199                         + name.substring(name.lastIndexOf(‘/‘)));
200                 storeFile.createNewFile();
201                 FileOutputStream fos = new FileOutputStream(storeFile);
202                 byte[] arrayOfByte = new byte[1024];
203                 while (true) {
204                     int i = localZipInputStream.read(arrayOfByte);
205                     if (i == -1)
206                         break;
207                     fos.write(arrayOfByte, 0, i);
208                 }
209                 fos.flush();
210                 fos.close();
211             }
212             localZipInputStream.closeEntry();
213         }
214         localZipInputStream.close();
215
216
217     }
218
219     /**
220      * 从apk包里面获取dex文件内容(byte)
221      * @return
222      * @throws IOException
223      */
224     private byte[] readDexFileFromApk() throws IOException {
225         ByteArrayOutputStream dexByteArrayOutputStream = new ByteArrayOutputStream();
226         ZipInputStream localZipInputStream = new ZipInputStream(
227                 new BufferedInputStream(new FileInputStream(
228                         this.getApplicationInfo().sourceDir)));
229         while (true) {
230             ZipEntry localZipEntry = localZipInputStream.getNextEntry();
231             if (localZipEntry == null) {
232                 localZipInputStream.close();
233                 break;
234             }
235             if (localZipEntry.getName().equals("classes.dex")) {
236                 byte[] arrayOfByte = new byte[1024];
237                 while (true) {
238                     int i = localZipInputStream.read(arrayOfByte);
239                     if (i == -1)
240                         break;
241                     dexByteArrayOutputStream.write(arrayOfByte, 0, i);
242                 }
243             }
244             localZipInputStream.closeEntry();
245         }
246         localZipInputStream.close();
247         return dexByteArrayOutputStream.toByteArray();
248     }
249
250
251     // //直接返回数据,读者可以添加自己解密方法
252     private byte[] decrypt(byte[] data) {
253         return data;
254     }
255 }

接着我们就要解决从内存dex的动态加载问题,于是根据我在看雪论坛上面学习的这几篇文章

http://blog.csdn.net/androidsecurity/article/details/9674251

http://www.kanxue.com/bbs/showthread.php?t=195865

实现了dex的读取数据

Jni关键代码基本都在译文博客中了,我们要做的是让它通过编译、得到so库。本地代码当然要有与之对应的java代码去加载才能用,通过上面对因为的总结,可以先这样定义       本地方法:
          static native int loadDex(byte[] dex,long dexlen);
       生成好对应的.h、.c文件之后把译文中给出的核心代码填上,下面才是难题,许多类型都是unknown的,ndk编译器会告诉你它不认识这些乱七八糟的玩意儿。接下来就是挨个补充定义了。
        看着u4、u1这些从java程序猿眼中怪怪的类型我不禁长出一口气——幸亏当年是C出身的。溯本清源,在源码 /dalvik/vm/Common.h 类中找到了这群货的宏定义,于是照葫芦画瓢,在jni目录里弄了一个伪造版的Common.h,搜刮了一下所有需要定义的类型之后,这个文件基本上是这个样子的:

  1 #ifndef DALVIK_COMMON_H_
  2 #define DALVIK_COMMON_H_
  3
  4 #include <stdbool.h>
  5 #include <stdint.h>
  6 #include <stdio.h>
  7 #include <assert.h>
  8
  9 static union { char c[4]; unsigned long mylong; }endian_test = {{ ‘l‘, ‘?‘, ‘?‘, ‘b‘ } };
 10 #define ENDIANNESS  ((char)endian_test.mylong)
 11
 12 //#if ENDIANNESS == "l"
 13 #define HAVE_LITTLE_ENDIAN
 14 //#else
 15 //#define HAVE_BIG_ENDIAN
 16 //#endif
 17
 18 #if defined(HAVE_ENDIAN_H)
 19 # include <endian.h>
 20 #else /*not HAVE_ENDIAN_H*/
 21 # define __BIG_ENDIAN 4321
 22 # define __LITTLE_ENDIAN 1234
 23 # if defined(HAVE_LITTLE_ENDIAN)
 24 #  define __BYTE_ORDER __LITTLE_ENDIAN
 25 # else
 26 #  define __BYTE_ORDER __BIG_ENDIAN
 27 # endif
 28 #endif /*not HAVE_ENDIAN_H*/
 29
 30 #if !defined(NDEBUG) && defined(WITH_DALVIK_ASSERT)
 31 # undef assert
 32 # define assert(x)  33 ((x) ? ((void)0) : (ALOGE("ASSERT FAILED (%s:%d): %s",  34 __FILE__, __LINE__, #x), *(int*)39=39, (void)0) )
 35 #endif
 36
 37 #define MIN(x,y) (((x) < (y)) ? (x) : (y))
 38 #define MAX(x,y) (((x) > (y)) ? (x) : (y))
 39
 40 #define LIKELY(exp) (__builtin_expect((exp) != 0, true))
 41 #define UNLIKELY(exp) (__builtin_expect((exp) != 0, false))
 42
 43 #define ALIGN_UP(x, n) (((size_t)(x) + (n) - 1) & ~((n) - 1))
 44 #define ALIGN_DOWN(x, n) ((size_t)(x) & -(n))
 45 #define ALIGN_UP_TO_PAGE_SIZE(p) ALIGN_UP(p, SYSTEM_PAGE_SIZE)
 46 #define ALIGN_DOWN_TO_PAGE_SIZE(p) ALIGN_DOWN(p, SYSTEM_PAGE_SIZE)
 47
 48 #define CLZ(x) __builtin_clz(x)
 49
 50 /*
 51  * If "very verbose" logging is enabled, make it equivalent to ALOGV.
 52  * Otherwise, make it disappear.
 53  *
 54  * Define this above the #include "Dalvik.h" to enable for only a
 55  * single file.
 56  */
 57 /* #define VERY_VERBOSE_LOG */
 58 #if defined(VERY_VERBOSE_LOG)
 59 # define LOGVV  ALOGV
 60 # define IF_LOGVV() IF_ALOGV()
 61 #else
 62 # define LOGVV(...) ((void)0)
 63 # define IF_LOGVV() if (false)
 64 #endif
 65
 66
 67 /*
 68  * These match the definitions in the VM specification.
 69  */
 70 typedef uint8_t u1;
 71 typedef uint16_tu2;
 72 typedef uint32_tu4;
 73 typedef uint64_tu8;
 74 typedef int8_t  s1;
 75 typedef int16_t s2;
 76 typedef int32_t s4;
 77 typedef int64_t s8;
 78
 79 /*
 80  * Storage for primitive types and object references.
 81  *
 82  * Some parts of the code (notably object field access) assume that values
 83  * are "left aligned", i.e. given "JValue jv", "jv.i" and "*((s4*)&jv)"
 84  * yield the same result.  This seems to be guaranteed by gcc on big- and
 85  * little-endian systems.
 86  */
 87
 88 #define OFFSETOF_MEMBER(t, f)  89   (reinterpret_cast<char*>(    90  &reinterpret_cast<t*>(16)->f) -   91    reinterpret_cast<char*>(16))
 92
 93 #define NELEM(x) ((int) (sizeof(x) / sizeof((x)[0])))
 94
 95 union JValue {
 96 #if defined(HAVE_LITTLE_ENDIAN)
 97     u1  z;
 98     s1  b;
 99     u2  c;
100     s2  s;
101     s4  i;
102     s8  j;
103     float   f;
104     double  d;
105     void* l;
106 #endif
107 #if defined(HAVE_BIG_ENDIAN)
108     struct {
109         u1_z[3];
110         u1z;
111     };
112     struct {
113         s1_b[3];
114         s1b;
115     };
116     struct {
117         u2_c;
118         u2c;
119     };
120     struct {
121         s2_s;
122         s2s;
123     };
124     s4  i;
125     s8  j;
126     float   f;
127     double  d;
128     void*   l;
129 #endif
130 };
131
132 /*
133  * Array objects have these additional fields.
134  *
135  * We don‘t currently store the size of each element.  Usually it‘s implied
136  * by the instruction.  If necessary, the width can be derived from
137  * the first char of obj->clazz->descriptor.
138  */
139 typedef struct   {
140    void*clazz;
141    u4  lock;
142    u4  length;
143    u1*  contents;
144 }ArrayObject ;
145
146 #endif  // DALVIK_COMMON_H_

这里面还有个大小端的问题,不过为求实验先通过就先定义死,过了再说。
        还有个值得一提的结构就是最后面的ArrayObject,这玩意定义在源码的/dalvik/vm/oo/Object.h 中,原本的定义是这样的:

1 struct Object {
2     ClassObject*clazz;
3     u4  lock;
4 };
5
6 struct ArrayObject : Object {
7     u4  length;
8     u8  contents[1];
9 };

如果还实实在在的去弄一个ClassObject,那就是java中毒已深的表现,根据看雪里面的相关讨论(就是文首提到的两篇),直接如上定义了。得到最后的C代码如下:

 1 #include "com_android_dexunshell_NativeTool.h"
 2 #include <stdlib.h>
 3 #include <dlfcn.h>
 4 #include <stdio.h>
 5
 6 JNINativeMethod *dvm_dalvik_system_DexFile;
 7 void (*openDexFile)(const u4* args,union  JValue* pResult);
 8
 9 int lookup(JNINativeMethod *table, const char *name, const char *sig,
10    void (**fnPtrout)(u4 const *, union JValue *))
11 {
12     int i = 0;
13     while (table[i].name != NULL)
14     {
15         LOGI("lookup %d %s" ,i,table[i].name);
16         if ((strcmp(name, table[i].name) == 0)
17                && (strcmp(sig, table[i].signature) == 0))
18         {
19             *fnPtrout = table[i].fnPtr;
20             return 1;
21            }
22            i++;
23     }
24      return 0;
25 }
26
27 /* This function will be call when the library first be load.
28  * You can do some init in the libray. return which version jni it support.
29  */
30 JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved)
31 {
32     void *ldvm = (void*) dlopen("libdvm.so", RTLD_LAZY);
33     dvm_dalvik_system_DexFile = (JNINativeMethod*) dlsym(ldvm,
34            "dvm_dalvik_system_DexFile");
35     if(0 == lookup(dvm_dalvik_system_DexFile, "openDexFile", "([B)I",
36         &openDexFile))
37      {
38            openDexFile = NULL;
39            LOGE("method does not found ");
40     }else
41     {
42         LOGI("method found ! HAVE_BIG_ENDIAN");
43      }
44      LOGI("ENDIANNESS is %c" ,ENDIANNESS );
45      void *venv;
46      LOGI("dufresne----->JNI_OnLoad!");
47      if ((*vm)->GetEnv(vm, (void**) &venv, JNI_VERSION_1_4) != JNI_OK)
48     {
49            LOGE("dufresne--->ERROR: GetEnv failed");
50            return -1;
51      }
52      return JNI_VERSION_1_4;
53 }
54
55 JNIEXPORT jint JNICALL Java_com_android_dexunshell_NativeTool_loadDex(
56    JNIEnv * env, jclass jv, jbyteArray dexArray, jlong dexLen)
57 {
58     // header+dex content
59     u1 * olddata = (u1*)(*env)-> GetByteArrayElements(env,dexArray,   NULL);
60     char* arr;
61      arr=(char*)malloc(16+dexLen);
62      ArrayObject *ao=(ArrayObject*)arr;
63      ao->length=dexLen;
64      memcpy(arr+16,olddata,dexLen);
65       u4 args[] = { (u4) ao };
66     union JValue pResult;
67     jint result;
68     LOGI("call openDexFile 33..." );
69     if(openDexFile != NULL)
70     {
71         openDexFile(args,&pResult);
72     }
73     else
74     {
75         result = -1;
76     }
77
78     result = (jint) pResult.l;
79     LOGI("Java_com_android_dexunshell_NativeTool_loadDex %d" , result);
80     return result;
81 }

Java层

底层代码基本了然,也就是说译文提供的思路基本实现,剩下其他加壳的事儿还要自己动脑筋补上。现在java层我们有一个可以使用的以byte数组为参数的加载dex的接口了:
static native int loadDex(byte[] dex,long dexlen);
要知道我们花这么大力气实现的这个方法,实际意义在于让源程序的dex数据在内存中传递,而不是必须保存在某个地方、以文件的方式。也就是说,我们需要一个新的DexClassLoader,去替换在上一篇提到的基础加壳方案中自定义Application—— ProxyApplication 类,通过反射设置到”android.app.LoadedApk”中mClassLoder属性的那个系统DexClassLoader,即至少那一段应该改成这样:

1 DynamicDexClassLoder dLoader = new DynamicDexClassLoder(base,srcdata,
2    libPath, (ClassLoader) RefInvoke.getFieldOjbect(
3  "android.app.LoadedApk", wr.get(), "mClassLoader"),
4  getPackageResourcePath(),getDir(".dex", MODE_PRIVATE).getAbsolutePath() );
5
6 RefInvoke.setFieldOjbect("android.app.LoadedApk", "mClassLoader",  wr.get(), dLoader);

没错,DynamicDexClassLoder 它的构造参数中应当去接收源程序的dex数据,以byte数组的形式,这样、相关把dex数组保存为文件那段代码可以删除,/data/data 中相关目录就找不到缓存dex文件的身影了;

替换DexClassLoader,要知道相对于系统版本的加载器我们的少了什么,又多出了什么,在一一对接上,就没问题了。少了什么呢?是dex文件路径、多出了什么呢?是dex byte数组,考虑到已经实现的jni库,那就是多了一个加载好的dex文件对应的cookie值。那么,这个
Cookie 是否能够完成替换呢?这需要到源码中找答案。
源码路径:libcore/dalvik/src/main/java/dalvik/system ,生成类图,取出DexClassLoader相关的一部分:

走读几遍代码基本就能了解,对于dex文件加载而言,DynamicDexClassLoder需要做的实际上只有一件事,复写findClass方法,使APK运行时能够找到和加载源程序dex中的类,至于如何实现,从类图上就可以看出,最后实际上追溯到DexFile类,可以利用到jni加载到的cookie,通过反射DexFile中的方法,实现我们的预期,具体实现如下:

package com.android.dexunshell;

import java.io.IOException;
import java.net.URL;
import java.util.Enumeration;

import com.eebbk.mingming.k7utils.ReflectUtils;

import android.content.Context;
import android.util.Log;
import android.view.LayoutInflater;

import dalvik.system.DexClassLoader;
import dalvik.system.DexFile;

public class DynamicDexClassLoder extends DexClassLoader {
     private static final String TAG = DynamicDexClassLoder.class.getName();
     private int cookie;
     private Context mContext;

     /**
      * 原构造
      *
      * @param dexPath
      * @param optimizedDirectory
      * @param libraryPath
      * @param parent
      */
     public DynamicDexClassLoder(String dexPath, String optimizedDirectory,
                        String libraryPath, ClassLoader parent) {
               super(dexPath, optimizedDirectory, libraryPath, parent);
     }

     /**
      * 直接从内存加载 新构造
      *
      * @param dexBytes
      * @param libraryPath
      * @param parent
      * @throws Exception
      */

     public DynamicDexClassLoder(Context context, byte[] dexBytes,
                        String libraryPath, ClassLoader parent, String oriPath,
                        String fakePath) {
               super(oriPath, fakePath, libraryPath, parent);
               setContext(context);
               setCookie(NativeTool.loadDex(dexBytes, dexBytes.length));
     }

     private void setCookie(int kie) {
               cookie = kie;
     }

     private void setContext(Context context) {
               mContext = context;
     }

     private String[] getClassNameList(int cookie) {
               return (String[]) ReflectUtils.invokeStaticMethod(DexFile.class,
                                 "getClassNameList", new Class[] { int.class },
                                 new Object[] { cookie });
     }

     private Class defineClass(String name, ClassLoader loader, int cookie) {
               return (Class) ReflectUtils.invokeStaticMethod(DexFile.class,
                                 "defineClass", new Class[] { String.class, ClassLoader.class,
                                                    int.class }, new Object[] { name, loader, cookie });
     }

     @Override
     protected Class<?> findClass(String name) throws ClassNotFoundException {
               Log.d(TAG, "findClass-" + name);
               Class<?> cls = null;

               String as[] = getClassNameList(cookie);
               for (int z = 0; z < as.length; z++) {
                        if (as[z].equals(name)) {
                                 cls = defineClass(as[z].replace(‘.‘, ‘/‘),
                                                    mContext.getClassLoader(), cookie);
                        } else {
                                 defineClass(as[z].replace(‘.‘, ‘/‘), mContext.getClassLoader(),
                                                    cookie);
                        }
               }

               if (null == cls) {
                        cls = super.findClass(name);
               }

               return cls;
     }

     @Override
     protected URL findResource(String name) {
               Log.d(TAG, "findResource-" + name);
               return super.findResource(name);
     }

     @Override
     protected Enumeration<URL> findResources(String name) {
               Log.d(TAG, "findResources ssss-" + name);
               return super.findResources(name);
     }

     @Override
     protected synchronized Package getPackage(String name) {
               Log.d(TAG, "getPackage-" + name);
               return super.getPackage(name);
     }

     @Override
     protected Class<?> loadClass(String className, boolean resolve)
                        throws ClassNotFoundException {
               Log.d(TAG, "loadClass-" + className + " resolve " + resolve);
               Class<?> clazz = super.loadClass(className, resolve);
               if (null == clazz) {
                        Log.e(TAG, "loadClass fail,maybe get a null-point exception.");
               }
               return clazz;
     }

     @Override
     protected Package[] getPackages() {
               Log.d(TAG, "getPackages sss-");
               return super.getPackages();
     }

     @Override
     protected Package definePackage(String name, String specTitle,
                        String specVersion, String specVendor, String implTitle,
                        String implVersion, String implVendor, URL sealBase)
                        throws IllegalArgumentException {
               Log.d(TAG, "definePackage" + name);
              return super.definePackage(name, specTitle, specVersion, specVendor,
                                 implTitle, implVersion, implVendor, sealBase);
     }

它的启动过程就是:

但是有一个地方我确实很难理解就是为什么自己写了一个loadDex的方法为什么在它的前面还是要有

super(oriPath, fakePath, libraryPath, parent);于是就有了我进一步的跟踪DexClassLoader的这一些方法的过程了,它的调用关系如下:DexClassLoder-> BaseDexClassLoader->DexPathList->makeDexElements-> loadDexFile-> loadDex->DexFile(String fileName)而且在
1 private static DexFile loadDexFile(File file, File optimizedDirectory)
2             throws IOException {
3         if (optimizedDirectory == null) {
4             return new DexFile(file);
5         } else {
6             String optimizedPath = optimizedPathFor(file, optimizedDirectory);
7             return DexFile.loadDex(file.getPath(), optimizedPath, 0);
8         }
9     }

我就明白了,原来DexLoader也实现了DexFile操作,但是这些操作的实现在这个过程中不仅仅只是完成了dex的一个加载,另外也实现了class的映射方便上层去调用底层的方法。

				
时间: 2024-10-14 20:11:18

关于apk加壳之动态加载dex文件的相关文章

利用DexClassLoader动态加载dex文件

Java中也有类加载器ClassLoader,其作用是动态装载Class文件,当我们从网络下载Class文件,或者在编译时不参与而在运行时动态调用时就需要用类加载器.由于Android对class文件进行了重新打包和优化,最终APK文件中包含的是dex文件,加载这种文件就需要用到DexClassLoader. DexClassLoader(dexPath, optimizedDirectory, libraryPath, parent) dexPath:目标类所在的APK或者jar包,/.../

实现Android 动态加载APK(Fragment or Activity实现)

尊重原创:http://blog.csdn.net/yuanzeyao/article/details/38565345 最近由于项目太大了,导致编译通不过(Android对一个应用中的方法个数貌似有限制),所以一直琢磨着能否将某些模块的APK不用安装,动态加载,通过在网上查找资料和网友的帮助,终于实现了APK的动态加载,网络上介绍APK动态加载的文章非常多,但是我觉得写得非常好的就是这位大牛的,我基本上就是使用他的这种方案,然后加入了自己的元素.这位大牛是通过Activity实现的,我稍作修改

Android中apk加固完善篇之内存加载dex方案实现原理(不落地方式加载)

一.前言 时隔半年,困扰的问题始终是需要解决的,之前也算是没时间弄,今天因为有人在此提起这个问题,那么就不能不解决了,这里写一篇文章记录一下吧.那么是什么问题呢? 就是关于之前的一个话题:Android中apk加固技术实现 关于这个问题,之前的一篇文章已经说过了,没有了解的同学可以点击这里:Android中apk加固技术实现 请务必仔细的看完这篇文章,不然今天说的内容会感觉很蛋疼的,因为今天的文章就是为了解决当初的加固技术遗留的问题,这里先大致来说一下加固apk的原理吧,先来看一张图: 看到这张

【转】Android类动态加载技术

http://www.blogjava.net/zh-weir/archive/2011/10/29/362294.html Android应用开发在一般情况下,常规的开发方式和代码架构就能满足我们的普通需求.但是有些特殊问题,常常引发我们进一步的沉思.我们从沉思中产生顿悟,从而产生新的技术形式. 如何开发一个可以自定义控件的Android应用?就像eclipse一样,可以动态加载插件:如何让Android应用执行服务器上的不可预知的代码?如何对Android应用加密,而只在执行时自解密,从而防

Android 插件开发,做成动态加载

为什么需要插件开发: 相信你对Android方法数不能超过65K的限制应该有所耳闻,随着应用程序功能不断的丰富,总有一天你会遇到一个异常: Conversion to Dalvik format failed:Unable toexecute dex: method ID not in [0, 0xffff]: 65536 可能有些同学会说,解决这个问题很简单,我们只需要在Project.proterty中配置一句话就Ok啦, dex.force.jumbo=true 是的,加入了这句话,确实可

Android中的动态加载机制--薛彦顺

在目前的软硬件环境下,Native App与Web App在用户体验上有着明显的优势,但在实际项目中有些会因为业务的频繁变更而频繁的升级客户端,造成较差的用户体验,而这也恰恰是Web App的优势.本文对网上Android动态加载jar的资料进行梳理和实践在这里与大家一起分享,试图改善频繁升级这一弊病. Android应用开发在一般情况下,常规的开发方式和代码架构就能满足我们的普通需求.但是有些特殊问题,常常引发我们进一步的沉思.我们从沉思中产生顿悟,从而产生新的技术形式. 如何开发一个可以自定

Android动态加载Dex机制解析

1.什么是类加载器? 类加载器(class loader)是 Java?中的一个很重要的概念.类加载器负责加载 Java 类的字节代码到 Java 虚拟机中. Java 虚拟机使用 Java 类的方式如下:Java 源程序(.java 文件)在经过 Java 编译器编译之后就被转换成 Java 字节代码(.class 文件).类加载器负责读取 Java 字节代码,并转换成java.lang.Class类的一个实例.每个这样的实例用来表示一个 Java 类.通过此实例的 newInstance()

Android动态加载那些事儿

基础 1.Java 类加载器 类加载器(class loader)是 Java?中的一个很重要的概念.类加载器负责加载 Java 类的字节代码到 Java 虚拟机中.本文首先详细介绍了 Java 类加载器的基本概念,包括代理模式.加载类的具体过程和线程上下文类加载器等,接着介绍如何开发自己的类加载器,最后介绍了类加载器在 Web 容器和 OSGi?中的应用. 2.反射原理 Java 提供的反射機制允許您於執行時期動態載入類別.檢視類別資訊.生成物件或操作生成的物件,要舉反射機制的一個應用實例,就

类加载器(DexClassLoader)与插件化(动态加载)

类加载器与插件化解析 2.1 类装载器 DexClassLoader 首先,我们需要了解关于java代码本地import的一些知识: import中所引用的类有两个特点: 1.必须存在于本地,当程序运行时需要该类时,内部类装载器会自动装载该类,这对程序员来讲是透明的,即程序员感知不到该过程 2.编译时必须在现场,否则编译过程会因为找不到引用文件而不能正常编译. 使用ClassLoader的必要说明,多用于动态加载一些自定义的类. 一般情况下,应用程序不需要创建一个全新的ClassLoader,而