技术分享 | Frida 实现 Hook 功能的强大能力

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: Frida 通过 C 语言将 QuickJS 注入到目标进程中,获取完整的内存操作权限,达到在程序运行时实时地插入额外代码和数据的目的。官方将调用代码封装为 python 库,当然你也可以直接通过其他的语言调用 Frida 中的 C 语言代码进行操作。## Frida安装和启动## - Frida 支持 python2 和 python3 版本,演示所使用的版本为 python3.8``

通过 C 语言将 QuickJS 注入到目标进程中,获取完整的内存操作权限,达到在程序运行时实时地插入额外代码和数据的目的。官方将调用代码封装为 python 库,当然你也可以直接通过其他的语言调用 Frida 中的 C 语言代码进行操作。

Frida安装和启动

  • Frida 支持 python2 和 python3 版本,演示所使用的版本为 python3.8
pip install frida-tools
  • 如果在安装中卡住,需要在 Frida 的 pypi 页面下载对应系统的 egg 文件,对应页面地址为:https://pypi.org/project/frida/#files ,并将该文件放置到个人文件夹路径下,例如 C:\Users\当前用户名,再重新使用命令安装。
  • 安装完毕后可以通过命令frida --version来查看安装的版本,确认是否安装成功。
  • 本次示例使用 Android App 作为目标程序,所以需要电脑端安装 SDK 环境,以便能够连接手机进行调试操作,还需在手机端准备一个 Frida-server,下载地址为:https://github.com/frida/frida/releases,下载匹配手机 CPU 架构和本地 Frida 版本的包。
  • 下载之后解压文件,使用adb push命令将文件推送到手机端,建议放置在/data/local/tmp文件夹中,并修改该文件的权限为 755,以便之后进行启动。
  • 通过 Frida 提供的一些小工具,对 Frida 的安装运行环境做简单的确认。
  • 首先准备一个 Android 模拟器或者真机,将上一步中提到的 Frida-server 推送到手机端中,在本示例中将放置在手机的/data/local/tmp文件夹内,并将文件命名为frida-server。
  • 通过adb shell命令连接手机,运行/data/local/tmp/frida-server &,将 Frida-server 放在系统后台自动运行。
  • 在本地电脑终端中运行frida-ps -U,结果如下展示手机中的进程信息,说明环境已经准备完毕。示例

目标应用介绍

  • 因为 Hook 需要通过分析源码中的逻辑来实现,所以先展示一下目标应用的源码部分,方便分析其中的逻辑,找到 Hook 时要修改的方法和变量。
  • 代码是简单的猜黑白游戏,通过按下黑或白按钮,与电脑结果进行对比,结果相同加 1 分,结果不同分数清零,当满 100 分时打出胜利提示语。具体代码如下:
package com.example.target_frida;

import androidx.appcompat.app.AppCompatActivity;

import android.annotation.SuppressLint;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    TextView winCountView;
    TextView battleInfoTextView;
    int winCount = 0;

    @SuppressLint("SetTextI18n")
    @Override
    public void onClick(View view) {
        if (winCount > 100) {
            return;
        }
        if (view.getId() == R.id.tvButtonBlack) {
            if (!getCPUResult()) {
                winCount++;
                winCountView.setText(winCount + "");
                battleInfoTextView.setText("Right!");
            } else {
                winCount = 0;
                winCountView.setText(winCount + "");
                battleInfoTextView.setText("Wrong! Clean All!");
            }
        } else if (view.getId() == R.id.tvButtonWhite) {
            if (getCPUResult()) {
                winCount++;
                winCountView.setText(winCount + "");
                battleInfoTextView.setText("Right!");
            } else {
                winCount = 0;
                winCountView.setText(winCount + "");
                battleInfoTextView.setText("Wrong! Clean All!");
            }
        }
        if (winCount >= 100) {
            battleInfoTextView.setText("Win 100 times!!!");
        }
    }

    @SuppressLint("SetTextI18n")
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        winCountView = findViewById(R.id.winCount);
        winCountView.setText(winCount + "");
        battleInfoTextView = findViewById(R.id.battleInfo);
        battleInfoTextView.setText("猜黑白!!!");
        Button buttonBlack = (Button) findViewById(R.id.tvButtonBlack);
        Button buttonWhite = (Button) findViewById(R.id.tvButtonWhite);
        buttonBlack.setOnClickListener(this);
        buttonWhite.setOnClickListener(this);
    }

    public boolean getCPUResult() {
        //true为白,false为黑
        return Math.random() > 0.5;
    }

}

