接着上篇中的frida常见的hook java层,本次补充hook so的native层。
在so文件中,分为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
}
});
});
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.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。
打开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位。