类加载器和双亲委派机制
类加载器
类加载的时机
1 | 隐式加载:当访问类的静态变量、为静态变量赋值、调用类的静态方法的时候会加载类 |
类加载器
1 | BootClassLoader:单例模式,用来加载系统类 |
dex加载过程中,主要逻辑都是在BaseDexClassLoader
中完成的
PathClassLoader
、DexClassLoader
、InMemoryDexClassLoader
继承自BaseDexClassLoader
对于一些app加固,其实用不到这么上层的函数,可能会找更底层的系统调用函数,或者自己实现相关的dex加载函数
demo
这里以DexClassLoader
为例,DexClassLoader
的实现如下
1 | public class DexClassLoader extends BaseDexClassLoader { |
可以看到DexClassLoader
是BaseDexClassLoader
的子类,而且调用DexClassLoader
的时候需要在最后一个参数指定父类的ClassLoader
在MainActivity.java
中测试MainAcvtivity
的classLoader
1 | Log.d("this is a test","String: " + String.class.getClassLoader()); |
得到MainAcvtivity
的classLoader
是dalvik.system.PathClassLoader
,String系统类的classLoader
是java.lang.BootClassLoader
可以使用getParent()
方法获取父classLoader
1 | Log.d("this is a test","String: " + MainActivity.class.getClassLoader().getParent()); |
得到PathClassLoader
的父classLoader
是java.lang.BootClassLoader
DexClassLoader
的第四个参数指定父classLoader
,如果父classLoader
不同,加载出来的也类不一样,最终可能会导致DexClassLoader.loadClass(...)
加载出的类不一样
一定要搞清楚的一点是,不同的classLoader
里的class不一样,有的class是在子类加载器中,不在父类加载器中
类的双亲委派机制
主要解决的是hook的时候找不到类的情况,如果确认类路径和类名正确,此时会枚举所有classLoader
,然后在每个classLoader
中加载对应的类
找不到类的情况一般就是:类是动态加载的,只有app里某个功能被触发之后才加载;类加载器不对,需要枚举所有classLoader
双亲委派机制的工作原理
如果一个类加载器收到了类加载请求,会先把这个请求委托给父类的加载器去执行,比如上面的DexClassLoader
,在收到加载类的请求的时候会交给第四个参数的classLoader
去加载
如果父类加载器还存在其他父类加载器,则进一步往上委托,依次类推,最终到达顶层的启动类加载器
如果父类加载器可以完成类的加载任务,就成功返回,而如果父类加载器无法完成加载,子类加载器才会尝试自己去加载
为什么要有双亲委派机制
避免重复加载,已经加载的class,可以直接读取
更加安全,无法自定义类来替代系统的类,可以防止核心API库被篡改,比如classLoader
a1里自定义了一个java.lang.String
类,然后要使用a1去加载String
类的时候,不会先加载a1里自定义的string
类,而是加载父加载器里的系统string
类
fridaHook中找不到类的情况
demo
MainActivity.java中部分代码如下
1 | public void loadDex() { |
如果这个时候想使用frida去找com.JohnDemo.app.Dynamic类,直接找是找不到的
1 | Java.perform(function () { |
为什么找不到,因为frida中默认的classLoader是PathClassLoader,PathClassLoader的父classLoader是BootClassLoader,这两个classLoader中都没有这个类,因为代码中是用DexClassLoader去加载的
frida 通过 Java.use()
查找类时,默认使用 Java.availableClasses
列表,而这个列表通常只包含 PathClassLoader
能访问的类,即app在 classes.dex
里默认打包的类。
1 | Java.perform(function () { |
加固对hook的影响
普通app运行流程
1 | BootClassLoader加载系统核心库 |
加固app运行流程
1 | BootClassLoader加载系统核心库 |
PathClassLoader
加载dex以后,PathClassLoader
的值会记录在LoadedApk
的mClassLoader
属性中,默认使用这个ClassLoader
去寻找类,因此加固app需要修正ClassLoader
举例
对于没有加固的app来说,比如class.dex就是真正的dex,然后这个dex里有一个类test,此时默认使用PathClassLoader去加载完class.dex之后,mClassLoader的值被置为PathClassLoader,这里使用frida是可以找到test类的
但是对于加固的app来说,PathClassLoader
是加载壳的dex,mClassLoader
的值也是加载壳的这个PathClassLoader
,而真正的dex是由DexClassLoader
去加载的,此时使用frida找不到test类
而且对于加固之后app,Android系统会修正ClassLoader
,为什么?因为如果不修正的化,mClassLoader
就是加载壳的PathClassLoader
,那么连系统自己都找不到test类,所以需要修正classLoader
常见的修正方式
1 | 插入ClassLoader,可以理解为把mClassLoader的值给设置成DexClassLoader,而DexClassLoader的父加载器是PathClassLoader,所以可以正常运行,对于这种情况,frida其实不需要做任何处理包括枚举所有loader,正常hook就行 |