本文的3道Crackme来自Android高研2W班6月测试题。
这三题本身难度不大,flag的求解都是通过爆破长度为5位的字符串(只包含数字),求其hash1+salt的摘要与程序中硬编码的密文进行比较,非常适合Frida入门。
这三道题分别考察了:
静态分析可知,输入的用户名和密码的字符串拼接作为参数,然后传入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
对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函数的引入。
因为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的反调试。
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
老师的要求,下面从三种方法过掉反调试
反编译重打包
onCreate方法中的init方法就是具有反调试功能的native层函数。
思路:查看对应的smali代码,然后将对调用init方法的smali语句进行删除,就可以了。
使用apktool解包,删除对应的smali代码,然后重打包,最后再重签名一下。
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)的代码段。
修改完之后,效果如下图:
这样进程就不会被杀掉了。
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的使用,难度不大,很适合初学者。一步一个脚印,加油~~~
最后附上题目。
[看雪官方培训]《安卓高级研修班(网课)》9月班开始招生!顶尖技术、挑战极限、工资翻倍!
最后于 21小时前 被ychangeol编辑 ,原因: