吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 18232|回复: 28
收起左侧

[第十三题] 【题目分析】【吾爱破解2014CrackMe大赛】

[复制链接]
codelive 发表于 2014-11-4 16:14
注意:只是分析,没有爆破或者注册机!


这个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的代码:
[C] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
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);

[C] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
解密代码如下:
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[inSize];
 
while(true)
{
    ret = fread(buffer, 1, inSize, pIn);
    if(ret <= 0)
    {
        break ;
    }
    for(int i = 0; i < ret; i ++)
    {
        buffer[i] = (~buffer[i]) + 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[inSize];
 
while(true)
{
    ret = fread(buffer, 1, inSize, pIn);
    if(ret <= 0)
    {
        break ;
    }
    for(int i = 0; i < ret; i ++)
    {
        buffer[i] = ~(buffer[i] - 0x01);
    }
 
    ret = fwrite(buffer, 1, ret, pOut);
}
delete []buffer;
 
fclose(pIn);
fclose(pOut);


分析算法不擅长,只能先尝试爆破, 从分析可以知道,最终验证函数是 libmodule.so的导出函数 begin_verify,那么就从这个函数进行分析
[C] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
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[v5] = *(_BYTE *)(v8 + v5);
                    ++v5;
                }
                v10 = (char *)&v21 - v7;
                while(v7 != 12)
                {
                    v23[v7] = v10[v7];
                    ++v7;
                }
            }
            else
            {
                for(i = v7 - 12; i != v7; ++i)
                    v23[i] = *(_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[v13];
                result = v11 / 62;
                if(dest[v11 % 62] != *(_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[4]))(*(_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进行动态跟踪,希望有这方面经验的能够一同探讨。

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

免费评分

参与人数 1热心值 +1 收起 理由
杨问天 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩.

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

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到了。

免费评分

参与人数 1威望 +5 热心值 +1 收起 理由
L4Nce + 5 + 1 cm大赛防御组一等奖奖励

查看全部评分

 楼主| 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的实现原理放出来,如果可以的话,我在详细的写写吧
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2025-4-6 08:59

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表