一、前言
时隔半年,困扰的问题始终是需要解决的,之前也算是没时间弄,今天因为有人在此提起这个问题,那么就不能不解决了,这里写一篇文章记录一下吧。那么是什么问题呢?
就是关于之前的一个话题: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加固策略就变得比较完美了,从效率和安全性上来说更加高了。而且在开始的也说过了,现在市场中有很多加固平台,但是加固本身还是存在一定的隐私风险的,所以现在加固一般都会很慎重的。不过内存加载方案解决了,还是很爽的!!