Android性能优化-Frida工具篇

一、Frida是什么

Dynamic instrumentation toolkit for developers, reverse-engineers, and security researchers.
开发人员,逆向工程和安全研究人员的动态代码插桩工具。

Frida主要用于动态分析和修改运行时的程序行为(支持 Android、iOS、Windows、macOS、Linux 等平台)。它的核心功能是通过注入 JavaScript 或 Python 脚本,实时 Hook 目标应用的函数、修改内存数据或拦截通信,广泛应用于逆向工程、安全测试、漏洞挖掘等领域。

从这个描述来看,Frida主要是逆向和漏洞挖掘用的,和我们应用开发和性能优化有什么关系?事实上,这是一个极其强大,甚至某些程度上可以说无所不能的工具,为什么这么说,我们慢慢来分析。

二、Frida能做什么(基础)?

Frida可以以极低的成本,实现对Java代码的动态Hook,以及实现对Native层代码的动态Hook,举例如下:

2.1 Hook Java代码

Java 复制代码
Java.perform(function () {
    const LottieAnimationView = Java.use('com.airbnb.lottie.LottieAnimationView');
    // Hook onDraw 方法
    LottieAnimationView.onDraw.implementation = function (canvas) {
        // 调用原始方法
        return this.onDraw(canvas);
    };
    console.log("[+] LottieAnimationView.onDraw() Hook 已安装");
});

上面的代码可以实现对LottieAnimView的监听,在LottieAnimView的onDraw方法回调的时候,我们可以得到回调,可以做进一步的处理,例如判断两帧之间的时间间隔。

2.2 Hook Native代码

Java 复制代码
const openAddr = Module.findExportByName("libc.so", "open");
Interceptor.attach(openAddr, {
    onEnter: function (args) {
        const pathPtr = args[0];
        const path = pathPtr.readUtf8String();
        console.log(`[libc] open() 路径: ${path}`);
    },
    onLeave: function (retval) {
        console.log(`[libc] open() 返回值: fd=${retval}`);
    }
});

这段代码会hook libc的open方法,在打开某个文件的时候打印文件路径。

三、环境搭建与使用

3.1 环境搭建

Frida的环境搭建非常简单(需要Root手机)

3.1.1 Window端

安装python环境

复制代码
pip install frida-tools
pip install frida

执行frida --version,得到正常输出即为安装成功 执行

复制代码
adb forward tcp:27042 tcp:27042

3.1.2 Android端

github.com/frida/frida...

  • 1、github仓库的release发行版中,找到与本地frida版本一致的tag,查找此文件:
  • 2、解压得到其中的文件,将其重命名为frida-server(这一步只是用于方便使用)
  • 3、adb push frida-server data/local/tmp/frida-server
  • 4、adb shell chmod +x data/local/tmp/frida-server
  • 5、adb shell "/data/local/tmp/frida-server &"

3.2 基本用法

3.2.1 ps

arduino 复制代码
//得到手机上所有进程以及进程名,-U的意思是使用USB设备,而不是本机
frida-ps -U

记住这里的进程名,后续会用到,这里三方应用一般是中文名,系统服务是英文,是app自己设置的,不同于我们一直使用的包名。

3.2.2 加载脚本

加载脚本有两种方式,一种是直接进程存在的情况,一种是不管进程在不在,都重新拉起。 进程存在:(这种情况使用进程名,一般是中文)

复制代码
frida -U -n 抖音 test.js

重新拉起:(这种情况下,需要使用包名)

复制代码
frida -U -f com.ss.android.ugc.aweme test.js

| 注入模式 | 描述 | 命令或参数|优点|主要用途|

| --- | --- | --- | --- | --- |

| Spawn模式 | 将启动App的权利交由Frida来控制,即使目标App已经启动,在使用Frida注入程序时还是会重新启动App | 在CLI模式中,Frida通过加上 -f 参数指定包名以spawn模式操作App | 适合于需要在App启动时即进行注入的场景,可以在App启动时即捕获其行为 | 当需要监控App从启动开始的所有行为时使用 |

| Attach模式 | 在目标App已经启动的情况下,Frida通过ptrace注入程序从而执行Hook的操作 |在CLI模式中,如果不添加 -f 参数,则默认会通过attach模式注入App |适合于已经运行的App,不会重新启动App,对用户体验影响较小|在App已经启动,或者我们只关心特定时刻或特定功能的行为时使用 |

