Frida基础hook汇总
本章主要总结对Java层代码以及native代码的hook
- Java以及Native函数的hook以及主动调用
- Native静态注册函数参数、返回值打印和替换
这里对自己写的demo为例进行hook(代码中包含了Java、JNI、C各种实现),源码链接放文末附件。
安装Frida的源码包,可以方便写Frida的时候随时查看函数实现
npm install --save @types/frida-gum
基础hook
包括Java层和native层的基础hook
Java层的静态函数
hook和主动调用的代码
Java.use("com.xiaoeryu.demoso1.MainActivity").stringFromJNI.implementation = function(){
var result = this.stringFromJNI();
console.log("stringFromJNI result is => ", result);
return result;
}
console.log("invoke stringFromJNI: " + Java.use('com.xiaoeryu.demoso1.MainActivity').stringFromJNI);
Java层非静态函数
hook和主动调用代码:这个函数没写返回值,我们就只打印一个log
Java.use('com.xiaoeryu.demoso1.MainActivity').init.implementation = function(){
console.log("hook init successfully!");
return this.init;
}
Java.choose('com.xiaoeryu.demoso1.MainActivity',{
onMatch:function(instance){
console.log('Found instance');
instance.init();
},onComplete:function(){console.log('Search complete!')}
})
静态注册函数的native层hook
对于第三方App通过查找它的导出函数可以获取获取完整函数名(如果不是调用RegisterNatives进行的动态注册的话)。用ida或者objection中的
memory list exports 库名.so
都可以
native层的hook不需要Java.perform因为它已经不在虚拟机中了。
找到native的so名称和函数的完整名
通过so库的名称和函数名找到函数地址
找到函数地址之后就可以通过
Interceptor.attach()
去hook这个函数获取它的参数或者返回值了需要注意的一点是,我们使用frida脚本去打印native层的参数的时候,直接打印的话只能打印它的地址,需要根据参数类型使用Frida提供的方法去解析(下面的代码以jstring为例)
function hook_nativelib(){
var nativelib_addr = Module.findBaseAddress('libdemoso1.so');
console.log("nativelib_addr is => ", nativelib_addr);
var myfirstjniJNI_addr = Module.findExportByName('libdemoso1.so','Java_com_xiaoeryu_demoso1_MainActivity_myfirstjniJNI');
console.log("myfirstjniJNI_addr is => ", myfirstjniJNI_addr);
Interceptor.attach(myfirstjniJNI_addr, {
onEnter: function(args){
console.log("Interceptor.attach myfristjniJNI args: ", args[0], args[1], args[2]);
console.log("args[2] jstring is ", Java.vm.getEnv().getStringUtfChars(args[2],null).readCString());
},onLeave:function(retval){
console.log("Interceptor.attach myfirstjniJNI retval: ", retval);
console.log("retval jstring is ", Java.vm.getEnv().getStringUtfChars(retval, null).readCString());
}
})
}
一些hook技巧
静态注册函数参数、返回值打印和替换
调用栈
主动调用、replace
符号hook == 偏移hook
枚举并保存结果
替换native层的参数和返回值
这里还是以前面hook的目标app中的目标函数为例
- 这个native函数的类型是jstring,它的返回值类型是使用Java提供的方法创建一个字符串来返回,所以我们要改变返回值同样也需要使用相同的类型来替换
这里查看Frida的源码 提供的方法
测试代码
- 这里替换掉了它的参数和返回值,只需要调用Frida提供的方法创建一个新的字符串,然后直接替换就可以了
C函数主动调用
先拿一个简单的C函数来进行测试,它没有复杂的参数
- 还是跟之前一样,不论是想对native层的函数进行hook还是主动调用都需要先找到它的地址,然后才能进行hook或者主动调用
- 通过函数名来找到它的地址,函数名要使用它在内存中的名字,这里的名字跟原本函数名字不同是因为它做了demangler处理,可以在解码网站 对它进行解码处理
function hookAndInvoke_add(){
// 获取函数地址
var r0add_addr = Module.findExportByName('libdemoso1.so', '_Z5r0addii');
console.log("r0add_addr is => ", r0add_addr);
// hook函数
Interceptor.attach(r0add_addr, {
onEnter: function(args){
console.log("x => ", args[0], "y => ", args[1]);
},onLeave:function(retval){
console.log("retval => ", retval);
}
})
// 进行主动调用
var r0add = new NativeFunction(r0add_addr, 'int', ['int', 'int']);
var r0add_result = r0add(1, 2);
// 打印返回值
console.log("r0add_result => ", r0add_result);
}
执行结果:
JNI函数主动调用
还以我们之前hook的myfirstjniJNI的native层实现为例,对它的调用需要我们先构造它的参数第一个参数env第二个参数jclass接下来才是传入的参数
如上图按这个函数的参数和返回值,先定义一个NativeFunction,然后进行主动调用。其实和刚才调用C函数差不多
但是如果我们在app源码中没有执行过这个函数的话,就需要再使用*Java.use()*在java层调用一下这个函数
调用栈
获取调用栈需要用到Frida提供的*backtrace()*方法
使用提供的方法可以在onEnter的时候打印出来
打印调用栈其实提供了两种方法
- ACCURATE这种方法,如果在有调试信息的情况下可以获取非常精准的结果
- 反之或许FUZZY这个猜测的方式效果更好
- 实际使用可以两者结合起来使用
replace
流程跟之前也是一样,找到函数地址后使用*Interceptor.replace()*直接替换就好了
function hook_replace(){
var myfirstjniJNI_addr = Module.findExportByName('libdemoso1.so', 'Java_com_xiaoeryu_demoso1_MainActivity_myfirstjniJNI');
console.log("myfirstjniJNI_addr is => ", myfirstjniJNI_addr);
// var myfirstjniJNI_invoke = new NativeFunction(myfirstjniJNI_addr, 'pointer', ['pointer', 'pointer', 'pointer']);
Interceptor.replace(myfirstjniJNI_addr, new NativeCallback(function(args0, args1, args2){
console.log("Interceptor.replace myfirstjniJNI args: ", args0, args1, args2);
return Java.vm.getEnv().newStringUtf("hookedReplaceXiaoyu");
}, 'pointer', ['pointer', 'pointer', 'pointer']
))
}
替换成功
枚举所有函数
使用Frida提供的API,枚举所有非动态注册的函数,然后再进行过滤找到想要寻找的就ok了
function EnumerateAllExports(){
var modules = Process.enumerateModules();
// console.log("modules is => ", JSON.stringify(modules));
for(var i = 0; i < modules.length; i++){
var module = modules[i];
var name = module.name;
var exports = module.enumerateExports();
console.log("name is => ", JSON.stringify(name), "export is => ", JSON.stringify(exports));
}
}
hook内容的补充
前面进行的所有hook或者主动调用、替换,都是找到目标函数的地址然后对这个函数进行操作。所以本质上都是对这个函数的地址进行hook(虽然使用ida也能找到目标函数的地址和基址,不过如果是动态加载的话它的地址是会变的。所以还是使用调用Frida提供的API获取地址比较方便)。
Frida提供的其它API
详细信息可以看Frida提供的文档 ,提供了很多的方法来获取更多的信息
Process
- 例如这里面的一些API,我们使用Frida附加到app之后就可以直接使用
Module
枚举模块的信息,具体都能枚举什么东 西可以在Frida的文档 中查看
// 第一种方式 function module(){ var nativelib_addr = Process.findModuleByAddress(Module.findBaseAddress("libutils.so")); // var nativelib_addr = Process.findModuleByAddress(Module.findBaseAddress("libdemoso1.so")); // console.log("nativelib_addr => ", JSON.stringify(nativelib_addr)); for(var i = 0; i < nativelib_addr.enumerateImports().length; i++){ console.log("enumerateSymbols => ", JSON.stringify(nativelib_addr.enumerateImports()[i])); } } // 第二种方式 function test(){ Java.perform(function(){ var imports = Module.enumerateImports("libutils.so"); for(var i = 0; i < imports.length; i++){ console.log("imports => ", JSON.stringify(imports[i])); } }) }
- 第一种获取到目标so的地址,通过枚举找出模块的信息
- 第二种更直接一点,直接用模块名枚举就行了
等等更多的不再列举,都可以通过Frida的文档 查看使用方法
附件:
- 标题: Frida基础hook汇总
- 作者: xiaoeryu
- 创建于 : 2024-03-24 10:52:52
- 更新于 : 2024-08-13 22:38:48
- 链接: https://github.com/xiaoeryu/2024/03/24/Frida基础hook汇总/
- 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。