Frida高级实战:从脚本到武器
说实话,Frida 这东西,刚接触时觉得就是个 hook 工具。用久了才发现,它简直是个移动安全领域的瑞士军刀。今天我要讲的这几个高级特性——内存搜索、类实例化、动态调用、RPC、Stalker,还有反调试绕过——都是我这些年实战中反复打磨出来的硬核技能。
嗯,咱们一个一个来。先说说内存搜索。
内存搜索:不只是找字符串
很多人以为内存搜索就是找字符串。其实不然。我在分析一个恶意 SDK 时,它把关键配置用 XOR 加密后藏在堆内存里。这时候就需要 Frida 的 Memory.scan 了。
// 搜索十六进制模式
var pattern = "48 83 EC 28 48 8B 05 ?? ?? ?? ??";
Memory.scan(ptr("0x40000000"), 0x100000, pattern, {
onMatch: function(address, size) {
console.log("找到模式在: " + address);
// 可以在这里读取或修改内存
},
onComplete: function() {
console.log("搜索完成");
}
});
我个人习惯用 Memory.scanSync 做批量搜索,性能更好。但要注意,搜索范围别太大,否则会卡死。我曾经在 64 位应用上扫了 500MB 堆内存,结果手机直接重启了……
Process.enumerateRanges('rw-') 看看哪些内存段是可读写的,缩小范围。
类实例化与动态调用
这个功能太强了。你可以直接 new 一个 Java 对象,然后调用它的方法。我在分析某社交应用的加密协议时,就是靠这个绕过了整个加密流程。
// 获取类引用
var StringCls = Java.use("java.lang.String");
var StringBuilderCls = Java.use("java.lang.StringBuilder");
// 实例化对象
var str = StringCls.$new("Hello Frida");
var sb = StringBuilderCls.$new();
// 动态调用方法
sb.append(str);
sb.append(" - 动态调用示例");
console.log(sb.toString());
// 调用静态方法
var SystemCls = Java.use("java.lang.System");
SystemCls.currentTimeMillis();
这里有个坑:$new 是 Frida 的语法,不是 Java 原生的。我第一次用的时候还纳闷为什么 new 关键字不行。另外,调用重载方法时要指定参数类型:
// 重载方法调用
var cls = Java.use("com.example.MyClass");
cls.method.overload('int', 'java.lang.String').call(cls.$new(), 42, "test");
Frida RPC:让 Python 和 Java 对话
RPC 是我最常用的功能之一。说白了,就是让 Python 脚本调用 Java 层的方法,或者反过来。我在做自动化测试时,经常用 RPC 来触发业务逻辑。
先看 JavaScript 端怎么暴露接口:
// JS 端注册 RPC 接口
rpc.exports = {
encrypt: function(data) {
var result = "";
Java.perform(function() {
var CryptoCls = Java.use("com.example.Crypto");
var instance = CryptoCls.$new();
result = instance.encrypt(data);
});
return result;
},
decrypt: function(data) {
// 类似实现
}
};
然后 Python 端调用:
import frida
device = frida.get_usb_device()
session = device.attach("com.example.app")
script = session.create_script(js_code)
script.load()
# 调用 RPC 接口
encrypted = script.exports.encrypt("hello")
print("加密结果:", encrypted)
我曾经用这个方案,在 10 分钟内完成了某金融应用的自动化登录流程。你想想看,不用逆向整个加密算法,直接调它的原生方法,多省事。
Frida Stalker:代码级跟踪器
Stalker 是 Frida 的代码跟踪引擎。它能让你看到每条指令的执行情况。说实话,这玩意儿性能开销不小,但关键时刻真能救命。
我在分析一个混淆严重的 native 库时,就是用 Stalker 一步步跟踪,才找到了真正的加密入口。
// 启动 Stalker 跟踪
var targetAddr = Module.findExportByName("libnative.so", "Java_com_example_Native_method");
Stalker.follow({
events: {
call: true, // 跟踪函数调用
ret: true, // 跟踪函数返回
exec: false // 跟踪每条指令(性能开销大)
},
onReceive: function(events) {
console.log("收到事件:", JSON.stringify(events));
},
transform: function(iterator) {
var instruction = iterator.next();
while (instruction) {
// 可以在这里修改指令
iterator.keep(); // 保留原指令
instruction = iterator.next();
}
}
});
绕过常见反调试
反调试是每个逆向工程师的必修课。我见过最狠的应用,同时用了 ptrace、TracerPid、时间差检测、文件完整性校验……嗯,咱们一个个破。
ptrace 检测绕过
ptrace 检测的原理很简单:一个进程只能被一个调试器 ptrace。应用自己 ptrace 自己,如果失败就说明已经被调试了。
// 方案一:hook ptrace 函数
var ptracePtr = Module.findExportByName("libc.so", "ptrace");
Interceptor.attach(ptracePtr, {
onEnter: function(args) {
var request = args[0].toInt32();
if (request === 0) { // PTRACE_TRACEME
console.log("拦截 ptrace(PTRACE_TRACEME)");
args[0] = ptr(-1); // 返回错误
}
},
onLeave: function(retval) {
// 让 ptrace 返回 0,表示成功
retval.replace(ptr(0));
}
});
我遇到过更狡猾的——它用 ptrace(PTRACE_ATTACH, pid) 来检测。这时候需要 hook 得更精细:
// 方案二:让 ptrace 始终返回成功
Interceptor.replace(ptracePtr, new NativeCallback(function(request, pid, addr, data) {
console.log("ptrace 被调用: request=" + request + ", pid=" + pid);
return 0; // 永远返回成功
}, 'int', ['int', 'int', 'pointer', 'pointer']));
TracerPid 检测绕过
TracerPid 是 Linux 内核提供的一个调试检测机制。当进程被调试时,/proc/self/status 里的 TracerPid 会变成调试器的 PID。
// hook 文件读取操作
var fopenPtr = Module.findExportByName("libc.so", "fopen");
Interceptor.attach(fopenPtr, {
onEnter: function(args) {
this.path = args[0].readCString();
if (this.path && this.path.indexOf("status") !== -1) {
console.log("检测到读取 status 文件:", this.path);
}
}
});
// 或者直接 hook 字符串比较
var strstrPtr = Module.findExportByName("libc.so", "strstr");
Interceptor.attach(strstrPtr, {
onEnter: function(args) {
var haystack = args[0].readCString();
var needle = args[1].readCString();
if (needle === "TracerPid") {
console.log("拦截 TracerPid 检测");
// 可以修改返回值
}
}
});
/proc/self/status 的文件大小。如果文件被修改过,大小不对也会触发反调试。所以,更稳妥的做法是 hook read 系统调用,在读取到 TracerPid 行时直接替换内容。
知识体系总览
下面这张图是我整理的 Frida 高级实战知识体系。你可以看到,这些技术不是孤立的,它们互相配合才能发挥最大威力。
你看,这五个模块其实是层层递进的。内存搜索是基础,类实例化让你能操控 Java 对象,RPC 打通了 Python 和 Java 的桥梁,Stalker 让你看到最底层的指令执行,而反调试绕过则是所有操作的前提——过不了反调试,后面都是白搭。
说实话,这些技术单独拿出来都不难。难的是在实战中灵活组合。我见过太多人,工具用得溜,但遇到真实应用就抓瞎。为什么?因为真实场景永远是混合的——既有反调试,又有混淆,还有各种保护。
嗯,今天就先聊这么多。记住一点:Frida 是武器,但真正的战斗力来自你对系统底层的理解。工具可以学,但思维方式需要慢慢磨。
公众号:蓝海资料掘金营,微信deep3321