系统 : Windows xp
程序 : k4n3
程序下载地址 :http://pan.baidu.com/s/1sklYrHN
要求 : 注册机编写
使用工具 : IDA Pro & OD & Hex workshop
可在“PEDIY CrackMe 2007”中查找关于此程序的讨论,标题为“非明码比较的Crackme的破解”。
使用IDA载入程序,打开字符串表,根据“Wow you did it... Now write a valid keygen with NO ASM RIPPING :X”的字符串提示
向上翻找到关键代码,首先来看看程序是怎样约束输入字串的:
004011BF |. 6A 45 push 45 ; /Count = 45 (69.) 004011C1 |. 50 push eax ; |Buffer 004011C2 |. A4 movs byte ptr es:[edi], byte ptr [esi>; | 004011C3 |. 8B3D A8404000 mov edi, dword ptr [<&USER32.GetDlgI>; |USER32.GetDlgItemTextA 004011C9 |. 68 E8030000 push 3E8 ; |ControlID = 3E8 (1000.) 004011CE |. 51 push ecx ; |hWnd 004011CF |. FFD7 call edi ; \GetDlgItemTextA 004011D1 |. 8BF0 mov esi, eax 004011D3 |. 85F6 test esi, esi ; 当用户名字串长度为0 004011D5 |. 0F84 4B010000 je 00401326 ; 出错 004011DB |. 83FE 40 cmp esi, 40 ; 当用户名长度大于64时 004011DE |. 0F87 42010000 ja 00401326 ; 出错 004011E4 |. 8B45 08 mov eax, dword ptr [ebp+8] 004011E7 |. 8D55 94 lea edx, dword ptr [ebp-6C] 004011EA |. 6A 13 push 13 ; /Count = 13 (19.) 004011EC |. 52 push edx ; |Buffer 004011ED |. 68 E9030000 push 3E9 ; |ControlID = 3E9 (1001.) 004011F2 |. 50 push eax ; |hWnd 004011F3 |. FFD7 call edi ; \GetDlgItemTextA 004011F5 |. 6BC0 03 imul eax, eax, 3 004011F8 |. C1E0 02 shl eax, 2 004011FB |. 05 CD000000 add eax, 0CD ; 密钥长度*3再左移2位,最后加上0CDH 00401200 |. 8945 FC mov dword ptr [ebp-4], eax 00401203 |. 817D FC A5010>cmp dword ptr [ebp-4], 1A5 ; 如果结果不是1A5,则跳转 0040120A |. 0F85 BC000000 jnz 004012CC ; 显示密钥有误 00401210 |. 33C0 xor eax, eax 00401212 |. 8A45 94 mov al, byte ptr [ebp-6C] ; 取密钥首个字母 00401215 |. 84C0 test al, al ; 判断字符值是否为0 00401217 |. 74 13 je short 0040122C 00401219 |. 8D4D 94 lea ecx, dword ptr [ebp-6C] ; 取密钥字串地址 0040121C |> 3C 30 /cmp al, 30 ; 如果字符小于30h 0040121E |. 0F82 C6000000 |jb 004012EA ; 则跳转至提示出错函数 00401224 |. 8A41 01 |mov al, byte ptr [ecx+1] ; 下一个字符 00401227 |. 41 |inc ecx ; 循环变量自增 00401228 |. 84C0 |test al, al ; 当处理到字符串结尾 0040122A |.^ 75 F0 \jnz short 0040121C ; 则退出循环
我们看到,密钥长度必须是一个固定值,否则将提示出错。经过推算,可以知道密钥长度应该是18.
这里,我们随便填写一个长度为18的密钥,例如:“123456789ABCDEF123”。让程序流程继续执行:
0040122C |> \E8 CFFDFFFF call 00401000 ; 该子程序清空eax,edx,ecx寄存器的值 00401231 |. 8D85 2CFFFFFF lea eax, dword ptr [ebp-D4] ; 取用户名字串地址 00401237 |. 50 push eax 00401238 |. E8 43FEFFFF call 00401080 ; 拼接用户名字串和‘ is a 。。。’字串,并根据字串算出一个值
这里,程序跳转进入401080子程序,我们F7进入:
00401080 /$ 55 push ebp 00401081 |. 8BEC mov ebp, esp 00401083 |. 51 push ecx 00401084 |. 53 push ebx 00401085 |. 56 push esi 00401086 |. 57 push edi 00401087 |. 68 80504000 push 00405080 ; ASCII "eheh" 0040108C |. 6A 00 push 0 0040108E |. E8 ADFFFFFF call 00401040 ; 通过字串eheh对eax的值做一些修改
继续F7进入子程序401040:
00401040 /$ 8B4C24 04 mov ecx, dword ptr [esp+4] 00401044 |. 56 push esi 00401045 |. 8B7424 0C mov esi, dword ptr [esp+C] 00401049 |. 33C0 xor eax, eax 0040104B |. 33D2 xor edx, edx 0040104D |. 8A4431 03 mov al, byte ptr [ecx+esi+3] 00401051 |. 8A5431 02 mov dl, byte ptr [ecx+esi+2] 00401055 |. C1E0 08 shl eax, 8 ; 左移8位 00401058 |. 03C2 add eax, edx 0040105A |. 33D2 xor edx, edx 0040105C |. 8A5431 01 mov dl, byte ptr [ecx+esi+1] 00401060 |. C1E0 08 shl eax, 8 00401063 |. 03C2 add eax, edx 00401065 |. 33D2 xor edx, edx 00401067 |. 8A1431 mov dl, byte ptr [ecx+esi] 0040106A |. 5E pop esi 0040106B |. C1E0 08 shl eax, 8 0040106E |. 03C2 add eax, edx 00401070 \. C3 retn
回到401080子程序领空中,发现整个子程序多次调用了401040子程序:
00401080 /$ 55 push ebp 00401081 |. 8BEC mov ebp, esp 00401083 |. 51 push ecx 00401084 |. 53 push ebx 00401085 |. 56 push esi 00401086 |. 57 push edi 00401087 |. 68 80504000 push 00405080 ; ASCII "eheh" 0040108C |. 6A 00 push 0 0040108E |. E8 ADFFFFFF call 00401040 ; 通过字串eheh对eax的值做一些修改 00401093 |. 83C4 08 add esp, 8 00401096 |. 8BD8 mov ebx, eax 00401098 |. E8 63FFFFFF call 00401000 ; 该子程序清空eax,edx,ecx寄存器的值 0040109D |. BF 70504000 mov edi, 00405070 ; ASCII " is a whore." 004010A2 |. 83C9 FF or ecx, FFFFFFFF ; ecx=FFFFFFFF 004010A5 |. 33C0 xor eax, eax 004010A7 |. F2:AE repne scas byte ptr es:[edi] ; 扫描es:[edi]中的字串,循环次数ecx,找到与al相同的值则停止 004010A9 |. F7D1 not ecx ; 执行完这条语句ecx=‘is a 。。’字串长度+1 004010AB |. 2BF9 sub edi, ecx ; 重新指向字串首地址 004010AD |. 8BF7 mov esi, edi 004010AF |. 8B7D 08 mov edi, dword ptr [ebp+8] ; edi指向用户名字串 004010B2 |. 8BD1 mov edx, ecx ; edx=‘is a 。。’字串长度+1 004010B4 |. 83C9 FF or ecx, FFFFFFFF 004010B7 |. F2:AE repne scas byte ptr es:[edi] ; 扫描es:[edi]中的字串,循环次数ecx,找到与al相同的值则停止 004010B9 |. 8BCA mov ecx, edx ; edx= ‘is a。。。’字串的长度+1 004010BB |. 4F dec edi ; 此时es:[edi]指向字串结尾 004010BC |. C1E9 02 shr ecx, 2 ; ecx右移两位 004010BF |. F3:A5 rep movs dword ptr es:[edi], dword p>; 拼接字串 004010C1 |. 8BCA mov ecx, edx 004010C3 |. 83E1 03 and ecx, 3 ; ecx=1 004010C6 |. F3:A4 rep movs byte ptr es:[edi], byte ptr>; 加上结尾 004010C8 |. 33FF xor edi, edi 004010CA |. 33F6 xor esi, esi 004010CC |> 8B45 08 /mov eax, dword ptr [ebp+8] ; 将拼接好的字串地址放入eax 004010CF |. 50 |push eax 004010D0 |. 56 |push esi 004010D1 |. E8 6AFFFFFF |call 00401040 ; 通过入栈的字串对eax的值做一些修改 004010D6 |. 8B8E 30504000 |mov ecx, dword ptr [esi+405030] 004010DC |. 83C4 08 |add esp, 8 ; 平衡堆栈 004010DF |. 33CF |xor ecx, edi ; ecx与edi异或 004010E1 |. 03C1 |add eax, ecx ; eax再加上ecx 004010E3 |. 8945 FC |mov dword ptr [ebp-4], eax ; 保存eax 004010E6 |. C145 FC 07 |rol dword ptr [ebp-4], 7 ; 将结果循环左移7次,每次从最高位(最左)移出的数据位都补充到最低位 004010EA |. 8B45 FC |mov eax, dword ptr [ebp-4] ; 再赋给eax 004010ED |. 83C6 04 |add esi, 4 ; 循环变量+4 004010F0 |. 33D8 |xor ebx, eax ; ebx^eax 004010F2 |. 47 |inc edi 004010F3 |. 83FE 40 |cmp esi, 40 004010F6 |.^ 7C D4 \jl short 004010CC 004010F8 |. 5F pop edi 004010F9 |. 8BC3 mov eax, ebx ; 保存ebx的值 004010FB |. 5E pop esi 004010FC |. 5B pop ebx 004010FD |. 8BE5 mov esp, ebp 004010FF |. 5D pop ebp 00401100 \. C3 retn
这里,连接字串后,我们的用户名居然和“is a whore”连接在了一起!。。。翻译过来就是“看雪是个妓女”。
突然明白了《加密与解密》中为什么一直用固定格式的用户名进行测试了(┬_┬)
我x,这程序员好绅(hen)士(tai)啊!!
这里暂且忍下,等完全破解了他的算法再好好调♂教一番。
我们继续回来,看看接下来的流程:
0040123D |. 8945 FC mov dword ptr [ebp-4], eax ; 保存eax 00401240 |. E8 BBFDFFFF call 00401000 ; 清空eax,edx,ecx寄存器的值 00401245 |. 8D8D 2CFFFFFF lea ecx, dword ptr [ebp-D4] ; 取拼接字串地址 0040124B |. 56 push esi 0040124C |. 51 push ecx 0040124D |. E8 BEFDFFFF call 00401010 ; 删去添加字串。 00401252 |. 83C4 0C add esp, 0C ; 平衡堆栈 00401255 |. 33C9 xor ecx, ecx 00401257 |> 8B45 FC /mov eax, dword ptr [ebp-4] ; 取401080子程序中计算的值 0040125A |. 33D2 |xor edx, edx ; 清零 0040125C |. BE 1A000000 |mov esi, 1A 00401261 |. F7F6 |div esi 00401263 |. 8A9415 10FFFF>|mov dl, byte ptr [ebp+edx-F0] ; 根据除法余数来取子密钥 0040126A |. 88540D C8 |mov byte ptr [ebp+ecx-38], dl ; 并保存 0040126E |. 8B45 FC |mov eax, dword ptr [ebp-4] ; 取401080子程序中计算的值 00401271 |. C1E0 03 |shl eax, 3 ; 左移3位 00401274 |. BA 45230100 |mov edx, 12345 00401279 |. F7E8 |imul eax 0040127B |. 03C2 |add eax, edx 0040127D |. 8945 FC |mov dword ptr [ebp-4], eax ; 保存 00401280 |. 41 |inc ecx ; 循环变量自增 00401281 |. 83F9 12 |cmp ecx, 12 00401284 |.^ 72 D1 \jb short 00401257 00401286 |. E8 75FDFFFF call 00401000 ; 该子程序清空eax,edx,ecx寄存器的值 0040128B |. 33C0 xor eax, eax ; 在此之前的指令不对用户输入的密钥做操作 ↑
至此,对于用户名字串的操作就结束了,接下来的是密钥处理:
0040128D |> /8A4C05 94 /mov cl, byte ptr [ebp+eax-6C] ; 迭代用户输入的密钥 00401291 |. |8A5405 C8 |mov dl, byte ptr [ebp+eax-38] ; 取之前算出的子密钥 00401295 |. |80E9 30 |sub cl, 30 00401298 |. |32D1 |xor dl, cl 0040129A |. |885405 C8 |mov byte ptr [ebp+eax-38], dl ; 结果存回子密钥 0040129E |. |40 |inc eax ; 循环变量自增 0040129F |. |83F8 12 |cmp eax, 12 004012A2 |.^\72 E9 \jb short 0040128D 004012A4 |. E8 57FDFFFF call 00401000 ; 该子程序清空eax,edx,ecx寄存器的值 004012A9 |. 8D55 C8 lea edx, dword ptr [ebp-38] ; 取子密钥 004012AC |. 52 push edx 004012AD |. E8 5EFEFFFF call 00401110 ; 对子密钥进行操作
F7进入查看:
00401110 /$ 8B4C24 04 mov ecx, dword ptr [esp+4] ; 取子密钥 00401114 |. 33C0 xor eax, eax ; 清零 00401116 |> 8A1408 /mov dl, byte ptr [eax+ecx] ; 迭代子密钥 00401119 |. 32D0 |xor dl, al ; 与循环变量异或 0040111B |. 881408 |mov byte ptr [eax+ecx], dl ; 再存回去 0040111E |. 40 |inc eax ; 循环变量自增 0040111F |. 83F8 12 |cmp eax, 12 00401122 |.^ 72 F2 \jb short 00401116 00401124 \. C3 retn
返回后,程序将操作结果与固定值进行对比:
004012B2 |. E8 49FDFFFF call 00401000 ; 该子程序清空eax,edx,ecx寄存器的值 004012B7 |. 8D45 C8 lea eax, dword ptr [ebp-38] ; 取子密钥 004012BA |. 68 14514000 push 00405114 ; ASCII "KEYGENNING4NEWBIES" 004012BF |. 50 push eax 004012C0 |. E8 6BFEFFFF call 00401130 ; 最终处理结束的子密钥是否等于固定密钥? 004012C5 |. 83C4 0C add esp, 0C ; 平衡堆栈 004012C8 |. 85C0 test eax, eax 004012CA |. 75 3C jnz short 00401308 004012CC |> 8B4D 08 mov ecx, dword ptr [ebp+8] 004012CF |. 6A 10 push 10 ; /Style = MB_OK|MB_ICONHAND|MB_APPLMODAL 004012D1 |. 68 0C514000 push 0040510C ; |Title = "Error" 004012D6 |. 68 FC504000 push 004050FC ; |Text = "Bad Serial :o(" 004012DB |. 51 push ecx ; |hOwner 004012DC |. FF15 AC404000 call dword ptr [<&USER32.MessageBoxA>>; \MessageBoxA 004012E2 |. 5F pop edi 004012E3 |. 33C0 xor eax, eax 004012E5 |. 5E pop esi 004012E6 |. 8BE5 mov esp, ebp 004012E8 |. 5D pop ebp 004012E9 |. C3 retn 004012EA |> 8B55 08 mov edx, dword ptr [ebp+8] 004012ED |. 6A 10 push 10 ; /Style = MB_OK|MB_ICONHAND|MB_APPLMODAL 004012EF |. 68 0C514000 push 0040510C ; |Title = "Error" 004012F4 |. 68 FC504000 push 004050FC ; |Text = "Bad Serial :o(" 004012F9 |. 52 push edx ; |hOwner 004012FA |. FF15 AC404000 call dword ptr [<&USER32.MessageBoxA>>; \MessageBoxA 00401300 |. 5F pop edi 00401301 |. 33C0 xor eax, eax 00401303 |. 5E pop esi 00401304 |. 8BE5 mov esp, ebp 00401306 |. 5D pop ebp 00401307 |. C3 retn 00401308 |> 8B55 08 mov edx, dword ptr [ebp+8] 0040130B |. 6A 40 push 40 ; /Style = MB_OK|MB_ICONASTERISK|MB_APPLMODAL 0040130D |. 68 F4504000 push 004050F4 ; |Title = "Great !" 00401312 |. 68 B0504000 push 004050B0 ; |Text = "Wow you did it... Now write a valid keygen with NO ASM RIPPING :X" 00401317 |. 52 push edx ; |hOwner 00401318 |. FF15 AC404000 call dword ptr [<&USER32.MessageBoxA>>; \MessageBoxA
至此,程序的整个加密, 比对流程已经分析完毕了。我们看到,此程序采用是的F(用户名)=F(函数)的形式进行加密,
也就是非明码比较。对于这种形式我们先将F(用户名)翻译成高级语言,再写出F(函数)的逆算法,就可以写出注册机。
废话不多说,马上复制一份http://www.cnblogs.com/ZRBYYXDM/p/5002789.html中搭建的MFC窗口程序,添加如下函数:
unsigned int CSerialNumber_KeygenDlg::F1040( unsigned char* str,int num ) { unsigned int eax = str[3+num]; unsigned int edx = str[2+num]; eax = eax << 8; eax += edx; edx = str[1+num]; eax = eax << 8; eax += edx; edx = str[num]; eax = eax << 8; eax += edx; return eax; }
并修改OnOk函数如下:
void CSerialNumber_KeygenDlg::OnOK() { // TODO: Add extra validation here CString str; GetDlgItem( IDC_EDIT_NAME )->GetWindowText( str ); //获取用户名 int len = str.GetLength(); //获取长度 if ( len == 0 || len > 64 ) MessageBox( "用户名必须长度不等于3、不高于64!" ); else { unsigned int Veax = 0; CString IAW = " is a whore."; unsigned char link[64 + 13] = {0}; //用户名最大程度+IAW长度 DWORD table[16] = { //异或常量表 0x12,0x5C,0x34,0x22, 0xAB,0x9D,0x54,0x00, 0xDD,0x84,0xAE,0x66, 0x31,0x78,0x73,0xCF }; str += IAW; memcpy( link,str,str.GetLength() ); //不复制结束符。 int i = 0; unsigned int Vecx = 0,Vedi = 0,Vebx= F1040( (unsigned char*)"eheh",0 ); for ( i = 0 ; i != 16 ; i++,Vedi++ ){ Veax = F1040( link,i*4 ); Vecx = table[i]; //逐个取出进行异或。 Vecx ^= Vedi; Veax += Vecx; __asm { push eax mov eax,Veax rol eax,7 mov Veax,eax pop eax } Vebx ^= Veax; } Veax = Vebx; str = str.Left( len ); //还原用户名 CString subkey = ""; //密钥 CString subkeytable = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; //子密钥 unsigned int Temp = Veax; for ( i = 0 ; i != 18 ; i++ ){ Temp = Veax; Temp %= 0x1A; subkey += subkeytable[(int)Temp]; Temp = Veax << 3; __asm { push eax push edx mov eax,Temp imul eax add eax,edx mov Temp,eax pop edx pop eax } Veax = Temp; } /*接下来先模拟F(密钥)函数的步骤,再根据这些步骤写出F(密钥)的逆算法 CString key = "123456789ABCDEF123"; //模拟一个输入的密钥。 for ( i = 0 ; i != key.GetLength() ; i++ ){ key.SetAt( i,( key.GetAt( i ) - 0x30 ) ); subkey.SetAt( i,( subkey[i] ^ key[i] ^ i ) ); } if ( subkey == "KEYGENNING4NEWBIES" ) //如果算出的subkey值与一个固定的字串相等,则注册成功 MessageBox( "done." ); */ CString Truekey; CString key= "KEYGENNING4NEWBIES"; for ( i = 0 ; i != key.GetLength() ; i++ ){ key.SetAt( i,( key[i] ^ subkey[i] ^ i ) ); Truekey += ( key.GetAt( i ) + 0x30 ); } GetDlgItem( IDC_EDIT_Number )->SetWindowText( Truekey ); } //CDialog::OnOK(); //屏蔽基类OnOk函数 }
再在OnInitDialog中添加此代码修改标题:SetWindowText(_T("k4n3_Keygen"));
运行效果:
太棒了!我们终于写出了注册机!
那么,今天的工作就到这里结束咯!
那怎么可能呢,我们还要好好调♂教 作者一下~(乖♂乖♂站♂好♂)
首先,用hex workshop打开程序,搜索“ is a whore.”字串,并用“ is my dad!!”替换并保存:
选择是,将改动保存至文件。
再用OD载入程序,在401238处下断。输入“pediy”,“123456789ABCDEF123”,单击check:
此时堆栈界面如图示:
2333333333333
别急,还可以有更多变化。例如:
更多姿势请自行解锁。。。