codelive 发表于 2014-11-4 16:14

【题目分析】【吾爱破解2014CrackMe大赛】

注意:只是分析,没有爆破或者注册机!


这个Android CM真的太让人头疼了,作者做了很足了反调试和静态分析处理,对so文件都进行了加密处理,分析的太累了,估计在今天20:00之前无法爆破完成,更不用说写注册机了,发此帖就算是记录一下分析过程吧,也供大家以后进行分析。

用到的工具: IDA ARM v6.5, Android逆向助手, Android开发环境Eclipse, 等等(工具使用方法和环境就不细说了)

App包含2个lib文件:libverify.so, libmodule.so

首先先解密so文件:

1. libverify.so
    导出了2个API : init_verify,verify

    函数init_verify执行时会解密部分加密的数据,包括JNI_OnLoad函数,解密完成后再执行JNI_OnLoad进行初始化工作.
    加密的算法还没有研究,简单的办法是用IDA附加运行后的Android App,然后找到libverify.so模块起始地址,对内存进行DUMP,这样出来的so文件就是解密后的,可以用IDA静态分析了。

      下面是部分JNI_OnLoad的代码:

      int __fastcall JNI_OnLoad(int a1)
      {
            v21 = a1;
            v22 = 0;
            if(sub_12A0())
                return -1;
            v1 = sub_13CC();
            stat("/data/data/com.example.crackme52/lib/libmodule.so", (struct stat *)&v23);
            dword_5048 = ((((_DWORD)v24 << 20) - (((_DWORD)v24 << 20 >= 1u) + ((_DWORD)v24 << 20) - 1) + (unsigned int)(v24 / 4096)) << 12)
                + 4096;
            v2 = (void *)open("/data/data/com.example.crackme52/lib/libmodule.so", 0);
            dword_504C = (int)v2;
            if(v2 != (void *)-1)
            {
                v2 = mmap(0, dword_5048, 7, 34, 0, 0);
                if(v2 == (void *)-1)
                {
                  v3 = (int)"JNITag";
                  v4 = (int)"Map load error\n";
                  goto LABEL_6;
                }
                v5 = sub_12A0();
                if(!v5)
                {
                  v6 = *(_WORD *)(v1 + 44);
                  v7 = v1 + *(_DWORD *)(v1 + 28);
                  while(v5 < v6 && *(_DWORD *)v7 != 1)
                  {
                        v7 += 32;
                        ++v5;
                  }
                  if(v5 != v6)
                  {
                        v8 = *(_DWORD *)(v7 + 40) + *(_DWORD *)(v7 + 48) - 4 + v1;
                        v9 = (const void *)(v8 - *(_DWORD *)v8);
                        memcpy(v2, (const void *)(v8 - *(_DWORD *)v8), 0x174u);
                        v10 = 0;
                        do
                        {
                            *((_BYTE *)v2 + v10) = ~(*((_BYTE *)v2 + v10) - 3);
                            ++v10;
                        } while(v10 != 372);
                        v11 = 0;
                        v12 = (int)((char *)v2 + *((_DWORD *)v2 + 7));
                        while(v11 < *((_WORD *)v2 + 22) && *(_DWORD *)v12 != 1)
                        {
                            v12 += 32;
                            ++v11;
                        }
                        memcpy(v2, v9, *(_DWORD *)(v12 + 16));
                        for(i = 0; i < *(_DWORD *)(v12 + 16); ++i)
                            *((_BYTE *)v2 + i) = ~(*((_BYTE *)v2 + i) - 3);
                        memcpy((char *)v2 + *(_DWORD *)(v12 + 40), (char *)v9 + *(_DWORD *)(v12 + 36), *(_DWORD *)(v12 + 48));
                        v14 = 0;
                        while(v14 < *(_DWORD *)(v12 + 48))
                        {
                            *((_BYTE *)v2 + v14 + *(_DWORD *)(v12 + 40)) -= 3;
                            v15 = (int)((char *)v2 + v14++ + *(_DWORD *)(v12 + 40));
                            *(_BYTE *)v15 = ~*(_BYTE *)v15;
                        }
                  }
                  *(_DWORD *)"ng;Ljava/lang/String;)V" = v2;
                  sub_1190();
                  *(_DWORD *)off_4F64 = (char *)v2
                        + *(_DWORD *)(*(_DWORD *)"java/lang/String;)V" + 16 * sub_136C("begin_verify") + 4);
                  v16 = sub_136C("__stack_chk_guard");
                  *(_DWORD *)((char *)v2 + sub_127C(*(_DWORD *)"ring;)V", *(_DWORD *)";)V", v16)) = dword_5040;
                  v17 = sub_136C("memcpy");
                  *(_DWORD *)((char *)v2 + sub_127C(dword_5028, dword_502C, v17)) = dword_5044;
                  if(!(*(int(__fastcall **)(int, int *, unsigned int))(*(_DWORD *)v21 + 24))(v21, &v22, 0x10004u))
                  {
                        v18 = v22;
                        if(v22)
                        {
                            v19 = (*(int(__fastcall **)(int, _DWORD))(*(_DWORD *)v22 + 24))(v22, "com/example/crackme52/MainActivity");
                            if(v19)
                              return ((*(int(__fastcall **)(int, int, void **, signed int))(*(_DWORD *)v18 + 860))(
                              v18,
                              v19,
                              &off_5004,
                              1) >> 31) | 0x10004;
                        }
                  }
                }
                return -1;
            }
            v3 = (int)"JNITag";
            v4 = (int)"Open libmodule.so error\n";
      LABEL_6:
            _android_log_print(4, v3, v4);
            return (int)v2;
      }


