使用 Frida 增强 FART:实现更强大的 Android 脱壳能力

版权归作者所有,如有转发,请注明文章出处:https://cyrus-studio.github.io/blog/

FART 和 Frida 结合会发生什么?

对 FART 进一步增强:

  1. 增强 FART 的脱壳能力:解决对抗 FART 的壳、动态加载的 dex 的 dump 和修复;

  2. 控制 FART 主动调用的范围,让 FART 更精细化,比如按需进行类甚至是函数的修复。

非双亲委派关系下动态加载的 dex 脱壳问题

由于动态加载的 dex 没有取改变 android 中 ClassLoader 双亲委派关系,所以动态加载的 dex 没有自动脱壳。

相关文章:

在 android studio 中创建一个 plugin module 其中包含一个 FartTest 类源码如下:

package com.cyrus.example.plugin

import android.util.Log

class FartTest {

    fun test(): String {
        Log.d("FartTest", "call FartTest test().")
        return "String from FartTest."
    }

}

把 plugin-debug.apk push 到 files 目录下

adb push "D:\Projects\AndroidExample\plugin\build\intermediates\apk\debug\plugin-debug.apk" /sdcard/Android/data/com.cyrus.example/files/plugin-debug.apk

ls 一下 files 目录是否存在 plugin-debug.apk

adb shell ls /sdcard/Android/data/com.cyrus.example/files

在 app 动态加载 files 目录下的 plugin-debug.apk 并调用 FartTest 的 test 方法

val apkPath = "/sdcard/Android/data/com.cyrus.example/files/plugin-debug.apk"

// 创建 DexClassLoader 加载 sdcard 上的 apk
val classLoader = DexClassLoader(
    apkPath,
    null,
    this@FartActivity.packageResourcePath,
    classLoader // parent 设为当前 context 的类加载器
)

// classLoader 加载 com.cyrus.example.plugin.FartTest 类并通过反射调用 test 方法
val pluginClass = classLoader.loadClass("com.cyrus.example.plugin.FartTest")
val constructor = pluginClass.getDeclaredConstructor()
constructor.isAccessible = true
val instance = constructor.newInstance()
val method = pluginClass.getDeclaredMethod("test")
method.isAccessible = true
val result = method.invoke(instance) as? String

log("动态加载:${apkPath}\n\ncall ${method}\n\nreuslt=${result}")

mClassLoader = classLoader

脱壳完成,但是没有对 plugin-debug.apk 中的目标类 FartTest 发起主动调用

word/media/image1.png

这时候 frida 就派上用场了,因为 frida 本身具有枚举所有 ClassLoader 的能力。

Frida + FART 脱壳动态加载的 dex

枚举出所有 ClassLoader 后,再结合 FART 的 api 就可以实现动态加载 dex 的脱壳。

function invokeAllClassloaders() {
    Java.perform(function () {
        try {
            // 获取 ActivityThread 类
            var ActivityThread = Java.use("android.app.ActivityThread");

            Java.enumerateClassLoaders({
                onMatch: function (loader) {
                    try {
                        // 过滤掉 BootClassLoader
                        if (loader.toString().includes("BootClassLoader")) {
                            console.log("[-] 跳过 BootClassLoader");
                            return;
                        }

                        // 调用 fartWithClassLoader
                        console.log("[*] 调用 fartwithClassloader -> " + loader);
                        ActivityThread.fartwithClassloader(loader);
                    } catch (e) {
                        console.error("[-] 调用失败: " + e);
                    }
                },
                onComplete: function () {
                    console.log("[*] 枚举并调用完毕");
                }
            });
        } catch (err) {
            console.error("[-] 脚本执行异常: " + err);
        }
    });
}


setImmediate(invokeAllClassloaders)

把 log 导出到 txt

adb logcat -v time > logcat.txt

打开 app 后执行脚本

frida -H 127.0.0.1:1234 -F -l fart_invoke_all_classloaders.js

从输出日志可以看到已经成功对 FartTest 类中方法发起主动调用

word/media/image2.png

局部变量的 ClassLoader 枚举不出来

但还有一个问题呢:局部变量的 ClassLoader 枚举不出来。

因为:

  • enumerateClassLoaders() 只枚举当前 VM 中可访问的、被 GC Root 持有的 ClassLoader;

  • 如果 DexClassLoader 作为临时变量创建后,没有被保存,就会被 GC 回收或无法遍历到。

比如,下面的 Kotlin 代码中,当 DexClassLoader 为局部变量时就没有枚举出这个 DexClassLoader 。

/**
 * 局部
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值