系统 : Windows xp
程序 : k4n
程序下载地址 :http://pan.baidu.com/s/1dEallrb
要求 : 注册机编写
使用工具 : IDA Pro & OD
“PEDIY CrackMe 2007”中关于此程序的破文标题为“一个CRACKME的算法分析”,可在“搜索”标签下查找。
使用IDA载入程序,根据成功/失败的字符串提示找到关键代码,本程序中关键代码在401085处:
00401085 |. 6A 66 push 66 ; /ControlID = 66 (102.) 00401087 |. 53 push ebx ; |hWnd 00401088 |. E8 159C0000 call <jmp.&USER32.GetDlgItem> ; \GetDlgItem 0040108D |. 6A 64 push 64 ; /Count = 64 (100.) 0040108F |. 8D95 48FFFFFF lea edx, dword ptr [ebp-B8] ; | 00401095 |. 52 push edx ; |Buffer 00401096 |. 50 push eax ; |hWnd 00401097 |. E8 129C0000 call <jmp.&USER32.GetWindowTextA> ; \GetWindowTextA 0040109C |. 6A 68 push 68 ; /ControlID = 68 (104.) 0040109E |. 53 push ebx ; |hWnd 0040109F |. E8 FE9B0000 call <jmp.&USER32.GetDlgItem> ; \GetDlgItem 004010A4 |. 6A 64 push 64 ; /Count = 64 (100.) 004010A6 |. 8D8D E4FEFFFF lea ecx, dword ptr [ebp-11C] ; | 004010AC |. 51 push ecx ; |Buffer 004010AD |. 50 push eax ; |hWnd 004010AE |. E8 FB9B0000 call <jmp.&USER32.GetWindowTextA> ; \GetWindowTextA 004010B3 |. 6A 67 push 67 ; /ControlID = 67 (103.) 004010B5 |. 53 push ebx ; |hWnd 004010B6 |. E8 E79B0000 call <jmp.&USER32.GetDlgItem> ; \GetDlgItem 004010BB |. 8BF0 mov esi, eax 004010BD |. 8D85 48FFFFFF lea eax, dword ptr [ebp-B8] 004010C3 |. 50 push eax 004010C4 |. E8 67050000 call 00401630 004010C9 |. 59 pop ecx 004010CA |. 8945 D8 mov dword ptr [ebp-28], eax 004010CD |. 8D95 E4FEFFFF lea edx, dword ptr [ebp-11C] 004010D3 |. 52 push edx 004010D4 |. E8 57050000 call 00401630 004010D9 |. 59 pop ecx 004010DA |. 68 EAB04000 push 0040B0EA 004010DF |. E8 4C050000 call 00401630 004010E4 |. 59 pop ecx 004010E5 |. 68 0EB14000 push 0040B10E 004010EA |. E8 41050000 call 00401630 004010EF |. 59 pop ecx 004010F0 |. 837D D8 03 cmp dword ptr [ebp-28], 3 ; 字符串是否小于等于3 004010F4 |. 7E 7B jle short 00401171 ; 是则出错 004010F6 |. 90 nop 004010F7 |. 90 nop 004010F8 |. 90 nop 004010F9 |. 90 nop 004010FA |. 33C9 xor ecx, ecx 004010FC |. 33D2 xor edx, edx 004010FE |. 33DB xor ebx, ebx 00401100 |. 33C0 xor eax, eax 00401102 |. 837D D8 32 cmp dword ptr [ebp-28], 32 ; 字符串是否大于等于50? 00401106 |. 7D 69 jge short 00401171 ; 是则出错 00401108 |. 90 nop 00401109 |. 90 nop 0040110A |. 90 nop 0040110B |. 90 nop 0040110C |> 0FBE840D 48FF>/movsx eax, byte ptr [ebp+ecx-B8] ; 逐个取出字符串中的字符 00401114 |. 41 |inc ecx ; 循环变量自增 00401115 |. 33C1 |xor eax, ecx ; 字符与循环变量异或 00401117 |. 03D8 |add ebx, eax ; 累加异或后的字符数据 00401119 |. 3B4D D8 |cmp ecx, dword ptr [ebp-28] ; 字符串是否处理完? 0040111C |.^ 75 EE \jnz short 0040110C 0040111E |. 6BC0 06 imul eax, eax, 6 ; eax *= 6 00401121 |. C1E3 07 shl ebx, 7 ; 左移7位,空位填0 00401124 |. 03C3 add eax, ebx 00401126 |. 8945 C8 mov dword ptr [ebp-38], eax 00401129 |. FF75 C8 push dword ptr [ebp-38] ; eax + ebx的值入栈 0040112C |. 68 38B44000 push 0040B438 ; ASCII "%lX" 00401131 |. 8D8D 80FEFFFF lea ecx, dword ptr [ebp-180] 00401137 |. 51 push ecx ; 缓冲区 00401138 |. E8 873D0000 call 00404EC4 0040113D |. 83C4 0C add esp, 0C 00401140 |. 8D85 80FEFFFF lea eax, dword ptr [ebp-180] 00401146 |. 50 push eax ; /String2 00401147 |. 8D95 E4FEFFFF lea edx, dword ptr [ebp-11C] ; | 0040114D |. 52 push edx ; |String1 0040114E |. E8 339C0000 call <jmp.&KERNEL32.lstrcmpA> ; \lstrcmpA 00401153 |. 85C0 test eax, eax 00401155 |. 75 0D jnz short 00401164 00401157 |. 68 3CB44000 push 0040B43C ; /Text = "Congratulations! IF this number comes *FROM YOUR* keygen, Write a tutorial dude ;)." 0040115C |. 56 push esi ; |hWnd 0040115D |. E8 289B0000 call <jmp.&USER32.SetWindowTextA> ; \SetWindowTextA 00401162 |. EB 18 jmp short 0040117C 00401164 |> 68 90B44000 push 0040B490 ; /Text = "This serial is *NOT* Valid!! Try again... : UNREGISTERED" 00401169 |. 56 push esi ; |hWnd 0040116A |. E8 1B9B0000 call <jmp.&USER32.SetWindowTextA> ; \SetWindowTextA 0040116F |. EB 0B jmp short 0040117C 00401171 |> 68 C9B44000 push 0040B4C9 ; /Text = "Name must contain more than 4 chars and less than 50 chars !!" 00401176 |. 56 push esi ; |hWnd 00401177 |. E8 0E9B0000 call <jmp.&USER32.SetWindowTextA> ; \SetWindowTextA
这里分析得出结论,程序通过对用户名字串进行操作,然后将之与密钥对比,得出结果。关键算法都差不多了解,现在只需探索下地址为00404EC4的子程序进行了什么操作:
00404EC4 /$ 55 push ebp 00404EC5 |. 8BEC mov ebp, esp 00404EC7 |. 8B45 08 mov eax, dword ptr [ebp+8] ; 缓冲区地址放入eax 00404ECA |. 8D4D 08 lea ecx, dword ptr [ebp+8] ; ecx指向缓冲区地址 00404ECD |. C600 00 mov byte ptr [eax], 0 ; 向缓冲区填0 00404ED0 |. 8D45 10 lea eax, dword ptr [ebp+10] 00404ED3 |. 50 push eax ; 经处理过的数值入栈 00404ED4 |. 8B55 0C mov edx, dword ptr [ebp+C] 00404ED7 |. 52 push edx ; ‘%IX’入栈 00404ED8 |. 51 push ecx ; 缓冲区地址入栈 00404ED9 |. 68 9C4E4000 push 00404E9C 00404EDE |. E8 B5010000 call 00405098 00404EE3 |. 83C4 10 add esp, 10 00404EE6 |. 5D pop ebp 00404EE7 \. C3 retn
咦?这里的函数调用和上一步好像没什么明显的不同?继续深入地址为405098的子程序看看。。。。
接着就发现一大堆乱七八糟的汇编指令。。。这么简单的CM怎么会有如此复杂的处理流程?
不如转换下思维好了,回到一开始的流程再想想。我们可以看见,调用完405098之后,ecx中就保存了密钥:
我们一边执行405098子程序,一边关注寄存器和堆栈的变化,慢慢搜索。
终于,在这一行发现了密钥的踪迹:
看到了吗?ebp+10的位置里存放着密钥“116E8”,往上翻,一定在不远处存放着处理字串的关键代码:
00407484 |. /EB 17 jmp short 0040749D 00407486 |> |4B /dec ebx 00407487 |. |8A03 |mov al, byte ptr [ebx] 00407489 |. |3C 0A |cmp al, 0A 0040748B |. |7D 08 |jge short 00407495 0040748D |. |83C0 30 |add eax, 30 00407490 |. |8806 |mov byte ptr [esi], al 00407492 |. |46 |inc esi 00407493 |. |EB 08 |jmp short 0040749D 00407495 |> |0245 1C |add al, byte ptr [ebp+1C] 00407498 |. |04 F6 |add al, 0F6 0040749A |. |8806 |mov byte ptr [esi], al 0040749C |. |46 |inc esi 0040749D |> \8D55 BC lea edx, dword ptr [ebp-44] 004074A0 |. 3BDA |cmp ebx, edx 004074A2 |.^ 75 E2 \jnz short 00407486
这几行汇编指令将一组数据以十六进制的格式转换为相应的字符串,用代码表示就是wsprintf( str,"%lX",res );
正如程序中调用00404EC4子程序时的方式一样:
00401129 |. FF75 C8 push dword ptr [ebp-38] ; eax + ebx的值入栈 0040112C |. 68 38B44000 push 0040B438 ; ASCII "%lX" 00401131 |. 8D8D 80FEFFFF lea ecx, dword ptr [ebp-180] 00401137 |. 51 push ecx ; 缓冲区 00401138 |. E8 873D0000 call 00404EC4
这里发现以上代码地址都是004开头(处于应用程序领空),也就是说以上流程处理都是CM作者自己实现的,虽然根据参数可以下判断
这就是个转换格式的函数,但如果CM作者对于转换的过程“动手动脚”,该怎么判断呢?
比如:本次的运算结果是116E8,转换成字符串"116E8"是没错,但万一程序中发现0FH数据,把它直接转换成字符1怎么办呢?
如果CM作者这样编码,我们写出的注册机将没有“通用性”。所以,发现手动实现的子程序,还是进入确认下关键算法比较稳妥。
好了,算法到这里分析完毕,我们根据加密算法开始编写注册机吧!
复制一份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 <= 3 || len >= 50 ) MessageBox( "用户名必须长度大于3、小于50!" ); else { int res = 0,sum = 0; //储存累加、运算结果. CString Temp = str; for ( int i = 0 ; i < len ; i++ ){ sum += ( Temp[i] ^ (i + 1) ); //字符与循环变量异或,并累加。 if ( (i + 1) == len ) //当前处理的是最后一个元素时, res = ( Temp[i] ^ (i + 1) ); //保存字符与循环变量异或的值。 } res *= 6; sum = sum << 7; res += sum; char* str1 = new char[50]; wsprintf( str1,"%lX",res ); //以十六进制存储. GetDlgItem( IDC_EDIT_Number )->SetWindowText( str1 ); } //CDialog::OnOK(); //屏蔽基类OnOk函数 }
再在OnInitDialog中添加此代码修改标题:SetWindowText(_T("k4n_Keygen"));
运行程序,并将解密得到的序列号黏贴至k4n程序中,单击check。。。
效果拔群: