系统 : Windows xp
程序 : crackme3
程序下载地址 :http://pan.baidu.com/s/1hqxzKR6
要求 : 注册机编写
使用工具 : IDA Pro & OD
“PEDIY CrackMe 2007”中关于此程序的破文标题为“一个简单的非明码比较(适合新手)【原创】”,可以在“搜索”标签下查找出原文。
用IDA载入程序,打开字符串表,发现“注册成功”/“注册失败”的提示:
双击'Well done,Cracker'进入定义,然后双击交叉引用进入调用它的地方:
loc_4015D9: ; CODE XREF: .text:004015C1j .text:004015D9 push 0 .text:004015DB push offset aYouDidIt ; "YOU DID IT" .text:004015E0 push offset aWellDoneCracke ; "Well done,Cracker" .text:004015E5 mov ecx, [ebp-20h] .text:004015E8 call ?MessageBoxA@CWnd@@QAEHPBD0I@Z ; CWnd::MessageBoxA(char const *,char const *,uint)
向上查找关键代码。。。
啊哈!这段代码中调用了两次GetWindowTextA函数,必定是用来获取用户名和密码:
.text:00401521 loc_401521: ; CODE XREF: .text:0040151Aj .text:00401521 mov eax, [ebp-20h] .text:00401524 add eax, 0E0h .text:00401529 push eax .text:0040152A mov ecx, [ebp-20h] .text:0040152D add ecx, 0A0h .text:00401533 call ?GetWindowTextA@CWnd@@QBEXAAVCString@@@Z ; CWnd::GetWindowTextA(CString &) .text:00401538 mov ecx, [ebp-20h] .text:0040153B add ecx, 0E4h .text:00401541 push ecx .text:00401542 mov ecx, [ebp-20h] .text:00401545 add ecx, 60h .text:00401548 call ?GetWindowTextA@CWnd@@QBEXAAVCString@@@Z ; CWnd::GetWindowTextA(CString &)
打开OD载入程序,在00401548处下断点,接着F9运行程序并尝试输入一组用户名密码:
哎?为什么没有中断在00401548处就提示错误?
再分析下程序:
004014F5 |. E8 AA030000 call <jmp.&MFC42.#3876_CWnd::GetWindowTextLengthA> 004014FA |. 8945 EC mov dword ptr [ebp-14], eax 004014FD |. 837D EC 05 cmp dword ptr [ebp-14], 5 00401501 |. 7F 05 jg short 00401508 00401503 |. E9 BB000000 jmp 004015C3 00401508 |> 8B4D E0 mov ecx, dword ptr [ebp-20] 0040150B |. 83C1 60 add ecx, 60 0040150E |. E8 91030000 call <jmp.&MFC42.#3876_CWnd::GetWindowTextLengthA> 00401513 |. 8945 E8 mov dword ptr [ebp-18], eax 00401516 |. 837D E8 05 cmp dword ptr [ebp-18], 5 0040151A |. 7F 05 jg short 00401521 0040151C |. E9 A2000000 jmp 004015C3
在获取字符串之前,程序调用了GetWindowTextLengthA来获取长度,并进行判断。如果用户名和序列号有一个是长度不 大于5的,就会显示出错!
那我们输入两个大于5的字符就可以通过检查了,在窗口中输入“pediy1”,“123456”,程序中断在00401548处:
00401548 |. E8 51030000 call <jmp.&MFC42.#3874_CWnd::GetWindowTextA> 0040154D |. 8B55 E0 mov edx, dword ptr [ebp-20] 00401550 |. 81C2 E0000000 add edx, 0E0 00401556 |. 52 push edx 00401557 |. 8D4D E4 lea ecx, dword ptr [ebp-1C] 0040155A |. E8 39030000 call <jmp.&MFC42.#858_CString::operator=> 0040155F |. 8B45 E0 mov eax, dword ptr [ebp-20] 00401562 |. 05 E4000000 add eax, 0E4 00401567 |. 50 push eax 00401568 |. 8D4D F0 lea ecx, dword ptr [ebp-10] 0040156B |. E8 28030000 call <jmp.&MFC42.#858_CString::operator=> 00401570 |. 33C0 xor eax, eax 00401572 |. 33DB xor ebx, ebx 00401574 |. 33C9 xor ecx, ecx 00401576 |. B9 01000000 mov ecx, 1 ; 异或变量初值1 0040157B |. 33D2 xor edx, edx 0040157D |. 8B45 E4 mov eax, dword ptr [ebp-1C] ; 用户名首地址存入eax 00401580 |> 8A18 /mov bl, byte ptr [eax] 00401582 |. 32D9 |xor bl, cl ; 字符与异或变量进行异或 00401584 |. 8818 |mov byte ptr [eax], bl ; 再存回去 00401586 |. 41 |inc ecx ; 异或变量自增 00401587 |. 40 |inc eax ; 下一个字符 00401588 |. 8038 00 |cmp byte ptr [eax], 0 0040158B |.^ 75 F3 jnz short 00401580 ; 处理完毕则退出循环 0040158D |. 33C0 xor eax, eax 0040158F |. 33DB xor ebx, ebx 00401591 |. 33C9 xor ecx, ecx 00401593 |. B9 0A000000 mov ecx, 0A ; 异或变量初值0Ah(10) 00401598 |. 33D2 xor edx, edx 0040159A |. 8B45 F0 mov eax, dword ptr [ebp-10] ; 序列号首地址存入eax 0040159D |> 8A18 /mov bl, byte ptr [eax] 0040159F |. 32D9 |xor bl, cl ; 字符与异或变量进行异或 004015A1 |. 8818 |mov byte ptr [eax], bl ; 再存回去 004015A3 |. 41 |inc ecx ; 异或变量自增 004015A4 |. 40 |inc eax ; 下一个字符 004015A5 |. 8038 00 |cmp byte ptr [eax], 0 004015A8 |.^ 75 F3 jnz short 0040159D ; 处理完毕则退出循环 004015AA |. 8B45 E4 mov eax, dword ptr [ebp-1C] 004015AD |. 8B55 F0 mov edx, dword ptr [ebp-10] 004015B0 |> 33C9 /xor ecx, ecx 004015B2 |. 8A18 |mov bl, byte ptr [eax] 004015B4 |. 8A0A |mov cl, byte ptr [edx] 004015B6 |. 3AD9 |cmp bl, cl 004015B8 |. 75 09 |jnz short 004015C3 ; f(用户名) ≠ f(序列号)则退出 004015BA |. 40 |inc eax 004015BB |. 42 |inc edx 004015BC |. 8038 00 |cmp byte ptr [eax], 0 004015BF |.^ 75 EF jnz short 004015B0 004015C1 |. EB 16 jmp short 004015D9 004015C3 |> 6A 00 push 0 004015C5 |. 68 6C304000 push 0040306C ; ASCII "ERROR" 004015CA |. 68 40304000 push 00403040 ; ASCII "One of the Details you entered was wrong" 004015CF |. 8B4D E0 mov ecx, dword ptr [ebp-20] 004015D2 |. E8 BB020000 call <jmp.&MFC42.#4224_CWnd::MessageBoxA>
原来程序采用的是F(用户名)= F(序列号)的验证形式!写出F(序列号)的逆算法即可算出序列号!
这里分析下源码可知,异或加密的信息,想要解密只需要用同样的密钥再异或一次即可。也就是说,F(序列号)的逆算法就是它本身!
复制一份http://www.cnblogs.com/ZRBYYXDM/p/5002789.html中搭建的MFC窗口程序,打开并修改OnOk函数如下:
void CSerialNumber_KeygenDlg::OnOK() { // TODO: Add extra validation here CString str; GetDlgItem( IDC_EDIT_NAME )->GetWindowText( str ); //获取用户名 int len = str.GetLength(); //获取长度 if ( len <= 5 ) //当字符串长度小于等于5时 MessageBox( "用户名必须长度大于5!" ); else { int XorVar = 1; //异或变量 CString Temp = ""; for ( int i = 0 ; i < len ; i++ ){ Temp += str[i] ^ XorVar; XorVar++; } XorVar = 0x0A; //异或变量 CString Serial = ""; for ( int j = 0 ; j != len ; j++ ){ Serial += Temp[j] ^ XorVar; XorVar++; } GetDlgItem( IDC_EDIT_Number )->SetWindowText( Serial ); } //CDialog::OnOK(); //屏蔽基类OnOk函数 }
再在OnInitDialog中添加此代码修改标题:SetWindowText(_T("crackme3_Keygen"));
运行程序,并将解密得到的序列号黏贴至crackme3程序中:
效果拔群: