frida-native
frida hook native 模板
感谢正己大佬开源:安卓逆向这档事
常用api(获取地址)
Process
Process 对象代表当前被Hook的进程,能获取进程的信息,枚举模块,枚举范围等
| API | 含义 |
|---|---|
Process.id |
返回附加目标进程的 PID |
Process.isDebuggerAttached() |
检测当前是否对目标程序已经附加 |
Process.enumerateModules() |
枚举当前加载的模块,返回模块对象的数组 |
Process.enumerateThreads() |
枚举当前所有的线程,返回包含 id, state, context 等属性的对象数组 |
Module
Module 对象代表一个加载到进程的模块(例如,在 Windows 上的 DLL,或在 Linux/Android 上的 .so 文件),能查询模块的信息,如模块的基址、名称、导入/导出的函数等
| API | 含义 |
|---|---|
Module.load() |
加载指定so文件,返回一个Module对象 |
enumerateImports() |
枚举所有Import库函数,返回Module数组对象 |
enumerateExports() |
枚举所有Export库函数,返回Module数组对象 |
enumerateSymbols() |
枚举所有Symbol库函数,返回Module数组对象 |
Module.findExportByName(soName,exportName)、Module.getExportByName(soName,exportName) |
寻找指定so中export库中的函数地址,没有会抛出null,异常. |
Module.findBaseAddress(name)、Module.getBaseAddress(name) |
返回so的基地址 |
Memory
Memory是一个工具对象,提供直接读取和修改进程内存的功能,能够读取特定地址的值、写入数据、分配内存等
| 方法 | 功能 |
|---|---|
Memory.copy() |
复制内存 |
Memory.scan() |
搜索内存中特定模式的数据 |
Memory.scanSync() |
同上,但返回多个匹配的数据 |
Memory.alloc() |
在目标进程的堆上申请指定大小的内存,返回一个NativePointer |
Memory.writeByteArray() |
将字节数组写入一个指定内存 |
Memory.readByteArray |
读取内存 |
hook打印基本类型
基本方式
//在Frida脚本中实现native层Hook的API函数是Interceptor.attach(addr, callbacks)
Interceptor.attach(addr, {
onEnter(args) {
//在函数调用前产生的回调,在这个函数中可以处理函数参数的相关内容.
//被Hook的函数参数内容是以数组的方式存储在args中.
},
onLeave(retval) {
//在被Hook的目标函数执行完成后执行的函数.
//被Hook的函数返回值用retval变量来表示.
}
});
整数型、布尔值类型、char类型、寄存器内容、hexdump
function hookTest2(){
Java.perform(function(){
//根据导出函数名打印地址
var helloAddr = Module.findExportByName("lib52pojie.so","Java_com_zj_wuaipojie_util_SecurityUtil_checkVip");
console.log(helloAddr);
if(helloAddr != null){
//Interceptor.attach是Frida里的一个拦截器
Interceptor.attach(helloAddr,{
//onEnter里可以打印和修改参数
onEnter: function(args){ //args传入参数
console.log(args[0]); //打印第一个参数的值,指ida的第一个参数,我们要用[2]获得参数.
console.log(this.context.x1); // 打印寄存器内容
console.log(args[1].toInt32()); //toInt32()转十进制
console.log(args[2].readCString()); //读取字符串 char类型
console.log(hexdump(args[2])); //内存dump
},
//onLeave里可以打印和修改返回值
onLeave: function(retval){ //retval返回值
console.log(retval);
console.log("retval",retval.toInt32());
}
})
}
})
}
字符串类型
因为这个比较特殊,没法直接打印(不知道为什么).有两种方式,通过类型转化获得,通过获取JNIenv环境获得
function hookTest2(){
Java.perform(function(){
//根据导出函数名打印地址
var helloAddr = Module.findExportByName("lib52pojie.so","Java_com_zj_wuaipojie_util_SecurityUtil_vipLevel");
if(helloAddr != null){
Interceptor.attach(helloAddr,{
//onEnter里可以打印和修改参数
onEnter: function(args){ //args传入参数
// 方法一
var jString = Java.cast(args[2], Java.use('java.lang.String'));
console.log("参数:", jString.toString());
// 方法二
var JNIEnv = Java.vm.getEnv();
var originalStrPtr = JNIEnv.getStringUtfChars(args[2], null).readCString();
console.log("参数:", originalStrPtr);
},
//onLeave里可以打印和修改返回值
onLeave: function(retval){ //retval返回值
var returnedJString = Java.cast(retval, Java.use('java.lang.String'));
console.log("返回值:", returnedJString.toString());
}
})
}
})
}
hook修改参数
整数型
function hookTest3(){
Java.perform(function(){
//根据导出函数名打印地址
var helloAddr = Module.findExportByName("lib52pojie.so","Java_com_zj_wuaipojie_util_SecurityUtil_checkVip");
console.log(helloAddr);
if(helloAddr != null){
Interceptor.attach(helloAddr,{
onEnter: function(args){ //args参数
args[2] = ptr(1000); //第一个参数修改为整数 1000,先转为指针再赋值.
console.log(args[0]);
},
onLeave: function(retval){ //retval返回值
retval.replace(20000); //返回值修改
console.log("retval",retval.toInt32());
}
})
}
})
}
字符串型
通过获得JNIenv环境获得字符串,变为cstring读取修改并返回.
function hookTest2(){
Java.perform(function(){
//根据导出函数名打印地址
var helloAddr = Module.findExportByName("lib52pojie.so","Java_com_zj_wuaipojie_util_SecurityUtil_vipLevel");
if(helloAddr != null){
Interceptor.attach(helloAddr,{
//onEnter里可以打印和修改参数
onEnter: function(args){ //args传入参数
var JNIEnv = Java.vm.getEnv();
var originalStrPtr = JNIEnv.getStringUtfChars(args[2], null).readCString();
console.log("参数:", originalStrPtr);
var modifiedContent = "至尊";
var newJString = JNIEnv.newStringUtf(modifiedContent);
args[2] = newJString;
},
//onLeave里可以打印和修改返回值
onLeave: function(retval){ //retval返回值
var returnedJString = Java.cast(retval, Java.use('java.lang.String'));
console.log("返回值:", returnedJString.toString());
var JNIEnv = Java.vm.getEnv();
var modifiedContent = "无敌";
var newJString = JNIEnv.newStringUtf(modifiedContent);
retval.replace(newJString);
}
})
}
})
}
获取so库内存
var moduleAddr1 = Process.findModuleByName("lib52pojie.so").base;
var moduleAddr2 = Process.getModuleByName("lib52pojie.so").base;
var moduleAddr3 = Module.findBaseAddress("lib52pojie.so");
获取函数地址(适用于内联hook,动态加载函数和未导出函数)
function hookTest6(){
Java.perform(function(){
//根据导出函数名打印基址
var soAddr = Module.findBaseAddress("lib52pojie.so");
console.log(soAddr);
var funcaddr = soAddr.add(0x1071C);
console.log(funcaddr);
if(funcaddr != null){
Interceptor.attach(funcaddr,{
onEnter: function(args){ //args参数
},
onLeave: function(retval){ //retval返回值
console.log(retval.toInt32());
}
})
}
})
}
函数地址计算
- 安卓里一般32 位的 so 中都是
thumb指令,64 位的 so 中都是arm指令 - 通过IDA里的opcode bytes来判断,arm 指令为 4 个字节(options -> general -> Number of opcode bytes (non-graph) 输入4)
- thumb 指令,函数地址计算方式: so 基址 + 函数在 so 中的偏移 + 1
arm 指令,函数地址计算方式: so 基址 + 函数在 so 中的偏移
inlinehook和读写asm
常见inlinehook框架:
Android-Inline-Hook
whale
Dobby
substrate
打印和修改寄存器
function inline_hook() {
var soAddr = Module.findBaseAddress("lib52pojie.so");
if (soAddr) {
var func_addr = soAddr.add(0x10428);//要修改的相应位置
Java.perform(function () {
Interceptor.attach(func_addr, {
onEnter: function (args) {
console.log(this.context.x22); //注意此时就没有args概念了
this.context.x22 = ptr(1); //赋值方法参考上一节课
},
onLeave: function (retval) {
}
}
)
})
}
}
解析地址指令为汇编
var soAddr = Module.findBaseAddress("lib52pojie.so");
var codeAddr = Instruction.parse(soAddr.add(0x10428));
console.log(codeAddr.toString());
patch
var soAddr = Module.findBaseAddress("lib52pojie.so");
var codeAddr = soAddr.add(0x10428);
Memory.patchCode(codeAddr, 4, function(code) {
const writer = new Arm64Writer(code, { pc: codeAddr });
writer.putBytes(hexToBytes("20008052"));
writer.flush();
});
function hexToBytes(str) {
var pos = 0;
var len = str.length;
if (len % 2 != 0) {
return null;
}
len /= 2;
var hexA = new Array();
for (var i = 0; i < len; i++) {
var s = str.substr(pos, 2);
var v = parseInt(s, 16);
hexA.push(v);
pos += 2;
}
return hexA;
}
arm_asm转hex:https://armconverter.com/
主动调用
喜欢我反复注入调用本地函数吗(bushi)
数据类型
| 数据类型 | 描述 |
|---|---|
| void | 无返回值 |
| pointer | 指针 |
| int | 整数 |
| long | 长整数 |
| char | 字符 |
| float | 浮点数 |
| double | 双精度浮点数 |
| bool | 布尔值 |
普通调用
var funcAddr = Module.findBaseAddress("lib52pojie.so").add(0x1054C);
//声明函数指针
//NativeFunction的第一个参数是地址,第二个参数是返回值类型,第三个[]里的是传入的参数类型(有几个就填几个)
var aesAddr = new NativeFunction(funcAddr , 'pointer', ['pointer', 'pointer']);
var encry_text = Memory.allocUtf8String("OOmGYpk6s0qPSXEPp4X31g=="); //开辟一个指针存放字符串
var key = Memory.allocUtf8String('wuaipojie0123456');
console.log(aesAddr(encry_text ,key).readCString());
jni的主动调用
ai给的:
Java.perform(() => {
const module = Process.getModuleByName("libtarget.so");
const funcAddress = module.base.add(0x1234);
const nativeFunc = new NativeFunction(
funcAddress,
'void',
['pointer', 'pointer']
);
const env = Java.vm.getEnv();
if (!env) {
Java.vm.attachCurrentThread().then(attachedEnv => {
callFunction(attachedEnv);
});
} else {
callFunction(env);
}
function callFunction(env) {
const context = Java.android.app.ActivityThread.currentApplication();
nativeFunc(env, context.$handle);
}
});
动态注册
放到本地文件夹fr中去了,可以直接用.

浙公网安备 33010602011771号