Frida 基础指南
最后更新:2026-03-29 相关文档:js逆向调试技巧(/js-ni-xiang-diao-shi-ji-qiao/) 补环境(/bu-huan-jing/) 签名算法还原(/qian-ming-suan-fa-huan-yuan/) Frida 是一款动态插桩(Dynamic Instrumentation)框架,可以在运行时向 Native 程序(Android/iOS App、PC 程序)注入 JavaScript 代码,实现: 直接在 Java.perform 外调用 Java API 会报错。 方法有多个重载时,不指定 overload 会
官方文档:https://frida.re/docs/
适用版本:Frida 16.x+(2026-05-08 核实)
最后更新:2026-03-29
1. 基础概念
Frida 是什么
Frida 是一款动态插桩(Dynamic Instrumentation)框架,可以在运行时向 Native 程序(Android/iOS App、PC 程序)注入 JavaScript 代码,实现:
- Hook 任意函数(Java 层 / Native 层 / OC/Swift 层)
- 读写内存、修改参数和返回值
- 追踪函数调用链
- 绕过 SSL Pinning、Root 检测
- 辅助 App 逆向、接口分析
架构
PC(Frida Tools / Python)
↕ USB / TCP
手机(frida-server 运行在 Root 权限下)
↕ 进程注入
目标 App 进程(frida-agent 注入后运行 JS 脚本)
安装
# 安装 Python 工具
pip install frida-tools
# 验证安装
frida --version
# 下载对应版本的 frida-server(手机端,需 Root)
# 地址:https://github.com/frida/frida/releases
# 选择 frida-server-xx.xx.x-android-arm64.xz
frida-server 部署(Android)
# 解压 frida-server
unxz frida-server-*.xz
# 推送到手机
adb push frida-server /data/local/tmp/
adb shell "chmod 755 /data/local/tmp/frida-server"
# 以 Root 权限启动
adb shell "su -c '/data/local/tmp/frida-server &'"
# 验证连接
frida-ps -U # 列出手机上所有进程
frida-ps -Ua # 只列出已安装的 App
2. 基础使用
常用命令行工具
# 列出设备上的进程
frida-ps -U
# 附加到进程(进程名或 PID)
frida -U -n "com.example.app" -l script.js
# 附加到应用(包名)并在启动时注入(用于初始化阶段的 Hook)
frida -U -f "com.example.app" -l script.js --no-pause
# 交互式 REPL(调试用)
frida -U -n "com.example.app"
# 指定设备
frida -D <device-id> -n "com.example.app" -l script.js
脚本结构
// script.js
// 等待 Java 虚拟机初始化完成
Java.perform(function () {
console.log("[*] Frida 脚本已加载");
// 在这里写 Hook 代码
});
3. Java 层 Hook
Hook 实例方法
Java.perform(function () {
// 获取目标类
var TargetClass = Java.use("com.example.app.crypto.SignUtil");
// Hook 方法(根据方法签名区分重载)
TargetClass.sign.implementation = function (data) {
// this:当前对象实例
console.log("[sign] 被调用,入参: " + data);
// 调用原始方法,获取原始返回值
var result = this.sign(data);
console.log("[sign] 返回值: " + result);
// 修改返回值(可选)
// return "fake_signature";
return result;
};
});
Hook 重载方法
Java.perform(function () {
var Crypto = Java.use("com.example.Crypto");
// 方法有多个重载时,用 overload 指定参数类型
Crypto.encrypt.overload("java.lang.String").implementation = function (data) {
console.log("[encrypt(String)] data: " + data);
return this.encrypt(data);
};
Crypto.encrypt.overload("java.lang.String", "java.lang.String").implementation = function (data, key) {
console.log("[encrypt(String, String)] data: " + data + ", key: " + key);
return this.encrypt(data, key);
};
});
Hook 静态方法
Java.perform(function () {
var Utils = Java.use("com.example.Utils");
// 静态方法也是一样的写法,用 this 调用原始方法(静态方法的 this 是类本身)
Utils.md5.implementation = function (input) {
var result = this.md5(input);
console.log("[md5] " + input + " => " + result);
return result;
};
});
Hook 构造函数
Java.perform(function () {
var OkHttpClient = Java.use("okhttp3.OkHttpClient$Builder");
OkHttpClient.$init.implementation = function () {
console.log("[OkHttpClient.Builder] 构造函数被调用");
console.log(Java.use("android.util.Log").getStackTraceString(
Java.use("java.lang.Exception").$new()
));
return this.$init();
};
});
4. 常用 Hook 场景
打印函数调用栈
Java.perform(function () {
var TargetClass = Java.use("com.example.Sign");
TargetClass.getSign.implementation = function () {
var result = this.getSign();
// 打印 Java 调用栈
var stack = Java.use("android.util.Log")
.getStackTraceString(Java.use("java.lang.Exception").$new());
console.log("[getSign] 调用栈:\n" + stack);
console.log("[getSign] 结果: " + result);
return result;
};
});
Hook 所有加载的类的某个方法
Java.perform(function () {
Java.enumerateLoadedClasses({
onMatch: function (className) {
if (className.includes("Encrypt")) {
try {
var clazz = Java.use(className);
var methods = clazz.class.getDeclaredMethods();
methods.forEach(function (method) {
console.log("[类] " + className + "." + method.getName());
});
} catch (e) {}
}
},
onComplete: function () {},
});
});
主动调用方法
Java.perform(function () {
// 主动调用静态方法
var CryptoUtil = Java.use("com.example.CryptoUtil");
var result = CryptoUtil.md5("hello world");
console.log("MD5 结果: " + result);
// 枚举所有实例,主动调用实例方法
Java.choose("com.example.TokenManager", {
onMatch: function (instance) {
console.log("token: " + instance.getToken());
},
onComplete: function () {},
});
});
5. 绕过常见检测
绕过 Root 检测
Java.perform(function () {
// Hook RootBeer 库
try {
var RootBeer = Java.use("com.scottyab.rootbeer.RootBeer");
RootBeer.isRooted.implementation = function () {
console.log("[RootBeer] isRooted → false");
return false;
};
} catch (e) {}
// Hook 常见的文件检测
var File = Java.use("java.io.File");
File.exists.implementation = function () {
var path = this.getAbsolutePath();
if (path.includes("su") || path.includes("superuser")) {
return false;
}
return this.exists();
};
});
绕过 SSL Pinning
Java.perform(function () {
// 方法 1:Hook OkHttp3 CertificatePinner
try {
var CertificatePinner = Java.use("okhttp3.CertificatePinner");
CertificatePinner.check.overload("java.lang.String", "java.util.List").implementation = function (host, certs) {
console.log("[SSL Pinning] 已绕过,host: " + host);
return; // 不调用原始方法,直接通过
};
} catch (e) {}
// 方法 2:Hook TrustManager(信任所有证书)
var TrustManagerImpl = Java.use("com.android.org.conscrypt.TrustManagerImpl");
TrustManagerImpl.verifyChain.implementation = function (untrustedChain, trustAnchorChain, host, clientAuth, ocspData, tlsSctData) {
return untrustedChain;
};
});
6. Native 层 Hook
// Hook so 库中的函数
var libso = Module.findBaseAddress("libcrypto.so");
if (libso) {
// 按导出函数名 Hook
var target = Module.findExportByName("libssl.so", "SSL_write");
if (target) {
Interceptor.attach(target, {
onEnter: function (args) {
console.log("[SSL_write] 调用");
// 打印参数(buf)
var buf = args[1];
var num = parseInt(args[2]);
console.log("[SSL_write] 数据: " + buf.readUtf8String(num));
},
onLeave: function (retval) {
console.log("[SSL_write] 返回: " + retval);
},
});
}
}
7. Python API(脚本化)
import frida
import sys
import json
PACKAGE_NAME = "com.example.app"
SCRIPT = """
Java.perform(function () {
var Sign = Java.use("com.example.Sign");
Sign.getSign.implementation = function(data) {
var result = this.getSign(data);
send({type: "sign", data: data, result: result});
return result;
};
});
"""
def on_message(message, data):
if message["type"] == "send":
payload = message["payload"]
print(f"[+] 签名: {payload['data']} → {payload['result']}")
elif message["type"] == "error":
print(f"[!] 错误: {message['stack']}")
def main():
device = frida.get_usb_device()
session = device.attach(PACKAGE_NAME)
script = session.create_script(SCRIPT)
script.on("message", on_message)
script.load()
print(f"[*] 已附加到 {PACKAGE_NAME},按 Ctrl+C 退出")
sys.stdin.read()
if __name__ == "__main__":
main()
8. 常用代码段速查
打印 Byte 数组
function bytes2hex(arr) {
return Array.from(arr).map(b => ('0' + (b & 0xFF).toString(16)).slice(-2)).join('');
}
function bytes2str(arr) {
return Java.use("java.lang.String").$new(arr, "UTF-8");
}
主动调用加密函数
Java.perform(function () {
var context = Java.use("android.app.ActivityThread")
.currentApplication().getApplicationContext();
var SharedPreferences = context.getSharedPreferences("config", 0);
var token = SharedPreferences.getString("token", "");
console.log("token: " + token);
});
9. 踩坑与注意事项
Java.perform 必须包裹所有 Java 操作
直接在 Java.perform 外调用 Java API 会报错。
Hook 时机:frida -f 与 frida -n 的区别
-f:在 App 启动前注入(适合 Hook 初始化阶段的加密,如 so 加载)-n:附加到已运行的进程(适合 Hook 业务逻辑)
重载选择错误
方法有多个重载时,不指定 overload 会只 Hook 第一个重载。若 Hook 后未触发,检查是否选对了重载。
frida-server 版本必须与 frida-tools 版本一致
版本不一致会导致连接失败或奇怪的报错,更新时两端都要同步更新。
最佳实践
attach 前确认进程名:用 frida-ps -U 列出设备所有进程,确认目标 App 的进程名(或包名),避免 attach 到错误进程;对于同名进程,用 -H 或 -D 加设备序列号指定设备。
Hook Java 方法时用 overload 明确指定参数类型:同一方法名可能有多个重载,Java.use('cls').method.overload('java.lang.String', 'int').implementation = ... 精确匹配,避免意外 Hook 错重载。
用 Interceptor.attach 的 onLeave 查看返回值:不仅要在 onEnter 看输入参数,在 onLeave(retval) 中打印 retval.toString() 或 hexdump(retval) 可以看到函数输出,对追踪加密结果尤其有用。
脚本调试用 console.log + frida -U -l script.js:通过文件加载脚本比 -e 内联脚本更易维护;修改脚本后直接 Ctrl+C 重启,不需要重新 attach(脚本支持 --no-pause 直接运行)。
Memory.scan 配合 frida-dump 提取内存中的明文密钥:加密密钥有时只在内存中出现(从接口动态获取并解密),用 Memory.scan 在堆内存中搜索特征字节序列,找到密钥地址后读出。
常见陷阱
陷阱:frida-server 在设备重启后失效
现象: 重启 Android 设备后 Frida 连接失败,需要重新 push frida-server。
原因: frida-server 放在 /data/local/tmp/ 的可执行文件在重启后不自动运行,需要手动启动或配置自启。
解决: 每次连接前先 adb shell /data/local/tmp/frida-server &;或使用 Magisk 模块自动启动 frida-server。
陷阱:Hook 后 App 直接崩溃
现象: 注入 Frida 脚本后 App 立即 crash,没有输出任何日志。
原因: App 有反 Frida 检测(检测 frida-server 端口 27042、/proc/self/maps 中的 frida 库、ptrace 检测等),或 Hook 脚本语法错误导致异常。
解决: 先用 frida --no-pause 只 attach 不注入脚本,确认 App 能运行;再用 objection explore 绕过常见反检测;或用 frida-gadget 模式替代 frida-server,减少检测特征。
陷阱:Java.use 的类在运行时找不到
现象: Java.use('com.example.Crypto') 报 ClassNotFoundException。
原因: 目标类可能在非主线程或特定 ClassLoader 中加载(如插件 APK、热修复框架)。
解决: 用 Java.enumerateClassLoaders 遍历所有 ClassLoader,找到包含目标类的 ClassLoader 后用 Java.classFactory.loader = targetLoader 切换。