FART主动调用组件源码分析
介绍
我们知道抽取加固的脱壳是主动调用函数,然后把函数体填充之后dump出来
FART主动调用的组件源码的大概流程如下
1 | 遍历ClassLoader |
在系统中主动调用函数的方法
1 | 无法使用Hook框架的主动调用 |
遍历ClassLoader
在ActivityThread.java中定义了一个开启子线程的函数
1 | public static void fartthread() { |
在这里会在子线程中调用fart()
方法,也就是说遍历ClassLoader
的操作是在子线程中完成的
1 | public static void fart() { |
跟进getClassloader()
方法
1 | public static ClassLoader getClassloader() { |
其实就是获取到类加载器,为什么要获取类加载器?我们知道app加载类都是通过mClassLoader
指向的classloader
去加载类,其修正有两种情况,目的就是获取到真正加载解密之后的dex的那个ClassLoader
1 | 插入ClassLoader,可以理解为把mClassLoader的值给设置成DexClassLoader,而DexClassLoader的父加载器是PathClassLoader,所以可以正常运行,对于这种情况,frida其实不需要做任何处理包括枚举所有loader,正常hook就行 |
那么这段代码就好理解了
1 | // 判断classloader是否为BootClassLoader,如果不是就主动调用 |
遍历ClassLoader中的类的所有函数
跟进fartwithClassloader()
方法,通过获取BaseDexClassLoader
的PathList
属性(dex文件的路径都会存储在这个属性中),然后获取DexPathList
的dexElements
属性,这个属性是一个数组,每个数组都是一个dex文件
1 | public static void fartwithClassloader(ClassLoader appClassloader) { |
完成对类中所有函数的主动调用
跟进loadClassAndInvoke()
方法,该方法实现了调用类中的所有函数的过程
1 | public static void loadClassAndInvoke(ClassLoader appClassloader, String eachclassname, Method dumpMethodCode_method) { |
注意加载类用的是resultclass = appClassloader.loadClass(eachclassname)
而不是class.forname
,为什么?因为在一些加固中,会丢入一些垃圾类,在这些垃圾类中定义一些静态方法,当类被加载的时候会执行导致程序退出,而loadClass
不会触发类的静态方法,从而避免这种情况,后面的代码就很好理解了,就是反射,重点在这一句dumpMethodCode_method.invoke(null, m);
,分析dumpMethodCode_method()
函数
这个函数是在fartwithClassLoader()
方法中使用的
1 | if (field.getName().equals("dumpMethodCode")) { |
而field
来自于DexFile
类,所以dumpMethodCode
是DexFile
类中的字段,在DexFile.java
中的定义如下
1 | private static native void dumpMethodCode(Object m); |
所以这个方法是c层实现的
1 | static void DexFile_dumpMethodCode(JNIEnv* env, jclass,jobject method) { |
在c层怎么主动调用java层的方法?需要把java的method转换成art的method,然后调用artMethod->invoke
调用art方法
跟进myfartInvoke()
方法
1 | extern "C" void myfartInvoke(ArtMethod* artmethod) REQUIRES_SHARED(Locks::mutator_lock_) { |
可以看到调用了artMethod,而且传参是随便构造的,因为也不知道要传什么参数,就随便传了
继续跟进artmethod->Invoke()
方法,可以看到会判断参数是否为瞎构造的,如果是,就保存方法体的代码,然后直接退出,不去执行方法,不然app可能会因为参数的问题而退出
1 | void ArtMethod::Invoke(Thread* self, uint32_t* args, uint32_t args_size, JValue* result, |
上面的不执行函数而保存函数体之后退出会导致什么问题?很明显,对于那些只有在函数运行时才解密填充方法体的加固方案,这样做是无效的,因为函数压根没执行,也就是说上面的方法的脱壳时机早了
dump函数体数据
要保存函数体代码,肯定要知道在字节形式下,函数体的起始地址和大小,跟进dumpArtMethod()
方法,该方法中会再次进行一次脱壳,然后才是保存函数体,只看保存函数体的代码
1 | extern "C" void dumpArtMethod(ArtMethod* artmethod) REQUIRES_SHARED(Locks::mutator_lock_) { |
魔改和迁移到Android 10
原先的代码中handleBindApplication()
方法是程序的入口,脱壳应该在壳代码加载完毕之后也就是app = data.info.makeApplication(data.restrictedBackupMode, null);
执行完毕之后,这里在之后执行了一个方法,启动了新的线程,在线程中调用脱壳的方法,这里其实没改,java层唯一改动的就是加了对类的判断,如果是系统类就不进行操作
1 | for (int j = 0; j < ElementsArray.length; j++) { |
so层代码的话,在DexFile.java
中定义了native方法
1 | private static native void saveMethodCodeItem(Object m); |
实现如下
1 | static void DexFile_saveMethodCodeItem(JNIEnv* env, jclass, jobject method) { |
so层主要改动的点是saveArtMethod
方法,也就是保存函数体的代码,前面整体脱壳的代码不变,后面改了一些方法的调用,有些方法在Android 10上用不了
1 | extern "C" void saveArtMethod(ArtMethod * artMethod) REQUIRES_SHARED(Locks::mutator_lock_) { |
dex重构
脱壳之后的数据要把bin文件中的方法体填充回dex文件
使用dexfixer工具,https://github.com/dqzg12300/dexfixer
Eclipse工程,用IDEA打开,需要运行的代码在/src/com.android/dx/unpacker/DexFixer.java
在源代码的lib目录下的.jar库右键选择add as library
然后在IDEA中设置启动参数,运行即可
打包成jar包的话,选file->Project Structure->Artifacts->点击+,选择JAR-> 选择From modules with dependencies->选择main class为DexFixer->上方菜单栏选build ->build artifacts
不足和改进方案
FART存在的问题
1 | 调用链深度不够,有些壳将原有函数体替换为解密代码,运行时才解密执行 |
改进方法
1 | 参照youpk的调用链深度 |
Frida和FART结合增强脱壳功能
利用frida主动调用FART的函数,对指定类进行脱壳,我们知道通过ClassLoader
进行脱壳的函数是fartWithClassloader
,就是找到对应 的classloader
之后主动调用fartWithClassloader
方法
1 | public static void fartWithClassloader(ClassLoader appClassloader) |
把handleBindApplication
方法里makeApplication
之后的开启线程的函数给注释掉,只把方法接口留在系统里,便于frida去调用
1 | Java.perform(function () { |