2. libmodule.so, 整个文件都进行了加密
    导出了1个API : begin_verify

    开始猜测可能是异或算法,但测试下来解密不正确,后来经过对比分析,猜出了算法:
    解密算法是 :文件的每个字节取反,然后再加1
                RR = (~BB) + 0x01;
    加密算法是 :文件的每个字节先减1, 再取反
                BB = ~(RR - 0x01);


      解密代码如下:
      FILE *pIn= fopen("D:/temp/vs/libmodule.so", "rb");
      FILE *pOut = fopen("D:/temp/vs/libmodule_out.so", "wb");
      int inSize = 13432; // 文件大小
      char *buffer = new char;

      while(true)
      {
            ret = fread(buffer, 1, inSize, pIn);
            if(ret <= 0)
            {
                break ;
            }
            for(int i = 0; i < ret; i ++)
            {
                buffer = (~buffer) + 0x01;
            }

            ret = fwrite(buffer, 1, ret, pOut);
      }
      delete []buffer;

      fclose(pIn);
      fclose(pOut);


      加密代码如下:
      FILE *pIn= fopen("D:/temp/crack/crackme52/armeabi/libmodule.p.so", "rb");
      FILE *pOut = fopen("D:/temp/crack/crackme52/armeabi/libmodule.so", "wb");
      int inSize = 13432; // 文件大小
      char *buffer = new char;

      while(true)
      {
            ret = fread(buffer, 1, inSize, pIn);
            if(ret <= 0)
            {
                break ;
            }
            for(int i = 0; i < ret; i ++)
            {
                buffer = ~(buffer - 0x01);
            }

            ret = fwrite(buffer, 1, ret, pOut);
      }
      delete []buffer;

      fclose(pIn);
      fclose(pOut);

分析算法不擅长,只能先尝试爆破, 从分析可以知道,最终验证函数是 libmodule.so的导出函数 begin_verify,那么就从这个函数进行分析

      int __fastcall begin_verify(int a1, int a2, int a3, int a4)
      {
            v20 = a2;
            v19 = a4;
            v4 = a1;
            v18 = a3;
            v25 = _stack_chk_guard;
            memcpy(dest, "QWERTYUIOPASDFGHJKLZXCVBNMqwertyuiopasdfghjklzxcvbnm0123456789", 0x3Fu);
            v5 = 0;
            v21 = 557788716;
            v22 = 0;
            result = (*(int(__fastcall **)(int, int))(*(_DWORD *)v4 + 672))(v4, v19);
            if(result == 10) // 此处条件成立
            {
                result = (*(int(__fastcall **)(int, int))(*(_DWORD *)v4 + 672))(v4, v18) - 8;
                if((unsigned int)result <= 0xC)
                {
                  v7 = (*(int(__fastcall **)(int, int))(*(_DWORD *)v4 + 672))(v4, v18);
                  v8 = (*(int(__fastcall **)(int, int, _DWORD))(*(_DWORD *)v4 + 676))(v4, v18, 0);
                  if(v7 <= 12)
                  {
                        while(v5 < v7)
                        {
                            v23 = *(_BYTE *)(v8 + v5);
                            ++v5;
                        }
                        v10 = (char *)&v21 - v7;
                        while(v7 != 12)
                        {
                            v23 = v10;
                            ++v7;
                        }
                  }
                  else
                  {
                        for(i = v7 - 12; i != v7; ++i)
                            v23 = *(_BYTE *)(v8 + i);
                  }
                  v11 = 0;
                  v12 = (*(int(__fastcall **)(int, int, _DWORD))(*(_DWORD *)v4 + 676))(v4, v19, 0);
                  v13 = 0;
                  while(1)
                  {
                        v11 += (unsigned __int8)v23;
                        result = v11 / 62;
                        if(dest != *(_BYTE *)(v12 + v13))
                            break;// 此处不能被执行
                        ++v13;
                        if(v13 == 12)
                        {
                            result = (*(int(__fastcall **)(int, _DWORD))(*(_DWORD *)v4 + 24))(v4, "android/widget/Toast");
                            v14 = result;
                            if(result)
                            {
                              result = (*(int(__fastcall **)(int, int, _DWORD, _DWORD))(*(_DWORD *)v4 + 452))(
                                    v4,
                                    result,
                                    "makeText",
                                    "(Landroid/content/Context;Ljava/lang/CharSequence;I)Landroid/widget/Toast;");
                              v15 = result;
                              if(result)
                              {
                                    v16 = *(int(__fastcall **)(_DWORD, _DWORD, _DWORD, _DWORD))(*(_DWORD *)v4 + 456);
                                    (*(void(__fastcall **)(int, _DWORD))(*(_DWORD *)v4 + 668))(v4, "You got it!");
                                    result = v16(v4, v14, v15, v20);
                                    v17 = result;
                                    if(result)
                                    {
                                        result = (*(int(__fastcall **)(int, int, _DWORD, char))(*(_DWORD *)v4 + 132))(
                                          v4,
                                          v14,
                                          "show",
                                          "()V");
                                        if(!v18) // 此处条件成立
                                          result = (*(int(__fastcall **)(int, int, int))(*(_DWORD *)v4 + 244))(v4, v17, result);
                                    }
                              }
                            }
                            break;
                        }
                  }
                }
            }
            if(v25 != _stack_chk_guard)
                _stack_chk_fail(result);
            return result;
      }


      把几个有判断的部分进行了跳转,使其不论什么条件都可以执行到
      (*(void(__fastcall **)(int, _DWORD))(*(_DWORD *)v4 + 668))(v4, "You got it!");

      经过多次测试和修改,程序还是不能显示Cracked信息,郁闷,如果不是爆破的问题,或许是文件有检验.

      自己对Android方面调试方面接触的太少,经验欠缺,在使用IDA动态调试的时候,IDA经常会崩溃或者异常,或许是作者的反调试处理,所以一直没办法用IDA进行动态跟踪,希望有这方面经验的能够一同探讨。

      因为工作上还有事情要忙,只能先分析到这里,后面有进展再继续吧。。。

