教你使用frida来快速解出安卓CTF题目
本帖最后由 顶风能尿三丈远 于 2020-12-14 17:42 编辑在现在的CTF比赛赛制中,前三名解出题目的队伍都会有额外加分,如何快速的解出问题,对于参赛选手来说至关重要。因为往往就是一分之差,淘汰多支队伍。公司第五届安全技术大赛半决赛出了一道名为hw02的apk题目,我们拿到了二血,这里我来分享一下,如何快速的capture apk’s flag
首先jeb来加载hw02.apk,工具jeb的下载链接可以参考吾爱爱盘下载链接 这里可以根据Manifest来分析出apk的主入口activity,这里是MainActivity
Input 变量为用户的输入,从上图代码所示,判断input输入的字符串格式 “flag{”开头 , “}” 结尾。字符串长度不能低于10。否则会报错 tryagain~ 或者 flag format error ~重点逻辑在this.checkFlag 这个native函数,它的处理逻辑在so层,输入的参数就是 flag{xxx}中间的那坨xxx。将apk解压缩,发现有两个架构的so文件,我们的测试机是64位的,那就从arm64-v8a里获取so吧使用ida工具来分析libcheckflag.so文件看到init_array 有初始化的过程
这里会修改.bss段变量的值,这个变量后面也没有用到,我们暂时不管。跟进JNI_OnLoad函数
JNI_OnLoad 函数对 checkFlag函数进行动态注册了
sub_DD78才是真正的checkFlag函数,我们进入sub_DD78
看第一个箭头,v13代表字符串的长度,这里v13 长度需要超过15,低于15长度就直接退出了。第二个箭头处Sub_DFC8是干嘛的?跟进去
GetStringUTFChars是将java层传入的jstring转化为char*, 这里我们简单的理解就是获取用户输入的字符串的。我们知道GetStringUTFChars有三个参数,这里ida对函数优化之后,没有显示任何参数。右键选择”Force call type”,就可以看到三个参数了。
接下来,我们只需要关注下面的三个重要的函数,经过这三个函数的运算之后,会产生一个新的值,固定值"bb4ME6An/z82AwX5r0FXgwJwzp3JaFgW7JtmKc4T9Q=="进行对比。相同就可以返回true了sub_E018 我们来看一下函数内部逻辑
sub_E1D8
sub_E56C
这三个函数内部逻辑都很复杂,如果直接去逆推,需要花很长时间,这里我们采用frida工具来帮助我们快速的解题。Hook函数实现代码
var libhm= Process.findModuleByName("libcheckflag.so");
if(libhm != undefined)
{
var modulebase = libhm.base;
console.log("base:"+modulebase);
var sub_E018 = modulebase.add(0xE018);
var sub_E56C = modulebase.add(0xE56C);
var sub_E7F8 = modulebase.add(0xE7F8);
Interceptor.attach(sub_E018,{
onEnter: function(args){
//console.log(Memory.readCString(args));
},
onLeave: function(retval){
//console.log("retval:"+retval);
//Memory.readUtf8String(retval);
//retval.replace(1)
}
});
Interceptor.attach(sub_E56C,{
onEnter: function(args){
//console.log(Memory.readCString(args));
},
onLeave: function(retval){
//console.log("retval:"+retval);
//Memory.readUtf8String(retval);
//retval.replace(1)
}
});
Interceptor.attach(sub_E7F8,{
onEnter: function(args){
//console.log(Memory.readCString(args));
},
onLeave: function(retval){
console.log("retval:"+retval);
var data = Memory.readUtf8String(retval);
var result = “bb4ME6An/z82AwX5r0FXgwJwzp3JaFgW7JtmKc4T9Q==”;
var index= (round-1) *4;
var result1 = result.substring(index,index+4);
if(data.substring(index,index+4) == result1)
{
console.log("[+]Found:"+Memory.readUtf8String(retval));
}
//retval.replace(1)
}
});
}
Frida hook了这些关键的函数之后,我们就可以看到函数的输入参数,和输出结果。我们尝试输入不同的字符来看输出情况最终得出结论:Input输入的字符长度,除去flag{} 之外有31位时,计算出的结果才和bb4M E6An /z82 AwX5 r0FX gwJw zp3J aFgW7Jtm Kc4 T9Q== 一样长,这个长度是线性增加的,可以采用爆破的方法。而且每输入3个字符,对应输出结果的4个字符。 所以我们先找第一个三字符 xyz对应bb4M第二组 三字符 xyz 对应E6An ,那这里我们以第二组为例,第一组我们爆破出来为qeo注意上面hook函数中的 var index = (round-1)*4; 这里的round对应的是第几组。
爆破函数实现代码var MainActivity$1 = Java.use("com.ssj.hw02.MainActivity$1");
var MainActivity = Java.use("com.ssj.hw02.MainActivity");
var ma = MainActivity.$new()
MainActivity$1.onClick.implementation =function(){
for(var j =0; j <pool.length; j++)
{
for(var i =0; i <pool.length; i++)
{
for(var k =0; k <pool.length; k++)
{
var data = "qeo"+pool+pool+pool+"1111111111111111111111111";
console.log(data);
ma.checkFlag(data);
}
}
}
}
这样就可以知道第二轮的3字符为irk我们再重新设置爆破函数中的变量var data = "qeoirk"+pool+pool+pool+"1111111111111111111111";将hook函数中的round=3以此类推,将所有的字符全部猜解出。 qeoirklnxcvxfgiefhdweruoulksdnm最终提交的flag为 flag{ qeoirklnxcvxfgiefhdweruoulksdnm} 这样就可以快速的爆破出所有的字符了,省去了大量的中间逆向和逻辑细节了,能够在ctf比赛中脱颖而出
奇奇小霸王龙 发表于 2020-12-24 15:56
V9不等于0XB9B1CB10就结束第一层循环,即v13小于15的情况;没事 我看错了!!
这里稍微有点绕,不过静下心是可以理顺的,这里应该是题目开发者有意设置的一个坑。 在另一个题目使用此方法,报错了。请问有 大神遇到过吗?
var MainActivity = Java.use("com.ssj.hw02.MainActivity");
var ma = MainActivity.$new(); //跑到这里报错
{:1_921:}分析的很详细、透彻! 谢谢分享 感谢分享,希望可以继续发一些安卓CTF的东西 大佬,来个样本学习一波? 感谢楼主分享 可以破解钉钉考试吗 YenKoc 发表于 2020-12-15 09:20
大佬,来个样本学习一波?
我努力了一把,但是最终还是不让我公开题目样本,对不住兄弟了 吾爱蛋蛋 发表于 2020-12-15 10:41
可以破解钉钉考试吗
钉钉考试是什么?单独的一款APP? 顶风能尿三丈远 发表于 2020-12-15 10:55
钉钉考试是什么?单独的一款APP?
钉钉里面的考试,可以用FD吗