Frida基础hook汇总

xiaoeryu Lv5

本章主要总结对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层的静态函数

image-20240319205221270

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层非静态函数

image-20240319213153309

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

image-20240319221014380
  • 对于第三方App通过查找它的导出函数可以获取获取完整函数名(如果不是调用RegisterNatives进行的动态注册的话)。用ida或者objection中的memory list exports 库名.so都可以

native层的hook不需要Java.perform因为它已经不在虚拟机中了。

  1. 找到native的so名称和函数的完整名

    通过so库的名称和函数名找到函数地址

  2. 找到函数地址之后就可以通过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中的目标函数为例

image-20240320180558522
  • 这个native函数的类型是jstring,它的返回值类型是使用Java提供的方法创建一个字符串来返回,所以我们要改变返回值同样也需要使用相同的类型来替换

这里查看Frida的源码 提供的方法

image-20240320181128555

测试代码

  • 这里替换掉了它的参数和返回值,只需要调用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()*方法

image-20240321113245401

使用提供的方法可以在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的文档 查看使用方法

附件:

demo源码

Frida Hook脚本

  • 标题: Frida基础hook汇总
  • 作者: xiaoeryu
  • 创建于 : 2024-03-24 10:52:52
  • 更新于 : 2024-03-24 10:55:27
  • 链接: https://github.com/xiaoeryu/2024/03/24/Frida基础hook汇总/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论