安卓逆向-frida hook Native层
字数 1318 2025-10-01 14:05:44
Frida Hook Native层技术详解
一、Native方法与JNI基础
1.1 Native方法定义
在Java中声明native方法时,使用native关键字表明该方法由其他语言(通常是C/C++)实现:
public native int cmpstr(String str);
1.2 JNI函数命名规范
JNI函数的命名遵循特定格式:Java_包名_类名_方法名
示例:Java_com_ad2001_frida0x8_MainActivity_cmpstr
二、Frida Hook Native层技术
2.1 基础函数Hook(Frida 0x8)
场景:通过校验取决于native层的字符串比较函数
Hook策略:拦截strcmp函数获取中间参数
function hook() {
// 查找strcmp函数地址
var target = Module.findExportByName("libc.so", "strcmp");
console.log("strcmp地址:", target);
// 拦截函数调用
Interceptor.attach(target, {
onEnter: function(args) {
// 读取第一个参数(输入字符串)
var input = Memory.readUtf8String(args[0]);
// 添加过滤条件避免过多输出
if (input.includes("aaa")) {
// 读取并打印第二个参数
console.log("strcmp参数2:", Memory.readUtf8String(args[1]));
}
},
onLeave: function(retval) {
// 可在此处修改返回值
}
});
}
关键点:
Module.findExportByName()用于查找导出函数Memory.readUtf8String()读取UTF-8格式字符串Interceptor.attach()拦截函数执行- 地址一次查找,多次调用拦截
2.2 修改返回值(Frida 0x9)
场景:只需返回特定值(1337)即可通过验证
public native int check_flag();
Hook策略:直接修改函数返回值
var target = Module.findExportByName("libnative.so", "Java_com_example_check_flag");
Interceptor.attach(target, {
onLeave: function(retval) {
// 修改返回值为1337
retval.replace(1337);
}
});
2.3 主动调用Native函数(Frida 0xA)
场景:发现敏感函数get_flag()未被主动调用
两种调用方式:
方式一:导出函数的主动调用
// 查找导出函数地址
var target = Module.findExportByName("libnative.so", "get_flag");
// 创建NativeFunction对象
const get_flag = new NativeFunction(target, 'void', ['int', 'int']);
// 主动调用
get_flag(1, 2);
方式二:未导出函数的主动调用
// 获取SO基址
var base = Module.findBaseAddress("libnative.so");
// 计算函数地址(基址+偏移)
var offset = 0x1234;
var target = base.add(offset);
// 创建NativeFunction并调用
const get_flag = new NativeFunction(target, 'void', ['int', 'int']);
get_flag(1, 2);
关键区别:
- 导出函数:可直接通过名称查找地址
- 未导出函数:需要SO基址+偏移地址计算
2.4 修改汇编代码(Frida 0xB)
场景:条件跳转永远不执行核心逻辑
解决方案:使用NOP指令替换条件跳转
// 获取目标地址
var target = Module.findExportByName("libnative.so", "target_function");
// 准备NOP指令(ARM64为4字节)
var nop = [0x1F, 0x20, 0x03, 0xD5];
// 写入NOP指令替换原指令
Memory.writeByteArray(target, nop);
技术细节:
- ARM64架构中指令长度固定为4字节
- NOP指令同样为4字节,可完全替换条件跳转指令
- 修改后需重新反编译查看伪代码变化
2.5 Hook构造函数与参数修改(Frida 0x7)
场景:需要修改Checker构造函数的参数
class Checker {
public Checker(int num1, int num2) {
// 构造函数逻辑
}
}
Hook策略:拦截构造函数并修改参数
// 拦截构造函数
Java.perform(function() {
var Checker = Java.use("com.example.Checker");
Checker.$init.implementation = function(num1, num2) {
// 修改参数值
num1 = 600;
num2 = 600;
// 调用原构造函数
this.$init(num1, num2);
};
});
三、关键技术总结
3.1 地址查找方法
- 导出函数:
Module.findExportByName("lib.so", "函数名") - 未导出函数:
Module.findBaseAddress("lib.so").add(偏移)
3.2 函数拦截与修改
- 参数读取:
Memory.readUtf8String()、Memory.readInt() - 返回值修改:
retval.replace(新值) - 主动调用:
new NativeFunction()
3.3 汇编级修改
- 指令替换:
Memory.writeByteArray(地址, 新指令) - ARM64特性:固定4字节指令长度
- 常用替换:条件跳转 → NOP指令
3.4 应用场景对应方案
- 获取中间参数:Hook基础库函数(strcmp等)
- 修改验证结果:直接修改返回值
- 触发未调用函数:主动调用Native函数
- 绕过条件检查:修改汇编代码
- 修改对象属性:Hook构造函数
四、完整示例模板
function main() {
Java.perform(function() {
// 1. Hook Native函数示例
var target = Module.findExportByName("libc.so", "strcmp");
Interceptor.attach(target, {
onEnter: function(args) {
console.log("参数1:", Memory.readUtf8String(args[0]));
console.log("参数2:", Memory.readUtf8String(args[1]));
}
});
// 2. 主动调用示例
var funcAddr = Module.findExportByName("libnative.so", "target_func");
const nativeFunc = new NativeFunction(funcAddr, 'int', ['int', 'string']);
var result = nativeFunc(123, "test");
// 3. 修改汇编示例
var patchAddr = Module.findBaseAddress("libnative.so").add(0x1234);
Memory.writeByteArray(patchAddr, [0x1F, 0x20, 0x03, 0xD5]);
});
}
setImmediate(main);
通过掌握这些技术,可以有效地分析和修改Android应用的Native层逻辑,实现各种逆向工程需求。