Frida 入门小练习
2020-07-08 10:56:08 Author: bbs.pediy.com(查看原文) 阅读量:896 收藏

内容介绍

本文的3道Crackme来自Android高研2W班6月测试题。

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

这三道题分别考察了:

  1. Frida静态函数的主动调用。
  2. Frida hook动态加载dex。
  3. native层去反调试。

第一题

静态分析

静态分析可知,输入的用户名和密码的字符串拼接作为参数,然后传入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的使用,难度不大,很适合初学者。一步一个脚印,加油~~~

最后附上题目。

[看雪官方培训]《安卓高级研修班(网课)》9月班开始招生!顶尖技术、挑战极限、工资翻倍!

最后于 21小时前 被ychangeol编辑 ,原因:


文章来源: https://bbs.pediy.com/thread-260536.htm
如有侵权请联系:admin#unsafe.sh