Hook 需求分析

  • 由于正常情况下,连赢 100 次的概率几乎为零,如果想要达到胜利条件,Hook 就是一个比较好的方式。
  • 首先分析一下源码,想要达到连赢 100 次的情况,可以有两种解决办法:一种是通过修改用来记录连赢次数的变量winCount,将连赢记录改成 99,这样只需要再赢一次就可以获得胜利;还有一种是通过修改getCPUResult方法的返回值,让其固定返回一种可能性,这样只需要选择对应的颜色就可以连续获胜。接下来通过实现第一个方案,看看使用 Frida 如何达到想要的效果。
  • 首先展示修改代码,然后再进行逐步讲解:
import time
import frida, sys

date_str = time.strftime('%m-%d %H:%M:%S')


def on_message(message, data):
    if message['type'] == 'send':
        print(f"[{date_str}] {message['payload']}")
    else:
        print(f"[{date_str}] {message}")


def run_all():
  # Java.perform方法:当 js 附加到目标的进程中时被执行,运行其中定义的函数
  # Java.choose方法:通过完整类名,获取它的实例,从而对实例中的数据进行修改
  # 通过 key:value 结构定义了两个函数:
  # onMatch 对应的函数在命中一个实例的时候被调用,传入函数中的参数 instance 就是被命中的实例
  # onComplete 函数会在所有实例遍历完毕之后被调用,可以做一些后续处理操作
    jscode = """
    Java.perform(function () {
      Java.choose("com.example.target_frida.MainActivity",{
        onMatch:function(instance){
            console.log("winCount value is "+instance.winCount.value);
            instance.winCount.value=99;
            console.log("winCount value is "+instance.winCount.value);
        },
        onComplete:function(){
            console.log("Complete!!!")
        }
      });
    });
    """
    # attach目标App进程
    target_app = 'com.example.target_frida'
    process = frida.get_usb_device().attach(target_app)
    # 将JS代码注入进程,并附加监听方法,用来获取返回的日志信息
    script = process.create_script(jscode)
    script.on('message', on_message)
    # 打印起始日志
    print(f'[{date_str}] Start Frida on {target_app}')
    # 加载注入的JS代码逻辑
    script.load()
    # 使用系统输入语句阻止函数运行完毕自动退出
    sys.stdin.read()


if __name__ == '__main__':
    run_all()
  • 代码中的 python 语句已经添加了注释,Hook 的核心逻辑,JS 语句作为字符串保存在 jscode 变量中。
  • 梳理一下整个 JS 语句的流程:通过Java.choose函数获取com.example.target_frida.MainActivity类的实例。在获取到实例时,首先使用console.log语句将当前实例中的 winCount 变量值(使用 winCount.value)打印到日志中,之后直接通过赋值语句把变量值改为 99,再次输出日志确认修改无误,修改逻辑就完成了。
  • 在手机端启动 Frida-server 和被测 App,电脑端运行脚本,可以看到在命令行中输出如下内容:
[04-15 18:19:57] Start Frida on com.example.target_frida
winCount value is 0
winCount value is 99
Complete!!!
  • 这时在 App 中选择一个颜色点击,只要选中正确的颜色,就可以成功达到预期的 100 次连胜目标,如果没能选中正确的颜色导致清零,可以再重复运行脚本修改一次数值,在这种情况下要达到预期的场景就很容易。

总结

第二个方案以及其他更多的可能性,就留给读者自行探索,在这里送上 Frida 官方 JavaScript API 链接:https://frida.re/docs/javascript-api/ ,可以通过这个链接找到你所需要的 JS 函数。

通过示例可以看到 Frida 实现 Hook 功能的强大能力,它可以定位到类的实例,并且对实例中的数据进行直接的修改,达到场景构建的目的。
推荐学习

