Frida 是一个知名的移动端 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
Note

一般来讲,模拟器采用 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 调试基础

两种模式:Spawn(生成)和 Attach(附加)。Spawn 模式是从头启动应用并调试,Attach 模式是在应用运行时才开始调试。

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
16
// 所有 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。

Security
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();
}
}
hook.js
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() 就行了。


© 2025-Present Watermelonabc | 萌 ICP 备 20251229 号

Powered by Hexo & Stellar 1.33.1 & Vercel & HUAWEI Cloud & Algolia

本博客总访问量:capoo-2

| 开往-友链接力 | 异次元之旅

中文独立博客列表 | 博客录 随机博客

AI 参与指数(IIIA)2 级

猫猫🐱

发表了 65 篇文章 · 总计 269.4k 字