一句话总结:调试器适合「停下来看」,Frida 适合「一直在旁边盯着」。这篇从 attach 一个本地进程开始,讲清 Interceptor 的三种用法(改返回、读参数、调内部函数)、JavaScript 与 Python 两侧怎么通信,以及版本差异和反 Frida 检测这几个坑。
前一篇讲 PE 是静态看结构,x64dbg 那篇是停下来单步。但有一类需求这两样都不顺手:我需要知道这个函数每次被调用时参数是什么,连续观察几百次。
在调试器里下断点,每次都要手动 F5,几十次之后就烦了。Frida 解决的就是这个:写一段脚本挂上去,它在后台记录,你该干嘛干嘛。
前置
pip install frida frida-tools
frida --veRSIon
Windows 本地进程直接 attach,不需要额外组件。Android 要往设备里推 frida-server(需 root,或使用可调试应用的重打包方案),这部分后面单独说。
注意一点:Frida 的位数要和目标一致。用 64 位 Python 装的 Frida 只能 attach 64 位进程,32 位目标得装 32 位 Python 环境。
最小可跑:二十行
import frida, sys
TARGET = "target.exe"
JS = """
const addr = Module.getExportByName(null, "IsDebuggerPresent");
console.log("[*] IsDebuggerPresent @ " + addr);
Interceptor.attach(addr, {
onEnter(args) {
this.caller = this.returnAddress;
console.log("[+] 被调用,返回地址 " + this.caller);
},
onLeave(retval) {
console.log(" 原返回值 " + retval);
retval.replace(0); // 强制返回「没有调试器」
}
});
"""
def on_message(msg, data):
if msg["type"] == "send":
print("[脚本]", msg["payload"])
else:
print("[", msg["type"], "]", msg.get("description") or msg.get("stack"))
session = frida.attach(TARGET)
script = session.create_script(JS)
script.on("message", on_message)
script.load()
print("[*] 已挂载,CtRL+C 退出")
sys.stdin.read()
跑起来之后,只要目标调用了 IsDebuggerPresent,你就能在终端看到一行日志,而且它的返回值被改成了 0。
这就是 Frida 的核心价值:不用停止进程,就能观察和改写行为。
Interceptor 的三种用法
改返回值。 上面那个例子。retval.replace(0) 能把检查函数的返回值改掉,绕过反调试最省力的一招。
注意 retval 是个 NativePointer,replace 要传同宽度的值。64 位下返回 bool 时用 retval.replace(0) 就行,传字符串会静默失败。
读参数。 onEnter(args) 里 args[0]、args[1] 是对应寄存器的值。如果参数是指针,得自己读内存:
Interceptor.attach(Module.getExportByName(null, "CreateFileW"), {
onEnter(args) {
const path = Memory.readUtf16String(args[0]); // 宽字符
console.log("[打开文件] " + path);
}
});
这里有个常见错误:CreateFileW 的 W 表示宽字符(UTF-16),要用 readUtf16String;CreateFileA 才是 readAnsiString。混用会读出乱码或者直接崩。
调内部函数。 想把某个没导出的函数当工具用,用 NativeFunction 包一层:
const base = Module.getBaseAddress("target.exe");
const fn = new NativeFunction(base.add(0x1A2C0), "int", ["pointer", "int"]);
const result = fn(ptr(0x1000), 42);
这一步在分析加密、解包这类活时特别有用:你不用重写算法,直接调它自己的实现。
图 1:Python 主控与目标进程内的 JS Agent 双层结构,以及三种典型用法
两侧怎么通信
Frida 是双层结构:Python 侧负责 attach、加载脚本、收消息;注入到目标进程里的 JS agent 负责实际 hook。
JS 里用 send() 往外发结构化数据:
send({ event: "call", API: "IsDebuggerPresent", ret: 0 });
Python 侧收:
def on_message(msg, data):
if msg["type"] == "send":
payload = msg["payload"]
if payload.get("event") == "call":
print(payload["api"], payload["ret"])
console.log 的输出也会通过 on_message 送过来,类型是 log。这意味着一件事:别在 JS 里用 send 传巨大的对象,每次跨进程都有序列化成本,高频调用时会明显拖慢目标。
要传二进制(比如内存 dump 的片段),用 send(payload, dataArrayBuffer) 的第二个参数,走单独的二进制通道。
Windows 本地与 Android 的差别
本地 Windows 进程:frida.attach("进程名或PID") 就完事,权限够就行(同用户、非保护进程)。
Android 麻烦得多:
- 需要
frida-server在设备上以 root 运行(adb push+chmod +x+ 后台启动) - 或者用「重打包 + 注入 gadget」的方案,不需要 root,但要改 APK 签名
- 现在很多应用有 Frida 检测,跑起来直接被拒绝
第三种情况最常见。检测手段包括:扫描 /proc/self/maps 里的 frida 字样、检测默认端口 27042、检查线程名 gum-js-loop、检测 frida-agent 的内存特征。绕这些需要改端口、改名、用定制编译的 gadget,属于另一个话题了。
坑清单
坑一:32/64 位不匹配。 报错通常是 unable to attach 或者进程直接崩。先确认 Python 和目标的位数一致。
坑二:API 名字在不同版本里不一样。 Module.findExportByName 在旧版本用得多,新版本改成了 Module.getExportByName。写脚本前用 frida --version 确认,报 undefined is not a function 基本就是这个原因。
坑三:hook 不到被内联的函数。 编译器的内联优化会把小函数直接展开,导出表里还有符号,但实际执行根本不走那条指令。表现在 hook 成功挂上、永远不触发。换更上层的函数,或者用 Interceptor.replace 整个替换。
坑四:字符串编码搞错。 W 结尾用 UTF-16、A 结尾用 ANSI,中文路径在后者下要处理代码页。乱码先查这个。
坑五:JS 里做重活拖慢目标。 在 onEnter 里跑循环、拼大字符串、频繁 send,目标进程会明显变卡,甚至触发超时检测。原则是只采必要数据,加工放到 Python 侧。
边界
Frida 是分析工具,用在你有权分析的目标上:自己的程序、靶场、明确授权的样本、自己的移动应用。
它的正经用途很广:给没有源码的第三方库加日志、观测加密前后的数据、验证补丁是否生效、做自动化回归测试(hook API 断言调用顺序)。
需要提醒的是:对在线游戏或他人应用使用 Frida,检测和封禁只是最轻的后果。技术本身中性,用在哪由人决定。
FAQ
Frida 和 x64dbg 该怎么选?
要看「一次性定位」还是「持续观测」。断点找根因用调试器,批量记录、长时间观察、自动改行为用 Frida。两者可以同时开。
需要 root 吗?
Windows 本地进程不需要。Android 通常需要 root 跑 frida-server,或者用 gadget 重打包方案绕过。
能不能 hook 系统 API?
能。Module.getExportByName(null, "名字") 的第一个参数传 null 就是全局搜索,会找到系统 DLL 里的导出。
hook 之后程序崩了怎么办?
先看 on_message 里的异常信息(Frida 会把 JS 异常和 native crash 分开报)。最常见的原因是参数类型读错(把指针当整数用了),另一类就是 replace 的宽度不对。
有没有办法检测自己被 hook 了?
有,读函数开头的字节看有没有被改成跳转。这也是很多反作弊的做法,属于攻防常规动作。
结语
Frida 的学习曲线在于「不知道有哪些 API」,而不在于概念难。真正要记的就三件事:怎么拿到函数地址、怎么在前后插逻辑、两侧怎么通信。
把这三件练熟,剩下的都是查文档的活。
参考资料
- Frida 官方文档 - JavaScript API 全量参考
- Frida Python 绑定 - attach、create_script 与消息通道
- 本站 x64dbg 入门 - 断点与调用栈的动态侧方法
- 本站 反调试手段识别 - 常见的 Frida 与调试器检测点
- 参考资料

发表评论
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。