3.2.3 基础使用

1、在Java环境中运行

Java 复制代码
Java.perform(function () {

}

2、Hook Class

Java 复制代码
const TargetClass = Java.use('com.example.app.MainActivity');
TargetClass.login.implementation = function (username, password) {
    console.log(`用户名: ${username}, 密码: ${password}`);
    return this.login(username, password); // 继续原逻辑
};

3、打印堆栈

Java 复制代码
console.log(Java.use("android.util.Log").getStackTraceString(
    Java.use("java.lang.Exception").$new()
));

4、构造函数

Java 复制代码
Java.use("classname").$init(params)

5、内部类

Java 复制代码
Java.use("classname$innerclassname")

6、取变量里面的值

Java 复制代码
obj.name.value

其中name是变量名,后面需要跟一个value,才能拿到实际的值 7、枚举某个类的实例

Java 复制代码
Java.choose(className, callbacks)

例如我们想拿到Activity的实例,做一些事情,就可以用这个方式来做。

3.2.4 demo

编写一个Js文件,hook某个Class的方法,打印一行日志,将其命名为lottie.js

Java 复制代码
Java.perform(function () {
    const LottieAnimationView = Java.use('com.airbnb.lottie.LottieAnimationView');
    // Hook onDraw 方法
    LottieAnimationView.onDraw.implementation = function (canvas) {
        // 调用原始方法
        return this.onDraw(canvas);
    };
    console.log("[+] LottieAnimationView.onDraw() Hook 已安装");
});

在windows目录下,执行上面的命令即可。

四、Frida的应用

4.1 安全

目前在一些公司里面,配置了安全团队,其中有一项就是扫描应用异常的安全行为。不知道大家有没有想过,他们是如何发现一些异常问题的?例如未同意隐私协议之前不准访问网络、一些敏感信息等?并且他们甚至可以拿到应用的堆栈。可能会有人觉得是他们开发了一些非常复杂的工具、或者系统给他们开了后门。

有一个例子是安全团队扫描到我们有异常的ContentObserver调用,这里由于安全的原因就不贴截图了。大致的堆栈是我们监听了ContentObserver,报的是对媒体库的监听,没有应用的堆栈。首先安全的这个堆栈是怎么拿到的?他们正是使用Frida来实现的,只是他们监控的是实际ContentObserver发生变化的onChange,但是这个对我们没有意义,我们需要的是实际注册的地方,我们可以Hook ContentObserver的registerContentObserver方法,来得到我们实际注册的URI,用于判断URI是否敏感,也就是应用是否有敏感行为。

代码如下:

Java 复制代码
Java.perform(function() {
    const ContentResolver = Java.use('android.content.ContentResolver');
    ContentResolver.registerContentObserver.overload(
        'android.net.Uri',
        'boolean',
        'android.database.ContentObserver'
    ).implementation = function(uri, notifyForDescendants, observer) {
        console.log('  URI: ' + uri.toString());
        return this.registerContentObserver(uri, notifyForDescendants, observer);
    };
});

除了上面的应用之外,在安全领域,Frida被大量使用,检测网络、定位等功能,不一而足。

4.2 逆向

除了安全领域,Frida也广泛的应用于逆向领域,可以用于分析混淆后的代码等。比如我们想要分析某个应用在某个地方的执行结果,例如在混淆代码中,看到某个地方的代码,想要知道执行的结果,通过常规的方式很难达成,但是可以通过Frida来达成,直接Hook这个类的方法, 在收到调用之后,可以执行自己的逻辑,将内容打印出来,并执行原有的方法。

这个实际上也可以应用于我们来debug自己的Release包,有的时候我们会遇到一些非常棘手的问题,只在Release包复现,但是debug包无法复现,这个时候我们就可以通过Frida来编写一些脚本,hook运行时,通过添加日志的形式来进行debug,避免大量没有意义的编译与验证。

举个简单的例子,遇到一个Release包才能复现的问题,我们需要在一些关键方法加日志,判断每次方法调用是否是同个对象(需要考虑混淆),代码如下:

Java 复制代码
Java.perform(function () {
    const HandleBitmap = Java.use('com.xx.xx.xx.xx.xx.xx$handleBitmap$1');
    const UpdateLocation = Java.use('com.xx.xx.xx.xx.xx.xx$updateLocation$1');
    HandleBitmap.invoke.overloads[0].implementation = function () {
        console.log("handle bitmap : " + this.this$0.value.hashCode());
        return this.invoke();
    };
    UpdateLocation.invoke.overloads[0].implementation = function () {
        console.log()
        const view = this.this$0.value;
        if (view.k.getSecond().intValue() != this.$mY.value) {
            console.log("update location : " + this.this$0.value.hashCode());
        }
        return this.invoke();
    };
});

这样我们在updateLocation的时候,可以知道每次调用的是否是同个对象,佐证一些猜想,从而实现我们定位Bug的目的,编写Js脚本的时间非常短,借助AIGC,可能在一两分钟内就可以完成编写与调试。

五、Frida的实现原理

5.1 核心架构

Frida 的架构分为三部分:

  • 主机端(Host):运行 Python/JavaScript 脚本,控制分析逻辑。
  • 设备端(Target):运行 frida-server 或 frida-gadget,负责注入目标进程。
  • 通信桥梁:通过 TCP/WebSocket 在主机和设备间传递数据和指令

​5.​2 进程注入机制​​

​5.2.1 注入方式​​

frida-server(需 Root)

以高权限运行在设备上,通过 ptrace 或 LD_PRELOAD 将 Frida 的运行时库注入目标进程。

流程

主机通过 adb 连接 frida-server。 frida-server 调用 fork() 创建子进程,附加到目标进程(如 zygote 或 App 进程)。 注入 frida-agent.so(核心引擎)到目标进程内存。

frida-gadget(非 Root)

frida-gadget.so 打包进目标 APK(需重签名),随应用启动自动加载。

限制:只能注入当前应用,无法控制系统进程。

5.2.2 注入后的内存布局

目标进程的内存中会加载以下模块:

frida-agent.so:核心引擎,负责脚本解析、Hook 管理和通信。

JavaScript 运行时:基于 V8 引擎(或 Duktape 轻量版),执行用户脚本。

动态生成代码:用于 Hook 的跳板代码(Trampoline)和内存补丁。

5.3 动态插桩原理

5.3.1 Java 层 Hook​​

实现方式 :通过修改 Android ART/Dalvik 虚拟机的运行时结构。 方法替换 :替换 Method 对象的 entry_point 或 code_item,指向 Frida 生成的代理代码。 JNI 桥接:将 Java 方法调用转发到 JavaScript 回调。

​5.3.2 Native 层 Hook​​

Inline Hook :修改函数入口的机器码,跳转到自定义代码。 ARM/Thumb 模式 :处理指令对齐和条件跳转。 指令修复 :保存被覆盖的原始指令,在回调中恢复执行。 PLT/GOT Hook:修改动态链接表的函数地址(适用于 libc.so 等)。

六、总结

Frida是非常强大的动态代码插桩工具,可以用其开发出强大的分析工具和性能优化工具,是我们进行优化的利器。反过来说,我们的应用很容易被Frida进行攻破,如果后续有更高的安全要求,这里是绕不过去的门槛。

参考

1、www.freebuf.com/articles/mo... 2、cloud.tencent.com/developer/a...

相关推荐
甜甜的资料库5 分钟前
基于微信小程序的作业管理系统源码数据库文档
java·数据库·微信小程序·小程序
有梦想的骇客6 小时前
书籍“之“字形打印矩阵(8)0609
java·算法·矩阵
yours_Gabriel6 小时前
【java面试】微服务篇
java·微服务·中间件·面试·kafka·rabbitmq
hashiqimiya8 小时前
android studio中修改java逻辑对应配置的xml文件
xml·java·android studio
liuzhenghua668 小时前
Python任务调度模型
java·运维·python
結城8 小时前
mybatisX的使用,简化springboot的开发,不用再写entity、mapper以及service了!
java·spring boot·后端
小前端大牛马8 小时前
java教程笔记(十一)-泛型
java·笔记·python
东阳马生架构8 小时前
商品中心—2.商品生命周期和状态的技术文档
java
星辰离彬9 小时前
Java 与 MySQL 性能优化:MySQL 慢 SQL 诊断与分析方法详解
java·spring boot·后端·sql·mysql·性能优化
q_19132846959 小时前
基于Springboot+Vue的办公管理系统
java·vue.js·spring boot·后端·intellij idea