查看原文
其他

Frida 入门小练习

ychangeol 看雪学院 2021-03-06

本文为看雪论坛优秀文章

看雪论坛作者ID:ychangeol



本文为看雪安卓高研2w班(6月班)优秀学员作品。


下面先让我们来看看讲师对学员学习成果的点评,以及学员的学习心得吧!



讲师点评 


此题学员在hook和主动调用阶段处理的很好,我们都知道Frida逆向分三个阶段:

1. 先hook、看参数和返回值 

2. 再构造参数、主动调用  

3. 最后配rpc导出结果,每一步搞完了没有任何疑问了,再进入下一步。


尤其是第三题,采用了反编译重打包修改Smali、反汇编so修改arm、以及frida hook native修改libc中的recvfrom函数的返回值这样三种方法,思路很好。



学员感想 

本文的3道Crackme来自Android高研2W班6月测试题。 这三题本身难度不大,flag的求解都是通过爆破长度为5位的字符串(只包含数字),求其hash1+salt的摘要与程序中硬编码的密文进行比较,非常适合Frida入门。 

这三道题分别考察了:

1. Frida静态函数的主动调用。

2. Frida hook动态加载dex。

3. native层去反调试。


ps. 题目附件请点击“阅读原文”下载。


现在,看雪《安卓高级研修班(网课)》9月班正在火热招生!名额有限,点击查看详情报名吧~





第一题


静态分析


静态分析可知,输入的用户名和密码的字符串拼接作为参数,然后传入vvvv方法。


 

进入vvvv方法后,input参数限制长度为5,经过eeeee方法得到的结果和p一致,就可以获得flag。

 

 

简单逆向分析可以看出,主要算法在eeeee方法中。

 

sssss方法在获取字符串的Sha-1值。

 

ccccc方法将sha-1的摘要转化成了16进制字符串的形式。

 

因为input的内容都是数字,并且只有5位,所以爆破空间只有10的5次方,直接爆破就可以了。


复制代码重建工程


重新创建工程,可以看出eeeee的逻辑:将输入的字符串 逐字符插入到SALT的byte数组当中。然后再进行sha-1运算,获得其16进制的摘要字符串。


 

直接进行暴力破解,脚本以及结果如下:


public static void bruteForce(){ for (int i1 ='0'; i1<= '9'; i1++) for (int i2 = '0';i2<='9'; i2++) for (int i3 = '0';i3<='9'; i3++) for (int i4 = '0';i4<='9'; i4++) for (int i5 = '0';i5<='9'; i5++){ String flag = num2str(i1)+num2str(i2)+num2str(i3)+num2str(i4)+num2str(i5); Log.d("test_flag",flag); if (vvvv(flag)){ System.out.println(flag); Log.d("test_flag is success",flag); return ; } }}private static String num2str(int i){ return String.valueOf(i-48);}


得到结果:



flag:66888。


Frida主动调用


对com.kanxue.pediy1.VVVV类中的方法eeeee进行主动调用。


function main(){ Java.perform(function(){ var flag = null; var enc1 = Java.use("java.lang.String").$new("6f452303f18605510aac694b0f5736beebf110bf"); console.log(enc1); // BruteForce var res = null; for (var i1 = 48;i1 < 58; i1++) for (var i2 = 48;i2 < 58; i2++) for (var i3 = 48;i3 < 58; i3++) for (var i4 = 48;i4 < 58; i4++) for (var i5 = 48;i5 < 58; i5++){ var flag = String.fromCharCode(i1,i2,i3,i4,i5); console.log("flag",flag); res = Java.use("java.lang.String").$new(Java.use("com.kanxue.pediy1.VVVVV").eeeee(flag)); if (enc1.equals(res)){ console.log("flag is success",flag); return; }
}
})
}setImmediate(main)


最终得到flag也是66888。




输入检查一下:





第二题


本题在第一题的基础上加入了dex的动态加载以及Native函数的引入。


利用frida解决


因为loadDexClass在OnclickListener中调用,所以当点击事件发生之后,要加载的DexClassLoader才会被创建。


可以首先Java.choose主动调用loadDexClass方法;然后Java.enumerateClassLoaders枚举所有的类加载器,找到存在“com.kanxue.pediy1.VVVVV”的类加载器,通过Java.classFactory.loader=loader,对类加载器进行替换。


function main(){ Java.perform(function(){ var flag = null; Java.choose("com.kanxue.pediy1.MainActivity",{ onMatch:function(instance){ console.log("hook instance method",instance.loadDexClass()); // console.log("hook instance stringFromJni",instance.stringFromJNI('66999').overload('java.lang.String')); },onComplete:function(){console.log("search complete")} }) Java.enumerateClassLoaders({ onMatch:function(loader){ console.log("Find ClassLoader",loader); try{ if (loader.findClass("com.kanxue.pediy1.VVVVV")){ console.log("Successfully Find ClassLoader",loader); Java.classFactory.loader = loader; } }catch(error){ console.log("found error",error);
}
},onComplete:function(){console.log("find complete")} }) var res = null; for (var i1 = 54;i1 < 55; i1++) for (var i2 = 54;i2 < 55; i2++) for (var i3 = 54;i3 < 58; i3++) for (var i4 = 54;i4 < 58; i4++) for (var i5 = 54;i5 < 58; i5++){ var flag = String.fromCharCode(i1,i2,i3,i4,i5); console.log("flag",flag); res = Java.use("java.lang.String").$new(Java.use("com.kanxue.pediy1.VVVVV").eeeee(flag)); console.log("res", res); if (enc1.equals(res)){ console.log("flag is success",flag); return; }
} })}setTimeout(main,2000)


替换掉loader之后,和题目1一样进行暴力破解,得到下面的结果。

 

但是输入结果并不对。所以,还需要去看一看so文件,分析一下stringFromJNI函数。




静态分析,猜测可能是将字符串转成数字,然后对数字加一。于是我把so文件拿出来,重新创建工程进行加载,如下图所示,验证结果与静态分析一致。




因此,输入结果应该是66999 -1 = 66998。

 

输入结果,获得flag,66998就是最终结果。check结果没有问题~





第三题


本题在第二题的基础上加入了native层对Frida的反调试。


Frida 反调试


v9 = a1;v14 = &v6;v13 = 16LL;v12 = 0;v11 = 16LL;v10 = 16LL;v15 = __memset_chk(&v6, 0LL, 16LL, 16LL);v6 = 2;inet_aton("0.0.0.0", &v8);while ( 1 ){ for ( i = 1; i <= 65533; ++i ) { v5 = socket(2LL, 1LL, 0LL); v7 = bswap32((unsigned __int16)i) >> 16; if ( (unsigned int)connect(v5, &v6, 16LL) != -1 ) { v20 = &v2; v19 = 7LL; v18 = 0; v17 = 7LL; v16 = 7LL; v21 = __memset_chk(&v2, 0LL, 7LL, 7LL); v26 = v5; v25 = &unk_14A2; v24 = -1LL; v23 = 1LL; v22 = 0; v33 = v5; v32 = &unk_14A2; v31 = -1LL; v30 = 1LL; v29 = 0; v28 = 0LL; v27 = 0; sendto(v5, &unk_14A2, 1LL, 0LL, 0LL, 0LL); v38 = v5; v37 = "AUTH\r\n"; v36 = -1LL; v35 = 6LL; v34 = 0; v45 = v5; v44 = "AUTH\r\n"; v43 = -1LL; v42 = 6LL; v41 = 0; v40 = 0LL; v39 = 0; sendto(v5, "AUTH\r\n", 6LL, 0LL, 0LL, 0LL); usleep(500LL); v50 = v5; v49 = &v2; v48 = 7LL; v47 = 6LL; v46 = 64; v57 = v5; v56 = &v2; v55 = 7LL; v54 = 6LL; v53 = 64; v52 = 0LL; v51 = 0LL; v3 = recvfrom(v5, &v2, 6LL, 64LL, 0LL, 0LL); if ( v3 != -1 ) { if ( (unsigned int)strcmp(&v2, "REJECT") ) { __android_log_print(4LL, "pediy", "not FOUND FRIDA SERVER"); } else { v1 = getpid(); kill(v1, 9LL); } } } close(v5); }}


