freeBuf
主站

分类

云安全 AI安全 开发安全 终端安全 数据安全 Web安全 基础安全 企业安全 关基安全 移动安全 系统安全 其他安全

特色

热点 工具 漏洞 人物志 活动 安全招聘 攻防演练 政策法规

点我创作

试试在FreeBuf发布您的第一篇文章 让安全圈留下您的足迹
我知道了

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

移动安全入门之apk签名校验
白Einzz 2023-02-15 14:26:53 65449
所属地 广东省

前述

通过修改smali,java层hook,so层hook等多种思路解决apk签名校验问题。下载某签名校验典型案例APP,对其重打包签名后打开应用即闪退,我们通过搜索signatures找到对应代码

1676430886_63ec4e262c100e3c53781.png!small?1676430886686

若无法直接通过关键词确定代码位置,也可以通过hook signatures堆栈的方式进行追踪

Java.perform(function(){
var signature = Java.use('android.content.pm.Signature');
signature.hashCode.implementation = function(){
console.log("find signature.hashCode");
console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Exception").$new()));
return this.hashCode();
}
signature.toByteArray.implementation = function(){
console.log("find signature.toByteArray");
console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Exception").$new()));
return this.toByteArray();
}


});

获取堆栈信息

1676430923_63ec4e4becbf02a8bc315.png!small?1676430924386

当我们通过hook修改方法qian()传入参数i的取值时,使用spawn模式hook程序直接退出,说明该方法确实为检测点

1676430947_63ec4e63d9797ae8cae21.png!small?1676430948385

查看引用qian()的代码片段可以看到qian默认传入的就是226776851

1676430957_63ec4e6d5a1606cd52401.png!small?1676430957890

在smali代码中直接注释掉qian(226776851)

1676430971_63ec4e7b1d78396d7ea8b.png!small?1676430972164

对app进行第一次重打包签名(磁盘搜索_killer.apk)并在测试机上安装发现仍然闪退无法正常运行,看代码发现另一个if语句this.eee.equals("")同样执行了Toast.makeText(this, 1, 1).show(),因此我们还需要解决bug()方法

1676430978_63ec4e823369214bd69ed.png!small?1676430978672

解决方式一:

通过代码可以知道只要eee的值不是空值就可以绕过该检测,,编写脚本时发现一旦执行bug()方法程序就会退出,该方法内部存在相关检测,因此不能让其执行同时又要返回一个非空值,hook脚本如下

Java.perform(function(){
let MainActivity = Java.use("com.bug.bt.MainActivity");
MainActivity["bug"].implementation = function () {
console.log('bug is called');
//let ret = this.bug();
var bb="1";
console.log('bug ret value is ' + bb);
return bb;
}
});

使用spawn模式启动上一步中第一次编译过的app(磁盘搜索_killer.apk)

frida -U -f com.bug.bt -l hook.js --no-pause

1676431059_63ec4ed39a23c6c0daca5.png!small?1676431060020

程序正常运行

1676431050_63ec4eca2c00ccb2ce913.png!small?1676431050807

解决方式二:

在第一次编译后的基础上再将smile代码中this.eee = bug()注释掉,同时将空字符修改为非空的内容

1676431102_63ec4efee2c6d82f2e78a.png!small?1676431103611


保存并编译,安装第二次重新编译后的app(磁盘搜索_killer2.apk)正常运行

1676431095_63ec4ef77e4ac898e4ae3.png!small?1676431096289


解决方式三:

实际上bug()方法实现在so层中

1676431209_63ec4f69c375275782ea3.png!small?1676431210226

使用IDA分析找到可疑方法Java_com_bug_bt_MainActivity_bug

1676431218_63ec4f726cafa7d50e3b9.png!small?1676431219335

查看伪c代码

1676431225_63ec4f7940993b641d392.png!small?1676431225774

发现其中调用了getSignHashCode()方法,其中if语句通过对比result与签名是否一致,当不一致时exit掉

1676431233_63ec4f815b9e0df802406.png!small?1676431234487

因此我们只需绕过if中的条件判断即可,把BEQ指令改为相反的BNE指令

1676431239_63ec4f87d93d0cd431ffe.png!small?1676431240751

将02 D0 字节修改为02 D1

1676431249_63ec4f910713f0a2aae94.png!small?1676431250180

在第一次编译后的app(磁盘搜索_killer.apk)基础上进行如上修改,替换掉原本的so文件后再次重新打包app(磁盘搜索_killer3.apk),正常运行

1676431186_63ec4f52994931917fc97.png!small?1676431187186

也可以直接将exit替换为nop指令,将前几位字节改为00 BF 00 BF或者00 00 00 00即可

1676431258_63ec4f9a24fa1b4953d66.png!small?1676431258893

解决方式四:

方式四-一:通过IDA分析并编写hook脚本将相关方法置空

使用IDA找到对应方法,获取其偏移量BFC

1676431400_63ec502835d933a5dbb36.png!small?1676431400981

查看其伪c可知传参和返回值类型

1676431418_63ec503a6035261699a71.png!small?1676431419354

静态地址 ( 基地址 ) + 偏移量 = 动态地址,同时Thumb指令需要加1

function main() {
var libnativeaddress=Module.findBaseAddress("libbug.so");
if(libnativeaddress!=null){
bypass_native(libnativeaddress);
}
}

function bypass_native(libnativeaddress){
var target_addr=libnativeaddress.add(0xBFC+1); 
console.log('target_addr===>',target_addr);
Interceptor.replace(target_addr,new NativeCallback(function(num1,num2){ 
console.log('replace success!!!!!');
return 2;
},'int',['int','int']));


}

1676431425_63ec5041c85d5a13ca262.png!small?1676431426266

也可以直接通过通过导出函数名定位 native 方法

