kn0sky 发表于 2020-10-7 09:12

160个CreakMe系列-001-Acid_burn

## 前言

初学逆向,目前以这网上盛传的“适合破解新手的160个CreakMe”为目标,来进行实践练习与学习;

本次我所使用的环境和工具如下:

操作系统:Windows 10 2004(物理机)

工具:PEiD、x32dbg

## 程序分析

第1个程序是一个验证的程序,提供了两种验证方式,分别是:通过序列号和用户名验证,和单独通过序列号验证

这次分析的目标是:实现验证的破解,以及分析出序列号的算法

## 分析目标01:暴力破解两种验证方式

### 1.1 用户名/序列号验证暴力破解

根据字符串搜索定位到弹窗那块的代码部分

修改判断逻辑

```asm
0042FAF8 | 8B55 F0                  | mov edx,dword ptr ss:         | :"Enter your serial here !"
0042FAFB | 8B45 F4                  | mov eax,dword ptr ss:            | :"CW-6560-CRACKED"
0042FAFE | E8 F93EFDFF            | call acid burn.4039FC                   |
;和正确的序列号进行对比
0042FB03 | 75 1A                  | jne acid burn.42FB1F                  |
;这里改成|0x9090,也就是nop填充这个751A即可强行跳过判断
0042FB05 | 6A 00                  | push 0                                  |
0042FB07 | B9 CCFB4200            | mov ecx,acid burn.42FBCC                | 42FBCC:"Congratz !!"
0042FB0C | BA D8FB4200            | mov edx,acid burn.42FBD8                | 42FBD8:"Good job dude =)"
0042FB11 | A1 480A4300            | mov eax,dword ptr ds:         |
0042FB16 | 8B00                     | mov eax,dword ptr ds:            |
0042FB18 | E8 53A6FFFF            | call acid burn.42A170                   |
0042FB1D | EB 18                  | jmp acid burn.42FB37                  |
0042FB1F | 6A 00                  | push 0                                  |
0042FB21 | B9 74FB4200            | mov ecx,acid burn.42FB74                | 42FB74:"Try Again!"
0042FB26 | BA 80FB4200            | mov edx,acid burn.42FB80                | 42FB80:"Sorry , The serial is incorect !"
0042FB2B | A1 480A4300            | mov eax,dword ptr ds:         |
0042FB30 | 8B00                     | mov eax,dword ptr ds:            |
0042FB32 | E8 39A6FFFF            | call acid burn.42A170                   |
0042FB37 | 33C0                     | xor eax,eax                           |
```

### 1.2 序列号单独验证暴力破解

搜索字符串,定位到弹窗的代码部分

修改判断逻辑:跟上面那个判断方法和修改方法一样

```asm
0042F4CA | 8B45 F0                  | mov eax,dword ptr ss:         | :"666"
0042F4CD | 8B55 F4                  | mov edx,dword ptr ss:            | :"Hello Dude!"
0042F4D0 | E8 2745FDFF            | call acid burn.4039FC                   |

0042F4D5 | 75 1A                  | jne acid burn.42F4F1                  |
; 用nop填充这个75 1A即可无条件判断成功

0042F4D7 | 6A 00                  | push 0                                  |
0042F4D9 | B9 64F54200            | mov ecx,acid burn.42F564                | ecx:"Failed!", 42F564:"Congratz!"
0042F4DE | BA 70F54200            | mov edx,acid burn.42F570                | 42F570:"God Job dude !! =)"
0042F4E3 | A1 480A4300            | mov eax,dword ptr ds:         |
0042F4E8 | 8B00                     | mov eax,dword ptr ds:            |
0042F4EA | E8 81ACFFFF            | call acid burn.42A170                   |
0042F4EF | EB 18                  | jmp acid burn.42F509                  |
0042F4F1 | 6A 00                  | push 0                                  |
0042F4F3 | B9 84F54200            | mov ecx,acid burn.42F584                | ecx:"Failed!", 42F584:"Failed!"
0042F4F8 | BA 8CF54200            | mov edx,acid burn.42F58C                | 42F58C:"Try Again!!"
0042F4FD | A1 480A4300            | mov eax,dword ptr ds:         |
0042F502 | 8B00                     | mov eax,dword ptr ds:            |
0042F504 | E8 67ACFFFF            | call acid burn.42A170                   |
0042F509 | 33C0                     | xor eax,eax                           |
```

## 分析目标02:分析出两种序列号的算法

### 2.1 用户名/序列号的序列号分析

搜索字符串`Sorry`,发现存在两处地方用到了这个字符串:一个一个判断

