惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

C
Check Point Blog
美团技术团队
Jina AI
Jina AI
人人都是产品经理
人人都是产品经理
The Cloudflare Blog
V
Visual Studio Blog
Google DeepMind News
Google DeepMind News
Hugging Face - Blog
Hugging Face - Blog
云风的 BLOG
云风的 BLOG
有赞技术团队
有赞技术团队
T
The Blog of Author Tim Ferriss
WordPress大学
WordPress大学
月光博客
月光博客
宝玉的分享
宝玉的分享
小众软件
小众软件
MongoDB | Blog
MongoDB | Blog
Apple Machine Learning Research
Apple Machine Learning Research
A
About on SuperTechFans
J
Java Code Geeks
博客园_首页
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
N
Netflix TechBlog - Medium
Vercel News
Vercel News
博客园 - 聂微东

Watermelonabc的Blog

又一个黑客模拟游戏 又一个黑客模拟游戏 家庭辅导一晚,有点感想 家庭辅导一晚,有点感想 福州大学单片机原理与应用(跨学科)复习 福州大学单片机原理与应用(跨学科)复习 福州大学毛概期末复习 福州大学毛概期末复习 x86 汇编语言详解 - 2 x86 汇编语言详解 -2 CCB 2026 Half-Final Guangzhou CCB 2026 Half-Final Guangzhou 视频也可以含恶意代码吗 视频也可以含恶意代码吗 Hello,2026 Hello,2026 2025 福州大学算法与数据结构复习 2025 福州大学算法与数据结构复习 2025 福州大学习概期末复习 2025 福州大学习概期末复习 Frida 入门 记一场贴吧交流 记一场贴吧交流 虚拟机保护 虚拟机保护 2025 强网杯 S9 Reverse 复现 2025 强网杯 S9 Reverse 复现 你如何在 FZU 取到快递 你如何在 FZU 取到快递 2025 福州大学离散数学笔记
Frida 入门
Watermelonabc · 2025-12-01 · via Watermelonabc的Blog

Frida 是一个知名的 Hook 框架
接下来我们主要以 Android Hook 为主题进行讲解

环境配置

分别在 PC 和目标 Android 上配置 Frida:

1
2
3
4
5
# 我使用 UV 管理 Python,因此没有一个全局的 Python
# uv venv --seed --clear venv
# venv\Scripts\activate

pip install frida frida-tools

记住 Frida 版本:

1
2
3
4
$ pip show frida

Name: frida
Version: 17.5.1

在 ADB shell 中查看 Android 设备架构:

1
2
3
$ uname -m

aarch64

一般来讲,模拟器采用 x86-64,真机采用 arm64(即此处的 aarch64)

Releases · frida/frida 下载对应版本的 Frida-server,例如此处应下 frida-server-17.5.1-android-arm64.xz。

解压得到的文件传输到 Android 上:

1
adb push frida-server-17.5.1-android-arm64 /data/local/tmp/

在 Android 上启动服务端:

1
2
3
4
5
6
adb shell
su # 需要在 Root 下运行,否则 SELinux 会报错
cd /data/local/tmp
chmod +x ./frida-server-17.5.1-android-arm64 # 赋予执行权限
./frida-server-17.5.1-android-arm64
 # 无报错即服务端正常运行

现在就可以用 Frida 了 😊

Frida 调试基础

Android 每个 APP 都运行在自己独立的虚拟机和沙盒中,如果每个 APP 的运行时环境都不共享,那么资源消耗是很恐怖的。而 Android 提供了 Zygote 进程,所有 APP 都是由 Zygote fork 出来的:

1
2
3
4
5
init 
└── Zygote(已预加载所有 Framework 类)
    ├── system_server
    ├── com.tencent.mm
    └── com.example.yourapp  ← 你要分析的 APP

Zygote 的作用是:提前把 Android Framework 所有的类加载好,每次 fork 新进程时直接继承,不用重新初始化。这样做可以实现极速启动和节省内存.

两种模式:Spawn(生成)和 Attach(附加)。Spawn 模式是从头启动应用并调试,Attach 模式是在应用运行时才开始调试。Spawn 模式下,Frida 利用 Zygote 控制进程创建,可以在业务代码运行前完成 Hook;Attach 模式下,APP 的 Application.onCreate 等初始化逻辑已经执行完,注入太晚。因此对抗加固的场景大多用 Spawn 模式。

