首页
论坛
课程
招聘
雪    币: 2131
活跃值: 活跃值 (604)
能力值: ( LV6,RANK:84 )
在线值:
发帖
回帖
粉丝

[原创]从三道题目入手入门frida

2020-7-6 18:12 3800

[原创]从三道题目入手入门frida

2020-7-6 18:12
3800

写在前面

  • 题目来自2w班6月习题,题目本身都没有加壳没有混淆,主要逻辑就是输入flag,程序本身利用hash加密后与程序已有密文进行对比,细节方面略有不同,但大体来说就是爆破,整个过程逻辑很清晰,都不难,基本上都是Frida一把梭。题目主要考察了以下知识点:
  1. frida java hook与静态函数的主动调用
  2. Frida遍历ClassLoader从而hook动态加载的dex的函数
  3. frida native hook去反调试

第一题

程序分析

Jadx打开程序

可知程序逻辑就是将用户名和密码进行拼接传入关键函数VVVVV.VVVV(this, str)
继续看VVVV函数

可得知信息

  1. 输入长度为5位
  2. frida hook这个函数直接强制返回true就行,这个check就这样pass了,但是拿不到正确的flag

后续的函数继续去观察会发现实际上就是一个sha1 + salt的加密,
又在运行程序后发现提示只能是数字,那么思路就清晰了,爆破!
解题方法根据要求写了两种,

1. Frida

直接上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;
                                    }
                                }
                            }
                        }
                    }
                }


        }
    })

最终拿到flag66888

2. 抠代码

将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函数代码,最终拿到flag66888

 

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
最终得到爆破得到的flag66999

但实际测试还不对,继续往回看,会发现有一个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老师要求,三种方法

1. 二次打包

使用apktoolapk转换为smali然后,修改调用init函数的smali语句.具体来说
1.1. apktool 命令反编译为smali.

 


1.2 删除调用init函数的smali语句

 


对应我这,也就是删除MainActivity.smali的第397和398行
1.3 二次打包

 


这样就过了反调试了

2. frida去hookpthread_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',[]));

}

3. 硬编码so

这里我注意到,在detect_frida_loop函数的最后,有一个strcmp函数的调用

 

 

对应汇编中

 


可以看到loc_1220分支和loc_1210分支分别为没有找到frida_server和退出进程,毫无疑问我们需要的是loc_1220,这里我选择将偏移为0x120C处的
B loc_1210这条汇编改为
B loc_1220,从而达到无论有没有检测到frida_server都不要退出进程的效果。

 

修改之后伪代码变成了

 

 

最终get Flag,之后的过程就和第二题一致了,脚本都不用变。
最终拿到flag99998.

写在最后

整体来说,三道题目其实都不是很难,其实就考察了一些frida的API的基本使用,希望能对刚入门frida的人有点帮助,最后附上题目附件



HWS计划·2020安全精英夏令营来了!我们在华为松山湖欧洲小镇等你

上传的附件:
最新回复 (14)
雪    币: 559
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
安和桥南 活跃值 2020-7-6 18:16
2
0
沙发,frida真滴好用,文档写的真滴不太好,多自己搞一下理解会深好多
雪    币: 9081
活跃值: 活跃值 (425)
能力值: ( LV8,RANK:120 )
在线值:
发帖
回帖
粉丝
pureGavin 活跃值 2020-7-6 18:50
3
0
mark,楼主辛苦了
雪    币: 98
活跃值: 活跃值 (365)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
Zard_ 活跃值 2020-7-7 09:16
4
0
最喜欢这种有文字有附件的了  感谢楼主  希望dalao多来点这样的教程 
雪    币: 567
活跃值: 活跃值 (5466)
能力值: ( LV9,RANK:166 )
在线值:
发帖
回帖
粉丝
0x指纹 活跃值 3 2020-7-7 14:45
5
0
感谢楼主无私分享~
雪    币: 856
活跃值: 活跃值 (116)
能力值: (RANK:10 )
在线值:
发帖
回帖
粉丝
看雪高研 活跃值 2020-7-8 10:44
6
0
这三道题主要考察`Frida Java Hook`三板斧“hook、invoke、rpc”中的前两板斧,即hook分析和主动调用。第一题是签到题掌握基础即可回答,第二题加入动态加载的Dex需要枚举Classloader,第三题设置了全端口检测Frida的Native层反调试,要求用三种解发去除这个反调试:其一是反编译重打包、其二是对so进行硬编码、其三是Frida hook native,小白abc童鞋非常优秀,三种解发全部实现。
雪    币: 238
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
wgetlaidu 活跃值 2020-7-14 15:24
7
0
console.log(VVVVV.VVVV.implementation) 为什么这个会输出null,而且到VVVVV.VVVV.implementation这里就结束了
雪    币: 2131
活跃值: 活跃值 (604)
能力值: ( LV6,RANK:84 )
在线值:
发帖
回帖
粉丝
小白abc 活跃值 2020-7-14 18:32
8
0
wgetlaidu console.log(VVVVV.VVVV.implementation) 为什么这个会输出null,而且到VVVVV.VVVV.implementation这里就结束了
???
雪    币: 238
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
wgetlaidu 活跃值 2020-7-15 10:05
9
0

代码不执行也不报错

雪    币: 2131
活跃值: 活跃值 (604)
能力值: ( LV6,RANK:84 )
在线值:
发帖
回帖
粉丝
小白abc 活跃值 2020-7-15 15:13
10
0
wgetlaidu 代码不执行也不报错

你点APP的login按钮了吗,我这运行没问题

最后于 2020-7-15 15:14 被小白abc编辑 ,原因:
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
wx_Null_732 活跃值 2020-7-31 23:00
11
0
请问第二题跟第三题的APK档是否有毁损?
我试了好几个模拟器都安装不了
雪    币: 192
活跃值: 活跃值 (35)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
pash 活跃值 2020-8-1 15:41
12
0

感谢楼主的教程。

第一题的for循环有点吓人,可以缩减下

Java.perform(function(){
    Java.use('com.kanxue.pediy1.VVVVV').VVVV.implementation = function(listener,input){
        for (var i = 10000; i <= 99999; i++) {
            var s = i.toString();
            console.log(s);
            var result = this.VVVV(listener,s);
            if(result == true){
                console.log('flag is ', s);
                return result;
            }
        }
    }
})


雪    币: 192
活跃值: 活跃值 (35)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
pash 活跃值 2020-8-1 15:48
13
0
wx_Null_732 请问第二题跟第三题的APK档是否有毁损? 我试了好几个模拟器都安装不了
第二第三个apk只有arm64的so,需要用真机或arm架构的模拟器
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
wx_Null_732 活跃值 2020-8-1 16:18
14
0
pash 第二第三个apk只有arm64的so,需要用真机或arm架构的模拟器
原来如此!
请问你有推荐的arm架构模拟器吗?
我手边目前没安卓手机....
雪    币: 192
活跃值: 活跃值 (35)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
pash 活跃值 2020-8-1 19:16
15
0
wx_Null_732 原来如此! 请问你有推荐的arm架构模拟器吗? 我手边目前没安卓手机....

用android 原生模拟器,最新的android 11 x86_64 可以兼容arm

游客
登录 | 注册 方可回帖
返回