1. 程式人生 > >關於apk加殼之動態載入dex檔案

關於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     }
複製程式碼

相關推薦

關於apk動態載入dex檔案

由於自己之前做了一個關於手機令牌的APK軟體,在實現的過程中儘管使用了native so進行一定的邏輯演算法保護,但是在自己逆向破解的過程中發現我的手機令牌關鍵資料能夠“輕易地”暴露出來,所以我就想進一步的對其進行加固。於是,我使用的網上常用的梆梆加固、愛加密和阿里的聚安全應用來對我的apk進行一個

Android應用安全外部動態載入DEX檔案風險

1. 外部動態載入DEX檔案風險描述 Android 系統提供了一種類載入器DexClassLoader,其可以在執行時動態載入並解釋執行包含在JAR或APK檔案內的DEX檔案。外部動態載入DEX檔案的安全風險源於:Anroid4.1之前的系統版本容許Android應用將動態載入的DEX檔案儲存

利用DexClassLoader動態載入dex檔案

Java中也有類載入器ClassLoader,其作用是動態裝載Class檔案,當我們從網路下載Class檔案,或者在編譯時不參與而在執行時動態呼叫時就需要用類載入器。由於Android對class檔案進行了重新打包和優化,最終APK檔案中包含的是dex檔案,載入這種檔案就需

AndroidApk

基於ADT環境開發的的實現,請參考: Android中的Apk的加固(加殼)原理解析和實現  類載入和dex檔案相關的內容,如:Android動態載入Dex機制解析  一、什麼是加殼? 加殼是在二進位制的程式中植入一段程式碼,在執行的時候優先取得程式的控制權,做

Android_動態載入.so檔案,解決apk安裝包過大的問題.

最近專案需要接入音視訊SDK,功能還沒開發,打出來的apk大了30多M… 曲線救國. 依賴的so都是音視訊的必要的核心SDK,不能刪除,但是又不想上傳一個超級大的apk到市場, 那麼解決的方案就是浪費使用者流量,畢竟現在網速那麼快,流量也幾乎等於不要錢了… 這叫

Untiy動態載入.dll檔案

這裡先說一下反射     System.Reflection名稱空間      (1) AppDomain:應用程式域,可以將其理解為一組程式集的邏輯容器        

網頁效能優化非同步載入js檔案

一個網頁的有很多地方可以進行效能優化,比較常見的一種方式就是非同步載入js指令碼檔案。在談非同步載入之前,先來看看瀏覽器載入js檔案的原理。 瀏覽器載入 JavaScript 指令碼,主要通過<script>元素完成。正常的網頁載入流程是這樣的。 瀏覽器一邊下載 HTML 網頁,一邊開始解析。

Android中外掛開發篇----動態載入Activity 免安裝執行程式

                一、前言又到週末了,時間過的很快,今天我們來看一下Android中外掛開發篇的最後一篇文章的內容:動態載入Activity(免安裝執行程式),在上一篇文章中說道了,如何動態載入資源(應用換膚原理解析),沒看過的同學,可以轉戰:當然,今天說道的內容還這這篇文章有關係。關於動態載入

HTML動態載入表格資料

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head>

log4j和log4j2怎麼動態載入配置檔案

應用場景與問題 當專案在執行時,我們如果需要修改log4j 1.X或者log4j2的配置檔案,一般來說我們是不能直接將專案停止執行再來修改檔案重新部署的。於是就有這樣一個問題:如何在不停止當前專案的執行的情況下,讓系統能夠自動地監控配置檔案的修改狀況,從而實現動態載入配置檔案的功能?

Kotlin通過Id操作View,Adapter和動態載入Xml檔案也可以類似操作

如果使用kotlin,什麼butterknife繫結,Xutil註解都不需要,只需要通過id就可以操作view,非常方便,但是在使用的過程中還是遇到兩個值得記錄的問題如下: 針對adapter中通過id來操作 針對動態載入佈局通過id來操作 其實兩者本質

Android 動態載入佈局檔案

本文轉自:原文地址 Android的基本UI介面一般都是在xml檔案中定義好,然後通過activity的setContentView來顯示在介面上,這是Android UI的最簡單的構建方式。其實,為了實現更加複雜和更加靈活的UI介面,往往需要動態生成UI介面,甚至根

java不重啟服務動態載入properties檔案

動態載入properties檔案內容,不需要重啟服務! 1 、Maven 工程,在resource下新建一個properties檔案 target/classes/config.properties user=dufy phoneNo=123456

tomcat6配置java專案啟動動態載入配置檔案

加大myeclispe 下面jdk記憶體 配置    -Xms64m -Xmx512m <Context className="org.apache.catalina.core.StandardContext"    cachingAllowed="true"

VUE 根據需要動態載入檔案元件

根據需要動態載入元件 核心方法 // 動態新增需要的版式 registerComponent(templateName) { return import(`../component/plate/mainBoard/${templateName}.vue

動態載入javascript檔案

動態載入javascript? 很俗的一個題目,如果你已經知道如何動態載入外部javascript,那麼你可以跳過這篇文章繼續閱讀其它的。如果你還不知道怎樣動態載入外部javascript檔案的話,請耐心的繼續閱讀。 什麼時候會用到動態載入javascript檔案的技術呢?其實很多的時候都

maven打包動態載入配置檔案

在以前釋出 LOCAL、SIT、生產 進行打包的時候,一般會選擇兩種解決方式 1. 每次更改配置檔案的內容(路徑、資料庫配置等)  2. 將不同的環境分開。打包測試就開啟測試環境的程式碼,打包釋出生產就開啟生產的專案程式碼。(此種情況適合流程化管理) 很多時候是 本地、測試、

Android動態載入Dex過程

 一、綜述       Android使用Dalvik虛擬機器載入可執行程式,所以不能直接載入基於class的jar,而是需要將class轉化為dex位元組碼,從而執行程式碼。優化後的位元組碼檔案可以存在一個*.jar中,只要其內部存放的是*.dex即可使用。       將

Android動態載入jar檔案

這裡用個例子來演示,具體流程是用Android Studio建一個Android專案並編寫相應程式碼,然後用Eclipse編寫一個java程式碼並打成jar包,再轉換成Android能識別的dexjar包,最後先安裝好APP,然後把jar包放到APP目錄下,ap

使用C#動態載入DLL檔案

** 使用C#動態載入DLL檔案 ** 1.首先用到kernel32.dll API函式,對於C#來說呼叫windows API 還是蠻簡單的事件。只需要宣告一下就可以了。 //載入DLL [DllImport("kernel32.dll", S