内容全面升级,4 个月 20+ 项目实战强化训练,资深测试架构师、开源项目作者亲授 BAT 大厂前沿最佳实践,带你一站式掌握测试开发必备核心技能(对标阿里P6+,年薪50W+)!直推 BAT 名企测试经理,普遍涨薪 50%+!

⬇️ 点击“阅读原文”,提升测试核心竞争力!
原文链接

相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
相关文章
|
Java API Android开发
Android native应用开发简明教程 (1) - 本地开发武器库概览
native应用比起Java应用来,跟Android版本的相关性更高一些。 所以,这些API都是根据平台版本号分成不同的目录的。 我们来看看Android为我们提供了哪些API
6261 0
|
9天前
|
开发工具 开发者
Flutter&鸿蒙next 状态管理高级使用:深入探讨 Provider
本文深入探讨了 Flutter 中 Provider 的高级用法,涵盖多 Provider 组合、Selector 优化性能、ChangeNotifierProxyProvider 管理依赖关系以及自定义 Provider。通过这些技巧,开发者可以构建高效、可维护的响应式应用。
61 2
|
9天前
|
调度 UED 开发者
Flutter&鸿蒙next 刷新机制的高级使用【衍生详解】
本文深入探讨了 Flutter 的刷新机制,包括状态管理、Widget 重建、性能优化和高级使用技巧。通过理解这些机制,开发者可以优化应用性能,提升用户体验。文章详细介绍了 StatefulWidget 和 StatelessWidget 的区别,以及如何使用 setState、InheritedWidget 和其他状态管理库(如 Provider、Bloc 和 Riverpod)来高效管理状态。此外,还提供了一些性能优化技巧,如减少 Widget 重建、使用 LayoutBuilder 和 AnimatedBuilder 等。希望本文能帮助读者更好地掌握 Flutter 的刷新机制,提升
65 1
|
5月前
|
Java Linux API
微信API:探究Android平台下Hook技术的比较与应用场景分析
微信API:探究Android平台下Hook技术的比较与应用场景分析
|
6月前
|
运维 监控 安全
应用研发平台EMAS常见问题之sophix ios flutter热更新如何解决
应用研发平台EMAS(Enterprise Mobile Application Service)是阿里云提供的一个全栈移动应用开发平台,集成了应用开发、测试、部署、监控和运营服务;本合集旨在总结EMAS产品在应用开发和运维过程中的常见问题及解决方案,助力开发者和企业高效解决技术难题,加速移动应用的上线和稳定运行。
178 0
|
Linux 测试技术 调度
新工具开源!一款iOS自动化利器(附地址)
tidevice 是阿里的内部的一个小组用来做 iOS 自动化用的工具,通过逆向iOS通信协议,使用纯Python实现。目前淘宝和其他部分事业部已经全面使用了该技术,进行iOS应用的性能采集,UI自动化。
2451 0
新工具开源!一款iOS自动化利器(附地址)
Flutter 高赞的路由管理插件 fluro 简介
fluro 是 Flutter 优秀的路由管理器,支持路由路径中携带路径参数和 query 参数,可以更灵活地进行页面参数的传递和转场动画。
1058 1
Flutter 高赞的路由管理插件 fluro 简介
|
存储 前端开发 JavaScript
【高级React技术】Hook在企业项目中的使用心得以及一些组件的原理(State Hook)
【高级React技术】Hook在企业项目中的使用心得以及一些组件的原理(State Hook)
|
Android开发 iOS开发
聊一聊在Airtest自动化中如何清除iOS后台应用
聊一聊在Airtest自动化中如何清除iOS后台应用
500 0
|
JSON 开发工具 Android开发
APICloud App开发上手经验之模块调用
APICloud 提供了很多方便好用的模块,只要学习自己想要使用的模块的文档,然后调用模块就可以使用了。使用模块可以大大减少自己需要写的代码量,而且对于新手来说,在自己开发经验不足的情况下,也许并不能胜任一些功能的开发,这时候使用模块就可以让自己的 APP 也拥有这些功能了,看到自己的 APP 也具有了丰富的功能会比较有成就感,不会使我们的学习感到枯燥,也不容易感觉学习太难而因此止步。
206 0