L4Nce 发表于 2014-11-7 17:07

感谢分享分析心得。虽然没有在比赛时完成破解,但期待赛后更完整的分析,52破解有你更精彩。

currwin 发表于 2014-11-7 17:35

开始猜测可能是异或算法,但测试下来解密不正确,后来经过对比分析,猜出了算法:
    解密算法是 :文件的每个字节取反,然后再加1
                RR = (~BB) + 0x01;
    加密算法是 :文件的每个字节先减1, 再取反
                BB = ~(RR - 0x01);
这是求补运算啊

codelive 发表于 2014-11-7 17:49

currwin 发表于 2014-11-7 17:35
开始猜测可能是异或算法,但测试下来解密不正确,后来经过对比分析,猜出了算法:
    解密算法是 :文件 ...

没错,只不过又进行了加1处理。

ThomasKing 发表于 2014-11-7 17:51

额,楼主真心不意思告诉你。 被我fake到了。

codelive 发表于 2014-11-7 17:55

ThomasKing 发表于 2014-11-7 17:51
额,楼主真心不意思告诉你。 被我fake到了。

这个CM是不是libmodule.so解密了也没用?因为最终不是调用libmodule.so中的begin_verify,而调用函数是在libverify.so中的数据,对数据进行解密,然后来调用的吧? libmodule.so 就是陷阱,对吧?

codelive 发表于 2014-11-7 17:58

ThomasKing 发表于 2014-11-7 17:51
额,楼主真心不意思告诉你。 被我fake到了。

v2 = (void *)open("/data/data/com.example.crackme52/lib/libmodule.so", 0);
后来我分析了,其实这个返回值v2,一直没有被用到。
所以libmodule.so是陷阱。
不知道是不是我的IDA 配置问题,无法对App进行调试?还请多多指教。

currwin 发表于 2014-11-7 20:40

codelive 发表于 2014-11-7 17:49
没错,只不过又进行了加1处理。

不不,求补的定义就是 按位取反+1

ThomasKing 发表于 2014-11-7 21:08

codelive 发表于 2014-11-7 17:58
v2 = (void *)open("/data/data/com.example.crackme52/lib/libmodule.so", 0);
后来我分析了,其实这个 ...

嗯嗯,大概是这样子的。

ThomasKing 发表于 2014-11-7 21:11

codelive 发表于 2014-11-7 17:55
这个CM是不是libmodule.so解密了也没用?因为最终不是调用libmodule.so中的begin_verify,而调用函数是在l ...

不知道。 防御组能不能把APK的实现原理放出来,如果可以的话,我在详细的写写吧
页: [1] 2 3
查看完整版本: 【题目分析】【吾爱破解2014CrackMe大赛】