先运行下这个CrackMe,没啥特别的:
用PEID侦测下,看到它是用汇编写的:
OD打开之,Ctrl+N看到熟悉的GetDlgItemTextA,轻松下个断点准备动工,但出乎意料的是输入用户名序列号并点击Check后,并没有断下来,而是直接弹出了验证失败对话框.为什么会这样呢?其实我当时也不知道咋回事....看了文档的解说后原来该程序中存在简单的反调试(anti-debug)手段,即运行时检测关键处是否有0xCC软断点,我们可以通过硬件断点来破除它.于是,我们在GetDlgItemTextA处下个硬件访问断点,再重来就断下来了,如下:
1 00401296 $ 55 push ebp 2 00401297 . 8BEC mov ebp, esp 3 00401299 . 60 pushad 4 0040129A . BE FE124000 mov esi, 004012FE 5 0040129F . 56 push esi 6 004012A0 . 64:FF35 00000>push dword ptr fs:[0] 7 004012A7 . 64:8925 00000>mov dword ptr fs:[0], esp 8 004012AE . FF35 3C304000 push dword ptr [40303C] ; /Count = 1E (30.) 9 004012B4 . 68 00304000 push 00403000 ; |Buffer = KGM1Tal.00403000 10 004012B9 . 68 EC030000 push 3EC ; |ControlID = 3EC (1004.) 11 004012BE . FF75 08 push dword ptr [ebp+8] ; |hWnd 12 004012C1 . E8 D6020000 call <jmp.&user32.GetDlgItemTextA> ; \GetDlgItemTextA 13 004012C6 . FF35 40304000 push dword ptr [403040] ; /Count = 14 (20.) 14 004012CC . 68 23304000 push 00403023 ; |Buffer = KGM1Tal.00403023 15 004012D1 . 68 ED030000 push 3ED ; |ControlID = 3ED (1005.) 16 004012D6 . FF75 08 push dword ptr [ebp+8] ; |hWnd 17 004012D9 . E8 BE020000 call <jmp.&user32.GetDlgItemTextA> ; \GetDlgItemTextA 18 004012DE . E8 4F000000 call 00401332 ; 检测过程 19 004012E3 . 68 53304000 push 00403053 ; zwatrqlcghpsxyenvbjdfkmu 20 004012E8 . E8 C9000000 call 004013B6 ; 进一步检测 21 004012ED . E8 DC010000 call 004014CE ; 最后一步检测
在该区域开头处查看信息面板看到调用此处的地址:
1 Local call from 00401279
来到调用处:
1 00401230 /. 55 push ebp 2 00401231 |. 8BEC mov ebp, esp 3 00401233 |. 817D 0C 10010>cmp [arg.2], 110 ; WM_INITDLALOG 4 0040123A |. 75 1E jnz short 0040125A 5 0040123C |. 68 057F0000 push 7F05 ; /RsrcName = IDI_WINLOGO 6 00401241 |. 6A 00 push 0 ; |hInst = NULL 7 00401243 |. E8 5A030000 call <jmp.&user32.LoadIconA> ; \LoadIconA 8 00401248 |. 50 push eax ; /lParam 9 00401249 |. 6A 01 push 1 ; |wParam = 1 10 0040124B |. 68 80000000 push 80 ; |Message = WM_SETICON 11 00401250 |. FF75 08 push [arg.1] ; |hWnd 12 00401253 |. E8 56030000 call <jmp.&user32.SendMessageA> ; \SendMessageA 13 00401258 |. EB 36 jmp short 00401290 14 0040125A |> 817D 0C 11010>cmp [arg.2], 111 ; WM_COMMAND 15 00401261 |. 75 1D jnz short 00401280 16 00401263 |. 817D 10 E9030>cmp [arg.3], 3E9 17 0040126A |. 75 24 jnz short 00401290 18 0040126C |. E8 A8020000 call 00401519 ; anti-debug 19 00401271 |. E8 33020000 call 004014A9 ; anti-debug 20 00401276 |. FF75 08 push [arg.1] 21 00401279 |. E8 18000000 call 00401296 ; 检测序列号过程
其中的00401519和004014A9即是两个反调试检测函数:
1 00401519 $ BF 96124000 mov edi, 00401296 ; Entry address 2 0040151E . B9 00010000 mov ecx, 100 3 00401523 . B0 99 mov al, 99 4 00401525 . 34 55 xor al, 55 ; 99H^55H=CCH 5 00401527 . F2:AE repne scas byte ptr es:[edi] ; 扫描从00401296开始的100H字节是否有0xCC 6 00401529 . 85C9 test ecx, ecx 7 0040152B . 74 06 je short 00401533 ; 检测正常 8 0040152D . 5E pop esi 9 0040152E . 33F6 xor esi, esi 10 00401530 . 57 push edi 11 00401531 .^ EB C2 jmp short 004014F5 ; 检测到跟踪,完蛋 12 00401533 > C3 retn
可以看到,00401519函数进行了软断点检测,其中的00401296就是点击Check按钮的控件响应函数地址.而004012A9做了个补充,它检测系统领空User.dll的GetDlgItemTextA实现处的开始6个字节是否下了软断点,因为破解者一般都习惯在开头处下断.这样,就形成了一个比较粗糙的反调试机制.
1 004014A9 $ BE 9C154000 mov esi, <jmp.&user32.GetDlgItemText>; Entry address 2 004014AE . 8B7E 02 mov edi, dword ptr [esi+2] 3 004014B1 . 8B3F mov edi, dword ptr [edi] 4 004014B3 . B9 06000000 mov ecx, 6 5 004014B8 . B0 CC mov al, 0CC 6 004014BA . F2:AE repne scas byte ptr es:[edi] 7 004014BC . 85C9 test ecx, ecx 8 004014BE . 74 06 je short 004014C6 9 004014C0 . 5E pop esi 10 004014C1 . 33F6 xor esi, esi 11 004014C3 . 57 push edi 12 004014C4 . EB 2F jmp short 004014F5 13 004014C6 > C3 retn
当然了,目前的各种反汇编反调试手段相当丰富刺激,根据我看的<<黑客反汇编解密>>(翻译水平实在叫人头痛)的介绍(笔记在公司,带不回来呀...),以后接触到了商业级软件的保护机制,会像速降一样刺激的....
接下来的工作就没啥特别的了,边调试边翻译汇编码边做记录就行了,熟悉和熟练了就会越来越快...何况这还不是C++写的...何况这个还不是完全优化版本...
最后写出KeyGen:
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <string.h> 4 5 void Keygen(const char* user, char* code) 6 { 7 //求序列号用的密匙 8 const char szBuffer[] = { "ZWATRQLCGHPSXYENVBJDFKMU" }; 9 10 unsigned char a = 0; 11 for(int i=0; i<strlen(user); ++i) a += user[i]; 12 a %= 0x18; 13 //序列号第2位必为E 14 code[1] = 'E'; 15 //序列号第1位 16 code[0] = szBuffer[a]; 17 int b = a; 18 if(b > 0x18) b -= 0x18; 19 b += a; 20 if(b > 0x18) b -= 0x18; 21 //序列号第3位 22 code[2] = szBuffer[b]; 23 //序列号第4-9位 24 for (int i=3; i<9; ++i) 25 { 26 b = b + code[i-1] - 'A'; 27 if(b > 0x18) b -= 0x18; 28 code[i] = szBuffer[b]; 29 } 30 //序列号第10位 31 int c = 0; 32 for(int i=0; i<9; ++i) c += code[i]; 33 c /= 9; 34 code[9] = c; 35 code[10] = '\0'; 36 } 37 38 int main() 39 { 40 char szUser[50]; 41 char szCode[50]; 42 printf("请输入用户名:"); 43 scanf("%s", szUser); 44 45 Keygen(szUser, szCode); 46 printf("序列号是: %s\n", szCode); 47 system("pause"); 48 49 return 0; 50 }