好友
阅读权限30
听众
最后登录1970-1-1
|
本帖最后由 pk8900 于 2017-12-14 08:26 编辑
昨晚,继续研究 了【适合破解新手的160个crackme练手】第39个CrackMe: damn.exe,虽然不是太难,还是琢磨了一晚上,分析完后编写了注册机,进行第二步PATCH探索,搜索论坛未找到关于这个CrackMe的帖子,所以把我分析的过程写下来:
【crackme简介】
下载地址:http://pan.baidu.com/share/link?shareid=541269&uk=4146939145
FASM,壳:ASPack 1.08.03 -> Alexey Solodovnikov,Name+Key验证方式,界面下另有一个LOCKED!按钮,看作者的About说明,分两部分,写出注册机,PATCH使程序显示UNLOCKED,因为程序有自较验,这块有些难度,我开始绕了不少弯路。
分析工具:X64dbg,OD,注册机编写:VS2013
【crackme截图】
【算法分析过程】
对<user32.GetDlgItemTextA>函数下断点,很快定位到程序算法处,共分为两部分。
0x01、对用户名进行处理,得到一个DWORD值:
[Asm] 纯文本查看 复制代码 004012F4 | 8B 0D 89 23 40 00 | mov ecx, dword ptr ds:[0x402389] | [0x00402389] --用户名长度
004012FA | 85 C9 | test ecx, ecx |
004012FC | 74 71 | je damn_d.40136F | 用户名不为空
004012FE | 49 | dec ecx | 长度-1
004012FF | 8B F1 | mov esi, ecx | ESI为循环判断数
00401301 | BF 53 23 40 00 | mov edi, damn_d.402353 | 402353: 用户名指针
00401306 | BB 4E 4D 41 44 | mov ebx, 0x44414D4E | EBX 初始为0x44414D4E(SERIAL)
0040130B | 33 D2 | xor edx, edx | EDX:用户名字节累加值(SUM)
0040130D | 8B CA | mov ecx, edx | ECX计数器清零=N
0040130F | 33 C0 | xor eax, eax | 循环开始
00401311 | 8A 04 0F | mov al, byte ptr ds:[edi+ecx] | AL 循环压入用户名字节[N]
00401314 | 03 D0 | add edx, eax | 用户名字节累加
00401316 | D1 CB | ror ebx, 0x1 | SERAL 循环右移 1位
00401318 | D3 CB | ror ebx, cl | SERAL 循环右移 N位
0040131A | 33 DA | xor ebx, edx | SERAL 异或 SUM
0040131C | 3B CE | cmp ecx, esi |
0040131E | 74 03 | je damn_d.401323 |
00401320 | 41 | inc ecx | 累加计数器N
00401321 | EB EC | jmp damn_d.40130F |
00401323 | 81 CB 10 10 10 10 | or ebx, ksfmon.10101010 | SERAL 位或 0x10101010 即最终SERAL
00401329 | 87 DA | xchg edx, ebx |
0040132B | BF 21 23 40 00 | mov edi, damn_d.402321 | 402321:输入序列号的指针
以上代码通过用户名算出正确的序列号过程,最终得到一个DWORD值,即为对应我们输入的用户名的序列号,接下来会进行对比较验:
[Asm] 纯文本查看 复制代码 00401323 | 81 CB 10 10 10 10 | or ebx, ksfmon.10101010 | SERAL 位或 0x10101010 即最终SERAL
00401329 | 87 DA | xchg edx, ebx |
0040132B | BF 21 23 40 00 | mov edi, damn_d.402321 | 402321:输入序列号的指针
00401330 | 8B 0D 8D 23 40 00 | mov ecx, dword ptr ds:[0x40238D] |
00401336 | 83 F9 08 | cmp ecx, 0x8 | 判断位数:必须为8位
00401339 | 75 34 | jne damn_d.40136F |
0040133B | 33 C9 | xor ecx, ecx |
0040133D | 33 C0 | xor eax, eax |
0040133F | C1 C2 08 | rol edx, 0x8 | 循环右移8位,将最高位字节移至最低位
00401342 | 8A C2 | mov al, dl |
00401344 | 8A D8 | mov bl, al | 取出 最高位1节位
00401346 | 24 0F | and al, 0xF | AL 为低位
00401348 | C0 EB 04 | shr bl, 0x4 | BL 为高位
0040134B | 80 E3 0F | and bl, 0xF | 以下四步为转十六进制为ASCII
0040134E | 3C 0A | cmp al, 0xA | A:'\n'
00401350 | 1C 69 | sbb al, 0x69 |
00401352 | 2F | das |
00401353 | 38 44 4F 01 | cmp byte ptr ds:[edi+ecx*2+0x1], al | 与序列号对应位进行比较
00401357 | 75 16 | jne damn_d.40136F |
00401359 | 8A C3 | mov al, bl |
0040135B | 3C 0A | cmp al, 0xA | 以下四步为转十六进制为ASCII
0040135D | 1C 69 | sbb al, 0x69 |
0040135F | 2F | das |
00401360 | 38 04 4F | cmp byte ptr ds:[edi+ecx*2], al | 与序列号对应位进行比较
00401363 | 75 0A | jne damn_d.40136F |
00401365 | 41 | inc ecx |
00401366 | 83 F9 04 | cmp ecx, 0x4 |
00401369 | 75 D2 | jne damn_d.40133D | 循环4次
0040136B | 33 C0 | xor eax, eax |
0040136D | 40 | inc eax | 比较成功的关键标志,返回1
0040136E | C3 | ret |
0040136F | 33 C0 | xor eax, eax | 比较成功的关键标志,返回0
00401371 | C3 | ret |
0x02:序列号比较部分:
通过上段代码分析,程序通过循环右移8位,依次取出SERAL的1个字节,然后拆分为高位和低位,转成ASCII值后也我们输入的序列号进行对比,完全一样的话,执行inc eax ,将EAX置1,这是比较成功的关键标志。
比较部分代码中这四句很意思,也很经典:[Asm] 纯文本查看 复制代码 0040134B | 80 E3 0F | and bl, 0xF | 以下四步为转十六进制为ASCII
0040134E | 3C 0A | cmp al, 0xA | A:'\n'
00401350 | 1C 69 | sbb al, 0x69 |
00401352 | 2F | das
短短4句代码,将16进制直接转为ASCII字符,百度一下,好像和BCD码有关,资料不多,也没太清楚这个BCD码的具体原理。
通过上述算法分析,就可以编写注册机了。
C++代码如下:
[C++] 纯文本查看 复制代码 #define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include <windows.h>
using namespace std;
void myRor1(DWORD * myd, unsigned char n);
char * yourname;
unsigned long serial;
void main()
{
serial = 0x44414D4E;
unsigned long name_add =0;
yourname = new char[260];
memset(yourname, 0, 260);
cout << "Enter your name:";
gets(yourname);
for (unsigned char x = 0; x < strlen(yourname); x++)
{
name_add += yourname[x];
myRor1(&serial, 1);
myRor1(&serial, x);
serial ^= name_add;
}
serial |= 0x10101010;
cout << "your serial is:" << hex << uppercase << serial << endl;
system("pause");
delete[] yourname;
}
void myRor1(DWORD * myd, unsigned char n)
{
_asm
{
MOV EBX, myd;
MOV eax, [EBX];
MOV cl, n;
ROR EAX,cl;
MOV [EBX], eax;
}
}
提供一组测试用户名:52pojie.cn 序列号:BB5274B2
接下来是第二部分,PATCH程序使程序显示为UNLOCKED。
通过程序API调用,<user32.LoadBitmapA>,很快定位到相应改变图片的代码位置:
[Asm] 纯文本查看 复制代码 00401190 | FF 75 08 | push dword ptr ss:[ebp+0x8] | HANDLE hDlg
00401193 | E8 61 02 00 00 | call <damn_d.GetDlgItem> | GetDlgItem
00401198 | A3 85 23 40 00 | mov dword ptr ds:[0x402385], eax |
0040119D | E8 D0 01 00 00 | call <damn_d.sub_401372> |
004011A2 | BF E7 22 40 00 | mov edi, damn_d.4022E7 | EDI指针:&L"f"
004011A7 | A1 13 23 40 00 | mov eax, dword ptr ds:[0x402313] | 关键偏移值
004011AC | 03 C0 | add eax, eax |
004011AE | 03 C0 | add eax, eax |
004011B0 | 03 F8 | add edi, eax |
004011B2 | 8B 3F | mov edi, dword ptr ds:[edi] |
004011B4 | 8B 07 | mov eax, dword ptr ds:[edi] |
004011B6 | 50 | push eax | LPCTSTR lpBitmapName
004011B7 | FF 35 95 23 40 00 | push dword ptr ds:[0x402395] | HMODULE hInstance
004011BD | E8 49 02 00 00 | call <damn_d.LoadBitmapA> | LoadBitmapA
004011C2 | A3 0F 23 40 00 | mov dword ptr ds:[0x40230F], eax |
分析上述代码,可以得知,GetDlgItem取得按钮的句柄,LoadBitmapA用来改变按钮图片, LPCTSTR lpBitmapName为显示的图片名,EDI指针:最开始指向"f",加上:[0x402313]*2*2的偏移后,来确定显示哪个图片,如果:[0x402313]==0,EDI指针无偏移,f图片为Locded,:[0x402313]==1时,EDI指针指向"f",f图片为Cracked,[0x402313]==2时,EDI指针指向"g",g图片为UnLockked,
所以关键值就是[0x402313],现在往上看这个调用:0040119D | E8 D0 01 00 00 | call <damn_d.sub_401372>,其代码如下:
[Asm] 纯文本查看 复制代码 00401372 | BF 00 20 40 00 | mov edi, damn_d.402000 |
00401377 | BE 00 10 40 00 | mov esi, <damn_d.EntryPoint> | 401000:"j"
0040137C | B9 20 03 00 00 | mov ecx, 0x320 | 循环较验 0042000+0x320段320字节代码
00401381 | 33 DB | xor ebx, ebx |
00401383 | 33 C0 | xor eax, eax |
00401385 | 8A 04 0F | mov al, byte ptr ds:[edi+ecx] |
00401388 | 03 D8 | add ebx, eax |
0040138A | D1 CB | ror ebx, 0x1 |
0040138C | 33 D9 | xor ebx, ecx |
0040138E | 49 | dec ecx |
0040138F | 75 F2 | jne damn_d.401383 |
00401391 | B9 B2 03 00 00 | mov ecx, 0x3B2 | 循环较验 0041000+0x3B2段0x3B2字节代码
00401396 | 33 D2 | xor edx, edx |
00401398 | 33 C0 | xor eax, eax |
0040139A | 8A 04 0E | mov al, byte ptr ds:[esi+ecx] |
0040139D | 03 D0 | add edx, eax |
0040139F | D1 CA | ror edx, 0x1 |
004013A1 | 33 D1 | xor edx, ecx |
004013A3 | 49 | dec ecx |
004013A4 | 75 F2 | jne damn_d.401398 |
004013A6 | 33 DA | xor ebx, edx |
004013A8 | C1 C3 03 | rol ebx, 0x3 |
004013AB | 03 D3 | add edx, ebx |
004013AD | 81 E2 55 55 55 55 | and edx, 0x55555555 |
004013B3 | 90 | nop |
004013B4 | 81 F2 45 54 14 11 | xor edx, 0x11145445 | 检验值为:0x11145445,异或结果存入EDX
004013BA | 74 08 | je damn_d.4013C4 |
004013BC | 33 D2 | xor edx, edx |
004013BE | 42 | inc edx | EDX置为1,程序最终显示Cracked
004013BF | E9 58 0C 00 00 | jmp damn_d.40201C |
004013C4 | E9 37 0C 00 00 | jmp damn_d.402000 |
上面这段代码会进行自检验,范围为0x 0042000~0x00402320和0041000~0x004013B2,取得异或和,与0x11145445进行异或比较,来进行自检验,检验值存在EDX中,最后的JMP跳转,分别如下:
[Asm] 纯文本查看 复制代码 0040201C | 89 15 13 23 40 00 | mov dword ptr ds:[0x402313], edx |
00402022 | C3 | ret |
[Asm] 纯文本查看 复制代码 00402000 | A1 0B 23 40 00 | mov eax, dword ptr ds:[0x40230B] |
00402005 | 85 C0 | test eax, eax |
00402007 | 75 13 | jne damn_d.40201C |
00402009 | E8 00 00 00 00 | call <damn_d.sub_40200E> | call $0
0040200E | 5F | pop edi |
0040200F | C6 47 0B 42 | mov byte ptr ds:[edi+0xB], 0x42 | 这两句代码会修改程序本身代码,使检验失败
00402013 | 66 C7 47 0C D1 E2 | mov word ptr ds:[edi+0xC], 0xE2D1 |
00402019 | EB 90 | jmp damn_d.401FAB |
0040201B | 40 | inc eax |
0040201C | 89 15 13 23 40 00 | mov dword ptr ds:[0x402313], edx |
00402022 | C3 | ret |
这两段代码都会把自校验的值EDX赋给:[0x402313](全局常量),如果异或后的值为2,刚可以实现程序的UnLocked效果,所以修改代码:
[Asm] 纯文本查看 复制代码 004013B4 | 81 F2 45 54 14 11 | xor edx, 0x11145445 | 检验值为:0x11145445,异或结果存入EDX
004013BA | 74 08 | je damn_d.4013C4 |
[Asm] 纯文本查看 复制代码 004013B4 | 81 F2 45 54 14 11 | xor edx, 0x11145447 | 改检验值为:0x11145447,异或结果存入EDX
004013BA | 74 08 | jmp damn_d.4013C4 | 改Jmp跳转
这样就可以避开程序的自校验范围,实现PATCH效果。当然也可以在别的地方修改,可能会麻烦一点。 |
附CrackMe三种状态的截图:
分析过程有不对或不到位的地方,欢迎大家回贴交流,共同探讨。
|
免费评分
-
查看全部评分
|