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

本文深入探讨了Android中APK加固技术的内存加载DEX解决方案,详细分析了DexClassLoader的工作原理及其实现自定义类加载器的方法,包括使用反射机制调用DexFile的openDexFile方法,以及解决Android 5.0版本中该方法缺失的问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、前言


时隔半年,困扰的问题始终是需要解决的,之前也算是没时间弄,今天因为有人在此提起这个问题,那么就不能不解决了,这里写一篇文章记录一下吧。那么是什么问题呢?

就是关于之前的一个话题:Android中apk加固技术实现

关于这个问题,之前的一篇文章已经说过了,没有了解的同学可以点击这里: Android中的Apk的加固(加壳)原理解析和实现

请务必仔细的看完这篇文章,不然今天说的内容会感觉很蛋疼的,因为今天的文章就是为了解决当初的加固技术遗留的问题,这里先大致来说一下加固apk的原理吧,先来看一张图:

看到这张图其实,还是很好理解的,就是我们把需要加固的apk,外部包装一层壳,而这个壳的作用是为了解密源apk的,比如现在360加固都是采用这种思想,我们可以看一个简单的360加固之后的程序的AndroidManifest.xml文件:

看到了吧,这里StubApplication就是360加固给需要加壳的apk添加的一层Application。这样启动加壳之后的apk,其实是先启动这个Application,然后这个Application就开始解密apk操作,然后动态加载apk运行源程序,所以这里我们还看到有一个加密apk的过程,可以看这张图:

这个就是把源程序的apk塞到壳apk的dex文件中,这样壳Application就可以读取dex中的数据,进行解密即可。

从上面的加固思想来看,还是有一些风险的,那就是对于加固的apk,他启动的时候实际上是先启动壳程序,所以这样就会把我们的一些数据暴露给了这些加固程序,所以在加固apk的时候还是要考虑慎重。

 

二、加固遗留的问题

脱壳Dex流程

好了,上面就简单说了一下如何加固apk的大体流程,那么在这个实现过程中当初有一个问题,就是我们解密之后的apk程序是放在/data/data/xxx/cache目录下的,然后在用DexClassLoader进行加载apk,然后运行程序,那么这里就存在两个问题了?

1、解密之后的apk源程序放在指定目录的话,还是存在被破解的风险,因为这种落地方式解密,是很容易获取解密之后的apk的

2、在解密得到源程序apk,然后再用DexClassLoader进行加载,这里相当于两次把apk加载到内存中,第一次是解密的时候,第二次是加载apk的时候,那么这效率就会大大降低了

好了看到了有这两个问题,那么其实我们的解决思路很简单,就是如何做到不落地的解密apk程序,在解密完之后得到apk数据,立马进行内存数据的字节码加载,不需要在保存到本地的apk作为中转站了。

三、解决问题思路


我们先来猜想一下,系统既然能够加载dex文件,那么他会不会有一个能够直接加载文件字节码的方法呢?因为不管怎么样,加载一个文件到最后还是需要解析dex文件,然后map到内存中的,那么我们可以通过源码来看看有没有这样的方法?

那么我们既然最后都是要加载,肯定是用DexClassLoader类,那么我们看看这个类的源码:

源码位置:Android源码目录\libcore\dalvik\src\main\java\dalvik\system\DexClassLoader.java

擦,我们看到,他只有一个构造方法,就是需要传入加载文件的路径,没有能够直接出入字节数据的方法,那怎么破呢?不急,我们继续看他的父类BaseDexClassLoader源码:

源码位置:Android源码目录\libcore\dalvik\src\main\java\dalvik\system\BaseDexClassLoader.java

 

其实这个类,就是PathClassLoader和DexClassLoader的共同父类,关于这两个加载器的区别,不了解的同学可以看这里:

Android中插件开发篇之----类加载器 这里就介绍了这两个类加载的区别和联系。

看到,在BaseDexClassLoader的构造方法中,有一个重要的类DexPathList,他就是解析加载文件的类,

源码位置:Android源码目录\libcore\dalvik\src\main\java\dalvik\system\DexPathList.java

看到了,这里知道了Android中能够加载的四种文件格式:dex/jar/zip/apk

查看他的构造方法:

有一个makeDexElements方法,进入查看:

在这里,用loadDexFile方法来加载文件,返回一个DexFile对象,那么我们再去查看这个类

源码位置:Android源码目录\libcore\dalvik\src\main\java\dalvik\system\DexPathList.java#loadDexFile

调用loadDex方法,返回DexFile对象:

源码位置:Android源码目录\libcore\dalvik\src\main\java\dalvik\system\DexFile.java

在进入看构造方法:

这里有一个核心的地方,调用了openDexFile方法,然后返回一个int值:

擦,原来openDexFile是一个native方法,读取dex文件放在native层做的,而且,我们看到返回值代表什么意思呢?我们可以简单的理解为,VM中会维护一个Map结构,保存的内容就是dexFile文件和他对应的cookie值,每次在寻找这个dex中的类功能的时候,都是需要这个cookie进行操作的。

同时我们这里无意中看到了一个非常重要的方法:openDexFile的重载形式,参数就是一个字节数组,那么我们是不是就可以使用这个方法直接来进行操作呢?

好了,到这里我们分析完了dex加载的Java层的流程了,我们获取到的信息有:

1、Android中能够动态加载的文件格式只有四种:dex/jar/zip/apk

2、在DexFile中有两个openDexFile方法,一个是传递文件名称,一个是传递文件字节码,同时这两个方法是native层的。

我们继续来看看默认的DexClassLoader类加载一个类的流程是什么?

首先看的是loadClass方法:

我们在DexClassLoader和BaseDexClassLoader中都没有找到这个方法,但是BaseDexClassLoader继承了ClassLoader类:

在loadClass方法中其实是调用了findClass方法返回一个Class对象的,在看这个方法,在BaseDexClassLoader中:

这个方法中又继续调用了DexPathList类的findClass方法:

在这个方法中继续调用了DexFile的loadClassBinaryName方法:

好吧,这里最后调用了defineClass方法,又是一个native的方法,注意这个方法的最后一个参数是我们上面说到的那个dex对应的cookie值。这个值是openDexFile方法返回的。