但 Spawn 模式也可以被某些 APP 检测到。

Spawn 模式:

1
frida -U -f <TARGET> -l <SCRIPT>
  • TARGET:APP 包名或可执行文件名
  • SCRIPT:用于调试的 Python, JS 或 TS 脚本。自己写或者用已有的。如果不附带脚本,则进入交互式调试。

TARGET 需要的包名可以通过 frida-ps -Ua(运行中的应用)/frida-ps -Uai(安装的应用)得到。

Spawn 方式启动后,会立刻暂停运行。需要继续运行,需要手动输入:%resume

旧版本有一个 --no-pause 会自动运行,但新版本已不支持。


Attach 模式:

1
frida -U -N TARGET -l SCRIPT

怎么写好一个调试脚本

脚本主要有三类内容:

  • Interceptor(拦截器):实现 Hook 函数、打印参数等调试操作。每次匹配到(对应函数),都会触发执行代码。
  • ApiResolver(解析器):模糊查找相关函数。只有第一次(解析时)匹配到,才会执行。
  • Stalker(跟踪器):跟踪代码的实际运行的过程。期间可以打印和查看对应的值,便于实现调试真正代码运行的逻辑。

对于 JS 脚本,一个简单的 CTF 示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 所有 Java API 调用必须在 Java.perform() 回调中执行
Java.perform(function () {
  // 获取目标类的引用
  var Vault = Java.use("com.heroctf.freeda1.utils.Vault");

  // 尝试调用目标方法并处理异常。延迟 1000ms 执行以减少 bug
  setTimeout(function () {
    try {
      var flag = Vault.get_flag();
      console.log("[SUCCESS] Flag: " + flag);
    } catch (e) {
      console.log("[ERROR] " + e);
    }
  }, 1000);
});

这个的脚本的目的是直接获取 Vault 类下的 get_flag() 方法的输出,得到 flag。

一些常用方法:

绕过检测

要用 Frida-server 调试的前提就是 Root,但显然大部分应用都不希望自己被 Frida 或者 Root 调戏,因此会做 Root 检测,一检测到特征就停止程序运行。

但是,代码总是有执行顺序的,只要我在检测前就把特征检测方法给 Hook 掉,你不就检查不出来了吗?另一种就是 “打入内部”,将 Frida-gadget 植入程序,可以实现免 Root 的 Hook。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.heroctf.freeda2.utils;

import android.content.Context;
import com.scottyab.rootbeer.RootBeer;

/* compiled from: r8-map-id-baa4c77f810b701c3077d4ce68a3d5b79ee91034c03e266e8ba1aed7e464c1a1 */
/* loaded from: classes.dex */
public final class Security {
    private Security() {
    }

    public static boolean detectRoot(Context context) {
        return new RootBeer(context).isRooted();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
Java.perform(function () {
  console.log("[+] Bypassing root detection...");

  // 绕过 RootBeer
  var RootBeer = Java.use("com.scottyab.rootbeer.RootBeer");
  RootBeer.isRooted.implementation = function () {
    console.log("[+] RootBeer.isRooted() bypassed");
    return false;
  };

  // 绕过 Vault 构造函数检测
  var Vault = Java.use("com.heroctf.freeda2.utils.Vault");
  Vault.$init.implementation = function () {
    console.log("[+] Vault constructor bypassed");
    // Don't call original constructor
  };

  setTimeout(function () {
    try {
      var flag = Vault.get_flag();
      console.log("[SUCCESS] Flag: " + flag);
    } catch (e) {
      console.log("[ERROR] " + e);
    }
  }, 1000);
});

RootBeer 是一个简单的 Root 权限检查库。作者也在 README 中坦陈 RootBeer 可以被绕过(查看 Bypassing Android’s RootBeer Library — Part 1

本题仅使用 RootBeer 的综合检测方法 isRooted() 来检测 Root,这倒是为我们省去了不少麻烦,因为我们只需要 Hook isRooted() 就行了。

Native Hook

与 Java 层不同,Native 层的函数是分散在 so 文件中的。在 Hook 前,Frida 需要找到目标函数,因为 so 是动态加载的。