Android平台使用Frida Stalker追踪Java函数的问题及可行性咨询
我尝试使用Frida追踪Android应用中调用的所有Java函数,但遇到了一些问题:
- 此前通过
class.function.implementation = function(...parameters) {}的方式hook所有函数来追踪,但在中型应用中扩展性差,难以实施。 - 了解到Frida的Stalker API可用于追踪Native函数,于是尝试了以下代码:
var threads = Process.enumerateThreadsSync(); threads.forEach(function (thread) { Stalker.follow(thread.id,{ events: { call: true }, onReceive: function (events) { var calls = Stalker.parse(events, { annotate: false }); calls.forEach(function (event) { var location = event[0]; var target = event[1]; var locationName = DebugSymbol.fromAddress(location).name; var targetName = DebugSymbol.fromAddress(target).name; console.log(locationName + " " + targetName); }); } }); });
但遇到两个问题:
- 应用启动后不久崩溃(怀疑是追踪了所有线程,包括Frida运行的线程);
- 没有Java方法日志被记录。
核心疑问:使用Frida的Stalker API通常无法追踪Java函数,还是我的方法存在疏漏?
核心结论
Stalker API本身是针对Native层指令级别的追踪工具,默认无法直接捕获Java方法调用——因为Java方法的执行是由ART虚拟机通过Native代码调度的,Stalker只能看到Native层面的调用链,无法直接解析出上层Java方法的符号信息。你的问题既有使用方式的疏漏,也有对Stalker适用场景的误解。
问题分析与修复
1. 应用崩溃问题
你确实踩了全线程追踪的坑:Stalker会对目标线程的指令做动态插桩,追踪所有线程(包括Frida自身的注入线程、ART虚拟机的后台系统线程)会导致系统资源耗尽、线程死锁,直接触发应用崩溃。
修复方式:过滤线程范围,只追踪目标应用的业务线程,比如排除包含"frida"、"zygote"等关键字的系统/工具线程:
var threads = Process.enumerateThreadsSync(); threads.forEach(function (thread) { if (!thread.name.includes("frida") && !thread.name.includes("zygote")) { Stalker.follow(thread.id, { events: { call: true }, bufferSize: 4 * 1024 * 1024, // 增大缓冲减少IO压力 onReceive: function (events) { var calls = Stalker.parse(events, { annotate: false }); calls.forEach(function (event) { var location = event[0]; var target = event[1]; // 处理无法解析符号的情况 var locationName = DebugSymbol.fromAddress(location).name || `0x${location.toString(16)}`; var targetName = DebugSymbol.fromAddress(target).name || `0x${target.toString(16)}`; console.log(`${locationName} -> ${targetName}`); }); }, onError: function (reason) { console.log(`Stalker错误: ${reason}`); } }); } });
2. 无Java方法日志问题
Stalker捕获的是Native内存地址,DebugSymbol.fromAddress只能解析Native函数的符号(比如ART虚拟机的art::interpreter::Execute),而Java方法本身并没有对应的Native符号——它们的执行逻辑是在ART虚拟机内部调度的,所以自然打不出Java方法名。
正确追踪Java函数的方案
优先使用Frida的Java层API做范围过滤的hook,这是最高效且易用的方式,完全可以解决中型应用的扩展性问题:
Java.perform(() => { // 只hook目标应用包名下的类,避免无差别hook系统类 const targetPackage = "com.your.app.package"; const loadedClasses = Java.enumerateLoadedClassesSync(); loadedClasses.forEach(className => { if (className.startsWith(targetPackage)) { try { const cls = Java.use(className); // 遍历类的所有方法 cls.$methods.forEach(method => { const methodName = method.name; // 跳过构造方法或已有实现的hook(可选) if (methodName !== "<init>") { cls[methodName].implementation = function(...args) { console.log(`[Java调用] ${className}.${methodName}`); // 执行原方法并返回结果 return this[methodName](...args); }; } }); } catch (e) { // 跳过无法hook的类(比如抽象类、系统核心类) console.log(`跳过hook类: ${className}, 原因: ${e.message}`); } } }); });
如果确实需要结合Stalker追踪Java方法(比如要关联Native调用栈),需要额外解析ART虚拟机的参数:当Stalker捕获到art::Method::Invoke这类Native函数时,通过其参数拿到Java方法的指针,再调用Frida的Java.vm.getEnv()相关API解析出Java方法名,但这种方式实现复杂、性能开销大,仅适合特定场景。
内容的提问来源于stack exchange,提问作者philipp

