安卓编写常见的hook技巧(2)
2023-1-30 22:20:0 Author: xz.aliyun.com(查看原文) 阅读量:15 收藏

接着上篇中的frida常见的hook java层,本次补充hook so的native层。

在so文件中,分为hook有导出函数和无导出函数。

一、hook导出函数基本框架

Java.perform(function x(){
    var str_name_so = "so文件名";    //需要hook的so名

    // 需要hook的有导出函数名,可以在Exports表中看到
    var ptr_func = Module.findExportByName(str_name_so, "有导出的函数名");


    Interceptor.attach(ptr_func,{
        //onEnter: 进入该函数前要执行的代码,其中args是传入的参数,一般so层函数第一个参数都是JniEnv,第二个参数是jclass,从第三个参数开始是我们java层传入的参数
        onEnter: function(args) {
            send("*******nativeGetPendingEntry");
            // send("args[2]=" + args[2]); //第一个传入的参数
            // send("args[3]=" + args[3]); //第二个参数
            send("=============================Stack strat=======================");
            send(Thread.backtrace(this.context, Backtracer.ACCURATE).map(DebugSymbol.fromAddress).join('\n'));
            send("=============================Stack end  =======================");

        },
        onLeave: function(retval){ //onLeave: 该函数执行结束要执行的代码,其中retval参数即是返回值
            send("return:"+retval); //返回值
            // retval.replace(100); //替换返回值为100
        }
    });
});

二、hook无导出函数基本框架

Java.perform(function x(){
    var str_name_so = "so文件名";    //需要hook的so名
    var n_addr_func_offset = so中的偏移地址;         //需要hook的函数的偏移 onReceivedError
    var n_addr_so = Module.findBaseAddress(str_name_so); //加载到内存后 函数地址 = so地址 + 函数偏移
    var n_addr_func = parseInt(n_addr_so, 16) + n_addr_func_offset;
    var ptr_func = new NativePointer(n_addr_func);

    Interceptor.attach(ptr_func,{
        //onEnter: 进入该函数前要执行的代码,其中args是传入的参数,一般so层函数第一个参数都是JniEnv,第二个参数是jclass,从第三个参数开始是我们java层传入的参数
        onEnter: function(args) {
            send("*******target func");
            // send("args[2]=" + args[2]); //第一个传入的参数
            // send("args[3]=" + args[3]); //第二个参数
            send("=============================Stack strat=======================");
            send(Thread.backtrace(this.context, Backtracer.ACCURATE).map(DebugSymbol.fromAddress).join('\n'));
            send("=============================Stack end  =======================");

        },
        onLeave: function(retval){ //onLeave: 该函数执行结束要执行的代码,其中retval参数即是返回值
            send("return:"+retval); //返回值
            // retval.replace(100); //替换返回值为100
        }
    });
});

三、Interceptor拦截器

其中Interceptor拦截器分为
● Interceptor.attach(target, callbacks[, data]):拦截位于 target 的方法的调用. target 是一个 NativePointer 类型的对象, 指明了您想要拦截的方法的地址。
● Interceptor.detachAll(): 分离所有之前附加上的回调。
● Interceptor.replace(target, replacement[, data]):使用 replacement 替换 target 处的方法. 这通常在您需要完全或部分地替换已有方法时很有用。
● Interceptor.revert(target): 将 target 处的方法还原到之前的实现。
● Interceptor.flush():确保任何待定的更改已提交到内存. 这应该仅在少有的必要的情况中被执行, 例如, 您刚刚 attach() 或 replace() 了一个您即将通过 NativeFunction 调用的方法. 待定的改动将自动地在当前线程即将离开 JavaScript 运行时或 send() 被调用时被齐平, 比如在 RPC 方法中返回, 以及调用 console 中的任意 API。

案例1

打开app

在任意输入字符串并点击CHECK的按钮,当错误是显示验证错误,利用该点进行全局搜索关键字

在com.testjava.jack.pingan2的MainActivity中发现其逻辑代码,其中当点击按钮时则触发onClick方法进行判断传入的字符串是否正确。跟踪CheckString方法

调用了cyberpeace.so文件,该方法的返回值为1时则返回验证通过

打开之后发现是可以导出的函数,利用导出函数的框架编写hook脚本

if(Java.available){
    Java.perform(function(){
        var n_addres_func = Module.findExportByName("libcyberpeace.so","Java_com_testjava_jack_pingan2_cyberpeace_CheckString");
        console.log("hooking address :" + n_addres_func);
        Interceptor.attach(n_addres_func,{
            onEnter:function (args){
                console.log("success hook so");
            },
            onLeave:function (retval){
                console.warn("the retval is : "+retval);
                var change = 1;
                retval.replace(change);
                console.error("the sec retval is "+retval);
            }
        })

    });
}

hook测试

案例二


app的版本为7.45.1,腾讯加固壳。利用frida脱壳,发现可能脱的并不完整。

在夜神模拟器中进行抓包,当打开的时候发现sn的参数

利用frida脱出来的dex文件如下

通过已脱下来的文件,在jadx中进行打开,全局搜索关键字。

在该bsb中的公共类a,其sb.append("&sn=" + a(str,z))中的参数a(str,z)。在跟踪中返回值为cob.b(str2).toLowerCase(),并其中有一个格外的参数值NativeSecureparam.readMD5Key()。

该值是通过调用ifeng_secure.so文件加载出来的,打开ida静态加载该so文件。

从exports模块中可以得知该函数是可以导出,而且该方法的内容为

if(Java.available){
    Java.perform(function (){
        var method_address = Module.findExportByName("libifeng_secure.so","Java_com_ifeng_daemon_facade_NativeSecureparam_readMD5Key");
        Interceptor.attach(method_address,{
            onEnter:function (args){
                result_pointer = args[2].toInt32();
                console.log("success hook so ");
                send("args:"+Memory.readCString(args[0])+","+args[1]+","+args[2]);
            },
            onLeave:function (retval){
                console.warn("the retval is :"+retval);
            }
        });
    });
}

案例三

该app为注册机破解,2016的腾讯ctf题
直接在夜神中打开app

通过任意的输入可得知其规则要求,查看so文件

从Java_com_tencent_tencent2016a_MainActivity_NativeCheckRegister可看到主要的运行逻辑,即

跟进sub_1634

在该函数下得到了对v8值的运行逻辑

跟进sub_1498

利用frida的无导出来进行hook

Java.perform(function () {
    send("Running Script");


    var base_addr = Module.findBaseAddress("libCheckRegister.so");//获取 so的基址

    var nativePointer = base_addr.add(0x1498+1)  //加上相对地址,得到绝对地址,因为为arm指令得加1
    var result_pointer;
    Interceptor.attach(nativePointer,{
        onEnter:
            function(args){
                result_pointer = args[0] //将数组进行保存,供下面的函数使用
                console.log(Memory.readCString(args[1]));//打印第二个参数值
            },
        onLeave:
            function(retval){
                var resultPointer = new NativePointer(result_pointer);
                var resultstr = Memory.readUtf8String(resultPointer);//读取数组里面的值
                send(retval.toInt32());  //打印结果
                send("result pointer:" + resultPointer +", result:" + resultstr);//打印解码后的字符串
            }
    });


    send("Hooks installed.");
});

构造一个长度超过20位长度的字符串,对其进行base64编码。即用户名6位,密码超过20位。


文章来源: https://xz.aliyun.com/t/12088
如有侵权请联系:admin#unsafe.sh