function main() {
var getSignHashCode_addr = Module.findExportByName("libbug.so","getSignHashCode");
if(getSignHashCode_addr!=null){
console.log("getSignHashCode_addr is -> ",getSignHashCode_addr);
bypass_getSignHashCode(getSignHashCode_addr);
}
}

function bypass_getSignHashCode(getSignHashCode_addr){
//console.log('replace start');
Interceptor.replace(getSignHashCode_addr,new NativeCallback(function(num1,num2){  
console.log('replace success!!!!!!!!!!');
return 2;
},'int',['int','int']));


}

1676431437_63ec504d33cc03584d293.png!small?1676431437633

方式四-二:将系统exit()置空

通过之前的分析就能知道最终执行了exit(),直接将exit置空即可

function main7() {
Java.perform(function(){
var openPtr = Module.getExportByName(null, 'exit');
Interceptor.replace(openPtr, new NativeCallback(function (flags) {
console.log('CCCryptorCreate called from:\n' +Thread.backtrace(this.context, Backtracer.ACCURATE).map(DebugSymbol.fromAddress).join('\n') + '\n');
console.log('success ');
}, 'void', ['int']));

});
}

同时我们打印堆栈也能看到是libbug.so的getSignHashCode方法调用了exit

1676431449_63ec5059e470e01393872.png!small?1676431450416

方式四-三:输出所有的导出函数后分析

hook导出函数(填写so的名字),如下图所示。

function main6() {
var exports1 = Module.enumerateExportsSync("libbug.so");
for(var i=0; i<exports1.length; i++){
console.log("name:address",exports1[i].name,exports1[i].address);
}
}

可以看到程序在崩溃之前加载了两个比较显眼的方法

1676431458_63ec5062c77bb7ff4f518.png!small?1676431459644

编写脚本hook出该方法的偏移1676431466_63ec506a2af973be12118.png!small?1676431466684

在ida中直接jump到对应地址即可看到该方法

1676431476_63ec507499faffd789d86.png!small?1676431477502

查看伪c确认其传参和反返回值类型后编写hook脚本将其置空

function main3() {
var exports1 = Module.enumerateExportsSync("libbug.so");
for(var i=0; i<exports1.length; i++){
if(exports1[i].name="getSignHashCode"){
console.log("name:",exports1[i].name,"address:",exports1[i].address);
bypass_getSignHashCode2(exports1[i].address);
break;
}
}
}

function bypass_getSignHashCode2(getSignHashCode_addr){
console.log('replace start');
Interceptor.replace(getSignHashCode_addr,new NativeCallback(function(num1,num2){  
console.log('replace success!!!!!!!!!!');
return 2;
},'int',['int','int']));
}

1676431491_63ec5083271f44d160068.png!small?1676431491809

  • 总结

解决方式4的众多脚本中除了直接hook系统exit的方案,其他方案在执行时都可能会遇到Error: expected a pointer的错误。原因是使用spawn方式启动应用时还没有加载到该so,frida找不到libbug.so

1676431540_63ec50b40a98172ac5aaa.png!small?1676431540731

将setImmediate()替换为setTimeout()解决以上问题,但若时间过长也会导致系统已经执行exit直接退出

1676431545_63ec50b9831033a944126.png!small?1676431546384

需要不断的尝试设置不同时间才能成功一次

1676431550_63ec50bec71bdce701a9a.png!small?1676431551243

那么怎样才能更有效的解决Error: expected a pointer的错误。解决思路是先判断是否加载了libbug.so,然后在执行相关方法。在 Android 系统中,动态加载 so 库最终是通过 dlopen 和 android_dlopen_ext 完成的,而dlopen主要用于加载一些系统的库,输出dlopenext加载的so

var dlopen_ext_addr = Module.findExportByName(null,"android_dlopen_ext");
Interceptor.attach(dlopen_ext_addr,{
onEnter: function(args){
console.log("dlopen_ext ->" + args[0].readCString());
},onLeave: function(retval){
console.log(retval);
}
});

运行结果如下可以看到加载了libbug.so

1676431557_63ec50c5053bf556b64b2.png!small?1676431557549

编写脚本确定在加载了libbug.so后,在执行下一步

function main() {
var boo=false;
var dlopen_ext_addr = Module.findExportByName(null,"android_dlopen_ext");
Interceptor.attach(dlopen_ext_addr,{
onEnter: function(args){
if(args[0].readCString().indexOf("libbug.so") != -1){
console.log("find libbug.so");
boo=true;

}
},onLeave: function(retval){
//console.log(retval);
if(boo){
getSignHashCode();
}
}
});
}

function getSignHashCode(){
var getSignHashCode_addr = Module.findExportByName("libbug.so","getSignHashCode");
if(getSignHashCode_addr!=null){
console.log("getSignHashCode_addr is -> ",getSignHashCode_addr);
bypass_getSignHashCode(getSignHashCode_addr);
}

}

function bypass_getSignHashCode(getSignHashCode_addr){
//console.log('replace start');
Interceptor.replace(getSignHashCode_addr,new NativeCallback(function(num1,num2){  
console.log('replace success!!!!!!!!!!');
return 2;
},'int',['int','int']));


}

成功

1676431565_63ec50cd340b7d92fa2cf.png!small?1676431565648

# 网络安全技术
本文为 白Einzz 独立观点,未经授权禁止转载。
如需授权、对文章有疑问或需删除稿件,请联系 FreeBuf 客服小蜜蜂(微信:freebee1024)
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
白Einzz LV.3
这家伙太懒了,还未填写个人描述!
  • 10 文章数
  • 19 关注者
记一次安卓测试多限制绕过(二)
2023-06-14
移动安全入门之frida主动调用
2023-05-11
记一次安卓测试多限制绕过
2023-05-09