Android APK加固-內存加載dex


Android APK加固-內存加載dex

分析DexClassLoader的構造方法

查看源碼可以到AndroidXref網站查看

http://androidxref.com/

1570587480978

1570587566974

1570587581706

1570587625848

查看代碼發現,DexClassLoader調用了父類BaseDexClassLoader構造

點擊父類名稱,繼續觀察父類源碼

1570587718294

發現構造有個核心功能DexPathList,繼續查看

1570587827805

觀察發現,DexPathList構造中,有一個方法makeDexElements像是創建了一個元素集,

跟入

1570587922853

函數判斷后綴名是否是dex,如果是就調用LoadDexFile的方法

這是它另外定義的后綴名

1570588002168

繼續觀察LoadDexFile

1570588290827

發現optimizedDirectory參數被optimizedPathFor方法轉為了一個路徑,加載dex文件的方法是DexFile類中的loadDex方法,繼續跟入

1570588549179

發現DexFile的loadDex返回了DexFile對象,參數分別為源路徑和輸出路徑

繼續查看DexFile的構造

1570589042448

發現DexFile類的構造方法中又有個方法openDexFile和加載Dex文件有關聯。繼續查看

1570589663881

發現openDexFIle返回了個native方法。其實現部分是在C++中。

繼續跟入

1570589848047


總結一下:

1.DexClassLoader調用了父類BaseDexClassLoader構造,發現父類構造有個核心功能DexPathList,

2.核心功能DexPathList構造中,有一個方法makeDexElements像是創建了一個元素集,

3.makeDexElements這個函數判斷后綴名是否是dex,如果是就調用LoadDexFile的方法,

4.在LoadDexFile方法中optimizedDirectory參數被optimizedPathFor方法轉為了一個路徑,而加載dex文件的方法是DexFile類中的loadDex方法,

5.DexFile的loadDex返回了DexFile對象,對象參數分別為源路徑和輸出路徑,

6.跟入發現DexFile類的構造方法中又有個方法openDexFile和加載Dex文件有關聯。

7.發現openDexFIle返回了個native方法。其實現部分是在C++中。

簡述:

makDexELements判斷了4種文件類型,dex/jar/zip/apk,所以android中能夠動態加載的構造方法中,就這四種。

DexClassLoader跟到最后發現最核心功能是openDexFile,native層的,傳遞文件字節碼,返回值是一個虛擬機的cookie值(java層)(C++層是pDexOrJar的指針)

分析DexClassLoader的loadClass方法

由於我們使用的是DexClassLoader,繼承自BaseDexClassLoader,而查看findClass方法在ClassLoader的源碼是必須要實現的,所以應該看BaseDexClassLoader的重寫方法。

1570592255439

發現其中有查找類的方法。調用findClass的pathList對象是在BaseDexClassLoader構造中創建的。

繼續分析DexPathList的findClass方法

1570592397390

發現返回類的對象代碼,是DexFile中的loadClassBinaryName

1570592638711

loadClassBinaryName方法中調用了defineClass,其中參數是名稱,類加載器,cookie值,cookie值是DexFile中的openDexFile方法的返回值。

defineClass方法是個native方法

流程:

1.ClassLoader.loadClass 方法

2.BaseDexClassLoader.findClass方法

3.DexPathList.findClass方法

4.DexFile.loadClassBinaryName方法

5.DexFile.defineClass,Native方法

與DexClassLoad的構造結合起來,可以看到,DexFile這個類是加載類的關鍵,在DexClassLoader的構造方法中,最后調用的openDexFIle方法,返回dalvik虛擬機中的一個cookie值,這個值正是loadClasss方法最后調用的defineClass的參數。

編寫自己的DexClassLoader

思路:

0.創建一個DexClassLoader的子類

1.創建構造方法,加入參數byte[]

2.使用反射調用openDexFile,獲取mCookie

3.重寫loadClass,使用反射調用defineClass

1.因為想要一個帶有傳入參數byte[]的DexClassLoader,所以新創建一個DexClassLoader子類MyDexClassLoader,創建構造方法加入參數byte[]。

 public MyDexClassLoader(byte bytes[],
                            String dexPath,
                            String optimizedDirectory,
                            String librarySearchPath,
                            ClassLoader parent) {
        super(dexPath, optimizedDirectory, librarySearchPath, parent);

        createDexClassLoader(bytes,parent);

   }

2.使用反射調用openDexFile,獲取mCookie。

為了方便,封裝了一個方法,實現調用openDexFile的邏輯,這個邏輯就是創建自己的DexClassLoader的邏輯。另外定義了兩個變量,,存放cookie和parentclassLoadr。

因為openDexFile方法是在DexFile類里面,所以代碼的邏輯應該是先獲取DexFile類,再獲取openDexFile,然后調用。

