首页
论坛
课程
招聘
[加密算法] [原创]通过逆向获得某练习程序的正确序列号
2021-4-30 16:32 2233

[加密算法] [原创]通过逆向获得某练习程序的正确序列号

2021-4-30 16:32
2233

软件界面:
图片描述

1.使用PEiD查看,是一个C++程序,看界面猜测应该是MFC

图片描述

2.关闭程序随机基址,使用xspy找到注册按钮响应函数

图片描述

3.使用x64Dbg进行动态分析,在响应函数处下断点

经测试得知,序列号格式为xxxx-xxxx-xxxx-xxxx
构建测试序列号: 1111-2222-3333-4444

4.找到弹出关键信息点,这里会弹出错误提示框,下API断点,MessageBoxA/W

图片描述
断在MessageBoxW出,栈回溯找调用点
图片描述
图片描述
运气不错直接找到了正确提示处,关键跳转为: 0078EE96 jne xxx,
往上翻翻可以看到,很有规律的比较,这里很大可能是比较序列号是否正确的地方
图片描述

5.到此函数顶部下断点,重新运行,部F8单步步过,观察堆栈,有没有出现有价值的信息

找到我们输入的序列号,首次出现位置: 0078EB0A,字符串地址:0028ABE8,在字符串处下硬件访问断点,直接运行
图片描述
断在此处:0078454E
图片描述
下方可以看到有一处访问了常量: 0xF0BB18
里面存放了一串有规则的字符串,先保留下来: ABJDEFGHICKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz+/
图片描述

6.经过分析,此处可能是在进行加密操作,先使用IDA静态分析一波,找到刚刚断下来的位置:0078454E , 直接F5编译成C代码

图片描述
需要注意到函数内部使用了byte_0xF0BB18数组,是我们第5步找到的固定字符串常量
可以看到确实有点像加密,有3个参数,函数返回的值是一个长度
弄清楚3个参数是什么:
参数1: 我们输入的序列号字符串地址
参数2: 存放加密后的字符串缓冲区
参数3: 0x13(是序列号的长度,固定的)
图片描述
图片描述
得到加密后的序列号: MTEnMSqoM9IoLTMpMpMjNDQqNA==
疑似Base64编码,但经测试并非Base64

7.到第4步找到的比较处,发现:

正确序列号比较的字符串与第6步得到的加密序列号有一点不同
第6步得到的加密序列号: MTEnMSqoM9IoLTMpMpMjNDQqNA==
进行比较的序列号: MUGmIVwhE0Cd@YCMpMjNDQqNA== (中间有一个空格)
图片描述
仔细分析发现前16个字符又被加密了一次,多次测试后发现二次加密的地址的固定的
记住被二次加密的序列号缓冲区地址: 00F0E37C,重新运行一次,观察缓冲区状况,发现是在0078ECE6处的函数里面进行了二次加密
图片描述
使用IDA静态分析此函数
图片描述
函数内部有大量的混淆代码,由于之前已经知道二次加密只操作了16字节,观察到下方有一个比较像加密的,经解密测试后,其余都是混淆代码,无实际意义

8.将正确序列号字符串化(注意小端存储数据):

MUTn]=tbR^db@U@~3WQjMTInM6==
图片描述

9.构建C代码解密:

有两处加密,逆向思维,应该先解密第二次加密

解密第二次加密:

1
2
3
4
5
void sub_78A810(BYTE* a1)
{
    for (int j = 0; j < 16; ++j)     // 加密前16个字节
        a1[j] ^= j;
}

传入第8步得到的正确序列号解密前16个字节得到:
MTVmY8reZWniLXNq3WQjMTInM6==

解密第一次加密(暴力枚举):

这一步需要详细分析一下之前IDA静态分析的函数内部实现,这里讲一下关键的几个点:
1.原来的加密:
每次取3个字符进行加密,加密后生成4个字符
最后1个字符特殊加密处理成了"=="
有一处加密没有用上
图片描述
2.暴力枚举解密:
逆向思维: 取4个字符解密成3个字符
最后一个字符: 加密序列长度28 可以整除4,所以只能取最后4个字符解密得到最后一个字符

1
2
3
4
5
6
7
8
9
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
void Decrypt(BYTE * a)
{
    int len = 0x1C; //加密后的序列号长度  0x1C == 28
 
    while (len >0)
    {
        if (len == 4)
        {
            for (int i = 0; i < 0x7F; i++)
            {
                if (byte_F0BB18[(i >> 2) & 0x3F] == a[0])
                {
                    if (byte_F0BB18[16 * (i & 3)] == a[1])
                    {
                        printf("%c", i);
                        break;
                    }
                }
            }
        }
 
        for (int i = 0; i < 0x7F; i++)
        {
            if (byte_F0BB18[(i >> 2) & 0x3F] == a[0])  //1  M
            {
                for (int j = 0; j < 0x7F; j++)
                {
                    if (byte_F0BB18[(j >> 4) | 16 * (i & 3)] == a[1])  //2 T
                    {
                        for (int k = 0; k < 0x7F; k++)
                        {
                            if (byte_F0BB18[(k >> 6) | 4 * (j & 0xF)] == a[2]) //3 E
                            {
                                if (byte_F0BB18[k & 0x3F] == a[3])
                                {
                                    printf("%c%c%c", i, j, k);
                                    break;
                                }
                            }
                        }                   
                    }
                }             
            }
        }
        a += 4;
        len -= 4;
    }
 
}

传入上一步解密了前16字节的字符串得到:
15pb-hell-stud-1212

 

测试结果:
图片描述
完美!

 

总结:
此次逆向是第一次尝试反推演序列号,过程的艰辛难以言表.
对于初学者来说,至少对我来说,有好几次都想放弃,几个小时的分析,却没有一点收获,觉得大概是不行了...
虽然过后感觉此程序逆向难度确实没有想象中难,可是当局者迷啊
但万幸还是坚持下来了,此次逆向对我逆向能力的提升有很大的帮助!

 

此次逆向的要点:
1.先观察错误提示,找到提示处,栈回溯,看看能不能找到提示成功点
2.找到成功点后,往上回溯,观察关键跳转,运气好可以直接找到比较处
3.如果有输入,找到我们输入的字符串缓冲区,下硬件访问断点

 

附件已经上传


第五届安全开发者峰会(SDC 2021)议题征集正式开启!

最后于 2021-5-5 09:12 被wx_断浪天涯编辑 ,原因: 上传附件
上传的附件:
收藏
点赞2
打赏
分享
最新回复 (2)
雪    币: 189
活跃值: 活跃值 (82)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
wx_浪里格朗 活跃值 2021-5-7 09:25
2
0
题又变了,感觉我学的时候没有做过这个题
雪    币: 200
活跃值: 活跃值 (52)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
菜狼 活跃值 2021-5-7 12:39
3
0
硬断那个地方应该是断unicode转换后的字符串吧。
游客
登录 | 注册 方可回帖
返回