第一个字符串是在登录是否成功这里判断的,这一块完整代码在1.1部分有,这里就只放关键部分了:

```asm
0042FAF8 | 8B55 F0                  | mov edx,dword ptr ss:         | :"123456"
0042FAFB | 8B45 F4                  | mov eax,dword ptr ss:            | :"CW-8528-CRACKED"
0042FAFE | E8 F93EFDFF            | call acid burn.4039FC                   |
0042FB03 | 75 1A                  | jne acid burn.42FB1F                  | 用户名/序列号验证
```

这里进行对比的那个字符串应该就是正确的值,用这个序列号去登录,即可登录成功

另一个字符串的位置在:

```asm
0042FA4D | A1 6C174300            | mov eax,dword ptr ds:         | 0043176C:&"hello"
0042FA52 | E8 D96EFDFF            | call acid burn.406930                   | 获取字符串长度
0042FA57 | 83F8 04                  | cmp eax,4                               | 如果大于4就跳转进行序列号验证,如果小于4则直接Sorry
0042FA5A | 7D 1D                  | jge acid burn.42FA79                  |
0042FA5C | 6A 00                  | push 0                                  |
0042FA5E | B9 74FB4200            | mov ecx,acid burn.42FB74                | 42FB74:"Try Again!"
0042FA63 | BA 80FB4200            | mov edx,acid burn.42FB80                | edx:&"d稝", 42FB80:"Sorry , The serial is incorect !"
0042FA68 | A1 480A4300            | mov eax,dword ptr ds:         |
0042FA6D | 8B00                     | mov eax,dword ptr ds:            |
0042FA6F | E8 FCA6FFFF            | call acid burn.42A170                   | MessageBox
0042FA74 | E9 BE000000            | jmp acid burn.42FB37                  |
```

判断用户名长度的,长度大于4则进行验证,长度小于4则直接返回验证失败

将jge改成jmp即可绕过用户名长度限制

经过反复测试发现,序列号的生成与用户名的首字符有关,比如1:4018,2:4100,a:7954,等

以及发现计算的数字会出现在内存地址0019E71E的位置上

继续测试,找到该地址是在哪里被填充的



是在这里,4018被填充到0019E71E的,这里又是movsb指令,是从0019E6AC地方复制来的,下面来找找这个地址的值是哪里来的

经过又一遍的步进,发现上面不远处的这个函数执行完会在0019E6AC填充中间数字:




进入函数接着步进发现了一个循环在填充中间数字:



这里的EAX里的值就是4018,中间数值依然是之前就算好的,这里的代码是通过对10取余一个一个数字填到内存中去,要找到中间数字是怎么算的还得往前找,找EAX的值是哪来的:



值是ESI地址里传给EAX 的,接着往上找ESI的地址(0019F72C)的值是哪来的:



生气,往里面找了半天,结果这个值就在最外面一层就算好了,计算方法是:

```
最终结果(十六进制) = 首字符的ASCII码(十六进制) * 29h * 2h
```

这么算的话,2开头的用户名应该是 32h * 29h * 2h = 1004h = 4100,和之前测试结果吻合

注册机代码:

```cpp
#include<stdio.h>
#include<windows.h>
#include<string.h>
int main() {
        char Username;
        printf_s("请输入用户名:");
        scanf_s("%s",Username,(unsigned int)sizeof(Username));

        int FirstASCII = Username;
        int Midcode = FirstASCII * 82;

        char* Front = (char*)"序列号为: CW";
        char Middle = {0};
        sprintf_s(Middle,"%d",Midcode);
        char* Behind = (char*)"CRACKED";

        char Res = {0};
        strcpy_s(Res,Front);
        strcat_s(Res, "-");
        strcat_s(Res, Middle);
        strcat_s(Res, "-");
        strcat_s(Res, Behind);
        printf("%s",Res);

        Sleep(10000);
        return 0;
}
```

效果展示:




### 2.2 序列号单独验证序列号分析

通过修改不同的序列号进行验证,看见反汇编窗口中都跟同样的值进行比较,所以判断这个序列号单独验证是一个固定的值,是硬编码进去的验证,值为:`Hello Dude!`

## 总结

第一次独自分析破解CreakMe,遇到了挺多坑,这次让我学到了要去找某一个数值是怎么冒出来的,不妨先看看函数调用之前的参数是什么,也许这会有帮助(对应2.1部分)

whngomj 发表于 2020-10-9 10:31

谢谢分享

深蓝海琼 发表于 2020-10-9 11:04

学习了学习了

honestzmh 发表于 2020-10-9 14:24

谢谢分享
页: [1]
查看完整版本: 160个CreakMe系列-001-Acid_burn