private ClassLoader mClassLoader;
    private int mCookie;
    private void createDexClassLoader(byte[] bytes, ClassLoader parent) {
        // android 4.1 DexFile.openDexFile(byte[])
        mClassLoader = parent;
        try {
            // 1. 獲取 DexFile 類類型
            Class clz = Class.forName("dalvik.system.DexFile");
            // 2. 獲取 openDexFile 方法對象
            Method method = clz.getDeclaredMethod("openDexFile",byte[].class);
            // 3. 調用方法,返回 cookie
            method.setAccessible(true);
            mCookie = (int) method.invoke(null,new Object[]{bytes});
       } catch (Exception e) {
            e.printStackTrace();
       }
   }

3.重寫loadClass

@Override
    public Class<?> loadClass(String name) throws ClassNotFoundException {

        // android 4.1 DexFile.defineClass(String name, ClassLoader loader, int cookie)
        Class c = null;
        try {
            // 獲取加載的類信息
            Class dexFile = Class.forName("dalvik.system.DexFile");
            // 獲取靜態方法
            Method method = dexFile.getDeclaredMethod("defineClass", String.class, ClassLoader.class, int.class);
            method.setAccessible(true);
            // 調用
            c = (Class)method.invoke(null,name, mClassLoader, mCookie);
            return c;
       } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
       }
        return super.loadClass(name);
   }

完整的MyDexClassLoader.java

package com.bluelesson.mydexclassloader;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

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

public class MyDexClassLoader extends DexClassLoader {
    public MyDexClassLoader(byte bytes[],
                            String dexPath,
                            String optimizedDirectory,
                            String librarySearchPath,
                            ClassLoader parent) {
        super(dexPath, optimizedDirectory, librarySearchPath, parent);

        createDexClassLoader(bytes,parent);

   }
    private ClassLoader mClassLoader;
    private int mCookie;
    private void createDexClassLoader(byte[] bytes, ClassLoader parent) {
        // android 4.1 DexFile.openDexFile(byte[])
        mClassLoader = parent;
        try {
            // 1. 獲取 DexFile 類類型
            Class clz = Class.forName("dalvik.system.DexFile");
            // 2. 獲取 openDexFile 方法對象
            Method method = clz.getDeclaredMethod("openDexFile",byte[].class);
            // 3. 調用方法,返回 cookie
            method.setAccessible(true);
            mCookie = (int) method.invoke(null,new Object[]{bytes});
       } catch (Exception e) {
            e.printStackTrace();
       }
   }

    @Override
    public Class<?> loadClass(String name) throws ClassNotFoundException {

        // android 4.1 DexFile.defineClass(String name, ClassLoader loader, int cookie)
        Class c = null;
        try {
            // 獲取加載的類信息
            Class dexFile = Class.forName("dalvik.system.DexFile");
            // 獲取靜態方法
            Method method = dexFile.getDeclaredMethod("defineClass", String.class, ClassLoader.class, int.class);
            method.setAccessible(true);
            // 調用
            c = (Class)method.invoke(null,name, mClassLoader, mCookie);
            return c;
       } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
       }
        return super.loadClass(name);
   }
}

使用MyDexClassLoader

為了測試方便,回顧之前的例子,動態加載Activity是比較簡單,我們可以把原先動態加載activity例子中,創建ClassLoader改為創建自己的MyDexClassLoader,加載Activity時,調用自己的loadClass。

首先新建一個簡單的activity,然后編譯代碼,將Main2Activity生成的calss文件轉為dex文件。然后將文件復制到項目中的assets目錄中,名為m2a.dex

先整理下思路,因為是內存中加載dex,所以把assets目錄中的dex文件讀取到byte數組中即可,然后創建自己的MyDexClassLoader,在獲取類型。步驟如下:

1.讀取文件,返回數組地址

2.創建dex文件的類加載器,返回DexClassLoader對象

3.使用loadClass獲取加載的類信息

4.創建Intent,啟動Activity

根據思路開始寫代碼。

封裝asset目錄讀取文件的方法

byte[] getdexFromAssets(String dexName){
        // 獲取assets目錄管理器
        AssetManager as = getAssets();
        // 合成路徑
        String path = getFilesDir() + File.separator + dexName;
        Log.i(TAG, path);
        try {
            // 創建文件流
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            // 打開文件
            InputStream is = as.open(dexName);
            // 循環讀取文件,拷貝到對應路徑
            byte[] buffer = new byte[1024];
            int len = 0;
            while ((len = is.read(buffer)) != -1) {
                out.write(buffer, 0, len);
           }
            return out.toByteArray();
       } catch (IOException e1) {
            // TODO Auto-generated catch block
            e1.printStackTrace();
       }
        return null;
   }

然后就可以用我們的MyDexLoader載入讀取到的數據,並運行它。

1.讀取文件到內存

2.用自己的MyDexLoader加載

3.加載載入的m2a .dex里面的類

