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的目的。

posted @ 2019-10-09 15:31  ltyandy  阅读(2573)  评论(0编辑  收藏  举报