type
Post
status
Published
date
Jul 8, 2026
slug
summary
本篇目标:当 Hook "不灵" 的时候,你能系统地排查出原因。前六篇给了你完整的武器库(Java Hook、Native Hook、主动调用、内存操作),但在实战中你会频繁遇到各种问题——类找不到、Hook 了但没输出、App 崩溃、日志被淹没……这些问题不是偶尔碰运气能解决的,你需要一套可重复的诊断方法论。本篇就是这个方法论。
tags
frida
Android
逆向工程
category
公众号
icon
password
网址
上次编辑时间
Jul 8, 2026 03:53 AM
comment
AI 总结
本篇目标:当 Hook "不灵" 的时候,你能系统地排查出原因。前六篇给了你完整的武器库(Java Hook、Native Hook、主动调用、内存操作),但在实战中你会频繁遇到各种问题——类找不到、Hook 了但没输出、App 崩溃、日志被淹没……这些问题不是偶尔碰运气能解决的,你需要一套可重复的诊断方法论。本篇就是这个方法论。
一、为什么需要一篇专门讲排错的文章
前面六篇的代码示例都很顺畅——给定类名、方法名、Hook、拿到结果。但真实的逆向场景常常没这么顺:
- 你从 jadx 复制的类名在 Frida 中报 ClassNotFoundException
- Hook 成功了(没有报错),但操作 App 后控制台一条日志都没有
- 加载脚本的瞬间 App 闪退
- 控制台被洪水般的日志淹没,有用的信息被冲掉
- Native Hook 后 App 行为完全异常
- 同一个脚本昨天能用今天不能用了
这些不是 Frida 的 bug,而是你对目标 App 的理解和 Frida 的行为之间存在"信息差"。本篇的目标是帮你系统化地缩小这个信息差。
二、日志管理:console.log、send() 与文件写入
在开始讲排错之前,先讲好日志基础设施——因为排错的第一步就是"看日志"。如果日志系统本身就有问题(丢日志、太多、格式乱),排错将寸步难行。
2.1 console.log:最基本的输出
console.log 的输出会通过 Frida 的通信通道从目标进程发送到你的电脑终端上显示。这个过程是异步的——你的 JS 代码不会等 console.log 的内容显示到屏幕上才继续执行。性能影响:每次
console.log 都涉及一次 IPC(进程间通信)。在高频 Hook 场景(如 Hook Socket.write,每秒可能触发几百次)中,过多的 console.log 会显著拖慢 App,甚至导致 Frida 通信管道拥堵——表现为日志输出严重延迟、App 卡顿、甚至 Frida 连接断开。
2.2 send():结构化消息通道
send() 是 Frida 提供的另一个通信机制。和 console.log(纯文本输出)不同,send() 可以发送结构化的 JSON 数据,并且支持附带二进制数据。Python 端接收:
send() vs console.log 的选择:
场景 | 推荐 | 原因 |
临时调试、快速查看 | console.log | 简单直接 |
需要在 Python 端处理数据 | send() | 结构化 JSON + 二进制 |
需要传输二进制数据(dump 内存、密文等) | send() | 支持 ArrayBuffer 附件 |
自动化工具、RPC 场景 | send() | Python 端可以编程处理 |
需要日志分级和过滤 | 自建日志函数 | 见下节 |
2.3 构建日志分级系统
在复杂的 Hook 脚本中,不同模块产生的日志混在一起很难阅读。建议用一个简单的日志工具:
2.4 将日志写入文件
当日志量很大时,控制台输出会丢失(Frida 的通信缓冲区有限)。可以通过 Java API 把日志写到 App 目录下的文件中:
pull 日志:
adb shell run-as com.example.app cat files/frida_log.txt > local_log.txt,或者 adb pull /data/data/com.example.app/files/frida_log.txt(需要 root)。
2.5 控制日志洪水
高频 Hook(如
MessageDigest.update、Socket.write)可能每秒触发几百次。不加控制地打印日志会导致:- Frida 通信管道拥堵 → 日志严重延迟
- App 运行速度下降数十倍
- 有用信息被海量重复日志淹没
控制策略:
三、frida-trace:快速侦察的利器
在写详细的 Hook 脚本之前,
frida-trace 可以帮你快速回答"这个方法到底有没有被调用"——10 秒命令就能回答的问题,没必要先写脚本。3.1 追踪 Java 方法
-j 参数的格式是 类名模式!方法名模式。* 是通配符。运行后,每当 App 调用匹配的方法时,终端会实时显示调用记录(含参数和返回值)。这让你在不写任何脚本的情况下就能观察方法的调用情况。
3.2 追踪 Native 函数
-i / -x 分别按函数名包含 / 排除;-I / -X 分别按模块名包含 / 排除(-I 是"包含整个模块",不是排除)。3.3 自定义追踪处理器
frida-trace 首次追踪一个函数时,会在当前目录下生成一个 __handlers__/ 目录,里面包含每个被追踪函数的处理器脚本。你可以编辑这些脚本来自定义行为:编辑保存后,下次运行
frida-trace 会自动使用修改后的处理器。3.4 frida-trace 的适用场景
场景 | 是否适合用 frida-trace |
快速确认某个方法是否被调用 | 非常适合 |
了解 App 启动时调用了哪些加密方法 | 非常适合 |
观察 Native 函数的调用顺序 | 适合 |
需要修改参数或返回值 | 不太适合(用自定义脚本更方便) |
需要复杂的条件过滤和数据处理 | 不太适合 |
需要 RPC 调用和自动化 | 不适合 |
典型工作流:先用
frida-trace 快速扫描,确认目标方法在哪里、被谁调用、多频繁调用——然后再写针对性的 Hook 脚本做深入分析。四、Hook 排错系统化流程
当 Hook 不符合预期时,按以下流程系统化排查。每一步都是在缩小问题范围。

Hook 排错决策树
4.1 第一步:确认 Frida 连接正常
在排查 Hook 问题之前,先确保 Frida 本身工作正常:
如果第三步能看到
Frida OK 输出,说明 Frida 连接正常。如果不能,回到第02篇排查环境问题。4.2 第二步:确认脚本是否有语法错误
JavaScript 语法错误会导致整个脚本不执行,但 Frida 的错误提示有时不够清晰。
如果只看到
脚本开始加载 但没看到 Java.perform 开始执行,说明 Java.perform 之前就出错了。如果两条都没看到,说明脚本压根没被加载(检查文件路径和语法)。4.3 第三步:Hook 设置是否成功
Hook 代码可能执行了但 Hook 没有实际生效——因为类找不到、方法签名错误等。关键是要**区分"设置阶段的失败"和"运行时的不触发"**。
4.4 第四步:方法是否被 App 调用了
如果 Hook 设置成功(没有报错),但操作 App 后没有看到
[HIT] 日志——问题可能不在 Frida,而在 App。可能性一:你操作的功能没有触发目标方法。 比如你 Hook 了
LoginManager.login,但在 App 中点击的是"注册"按钮——注册流程调用的是 RegisterManager.register,根本不走 login。用
frida-trace 确认:可能性二:App 使用了不同的代码路径。 混淆后的 App 可能有多个看起来相似的方法,jadx 中你看到的
a.b.c.d() 可能不是实际被调用的那个 d()。可能性三:方法确实被调用了,但在另一个进程中。 很多 App 使用多进程架构(加密操作在
:crypto 进程中,网络操作在 :network 进程中)。见 §4.5 详细讲解。4.5 第五步:检查是否是多进程问题
Android App 可以在
AndroidManifest.xml 中为不同组件指定不同的进程名(android:process 属性)。例如:当你用
frida -U -f com.example.app 注入时,Frida 只注入主进程。如果目标方法在子进程中运行,你的 Hook 自然不会触发。诊断方法:
如果有多个进程,尝试分别注入:
但
-n 只能附加已运行的子进程。如果加密操作在 App 启动早期就执行(典型场景:加固壳在子进程中解密),子进程往往在你来得及附加前就跑完了关键逻辑。这种情况需要 child gating——让 Frida 在 fork 出子进程时立即挂起,待脚本就位再 resume:
五、打印调用栈:定位"谁调用了我"
很多排错场景里,问题不在"方法有没有被 Hook 到",而在"这个方法是从哪条代码路径来的"。比如:你 Hook 到了
Cipher.doFinal,但 App 里有 30 个地方调用它,你想知道当前这次调用是登录流程还是消息加密。打印调用栈是回答这类问题最直接的工具。5.1 Java 层调用栈
Java 层有两种打印调用栈的方式,效果接近,写法不同:
两种方式都会打印形如
at com.example.app.LoginManager.encryptPassword(LoginManager.java:42) 的调用栈帧。实战中优先用方式一——Log.getStackTraceString 自带格式化、输出更紧凑;需要按帧过滤时再用方式二。5.2 Native 层调用栈
Native Hook 中用
Thread.backtrace 取栈,配合 DebugSymbol.fromAddress 解析符号:输出形如:
两种 Backtracer 模式:
模式 | 速度 | 精度 | 适用 |
Backtracer.ACCURATE | 慢 | 高(依赖栈展开元数据) | 默认选择 |
Backtracer.FUZZY | 快 | 低(启发式扫描栈) | ACCURATE 取不到栈帧时退而求其次 |
如果调用来自 Java 层(典型情况:JNI 函数),Native 栈底部会出现
art_quick_invoke_stub 这类 ART 内部符号——这是 Java→Native 的边界,再往下要切到 Java 栈才看得清。最完整的做法是 Java 栈 + Native 栈同时打印:在 JNI Hook 里既调 Thread.backtrace 也借 Java API 取一次 Java 栈。
Java 栈与 Native 栈在 JNI 边界拼接
5.3 调用栈裁剪与过滤
完整调用栈往往几十帧,多数是 Framework 或运行时的"水帧"。过滤掉无信息帧能让输出可读得多:
5.4 什么时候打印调用栈
打印调用栈对 IPC 通信和 App 性能有可观开销——一次完整 Java 栈格式化大约几百字节、几毫秒。建议:
- 侦察阶段打:首次找到目标方法被调用时,打一次完整栈,建立"调用路径地图"。
- 复现阶段不打:调用路径明确后,去掉栈打印只留参数日志。
- 条件触发打:用 §2.5 的条件过滤策略——只在特定参数值或第 N 次调用时打印,避免日志洪水。
六、常见错误完整排查手册
6.1 ClassNotFoundException
这是 Frida Java Hook 的头号错误。 第03篇和第05篇都涉及了部分原因,这里给出完整的排查清单。
排查步骤(按可能性从高到低):
Step 1:检查类名拼写。
Step 2:确认类是否已加载。
如果搜不到——类可能确实还没被加载(App 还没执行到加载它的代码)。等 App 充分运行后再试。
Step 3:检查是否是 ClassLoader 问题。
Step 4:App 是否有加固?
加固 App 的业务类被加密存储在 DEX 中,只有壳解密后才能加载。如果你在 App 启动早期就尝试
Java.use,类确实不存在。诊断方法:
辅助判定:除 Application 类名外,还可看
lib/<abi>/ 下是否有特征 SO(如 libDexHelper.so、libsecexe.so、libBugly_*.so、libshellx.so 等),以及 assets/ 下是否有未知的加密 dex 文件。
对于加固 App,需要等壳解密完成后再 Hook。通常的做法是 Hook
Application.onCreate 或 ClassLoader.loadClass,在类被加载时再执行你的 Hook 逻辑:6.2 Hook 设置成功但不触发
这是第二常见的问题——没有报错,但
implementation 回调从未被调用。
Hook 不触发决策树
排查清单:
可能原因 | 诊断方法 | 解决方案 |
操作没有触发目标方法 | frida-trace 确认 | 确认正确的操作路径 |
多进程,方法在子进程中 | frida-ps -U | grep 包名 | 注入正确的进程 |
方法被 ProGuard 混淆 | jadx 搜索原始名 → 找混淆后的名字 | 用混淆后的类名/方法名 |
方法有多个重载,Hook 了错误的版本 | 枚举所有重载 | 逐个尝试或用 overloads 全部 Hook |
JIT 编译导致 Hook 被绕过 | 加 Java.deoptimizeEverything() | 关闭 JIT |
Hook 时机过晚,方法只在启动时调用一次 | Spawn 模式启动 | frida -U -f 包名 |
方法确实没有被调用(死代码) | jadx 中查看调用关系 | 换一个 Hook 点 |
用 overloads 一次性 Hook 所有重载版本(最强壮的方式):
6.3 Hook 后 App 崩溃
崩溃类型一:加载脚本的瞬间就崩溃。
通常是 App 检测到了 Frida 并主动退出。诊断:
崩溃类型二:操作 App 触发 Hook 时崩溃。
常见原因:
崩溃类型三:Native Hook 后崩溃。
诊断 Native 崩溃:
崩溃日志中的
signal 11 (SIGSEGV) 表示段错误(非法内存访问),signal 6 (SIGABRT) 表示主动退出(通常是检测到异常后 abort()),signal 5 (SIGTRAP) 可能是反调试检测触发的断点。6.4 Error: expected a pointer(类型错误)
这个错误通常出现在
Interceptor.attach 的第一个参数不是一个有效的 NativePointer。6.5 Unable to find method(方法找不到)
原因一:方法名拼写错误或方法不存在。
原因二:overload 参数类型写错。
Frida overload 中常见 Java 类型的写法:
Java 类型 | overload 中的写法 |
int | "int" |
long | "long" |
boolean | "boolean" |
byte | "byte" |
float | "float" |
double | "double" |
String | "java.lang.String" |
Object | "java.lang.Object" |
Context | "android.content.Context" |
int[] | "[I" |
byte[] | "[B" |
long[] | "[J" |
String[] | "[Ljava.lang.String;" |
Object[] | "[Ljava.lang.Object;" |
List | "java.util.List" |
Map | "java.util.Map" |
数组类型的 JVM 编码规则:
[ 表示一维数组,I = int, B = byte, J = long, F = float, D = double, Z = boolean, C = char, S = short。对象数组格式为 [L全限定类名;(注意末尾的分号)。二维数组如 int[][] 是 "[[I"。
七、高级排错技巧
7.1 延迟 Hook:等待类加载
有些类只在 App 运行到特定页面或功能时才加载。你可以用"延迟 Hook"模式——先等待目标类出现,再设置 Hook:
另一种更优雅的方式——监听类加载事件:
7.2 多 Hook 冲突排查
当你同时加载多个脚本(或一个脚本中对同一个方法 Hook 了两次),可能出现奇怪的行为——某些 Hook 不触发,或者触发顺序异常。
原则:一个 Java 方法同一时刻只能有一个
implementation 替换。后设置的 Hook 会覆盖先设置的。7.3 logcat 配合 Frida 排错
Android 的
logcat 是与 Frida 互补的信息源。App 自身的日志(Log.d、Log.e)、系统日志、崩溃信息都在 logcat 中。在 Frida 中也可以 Hook
android.util.Log 来捕获 App 的日志输出:八、排错速查清单
遇到问题时,按这张清单逐项检查:
总结
本篇建立了一套系统化的 Frida 排错方法论。核心要点:
日志管理是排错的基础设施。
console.log 适合快速调试,send() 适合结构化数据传输和自动化场景。在高频 Hook 中必须控制日志量——采样、条件过滤、限速是三种基本策略。当日志量太大时,写入文件比打印到控制台更可靠。frida-trace 是快速侦察的利器。在写详细脚本之前,先用
frida-trace -j '类名!方法名' 确认方法是否被调用、什么时候被调用、参数长什么样。它能在 10 秒内回答你可能要花 10 分钟写脚本才能回答的问题。排错的核心思路是逐步缩小范围:Frida 连接 → 脚本加载 → Hook 设置 → 方法触发 → 输出正常。每一步都有明确的诊断方法,不要跳步猜测。
五个最常见的问题及其快速解法:
- → 检查拼写、ClassLoader 切换、等待壳解密
- Hook 不触发 → frida-trace 确认、全重载 Hook、检查多进程、JIT 关闭
- Hook 后崩溃 → 检查返回值、确保调用原始方法、检查 logcat
- 方法名找不到 → 枚举所有方法、注意混淆后的名字
- Native Hook expected a pointer → 检查函数地址是否为 null、SO 是否加载
- Author:24th
- URL:https://24th.top/article/397e5b08-46db-8035-b367-ff18b1f4173c
- Copyright:All articles in this blog, except for special statements, adopt BY-NC-SA agreement. Please indicate the source!








