zoukankan      html  css  js  c++  java
  • 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的目的。

  • 相关阅读:
    Objective-C基础3
    C语言回顾-结构体、枚举和文件
    C语言回顾-内存管理和指针函数
    C语言回顾-字符串指针
    C语言回顾-指针
    C语言回顾-二维数组
    Objective-C基础2
    C语言回顾-整型变量修饰符和一维数组
    sql server 判断是否存在数据库,表,列,视图
    大文件数据库脚本导入解决方案
  • 原文地址:https://www.cnblogs.com/ltyandy/p/11642108.html
Copyright © 2011-2022 走看看