zbnysjwsnd8 发表于 2017-12-10 19:03

一个CrackMe的分析

**0x00 初探**
程序有ASLR,不方便分析,使用FFI去掉。
![这里写图片描述](https://attach.52pojie.cn/album/201712/10/185326ozr2ik9rtlpupp6k.png)
OD载入后,发现程序退出。
![这里写图片描述](https://attach.52pojie.cn/album/201712/10/185327jqz0a9xw4la4lw1l.png)
给ExitProcess下断点,然后重新载入程序,成功断下。修改代码为retn 4即可。
![这里写图片描述](https://attach.52pojie.cn/album/201712/10/185327r21q6w63858q5wmm.png)
此时即可使用OD调试。运行后,[确认]按钮是禁止状态。
**0x01 寻找编辑框的输入事件**
用彗星探测一下编辑框的句柄
![这里写图片描述](https://attach.52pojie.cn/album/201712/10/185328v1swd3mqnp5ak72z.png)
给GetWindowTextW下条件断点。
![这里写图片描述](https://attach.52pojie.cn/album/201712/10/185328s6ys22zxyy5xyop5.png)
输入一个字节即可断下。
![这里写图片描述](https://attach.52pojie.cn/album/201712/10/185328d56f1fz8gccuz8fo.png)
单步回到004020D9处。
程序首先计算注册码的长度:
![这里写图片描述](https://attach.52pojie.cn/album/201712/10/185329nrv747dh105wr0e0.png)
然后判断长度是否是16个字符,不是则返回。
![这里写图片描述](https://attach.52pojie.cn/album/201712/10/185329e7ckj7jjdeyezdyo.png)
构造一个长度是16个字符的key即可通过。我构造的是0123456789ABCDEF。
程序将16个字符的key转换成ANSI字符的形式。
![这里写图片描述](https://attach.52pojie.cn/album/201712/10/185330y9jjemijsgwcrspj.png)
然后调用函数004017C0。
**0x02 重点函数004017C0的逆向**
函数比较长,我直接放代码。注释已经标记。
```
004017C0   $55            push    ebp
004017C1   .8BEC          mov   ebp, esp
004017C3   .8B4D 08       mov   ecx, dword ptr                      ;Key
004017C6   .83EC 08       sub   esp, 8
004017C9   .8BC1          mov   eax, ecx
004017CB   .56            push    esi
004017CC   .8D70 01       lea   esi, dword ptr
004017CF   .90            nop
004017D0   >8A10          mov   dl, byte ptr
004017D2   .40            inc   eax
004017D3   .84D2          test    dl, dl
004017D5   .^ 75 F9         jnz   short 004017D0
004017D7   .2BC6          sub   eax, esi                                     ;再次得到Key的长度
004017D9   .83F8 10       cmp   eax, 10                                    ;不是16个字节则触发异常
004017DC   .74 01         je      short 004017DF
004017DE      FF            db      FF
004017DF   >BA 58145700   mov   edx, 00571458
004017E4   .33F6          xor   esi, esi
004017E6   .2BD1          sub   edx, ecx
004017E8   >8A01          mov   al, byte ptr                            ;取出一个字符 判断是否是数字或者是大写字母
004017EA   .3C 29         cmp   al, 29
004017EC   .7E 08         jle   short 004017F6
004017EE   .3C 40         cmp   al, 40
004017F0   .7D 06         jge   short 004017F8
004017F2   .2C 30         sub   al, 30
004017F4   .EB 0A         jmp   short 00401800
004017F6   >3C 40         cmp   al, 40
004017F8   >7E 52         jle   short 0040184C
004017FA   .3C 47         cmp   al, 47
004017FC   .7D 4E         jge   short 0040184C                               ;如果不是数字或者不是大写字母则返回NULL
004017FE   .2C 37         sub   al, 37
00401800   >88040A      mov   byte ptr , al                     ;将对应的数字或者大写字母转换成整数保存('0' -> 0;'A' -> A)
00401803   .46            inc   esi
00401804   .41            inc   ecx
00401805   .83FE 10       cmp   esi, 10
00401808   .^ 7C DE         jl      short 004017E8
0040180A   .BA 02000000   mov   edx, 2
0040180F   .81EA 59145700 sub   edx, 00571459
00401815   .8955 FC       mov   dword ptr , edx
00401818   .33C0          xor   eax, eax
0040181A   .BA 03000000   mov   edx, 3
0040181F   .81EA 59145700 sub   edx, 00571459
00401825   .53            push    ebx
00401826   .8D48 01       lea   ecx, dword ptr
00401829   .8955 F8       mov   dword ptr , edx
0040182C   .57            push    edi
0040182D   .8D49 00       lea   ecx, dword ptr
00401830   >8BF8          mov   edi, eax
00401832   .83E7 01       and   edi, 1
00401835   .75 1C         jnz   short 00401853
00401837   .8A90 58145700 mov   dl, byte ptr                   ;取出一个字节
0040183D   .C0E2 04       shl   dl, 4                                        ;左移四位
00401840   .8BF0          mov   esi, eax
00401842   .D1EE          shr   esi, 1
00401844   .8896 58145700 mov   byte ptr , dl                  ;保存
0040184A   .EB 19         jmp   short 00401865
0040184C   >32C0          xor   al, al
0040184E   .5E            pop   esi
0040184F   .8BE5          mov   esp, ebp
00401851   .5D            pop   ebp
00401852   .C3            retn
00401853   >8BD0          mov   edx, eax
00401855   .D1EA          shr   edx, 1
00401857   .8DB2 58145700 lea   esi, dword ptr
0040185D   .8A90 58145700 mov   dl, byte ptr
00401863   .0016          add   byte ptr , dl
00401865   >8A98 59145700 mov   bl, byte ptr                   ;再取出一个字节
0040186B   .8BD1          mov   edx, ecx
0040186D   .83E2 01       and   edx, 1
00401870   .8BF1          mov   esi, ecx
00401872   .75 0D         jnz   short 00401881
00401874   .C0E3 04       shl   bl, 4
00401877   .D1EE          shr   esi, 1
00401879   .889E 58145700 mov   byte ptr , bl
0040187F   .EB 0E         jmp   short 0040188F
00401881   >D1EE          shr   esi, 1
00401883   .009E 58145700 add   byte ptr , bl                  ;和前一次运算后的结果相加
00401889   .8DB6 58145700 lea   esi, dword ptr
0040188F   >8B75 FC       mov   esi, dword ptr
00401892   .8A98 5A145700 mov   bl, byte ptr
00401898   .8DB406 591457>lea   esi, dword ptr
0040189F   .85FF          test    edi, edi
004018A1   .75 0D         jnz   short 004018B0
004018A3   .C0E3 04       shl   bl, 4
004018A6   .D1EE          shr   esi, 1
004018A8   .889E 58145700 mov   byte ptr , bl
004018AE   .EB 0E         jmp   short 004018BE
004018B0   >D1EE          shr   esi, 1
004018B2   .009E 58145700 add   byte ptr , bl
004018B8   .8DB6 58145700 lea   esi, dword ptr
004018BE   >85D2          test    edx, edx
004018C0   .75 1D         jnz   short 004018DF
004018C2   .8A90 5B145700 mov   dl, byte ptr                   ;再取出一个字节
004018C8   .8B75 F8       mov   esi, dword ptr
004018CB   .C0E2 04       shl   dl, 4                                        ;左移四位
004018CE   .8DB406 591457>lea   esi, dword ptr
004018D5   .D1EE          shr   esi, 1
004018D7   .8896 58145700 mov   byte ptr , dl                  ;保存
004018DD   .EB 1E         jmp   short 004018FD
004018DF   >8B55 F8       mov   edx, dword ptr
004018E2   .8DB402 591457>lea   esi, dword ptr
004018E9   .8A90 5B145700 mov   dl, byte ptr                   ;再取出一个字节
004018EF   .D1EE          shr   esi, 1                                       ;右移一位
004018F1   .0096 58145700 add   byte ptr , dl                  ;加上前一次运算后的结果
004018F7   .8DB6 58145700 lea   esi, dword ptr
004018FD   >83C1 04       add   ecx, 4
00401900   .83C0 04       add   eax, 4
00401903   .83F9 11       cmp   ecx, 11
00401906   .^ 0F8C 24FFFFFF jl      00401830
0040190C   .B8 58145700   mov   eax, 00571458
00401911   .5F            pop   edi
00401912   .8D50 01       lea   edx, dword ptr
00401915   .5B            pop   ebx
00401916   >8A08          mov   cl, byte ptr                            ;取出一个字节
00401918   .40            inc   eax
00401919   .84C9          test    cl, cl                                       ;判断是否是0
0040191B   .^ 75 F9         jnz   short 00401916
0040191D   .2BC2          sub   eax, edx                                     ;得到00字节的偏移
0040191F   .83F8 08       cmp   eax, 8                                       ;和8比较
00401922   .0f95c0      setne   al                                           ;不相等则返回1 相等返回0
00401925   .5E            pop   esi
00401926   .8BE5          mov   esp, ebp
00401928   .5D            pop   ebp
00401929   .C3            retn
```
分析易知,这个函数的作用就是将输入的十六进制文本转换成字节集的形式,然后判断00字节的偏移是否是8,如果是8则返回0。
返回后,程序会判断这个函数的返回值,如果返回1则激活[确定]按钮,返回0则不激活[确定]按钮
![这里写图片描述](https://attach.52pojie.cn/album/201712/10/185331e6858uzusun5p5ks.png)
**0x03 寻找按钮事件**
由刚刚分析的十六进制文本转换成字节集的函数可以得知,结果被存放在00571458处。给00571458下硬件访问断点,大小是1。然后点击[确定]按钮。在0040193A处断下。接着这个函数往下分析。
![这里写图片描述](https://attach.52pojie.cn/album/201712/10/185332ur53kqmz4klamk4q.png)
这段函数再判断00字节的偏移是不是8,如果是8则成功,否则触发异常。
这个和之前那个十六进制文本转换字节集的函数的验证结果是矛盾的。
那个函数判断00字节的偏移是否是8,如果是8则不会解锁[确定]按钮。但是在[确定]的按钮事件里又判断这个偏移是否是8,如果是8则成功,不是8则触发异常。
说明这个是一个烟雾弹。不是真正的算法。
**0x04 分析真正的算法**
虽然按钮事件是烟雾弹,但是我猜测这个字节集绝对会用上,因此我没有删掉这个硬件断点。
再按F9,即可来到真正的算法函数。看来我的猜想是正确的。
来到00401987处,找到函数头00401970即可。
![这里写图片描述](https://attach.52pojie.cn/album/201712/10/185332drqo3pop2ewnsruy.png)
用IDA载入,发现算法函数中有调用一个call,然后用转换成十六进制的Key去解一个方程组,八元一次方程组。
![这里写图片描述](https://attach.52pojie.cn/album/201712/10/185333wjhjj5j2zh40t241.png)
先解这个八元一次方程组,我手解的时候发现后面几个字节是除不开的。。。于是只好写个脚本来跑。
代码如下:
```
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
      int a;//77 33 31 6C 64 30 6E 65
      for(a = 0;a <= 255;a++)
      {
                for(a = 0;a <= 255;a++)
                {
                        for(a = 0;a <= 255;a++)
                        {
                              for(a = 0;a <= 255;a++)
                              {
                                        if((unsigned __int8)(a + a + a + a) != 71)
                                                continue;
                                        if((unsigned __int8)a != (unsigned __int8)a + 68)
                                                continue;
                                        if((unsigned __int8)a != (unsigned __int8)a + 2)
                                                continue;
                                        if((unsigned __int8)a != (unsigned __int8)a - 59)
                                                continue;
                                        //77 33 31 6C
                                        for(int i = 0;i < 4;i++)
                                                printf("%0.02X ",a);
                                        goto NextWhile;
                              }
                        }
                }
      }
NextWhile:
      for(a = 0;a <= 255;a++)
      {
                for(a = 0;a <= 255;a++)
                {
                        for(a = 0;a <= 255;a++)
                        {
                              for(a = 0;a <= 255;a++)
                              {
                                        if((unsigned __int8)(a + a + a) != 3)
                                                continue;
                                        if((unsigned __int8)a != (unsigned __int8)a + 10)
                                                continue;
                                        if((unsigned __int8)a != (unsigned __int8)a + 9)
                                                continue;
                                        if((unsigned __int8)a != (unsigned __int8)a + 52)
                                                continue;
                                        //64 30 6E 65
                                        for(int i = 4;i < 8;i++)
                                                printf("%0.02X ",a);
                                        goto Next;
                              }
                        }
                }
      }
Next:
      puts("");
      system("pause");
      return 0;
}
```
运行结果如图所示:
![这里写图片描述](https://attach.52pojie.cn/album/201712/10/185333fy7raaukiu98vaj8.png)
由此可以得知,正确Key的十六进制形式应为77 33 31 6C 64 30 6E 65
**0x05 重点函数00401970的逆向**
函数代码如图所示:
![这里写图片描述](https://attach.52pojie.cn/album/201712/10/185334wro8czspflyrfyo3.png)
这段算法是Rijndael算法中的S-Box变换。
Flag只有16个字节,每个字节都是从00~FF的,因此我用的枚举。
方式就是一个字节一个字节枚举,和刚刚的解方程的代码的枚举方式差不多。
为了节省时间,我写了一个DLL来做这些工作,然后注入进去枚举Key,得到正确的Key后用信息框提示。
代码如下:
![这里写图片描述](https://attach.52pojie.cn/album/201712/10/185334d7ze6molto77o6i6.png)
Hex函数的功能就是将十六进制的字节(00~FF)转换成文本。
![这里写图片描述](https://attach.52pojie.cn/album/201712/10/185335e2bsns6k8ke75rci.png)
将这些代码编译成一个DLL,然后注入到CM的进程中即可。得到结果如下:
![这里写图片描述](https://attach.52pojie.cn/album/201712/10/185335pkrze8zy8rrla1dl.png)
拿到Flag:C7C536FC625CEFCD
放到原版CM中验证一下,可以通过。
![这里写图片描述](https://attach.52pojie.cn/album/201712/10/185335y8znt3zqz1seqtjq.png)
全文完。

附:CrackMe源文件和两个脚本的源代码

kk1212 发表于 2017-12-10 19:23

很难得的学习笔记。感谢、

丨烟花丶易冷 发表于 2017-12-10 22:09

路过留名

不羡云卿颜_ 发表于 2017-12-10 23:12

学习了,谢谢楼主分享

Dispa1r 发表于 2017-12-11 08:16

幸好没玩这个,这么复杂{:301_998:}

rendercc 发表于 2017-12-11 08:35


幸好没玩这个,这么复杂

郁郁之风 发表于 2017-12-11 08:49

感谢楼主分享

h19981126g 发表于 2017-12-11 09:50

略叼,看不懂!~~~

xiaofengzi 发表于 2017-12-11 14:42

下载下来实际操作操作,学学楼主的分析过程

a763947059 发表于 2017-12-11 15:15

不懂^_^^_^^_^^_^
页: [1] 2 3 4
查看完整版本: 一个CrackMe的分析