反调试逻辑:

通过一直循环创建Socket连接,遍历端口,检查端口是否被占用,收到“REJECT”时,说明frida-server正在运行,直接杀掉进程。

 

根据r0ysue老师的要求,下面从三种方法过掉反调试:


1. 反编译重打包


onCreate方法中的init方法就是具有反调试功能的native层函数。


思路:查看对应的smali代码,然后将对调用init方法的smali语句进行删除,就可以了。


使用apktool解包,删除对应的smali代码,然后重打包,最后再重签名一下。


2. so硬编码


.text:00000000000011F8.text:00000000000011F8 loc_11F8 ; CODE XREF: detect_frida_loop(void *)+358j.text:00000000000011F8 ADRP X1, #aReject@PAGE ; "REJECT".text:00000000000011FC ADD X1, X1, #aReject@PAGEOFF ; "REJECT".text:0000000000001200 ADD X0, SP, #0x1F0+var_19C.text:0000000000001204 BL .strcmp.text:0000000000001208 CBNZ W0, loc_1220.text:000000000000120C B loc_1210.text:0000000000001210 ; ---------------------------------------------------------------------------.text:0000000000001210.text:0000000000001210 loc_1210 ; CODE XREF: detect_frida_loop(void *)+370j.text:0000000000001210 BL .getpid.text:0000000000001214 MOV W1, #9.text:0000000000001218 BL .kill.text:000000000000121C B loc_123C.text:0000000000001220 ; ---------------------------------------------------------------------------.text:0000000000001220.text:0000000000001220 loc_1220 ; CODE XREF: detect_frida_loop(void *)+36Cj.text:0000000000001220 ADRP X1, #aPediy@PAGE ; "pediy".text:0000000000001224 ADD X1, X1, #aPediy@PAGEOFF ; "pediy".text:0000000000001228 ADRP X2, #aNotFoundFridaS@PAGE ; "not FOUND FRIDA SERVER".text:000000000000122C ADD X2, X2, #aNotFoundFridaS@PAGEOFF ; "not FOUND FRIDA SERVER".text:0000000000001230 MOV W0, #4.text:0000000000001234 BL .__android_log_print.text:0000000000001238 B loc_123C


思路:修改跳转地址,使程序无法进入kill(v1, 9LL)的代码段。


修改完之后,效果如下图: 



这样进程就不会被杀掉了。


3. frida hook


思路:除了在strcmp的跳转处hook,还可以hook recvfrom方法,修改返回值为-1,从而无法kill掉进程。


代码:


function hook_native(){ var recvfrom_addr = Module.findExportByName("libc.so","recvfrom"); console.log("recvfrom_addr:",recvfrom_addr); if (recvfrom_addr){ Java.perform(function(){ Interceptor.attach(recvfrom_addr,{ onEnter:function(args){ },onLeave:function(retval){ retval.replace(-1); console.log("hook complete"); } }) }) }}


然后后面和题目二的解法完全相同。


最终flag值为:99998。


输入检查结果,没有问题。





总结


这三道题可以帮助我们熟悉Frida基本API的使用,难度不大,很适合初学

者。一步一个脚印,加油~~~




- End -



看雪ID:ychangeol

https://bbs.pediy.com/user-848457.htm

  *本文由看雪论坛 ychangeol 原创,转载请注明来自看雪社区。





推荐文章++++

* XposedFridaBridge:使用Frida加载Xposed插件

* TSG CTF 2020 Reverse-ing

* 观赏某大佬分析病毒后的一次复现分析

* 分析钓鱼邮件搭载的Excel 4.0恶意宏

* ollvm算法还原案例分享







公众号ID:ikanxue
官方微博:看雪安全
商务合作:wsc@kanxue.com



ps. 觉得对你有帮助的话,别忘点分享,点赞和在看,支持看雪哦~


“阅读原文”一起来充电吧!

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存