Android APK加固-内存加载dex
Android APK加固-内存加载dex
分析DexClassLoader的构造方法
查看源码可以到AndroidXref网站查看
查看代码发现,DexClassLoader调用了父类BaseDexClassLoader构造
点击父类名称,继续观察父类源码
发现构造有个核心功能DexPathList,继续查看
观察发现,DexPathList构造中,有一个方法makeDexElements像是创建了一个元素集,
跟入
函数判断后缀名是否是dex,如果是就调用LoadDexFile的方法
这是它另外定义的后缀名
继续观察LoadDexFile
发现optimizedDirectory参数被optimizedPathFor方法转为了一个路径,加载dex文件的方法是DexFile类中的loadDex方法,继续跟入
发现DexFile的loadDex返回了DexFile对象,参数分别为源路径和输出路径
继续查看DexFile的构造
发现DexFile类的构造方法中又有个方法openDexFile和加载Dex文件有关联。继续查看
发现openDexFIle返回了个native方法。其实现部分是在C++中。
继续跟入
总结一下:
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的重写方法。
发现其中有查找类的方法。调用findClass的pathList对象是在BaseDexClassLoader构造中创建的。
继续分析DexPathList的findClass方法
发现返回类的对象代码,是DexFile中的loadClassBinaryName
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的目的。