hash
加密后与程序已有密文进行对比,细节方面略有不同,但大体来说就是爆破,整个过程逻辑很清晰,都不难,基本上都是Frida
一把梭。题目主要考察了以下知识点:frida java hook
与静态函数的主动调用 Frida
遍历ClassLoader
从而hook
动态加载的dex
的函数 frida native hook
去反调试 Jadx打开程序
可知程序逻辑就是将用户名和密码进行拼接传入关键函数VVVVV.VVVV(this, str)
,
继续看VVVV
函数
可得知信息
后续的函数继续去观察会发现实际上就是一个sha1
+ salt
的加密,
又在运行程序后发现提示只能是数字,那么思路就清晰了,爆破!
解题方法根据要求写了两种,
直接上frida脚本,关键代码如下
Java.perform(function(){ Java.use('com.kanxue.pediy1.VVVVV').VVVV.implementation = function(listener,input){ console.log('input= ',input); for (var i = '0'; i <= '9'; i++) { //System.out.println(i); for (var j = '0'; j <= '9'; j++) { for (var k = '0'; k <= '9'; k++) { for (var t = '0'; t <= '9'; t++) { for (var y = '0'; y <= '9'; y++) { var newInput = Java.use('java.lang.String').$new(i.toString() + j.toString() + k.toString() + t.toString() + y.toString()) console.log(newInput) var result = this.VVVV(listener,newInput) if(result == true){ console.log('flag is ',newInput) return result; } } } } } } } })
最终拿到flag
为66888
将APK中函数直接全部copy到一个java工程,运行一遍就行,实际上也是爆破,没啥差别
import java.io.UnsupportedEncodingException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; public class VVVVV { public static void main(String[] args){ for (int i = '0'; i <= '9'; i++) { //System.out.println(i); for (int j = '0'; j <= '9'; j++) { for (int k = '0'; k <= '9'; k++) { for (int t = '0'; t <= '9'; t++) { for (int y = '0'; y <= '9'; y++) { StringBuilder sb = new StringBuilder(); String input = sb.append((char) i).append((char)j).append((char)k) .append((char)t).append((char)y).toString(); //System.out.println(input.length()); if(VVVV(input)){ System.out.println("flag is " + input); return; } } } } } } } }
这里只展示了main
函数代码,最终拿到flag
为66888
check
一下
拿到flag!!!
Jadx打开程序
可以发现实际上就是动态加载dex并调用动态加载的dex中的VVVV
函数
jadx打开需要动态加载的dex
其实会发现算法和第一题是一样的。
直接写frida脚本,遍历ClassLoader
选择正确的classLoader
再对函数进行主动调用爆破得到
function enumerateClassLoader(){ Java.perform(function(){ Java.enumerateClassLoaders({ onMatch: function(loader){ //console.log('classLoader',classLoader.toString()); if(loader.toString().indexOf('dalvik.system.DexClassLoader')>-1){ console.log('find classLoader',loader.toString()); Java.classFactory.loader = loader; hookVVVV() return; } },onComplete: function(){ console.log('search complete!'); } }) }) } function hookVVVV(){ Java.perform(function(){ console.log('loader',Java.classFactory.loader); for (var i = '0'; i <= '9'; i++) { for (var j = '0'; j <= '9'; j++) { for (var k = '0'; k <= '9'; k++) { for (var t = '0'; t <= '9'; t++) { for (var y = '0'; y <= '9'; y++) { var newInput = Java.use('java.lang.String').$new(i.toString() + j.toString() + k.toString() + t.toString() + y.toString()) //console.log(newInput) var result = Java.use('com.kanxue.pediy1.VVVVV').VVVV(newInput) if(result == true){ console.log('flag is ',newInput) return; } } } } } } }) } setImmediate(enumerateClassLoader)
这里需要注意的是,脚本attach的时间,要在第一次输入进行check后再将脚本attach上去这样才能保证遍历ClassLoader
能够得到DexClassLoader
,即校验函数正确的ClassLoader
。
最终得到爆破得到的flag
为66999
但实际测试还不对,继续往回看,会发现有一个stringFromJNI
的一层调用,这个函数是native
函数,使用IDA打开
这个函数有点骚。。。就是将输入的数字字符串转换为int
然后加了一返回了。。。
那么flag就很明显了,应该为66999-1 = 66998
check
一下
正确!
这一题,其实就是第二题的进阶版,在第二题的基础上加了反调试,所以在这里只分析反调试部分,其余不管
这一题和第二题不同的地方在于在OnCreate
函数中,调用了init
函数,而这个函数是native
函数
IDA
打开这个so
文件.
观察JNI_Onload函数可以发现java层的init
函数被动态注册到init
函数上去了. 详细介绍的话就涉及JNINativeMethod
结构体了。这里不再赘述。
接下来就简单了,跟踪到init
函数
观察函数名,可以清楚的了解到,这个函数实际上就是创建了一个新线程用于检测frida
,跟踪这个函数, 伪代码如下
void __fastcall __noreturn detect_frida_loop(void *a1) { 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(i) >> 16; if ( 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 ( 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
老师要求,三种方法
使用apktool
将apk
转换为smali然后,修改调用init
函数的smali
语句.具体来说
1.1. apktool
命令反编译为smali.
1.2 删除调用init
函数的smali
语句
对应我这,也就是删除MainActivity.smali
的第397和398行
1.3 二次打包
这样就过了反调试了
pthread_create
函数这里就不赘述了,直接几乎照抄的r0ysue
大佬星球里的,链接见这(r0ysue大佬,等会记得广告费结一下,溜了溜了),这里也贴上我抄的代码吧(这里其实还有很多种其他更简单的方法,比如hook
strcmp
函数等等,我这里的代码还有很多的改进之处,仅供参考)
function hook_pthread_create(){ var pt_create_func = Module.findExportByName(null,'pthread_create'); var detect_frida_loop_addr = null; console.log('pt_create_func:',pt_create_func); Interceptor.attach(pt_create_func,{ onEnter:function(){ if(detect_frida_loop_addr == null) { var base_addr = Module.getBaseAddress('libnative-lib.so'); if(base_addr != null){ detect_frida_loop_addr = base_addr.add(0xe9c) console.log('this.context.x2: ', detect_frida_loop_addr , this.context.x2); if(this.context.x2.compare(detect_frida_loop_addr) == 0) { hook_anti_frida_replace(this.context.x2); } } } }, onLeave : function(retval){ // console.log('retval',retval); } }) } function hook_anti_frida_replace(addr){ console.log('replace anti_addr :',addr); Interceptor.replace(addr,new NativeCallback(function(a1){ console.log('replace success'); return; },'pointer',[])); }
这里我注意到,在detect_frida_loop
函数的最后,有一个strcmp
函数的调用
对应汇编中
可以看到loc_1220
分支和loc_1210
分支分别为没有找到frida_server
和退出进程,毫无疑问我们需要的是loc_1220
,这里我选择将偏移为0x120C
处的B loc_1210
这条汇编改为B loc_1220
,从而达到无论有没有检测到frida_server
都不要退出进程的效果。
修改之后伪代码变成了
最终get Flag,之后的过程就和第二题一致了,脚本都不用变。
最终拿到flag
为99998
.
整体来说,三道题目其实都不是很难,其实就考察了一些frida的API的基本使用,希望能对刚入门frida的人有点帮助,最后附上题目附件