上面分析完了dex的加载流程,下面总结一下就是:

ClassLoader的loadClass方法=》BaseDexClassLoader的findClass方法=》DexPathList的findClass方法=》DexFile的loadClassBinaryName方法=》DexFile的defineClass方法

四、实践操作


我们知道了这些信息之后,下面我们就来进行操作吧!

我们知道DexClassLoader提供的只有一个构造方法,接受的是加载文件路径,所以我们如果想让其接受加载字节码的话,只能重写我们自己的ClassLoader了。但是在重写一个ClassLoader的时候,我们需要注意三个重要的方法:findClass/defineClass/loadClass

关于这三个方法的特点是干什么的,具体参见这篇文章:Java高新技术第一篇:类加载器详解

他们三者有一个执行顺序:

在需要使用到一个类的时候,首先调用findClass去寻找到这个类文件,然后定义这个类,解析class文件格式,最后是加载这个类,当然在这个过程中可能涉及到Java中类加载器的双重委派机制,这里就不做太多的解释了。不过从这三个过程中我们可以看到:

一般是findClass方法中会抛出ClassNotFoundException的异常,defineClass会抛出NoClassDefFoundError的错误,我们看到findClass是在外部存储器中查找class文件的,defineClass是在内存中定义class的时候

所以总结:

加载时从外存储器找不到需要的class就出现ClassNotFoundException 
连接时从内存找不到需要的class就出现NoClassDefFoundError

那么我们的流程很清楚了:

肯定要重写findClass方法,在这个方法中需要做一些事情,就是需要进行class的名称转化,我们知道在代码中类的名称是用点号进行连接的,但是在磁盘中的文件是靠路径符/来进行连接的,所以这里需要做一个转化。同时需要把dex文件中的其他类进行define,所以这里还有一个问题,就是如何获取dex中所有的类,还好这个方法在DexFile中,叫做getClassNameList:

也是一个native方法

在磁盘中找到了这个类的话,那么这时候就需要调用defineClass方法,进行定义,之后得到了Class对象。

具体实现步骤如下:

1、需要使用反射机制调用DexFile类的openDexFile方法,载入字节码,这里调用的是参数为字节码的方法。然后得到dex对应的cookie值,保存。

2、重写findClass方法,在这个方法中还是需要使用反射机制调用DexFile类的getClassNameList方法获取dex中的所有类,然后再次调用defineClass方法,这里依然是用反射机制调用DexFile的defineClass方法,而且这里需要传递上面的cookie值。

3、最后在重写loadClass方法,加载指定类

注意需要反射的几个方法的结构如下:

1》native private static int openDexFile(byte[] fileContents);

2》native private static String[] getClassNameList(int cookie);

3》private native static Class defineClass(String name, ClassLoader loader, int cookie);

所以我们下面在用反射调用的时候,注意传递的参数。

从上面的流程看到,我们用到很多反射,所以这里定义一个反射功能类RefInvoke。下面就开始正式coding了,首先看看我们自己定义的DexClassLoader类的构造方法:

构造方法接受的是字节数组参数:

反射调用openDexFile方法,返回cookie值

在来看一下findClass方法:

这里首先使用反射调用getClassNameList方法获取dex中的所有类,然后在用反射调用defineClass方法,同时记得转化路径符,得到class之后返回即可。这里的两个方法都是反射调用的:

最后再来看一下laodClass方法吧:

这里直接调用了父类的loadClass方法返回一个Class对象即可。

好了,上面我们的自己的类加载器就定义好了,下面就来测试一下吧,测试这里很简单,就是用一个demo的classes.dex文件进行测试即可,这里没有涉及到什么的加密和解密了,因为不是本文的重点。

这里很简单,得到dex的字节码,然后在调用injectDexClassLoader方法:

这里我们构造一个自定义的类加载器:DynamicDexClassLoader,然后使用findClass进行直接获取Class类对象,当然这里使用loadClass方法也是可以的。最后还要记得设置系统的ClassLoader,为了classes.dex中的Activity正常加载进来,这个知识点可以参考这篇文章: Android中插件开发篇之----动态加载Activity(免安装运行程序) 为什么要这么做,这里就不多解释了。

好了,下面我们来运行程序:

擦,openDexFile方法没找到,怎么会没找到呢?这时候我们为了排查问题,就在把DexFile类中所有的方法和方法的参数打印一下:

再次运行看看结果:

我擦,怎么只有一个openDexFile方法了,但是我们上面分析源码的时候,有一个openDexFile(byte[] ...)的方法的呀!

好吧,在一顿蛋疼之后,想到了可能是系统版本问题,我们上面的源码分析是Android4.2的,但是我运行设备是5.0的,是不是google在新版本中去除这个方法了?我们速度查看了Android5.0的DexFile源码:

麻蛋,果然如此,找不到openDexFile(byte[]...)的方法了,而且也没有类似于这类的方法了,只有传递String参数的方法了。好吧,到这里感觉好绝望,为何在新版本中夭折了这个方法呢?

不过上天自古有好生之德,我们在冷静想一想,是否还记得不管openDexFile(byte[]...)这个方法是否存在了,这里的方法都是native层的,而且,及时夭折了,本质还是没有改变,那就是底层还是会有一个方法去解析dex文件得到字节码,然后进行加载到内存中的,所以我们可以坚信google夭折的肯定是Java层的代码,所以native层的代码肯定没有改变,所以坚信这点,我们查看了DexFile对应的native源码:

源码目录:Android源码目录\\dalvik\vm\native\dalvik_system_DexFile.cpp

这里的源码还是Android4.2的,因为我们为了分析问题,Android5.0肯定没有了,因为他把这个方法给夭折了,5.0对应的native源码目录为:Android源码art\runtime\native\dalvik_system_DexFile.cpp

看到了没有这个方法了,所以看4.2的源码,来查找被夭折的方法openDexFile(byte[]...)对应的native方法是啥?我们看到,openDexFile对应的native方法是:Dalvik_dalvik_system_DexFile_openDexFile_bytearray

再来看看这个方法的具体实现:

这里的参数会有点看不懂,其实很简单

第一个参数代表我们需要传递的参数对应的指针的数组,这么简单的理解吧,比如现在有两个参数字节数组,和字节大小,那么这个参数就是args[0]=字节数组对象的指针,args[1]=字节大小指针,这里可以看到C语言中的指针太无敌了,什么都可以干。

第二参数代表返回值指针,原理实现和上面的参数指针一样

这里使用了dvm系列的方法打开文件的。

好了,到这里,其实我们总结一下,我们现在遇到的问题:

Android5.0把openDexFile(byte[]...)方法给夭折了,但是我们分析了4.2的源码之后,发现openDexFile其实对应的是native层的

Dalvik_dalvik_system_DexFile_openDexFile_bytearray方法,那么5.0会在底层把这个方法也给夭折了吗?其实我们猜想是不会的,因为他不管怎么样,最终还是会调用这个方法来解析dex文件,然后进行加载到内存中,那么这个方法在哪里呢?我们该怎么执行他呢?这里的两个问题其实很简单:

第一个问题:我们知道Android中只要底层涉及到VM的native代码都有一个著名的共享库文件,那就是libdvm.so,如果这个方法没被夭折,那么肯定是在这里

我们可以查看设备中的这个库文件:

我们把它pull到本地,然后用IDA打开进行查看:

这里很多dex开头和dvm开头的底层函数。

第二个问题:我们需要借助于两个系统函数:dlopen和dlsym这两个函数,他们的功能就是打开一个共享库文件,然后可以根据传递的函数名和变量名得到函数指针和变量指针

dlopen函数以指定模式打开指定的动态链接库文件,并返回一个句柄给dlsym()的调用进程
dlsym根据动态链接库操作句柄与符号,返回符号对应的地址。使用这个函数不但可以获取函数地址,也可以获取变量地址。

其实说的简单点,就类似于Java中的反射,我们用ClassLoader加载一个jar文件,然后用反射去访问方法和得到变量等信息。

好了既然上面的两个问题解决了,下面就来写个代码验证一下我们的猜想,看看libdvm.so中是否还存在这个函数

那么这里肯定要设计JNI了,关于AndroidStudio中如何使用NDK,这里不解释了,网上自行搜索即可。

不过这里为了检测方便,我们在java层定义了一个native方法:

public static native int loadDex(byte[] dex,long dexlen);

他的功能其实很简单,就是上面DexFile被夭折的openDexFile(byte[]...)方法,这里多传递了一个dexlen长度参数,是为了native层容易处理,不需要在去计算大小了。再来看看native层:

这里应该在JNI_OnLoad函数中进行dlopen和dlsym操作,因为时机比较早

这里有一行重要的代码:

dvm_dalvik_system_DexFile = (JNINativeMethod*) dlsym(ldvm, "dvm_dalvik_system_DexFile");

这个是获取libdvm.so中的一个JNINativeMethod结构体变量,Andoird 中使用了一种不同传统Java JNI的方式来定义其native的函数。其中很重要的区别是Andorid使用了一种Java 和C 函数的映射表数组,并在其中描述了函数的参数和返回值。这个数组的类型是JNINativeMethod,定义如下:

typedef struct {
const char* name;
const char* signature;
void* fnPtr;
} JNINativeMethod;
第一个变量name是Java中函数的名字。
第二个变量signature,用字符串是描述了函数的参数和返回值
第三个变量fnPtr是函数指针,指向C函数。

那么我们得到这个结构体之后,就可以知道所有其对应的JNI函数列表了,这里定义了lookup函数来做这个事情:

这个函数的作用就是判断传递进来的函数是否为JNINativeMethod数据结构中的native函数。如果是的话,就赋值给JValue指针,这里的JValue指针就是一个函数指针:openDexFile:

所以这里我们看到JNI_OnLoad函数中调用lookup函数的时候传递的函数名是:openDexFile,而不是我们上面猜测的那个函数:

Dalvik_dalvik_system_DexFile_openDexFile_bytearray  

就是因为我提前运行测试了,打印log之后发现:

所以这里,我们的猜想是错误的了,就是so中并不存在Dalvik_dalvik_system_DexFile_openDexFile_bytearray 这个函数了,而是openDexFile这个函数,好了,既然猜想错了,但是我们还是找到了这个底层的函数,那么就简单了,执行这个函数即可,因为上面我们已经得到了这个函数的指针了:

这里,我们先把Java层传递进来的字节内容和字节大小构造成一个u4类型的参数指针,然后调用openDexFile函数,得到返回值,返回给Java层即可,不过这里有一个点就是有一个类型是ArrayObject的,这个我们可以去这个源码头文件Object.h中找到copy过来就可以了:

头文件的源码目录:Android源码目录\dalvik\vm\oo\Object.h

native层的代码也看完了,下面我们就来验证一下看看libdvm.so库中的的openDexFile函数好不好使,我们在Java层修改一下自定义的类加载器的代码:

使用我们的native方法:loadDex,传递dex的字节数组和字节大小

那么下面我们来看看运行结果:

看到了,这里是native层的日志,看到openDexFile找到了

我们使用findClass去加载MainActivity类,成功了,我们再看运行结果:

擦擦擦,成功了,哈哈,好兴奋呀。。。我们成功的实现了内存加载dex方案,解决了之前apk加固遗留的两个问题。

资源下载:http://download.youkuaiyun.com/detail/jiangwei0910410003/9538313

项目文件

MyApplication.java

package cn.wjdiankong.dexfiledynamicload;

import android.app.Application;
import android.content.Context;
import android.os.Environment;
import android.util.ArrayMap;
import android.util.Log;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.lang.ref.WeakReference;
import java.lang.reflect.Array;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * Created by i on 2016/4/9.
 */
public class MyApplication extends Application{

    static {
        System.loadLibrary("nativetool");
    }

    private Context mContext;

    @Override
    protected void attachBaseContext(Context base) {
        super.attachBaseContext(base);
        mContext = base;
        //startNativeLoadDynDex();
    }

    /*private void startNativeLoadDynDex(){
        new Thread(new Runnable() {
            @Override
            public void run() {
                File file = getExternalCacheDir();
                String apkPath = *//*file.getAbsolutePath()*//*Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "classes.dex";
                Log.i("jw", "apkpath:"+apkPath);
                Log.i("jw", "file exist:"+new File(apkPath).exists());
                ByteArrayOutputStream bos = null;
                FileInputStream fis = null;
                try{
                    fis = new FileInputStream(apkPath);
                    bos = new ByteArrayOutputStream();
                    byte[] buffer = new byte[1024];
                    int len;
                    while((len=fis.read(buffer)) != -1){
                        bos.write(buffer, 0, len);
                    }
                    bos.flush();
                    injectDexClassLoader(bos.toByteArray());
                }catch (Exception e){
                    Log.i("jw", "error:"+Log.getStackTraceString(e));
                }finally {
                    try{
                        bos.close();
                        fis.close();
                    }catch (Exception e){
                    }
                }
            }
        }).start();
    }

    private void injectDexClassLoader(byte[] dexContent){

        Object currentActivityThread = RefInvoke.invokeStaticMethod(
                "android.app.ActivityThread", "currentActivityThread",
                new Class[] {}, new Object[] {});
        String packageName = getPackageName();
        ArrayMap mPackages = (ArrayMap) RefInvoke.getFieldOjbect(
                "android.app.ActivityThread", currentActivityThread,
                "mPackages");
        WeakReference refToLoadedApk = (WeakReference) mPackages.get(packageName);
        ClassLoader clzLoader = (ClassLoader) RefInvoke.getFieldOjbect("android.app.LoadedApk", refToLoadedApk.get(), "mClassLoader");
        DynamicDexClassLoder dLoader = new DynamicDexClassLoder(
                mContext,
                dexContent,
                null,
                clzLoader,
                getPackageResourcePath(),getDir(".dex", MODE_PRIVATE).getAbsolutePath()
        );

        *//*Object pthList = RefInvoke.getFieldOjbect(
                "dalvik.system.BaseDexClassLoader", clzLoader, "pathList");
        Class pathListClz = pthList.getClass();
        Method[] methods = pathListClz.getMethods();
        for(Method method : methods){
            Log.i("jw", "method name:"+method.getName());
        }
        Log.i("jw", "pathList :" + pthList);
        Object dexElements = RefInvoke.getFieldOjbect(
                "dalvik.system.DexPathList", pthList, "dexElements");*//*

        try{
            clazz = dLoader.findClass("cn.wjdiankong.demo.MainActivity");
            Log.i("jw", "class name:"+clazz.getName());
        }catch (Exception e){
            Log.i("jw", "error:"+Log.getStackTraceString(e));
        }

        RefInvoke.setFieldOjbect("android.app.LoadedApk", "mClassLoader", refToLoadedApk.get(), dLoader);
    }*/

    private static Class clazz;

    public static Class getEntryActivityClass(){
        return clazz;
    }

    private void startLoadDynDex(){
        new Thread(new Runnable() {
            @Override
            public void run() {
                File file = getExternalCacheDir();
                String apkPath = file.getAbsolutePath() + File.separator + "AndroidDemo.apk";
                Log.i("jw", "apkpath:"+apkPath);
                Log.i("jw", "file exist:"+new File(apkPath).exists());
                ByteArrayOutputStream bos = null;
                FileInputStream fis = null;
                try{
                    fis = new FileInputStream(apkPath);
                    bos = new ByteArrayOutputStream();
                    byte[] buffer = new byte[1024];
                    int len;
                    while((len=fis.read(buffer)) != -1){
                        bos.write(buffer, 0, len);
                    }
                    bos.flush();
                    loadDynDex(bos.toByteArray());
                }catch (Exception e){
                    Log.i("jw", "error:"+Log.getStackTraceString(e));
                }finally {
                    try{
                        bos.close();
                        fis.close();
                    }catch (Exception e){
                    }
                }
            }
        }).start();
    }

    /**
     * @throws InvocationTargetException
     *
     * */
    public void loadDynDex(byte[] dexContent) throws NoSuchMethodException,
            ClassNotFoundException, IllegalArgumentException,
            IllegalAccessException, InvocationTargetException {

        Log.i("jw", "load dex len:"+dexContent.length);

        /**
         * Class obj_class = Class.forName(class_name); Method method =
         * obj_class.getMethod(method_name,pareTyple); return
         * method.invoke(null, pareVaules);
         * */
        Class DexFileClz = Class.forName("dalvik.system.DexFile");

        Class[] paratype = new Class[1];
        paratype[0] = byte[].class;

        Object[] paraobj = new Object[1];
        paraobj[0] = dexContent;

        Method[] methods = DexFileClz.getMethods();
        for(Method method : methods){
            Log.i("jw", "name:"+method.getName());
        }

        Method openDexFilemtd = DexFileClz.getDeclaredMethod("openDexFile",
                paratype);
        openDexFilemtd.setAccessible(true);
        int retv = (Integer) openDexFilemtd.invoke(null, paraobj);
        Log.i("jw", "return value : " + retv);

        int cookie = retv;

        paratype = new Class[1];
        paratype[0] = int.class;

        Method getClassNameListMtd = DexFileClz.getDeclaredMethod(
                "getClassNameList", paratype);
        getClassNameListMtd.setAccessible(true);
        paraobj[0] = cookie;
        String[] clznms = (String[]) getClassNameListMtd.invoke(null, paraobj);

        paratype = new Class[3];
        paratype[0] = String.class;
        paratype[1] = ClassLoader.class;
        paratype[2] = int.class;

        paraobj = new Object[3];
        paraobj[0] = "";
        paraobj[1] = getClassLoader();
        paraobj[2] = cookie;

        Method defineClassMtd = DexFileClz.getDeclaredMethod("defineClass",
                paratype);
        defineClassMtd.setAccessible(true);

        for (int i = 0; i < clznms.length; i++) {
            paraobj[0] = clznms[i];
            Class loadedclz = (Class) defineClassMtd.invoke(null, paraobj);
            Log.i("jw", "classname:"+loadedclz.getName());
        }
        updateClassLoader(cookie);
    }

    /**
     * 因为DexClassLoader在findClass的时候用到的只有: String Classname, ClassLoader loader,
     * int cookie �?��,通过替换原始DexClassLoader的cookie为当前Dex的cookie的方�?
     * 就可以将原始的DexClassLoader替换为能够动态加载新Dex的ClassLoader
     * */
    public void updateClassLoader(int newCookie) {
        Object currentActivityThread = null;

        Class paraTypes = null;
        Object paraObjs = null;

        currentActivityThread = RefInvoke.invokeStaticMethod(
                "android.app.ActivityThread", "currentActivityThread",
                new Class[] {}, new Object[] {});
        String packageName = this.getPackageName();

        ArrayMap mPackages = (ArrayMap) RefInvoke.getFieldOjbect(
                "android.app.ActivityThread", currentActivityThread,
                "mPackages");

        WeakReference refToLoadedApk = (WeakReference) mPackages
                .get(packageName);

        Object loadedApk = refToLoadedApk.get();
        Object clzLoader = RefInvoke.getFieldOjbect("android.app.LoadedApk",
                loadedApk, "mClassLoader");
        Log.i("SN", "mClassLoader :" + clzLoader);
        Object pthList = RefInvoke.getFieldOjbect(
                "dalvik.system.BaseDexClassLoader", clzLoader, "pathList");
        Log.i("SN", "pathList :" + pthList);
        Object dexElements = RefInvoke.getFieldOjbect(
                "dalvik.system.DexPathList", pthList, "dexElements");

        int length = Array.getLength(dexElements);

        for (int i = 0; i < length; i++) {
            Object ele = Array.get(dexElements, i);
            try {
                Object dexFile = RefInvoke.getFieldOjbect(
                        "dalvik.system.DexPathList$Element", ele, "dexFile");
                // 如果没有抛出异常,说明成功获取到了dex文件
                RefInvoke.setFieldOjbect("dalvik.system.DexFile", "mCookie",
                        dexFile, newCookie);
            } catch (Exception e) {
                continue;
            }
            break;// 只处理遇到的第一个dex文件
        }
    }

}

MainActivity.java

package cn.wjdiankong.dexfiledynamicload;

import android.content.Intent;
import android.content.res.AssetManager;
import android.content.res.Resources;
import android.os.Environment;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.ArrayMap;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.lang.ref.WeakReference;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;

public class MainActivity extends AppCompatActivity {

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

        startNativeLoadDynDex();
    }

    private void startNativeLoadDynDex(){
        new Thread(new Runnable() {
            @Override
            public void run() {
                String dexPath = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "classes.dex";
                Log.i("jw", "apkpath:"+dexPath);
                Log.i("jw", "file exist:" + new File(dexPath).exists());

                ByteArrayOutputStream bos = null;
                FileInputStream fis = null;
                try{
                    fis = new FileInputStream(dexPath);
                    bos = new ByteArrayOutputStream();
                    byte[] buffer = new byte[1024];
                    int len;
                    while((len=fis.read(buffer)) != -1){
                        bos.write(buffer, 0, len);
                    }
                    bos.flush();
                    injectDexClassLoader(bos.toByteArray());
                }catch (Exception e){
                    Log.i("jw", "error:"+Log.getStackTraceString(e));
                }finally {
                    try{
                        bos.close();
                        fis.close();
                    }catch (Exception e){
                    }
                }
            }
        }).start();
    }

    private void injectDexClassLoader(byte[] dexContent){
        Object currentActivityThread = RefInvoke.invokeStaticMethod(
                "android.app.ActivityThread", "currentActivityThread",
                new Class[] {}, new Object[] {});
        String packageName = getPackageName();
        ArrayMap mPackages = (ArrayMap) RefInvoke.getFieldOjbect(
                "android.app.ActivityThread", currentActivityThread,
                "mPackages");
        WeakReference refToLoadedApk = (WeakReference) mPackages.get(packageName);
        ClassLoader clzLoader = (ClassLoader) RefInvoke.getFieldOjbect("android.app.LoadedApk",
                refToLoadedApk.get(), "mClassLoader");
        DynamicDexClassLoder dLoader = new DynamicDexClassLoder(
                getApplicationContext(),
                dexContent,
                null,
                clzLoader,
                getPackageResourcePath(),getDir(".dex", MODE_PRIVATE).getAbsolutePath()
        );
        try{
            Class clazzR = dLoader.findClass("cn.wjdiankong.demo.R$layout");
            Field field = clazzR.getField("main_activity");
            Log.i("jw", "field:"+Integer.toHexString((Integer)field.get(null)));

            Class clazz = dLoader.findClass("cn.wjdiankong.demo.MainActivity");
            Log.i("jw", "class name:"+clazz.getName());

            Intent intent = new Intent(this, clazz);
            startActivity(intent);
        }catch (Exception e){
            Log.i("jw", "error:"+Log.getStackTraceString(e));
        }
        RefInvoke.setFieldOjbect("android.app.LoadedApk", "mClassLoader", refToLoadedApk.get(), dLoader);
    }

    //以下是加载资源
    protected AssetManager mAssetManager;//资源管理器
    protected Resources mResources;//资源
    protected Resources.Theme mTheme;//主题

    protected void loadResources(String dexPath) {
        try {
            AssetManager assetManager = getAssets();
            Method addAssetPath = assetManager.getClass().getMethod("addAssetPath", String.class);
            Object obj = addAssetPath.invoke(assetManager, dexPath);
            Log.i("jw", "add asset obj:"+obj);
            mAssetManager = assetManager;
        } catch (Exception e) {
            Log.i("inject", "loadResource error:"+Log.getStackTraceString(e));
            e.printStackTrace();
        }
        Resources superRes = getResources();
        superRes.getDisplayMetrics();
        superRes.getConfiguration();
        mResources = new Resources(mAssetManager, superRes.getDisplayMetrics(),superRes.getConfiguration());
        mTheme = mResources.newTheme();
        mTheme.setTo(super.getTheme());
    }

    @Override
    public AssetManager getAssets() {
        return mAssetManager == null ? super.getAssets() : mAssetManager;
    }

    @Override
    public Resources getResources() {
        return mResources == null ? super.getResources() : mResources;
    }

    @Override
    public Resources.Theme getTheme() {
        return mTheme == null ? super.getTheme() : mTheme;
    }


}

DynamicDexClassLoder.java

package cn.wjdiankong.dexfiledynamicload;

/**
 * Created by i on 2016/4/9.
 */
import java.lang.reflect.Method;
import java.net.URL;
import java.util.Enumeration;

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

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

public class DynamicDexClassLoder extends DexClassLoader {

    private static final String TAG = "jw";
    private int cookie;
    private Context mContext;

    /**
     * @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);

        //打印DexFile类的所有方法
        try{
            Class clazz = DexFile.class;
            Method[] methods = clazz.getMethods();

            Log.i("jw", "*******************************");
            for(Method method : methods){
                Log.i("jw", "method:"+method.getName());
            }
            Log.i("jw", "*******************************");

            Method[] methods1 = clazz.getDeclaredMethods();
            for(Method method : methods1){
                Log.i("jw", "++++++++++++++++++++++++++");
                Log.i("jw", "method:"+method.getName());
                Class returns = method.getReturnType();
                Log.i("jw", "return:"+returns);
                Class[] parms = method.getParameterTypes();
                for(Class cla : parms){
                    Log.i("jw", "parm:"+cla.getName());
                }
            }
            Log.i("jw", "*******************************");
        }catch (Exception e){

        }

        int cookie = NativeTool.loadDex(dexBytes, dexBytes.length);

        //反射调用openDexFile方法获取dex对应的cookie值
        /*int cookie = (Integer) RefInvoke.invokeDeclaredStaticMethod(
                DexFile.class.getName(),
                "openDexFile",
                new Class[]{Object.class},
                new Object[]{dexBytes});*/

        Log.i("jw", "cookie:"+cookie);

        setCookie(cookie);

    }

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

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

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

    private Class defineClass(String name, ClassLoader loader, int cookie) {
        Log.i(TAG, "define class:"+name);
        return (Class) RefInvoke.invokeDeclaredStaticMethod(DexFile.class.getName(),
                "defineClassNative", 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++) {
            Log.i("jw", "classname:"+as[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 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;
    }

}

NativeTool.java

package cn.wjdiankong.dexfiledynamicload;

/**
 * Created by i on 2016/4/9.
 */
public class NativeTool {

    public static native int loadDex(byte[] dex,long dexlen);

}

RefInvoke.java

package cn.wjdiankong.dexfiledynamicload;

import android.util.Log;

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

public class RefInvoke {
	
	public static  Object invokeStaticMethod(String class_name, String method_name, Class[] pareTyple, Object[] pareVaules){
		
		try {
			Class obj_class = Class.forName(class_name);
			Method method = obj_class.getMethod(method_name,pareTyple);
			return method.invoke(null, pareVaules);
		} catch (Exception e) {
			Log.i("jw", "invoke static method:"+Log.getStackTraceString(e));
		}
		return null;
		
	}

	public static  Object invokeDeclaredStaticMethod(String class_name, String method_name, Class[] pareTyple, Object[] pareVaules){

		try {
			Class obj_class = Class.forName(class_name);
			Method method = obj_class.getDeclaredMethod(method_name,pareTyple);
			method.setAccessible(true);
			return method.invoke(null, pareVaules);
		} catch (Exception e) {
			Log.i("jw", "invoke static method:"+Log.getStackTraceString(e));
		}
		return null;

	}
	
	public static  Object invokeMethod(String class_name, String method_name, Object obj ,Class[] pareTyple, Object[] pareVaules){
		
		try {
			Class obj_class = Class.forName(class_name);
			Method method = obj_class.getMethod(method_name,pareTyple);
			return method.invoke(obj, pareVaules);
		} catch (Exception e) {
			Log.i("jw", "invoke method :"+Log.getStackTraceString(e));
		}
		return null;
		
	}
	
	public static Object getFieldOjbect(String class_name,Object obj, String filedName){
		try {
			Class obj_class = Class.forName(class_name);
			Field field = obj_class.getDeclaredField(filedName);
			field.setAccessible(true);
			return field.get(obj);
		} catch (SecurityException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (NoSuchFieldException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IllegalArgumentException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return null;
		
	}
	
	public static Object getStaticFieldOjbect(String class_name, String filedName){
		
		try {
			Class obj_class = Class.forName(class_name);
			Field field = obj_class.getDeclaredField(filedName);
			field.setAccessible(true);
			return field.get(null);
		} catch (SecurityException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (NoSuchFieldException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IllegalArgumentException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return null;
		
	}
	
	public static void setFieldOjbect(String classname, String filedName, Object obj, Object filedVaule){
		try {
			Class obj_class = Class.forName(classname);
			Field field = obj_class.getDeclaredField(filedName);
			field.setAccessible(true);
			field.set(obj, filedVaule);
		} catch (SecurityException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (NoSuchFieldException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IllegalArgumentException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}	
	}
	
	public static void setStaticOjbect(String class_name, String filedName, Object filedVaule){
		try {
			Class obj_class = Class.forName(class_name);
			Field field = obj_class.getDeclaredField(filedName);
			field.setAccessible(true);
			field.set(null, filedVaule);
		} catch (SecurityException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (NoSuchFieldException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IllegalArgumentException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}		
	}

}

common.h

#ifndef DALVIK_COMMON_H_
#define DALVIK_COMMON_H_

#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <assert.h>

static union { char c[4]; unsigned long mylong; }endian_test = {{ 'l', '?', '?', 'b' } };
#define ENDIANNESS  ((char)endian_test.mylong)

//#if ENDIANNESS == "l"
#define HAVE_LITTLE_ENDIAN
//#else
//#define HAVE_BIG_ENDIAN
//#endif

#if defined(HAVE_ENDIAN_H)
# include <endian.h>
#else /*not HAVE_ENDIAN_H*/
# define __BIG_ENDIAN 4321
# define __LITTLE_ENDIAN 1234
# if defined(HAVE_LITTLE_ENDIAN)
#  define __BYTE_ORDER __LITTLE_ENDIAN
# else
#  define __BYTE_ORDER __BIG_ENDIAN
# endif
#endif /*not HAVE_ENDIAN_H*/

#if !defined(NDEBUG) && defined(WITH_DALVIK_ASSERT)
# undef assert
# define assert(x) \
((x) ? ((void)0) : (ALOGE("ASSERT FAILED (%s:%d): %s", \
__FILE__, __LINE__, #x), *(int*)39=39, (void)0) )
#endif

#define MIN(x,y) (((x) < (y)) ? (x) : (y))
#define MAX(x,y) (((x) > (y)) ? (x) : (y))

#define LIKELY(exp) (__builtin_expect((exp) != 0, true))
#define UNLIKELY(exp) (__builtin_expect((exp) != 0, false))

#define ALIGN_UP(x, n) (((size_t)(x) + (n) - 1) & ~((n) - 1))
#define ALIGN_DOWN(x, n) ((size_t)(x) & -(n))
#define ALIGN_UP_TO_PAGE_SIZE(p) ALIGN_UP(p, SYSTEM_PAGE_SIZE)
#define ALIGN_DOWN_TO_PAGE_SIZE(p) ALIGN_DOWN(p, SYSTEM_PAGE_SIZE)

#define CLZ(x) __builtin_clz(x)

/*
 * If "very verbose" logging is enabled, make it equivalent to ALOGV.
 * Otherwise, make it disappear.
 *
 * Define this above the #include "Dalvik.h" to enable for only a
 * single file.
 */
/* #define VERY_VERBOSE_LOG */
#if defined(VERY_VERBOSE_LOG)
# define LOGVV  ALOGV
# define IF_LOGVV() IF_ALOGV()
#else
# define LOGVV(...) ((void)0)
# define IF_LOGVV() if (false)
#endif


/*
 * These match the definitions in the VM specification.
 */
typedef uint8_t u1;
typedef uint16_t u2;
typedef uint32_t u4;
typedef uint64_t u8;
typedef int8_t  s1;
typedef int16_t s2;
typedef int32_t s4;
typedef int64_t s8;

/*
 * Storage for primitive types and object references.
 *
 * Some parts of the code (notably object field access) assume that values
 * are "left aligned", i.e. given "JValue jv", "jv.i" and "*((s4*)&jv)"
 * yield the same result.  This seems to be guaranteed by gcc on big- and
 * little-endian systems.
 */

#define OFFSETOF_MEMBER(t, f) \
  (reinterpret_cast<char*>(   \
 &reinterpret_cast<t*>(16)->f) -  \
   reinterpret_cast<char*>(16))

#define NELEM(x) ((int) (sizeof(x) / sizeof((x)[0])))

union JValue {
#if defined(HAVE_LITTLE_ENDIAN)
    u1  z;
    s1  b;
    u2  c;
    s2  s;
    s4  i;
    s8  j;
    float   f;
    double  d;
    void* l;
#endif
#if defined(HAVE_BIG_ENDIAN)
    struct {
        u1_z[3];
        u1z;
    };
    struct {
        s1_b[3];
        s1b;
    };
    struct {
        u2_c;
        u2c;
    };
    struct {
        s2_s;
        s2s;
    };
    s4  i;
    s8  j;
    float   f;
    double  d;
    void*   l;
#endif
};

/*
 * Array objects have these additional fields.
 *
 * We don't currently store the size of each element.  Usually it's implied
 * by the instruction.  If necessary, the width can be derived from
 * the first char of obj->clazz->descriptor.
 */

/*typedef struct {
    void* clazz;
    u4  lock;
}Object;*/

typedef struct {
    void*clazz;
    u4  lock;
    u4  length;
    u1*  contents;
}ArrayObject ;

/*typedef struct {
    u4  instanceData[1];
    int length() const;
    int utfLength() const;
    ArrayObject* array() const;
    const u2* chars() const;
}StringObject;*/

#endif  // DALVIK_COMMON_H_

cn_wjdiankong_dexfiledynamicload_NativeTool.h

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class cn_wjdiankong_dexfiledynamicload_NativeTool */

#ifndef _Included_cn_wjdiankong_dexfiledynamicload_NativeTool
#define _Included_cn_wjdiankong_dexfiledynamicload_NativeTool
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     cn_wjdiankong_dexfiledynamicload_NativeTool
 * Method:    loadDex
 * Signature: ([BJ)I
 */
JNIEXPORT jint JNICALL Java_cn_wjdiankong_dexfiledynamicload_NativeTool_loadDex
  (JNIEnv *, jclass, jbyteArray, jlong);

#ifdef __cplusplus
}
#endif
#endif

 NativeTool.c

#include "cn_wjdiankong_dexfiledynamicload_NativeTool.h"
#include "common.h"
#include <stdlib.h>
#include <dlfcn.h>
#include <stdio.h>

#include <android/log.h>

#define  LOG_TAG    "jw"
#define  LOGI(...)  __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)

JNINativeMethod *dvm_dalvik_system_DexFile;
void (*openDexFile)(const u4* args, union JValue* pResult);

int lookup(JNINativeMethod *table, const char *name, const char *sig,
           void (**fnPtrout)(u4 const *, union JValue *)) {
    int i = 0;
    while (table[i].name != NULL)
    {
        LOGI("lookup %d %s" ,i,table[i].name);
        if ((strcmp(name, table[i].name) == 0)
            && (strcmp(sig, table[i].signature) == 0))
        {
            *fnPtrout = table[i].fnPtr;
            return 1;
        }
        i++;
    }
    return 0;
}

/* This function will be call when the library first be load.
 * You can do some init in the libray. return which version jni it support.
 */
JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) {

    void *ldvm = (void*) dlopen("libdvm.so", RTLD_LAZY);
    dvm_dalvik_system_DexFile = (JNINativeMethod*) dlsym(ldvm, "dvm_dalvik_system_DexFile");

    //openDexFile
    if(0 == lookup(dvm_dalvik_system_DexFile, "openDexFile", "([B)I",&openDexFile)) {
        openDexFile = NULL;
        LOGI("openDexFile method does not found ");
    }else{
        LOGI("openDexFile method found ! HAVE_BIG_ENDIAN");
    }

    LOGI("ENDIANNESS is %c" ,ENDIANNESS );
    void *venv;
    LOGI("dufresne----->JNI_OnLoad!");
    if ((*vm)->GetEnv(vm, (void**) &venv, JNI_VERSION_1_4) != JNI_OK) {
        LOGI("dufresne--->ERROR: GetEnv failed");
        return -1;
    }
    return JNI_VERSION_1_4;
}

JNIEXPORT jint JNICALL Java_cn_wjdiankong_dexfiledynamicload_NativeTool_loadDex(JNIEnv* env, jclass jv, jbyteArray dexArray, jlong dexLen)
{
    // header+dex content
    u1 * olddata = (u1*)(*env)-> GetByteArrayElements(env,dexArray,NULL);
    char* arr;
    arr = (char*)malloc(16 + dexLen);
    ArrayObject *ao=(ArrayObject*)arr;
    ao->length = dexLen;
    memcpy(arr+16,olddata,dexLen);
    u4 args[] = { (u4) ao };
    union JValue pResult;
    jint result;
    if(openDexFile != NULL) {
        openDexFile(args,&pResult);
    }else{
        result = -1;
    }

    result = (jint) pResult.l;
    LOGI("Java_cn_wjdiankong_dexfiledynamicload_NativeTool_loadDex %d" , result);
    return result;
}

五、知识梳理


1、我们在之前apk加固中遗留的两个问题

1》、解密之后的apk源程序放在指定目录的话,还是存在被破解的风险,因为这种落地方式解密,是很容易获取解密之后的apk的

2》、在解密得到源程序apk,然后再用DexClassLoader进行加载,这里相当于两次把apk加载到内存中,第一次是解密的时候,第二次是加载apk的时候,那么这效率就会大大降低了

那么我们带着这两个问题,就思考,结果这两个问题的最好办法就是如何能够动态加载内存数据,而不是有一个中间产物apk,但是我们看到DexClassLoader只有一个构造方法,是接受加载文件的路径的,那么我们就猜想,不管加载上层如何,底层都是需要解析dex文件,然后加载到内存中的,所以肯定在某个地方有加载字节数据的,所以我们去查看DexClassLoader源码

2、我们通过分析DexClassLoader源码了解了Android中动态加载的流程

这里涉及到了几个类:DexClassLoader/ClassLoader/BaseDexClassLoader/DexPathList/DexFile

其中,BaseDexClassLoader是DexClassLoader的父类,BaseDexClassLoader继承了ClassLoader,他们互相调用的流程:

ClassLoader的loadClass方法=》BaseDexClassLoader的findClass方法=》DexPathList的findClass方法=》DexFile的loadClassBinaryName方法=》DexFile的defineClass方法

这里最终都是回到了DexFile中的几个native方法:

Class defineClass(String name, ClassLoader loader, int cookie)

Class loadClassBinaryName(String name, ClassLoader loader)

int openDexFile(String sourceName, String outputName,int flags) 

我们在分析的过程中,在DexFile中发现了一个重要的方法:int openDexFile(byte[] fileContents)

这个方法可以加载字节数组,那么我们就开始尝试用反射机制来操作DexFile来实现自定义类加载器

3、实现自己的类加载器的主要功能

1》在类加载器的构造方法中反射调用openDexFile方法得到一个cookie值

2》重写findClass方法,在这里首先通过反射调用getClassNameList方法,需要传递上面的cookie值,得到dex中所有的类,然后在进行类路径的转化把点号转化成斜杠,然后在反射调用defineClass方法,需要传递上面的cookie值,然后返回一个Class对象

这里我们看到一个重要的值,就是cookie,这个其实就是对应加载的dex的值,后续如果要访问这个dex附属的对象都可以使用这个cookie值

4、实践之后发现报错

实现了上面的功能之后,使用一个demo的classes.dex文件进行测试,运行之后发现报错,错误是找不到DexFile中的openDexFile方法,然后我们为了查找问题,就打印了DexFile类的所有方法,结果发现的确没有openDexFile(byte[]...)方法,这时候就蛋疼了,为何看源码中有个方法,但是运行却找不到呢?考虑之后发现应该是系统版本的问题,就去查看了5.0的DexFile源码,发现的确没有这个方法了,所以猜想是google把这个方法给删除了,那么这时候就蛋疼了。

5、从新整理思路继续探索

经过一刻的蛋疼之后,想一想还是开始的思路,不管google删除了这个方法,底层肯定还是会解析dex文件,加载到内存中的,那么肯定还是会有加载字节数据的方法,可能是在底层中,所以又有了灵感,去查看了4.2的源码,看看DexFile的native层源码,看到了一个和上层openDexFile做映射的函数:

Dalvik_dalvik_system_DexFile_openDexFile_bytearray,然后就想这个函数是否还存在,如果在是在哪里?我们该怎么访问他呢?所以就需要解决这两个问题:

1》我们知道Android关于VM的底层功能都在libdvm.so这个共享库中,所以可能会存在这里

2》如果存在共享库中,我们可以使用dlopen和dlsym两个系统函数获取so库中的函数指针

好了,有了这个思路,我们就去实践

6、猜想还是有一个加载字节数组的函数

在实践中,我们在java层做了一个类似于openDexFile的native方法:loadDex(byte[]...int...),然后在底层去操作,可惜的是,我们在实践中发现没有这个函数,我们的猜想错了,这时候又开始蛋疼了,怎么搞了呢?但是我们还是坚信我们的思路,肯定有一个方法存在的,这时候我们干了一件事就是可以使用dlsym函数获取一个变量指针,得到JNINativeMethod结构体指针,他是DexFile对应的所有native函数,我们打印这个结构体,结果发现了两个方法和签名,其中有一个openDexFile函数就是我们想要找的函数。

7、最终实践,成功

找到了这个函数就好办了,把这个函数和Java层的loadDex做映射,再次实践,测试程序,成功的加载了,运行也成功了。

 

六、技术概要


1、了解到了dlopen和dlsym函数的作用,使用IDA分析so中的函数,然后在使用这两个函数进行so中指定函数的调用即可

2、如何获取一个JNINativeMethod结构体中所有的native函数

3、了解了Android中的自定义类加载器的流程和步骤

 

七、总结


在之前的加固策略弄完之后,遗留的这个问题一直存在的,只是没时间弄,也都快忘了,只是最近工作中又接触到这块了,所以就开始回顾起来,必须解决了,有了内存加载dex的方案之后,之前的apk加固策略就变得比较完美了,从效率和安全性上来说更加高了。而且在开始的也说过了,现在市场中有很多加固平台,但是加固本身还是存在一定的隐私风险的,所以现在加固一般都会很慎重的。不过内存加载方案解决了,还是很爽的!!

 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值