4.替換ClassLoader

5.啟動

 public void btnClick(View view) {

        // 1. 獲取dex字節數組
        byte bytes[] = getdexFromAssets("m2a.dex");
        // 2. 加載dex,返回dexClassLoader對象
        MyDexClassLoader dex = new MyDexClassLoader(bytes,getPackageCodePath(),
                getCacheDir().toString(),null,getClassLoader()
               );
        // 3. 加載類
        Class clz = null;
        try {
            clz = dex.loadClass("com.bluelesson.mydexclassloader.Main2Activity");
       } catch (ClassNotFoundException e) {
            e.printStackTrace();
       }
        // 4. 替換ClassLoader
        replaceClassLoader1(dex);
        // 5. 啟動activity
        startActivity(new Intent(this,clz));
   }

完整的MainActivity.java

package com.bluelesson.mydexclassloader;

import android.content.Intent;
import android.content.res.AssetManager;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

import dalvik.system.DexClassLoader;

public class MainActivity extends AppCompatActivity {

    private static final String TAG = "15pb-log";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
   }

    public void btnClick(View view) {

        // 1. 獲取dex字節數組
        byte bytes[] = getdexFromAssets("m2a.dex");
        // 2. 加載dex,返回dexClassLoader對象
        MyDexClassLoader dex = new MyDexClassLoader(bytes,getPackageCodePath(),
                getCacheDir().toString(),null,getClassLoader()
               );
        // 3. 加載類
        Class clz = null;
        try {
            clz = dex.loadClass("com.bluelesson.mydexclassloader.Main2Activity");
       } catch (ClassNotFoundException e) {
            e.printStackTrace();
       }
        // 4. 替換ClassLoader
        replaceClassLoader1(dex);
        // 5. 啟動activity
        startActivity(new Intent(this,clz));
   }

    byte[] getdexFromAssets(String dexName){
        // 獲取assets目錄管理器
        AssetManager as = getAssets();
        // 合成路徑
        String path = getFilesDir() + File.separator + dexName;
        Log.i(TAG, path);
        try {
            // 創建文件流
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            // 打開文件
            InputStream is = as.open(dexName);
            // 循環讀取文件,拷貝到對應路徑
            byte[] buffer = new byte[1024];
            int len = 0;
            while ((len = is.read(buffer)) != -1) {
                out.write(buffer, 0, len);
           }
            return out.toByteArray();
       } catch (IOException e1) {
            // TODO Auto-generated catch block
            e1.printStackTrace();
       }
        return null;
   }

    public void replaceClassLoader1(DexClassLoader dexClassLoader){
        try {
            // 1. 獲取ActivityThead類對象
            // android.app.ActivityThread
            // 1.1 獲取類類型
            Class clzActivityThead = Class.forName("android.app.ActivityThread");
            // 1.2 獲取類方法
            Method currentActivityThread = clzActivityThead.getMethod("currentActivityThread",new Class[]{});
            // 1.3 調用方法
            currentActivityThread.setAccessible(true);
            Object objActivityThread = currentActivityThread.invoke(null);

            // 2. 通過類對象獲取成員變量mBoundApplication
            //clzActivityThead.getDeclaredField()
            Field field = clzActivityThead.getDeclaredField("mBoundApplication");
            // AppBindData
            field.setAccessible(true);
            Object data = field.get(objActivityThread);
            // 3. 獲取mBoundApplication對象中的成員變量info
            // 3.1 獲取 AppBindData 類類型
            Class clzAppBindData = Class.forName("android.app.ActivityThread$AppBindData");
            // 3.2 獲取成員變量info
            Field field1 = clzAppBindData.getDeclaredField("info");
            // 3.3 獲取對應的值
            //LoadedApk
            field1.setAccessible(true);
            Object info = field1.get(data);
            // 4. 獲取info對象中的mClassLoader
            // 4.1 獲取 LoadedApk 類型
            Class clzLoadedApk = Class.forName("android.app.LoadedApk");
            // 4.2 獲取成員變量 mClassLoader
            Field field2 = clzLoadedApk.getDeclaredField("mClassLoader");
            field2.setAccessible(true);

            // 5. 替換ClassLoader
            field2.set(info,dexClassLoader);

       } catch (Exception e) {
            e.printStackTrace();
       }
   }

}

在4.4的版本運行后提示錯誤:沒有這樣的方法,換成4.1進行測試即可。

小結

這個實驗表面上看像是在assets文件夾里還存在着m2a.dex文件,但,這整個過程是,先加載m2a.dex到內存,(加載m2a. dex只是為了要讓內存中存在dex數據)然后再用MyDexClassLoader讀出來。也就是說,只要內存中用其他方法存在dex數據,用MyDexClassLoader是可以直接讀取數據,assets文件夾里面就可以沒有dex文件。達到從內存中動態加載dex加固APK的目的。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM