本帖最后由 xiangshen 于 2011-11-23 19:59 编辑
[文件标题:一个名为CrackMe02的CrackMe算法分析 [文章作者]:willjhw [作者邮箱]:466684954@qq.com [软件名称]:CrackMe02 [下载地址]:附件 [运行环境]:Windows xp [使用工具]:OD,PEID [作者声明]:
只是感兴趣,没有其他目的。失误之处敬请诸位大侠赐教!
[详细过程]:
马上要去实习了,自己也想做逆向方面的东西,所以最近加强的逆向方面的锻炼,最近搞到一个某高校某次比赛的程序,于是拿来分析下,感觉很有收获,在此总结过后与吾爱破解的各位分享下。
老规矩,先用PEID查壳,如下图:
很明显是一个vc++的程序,直接上od吧。 F9允许后发现是个控制台程序,我们在输入函数后的地方下断,就是40101e吧。 0040101E |. 8B8424 0C1000 MOV EAX,DWORD PTR SS:[ARG.1]
00401025 |. 83C4 08 ADD ESP,8
00401028 |. 83F8 01 CMP EAX,1
0040102B |. 75 20 JNE SHORT 0040104D
0040102D |. 8D4C24 00 LEA ECX,[LOCAL.1023]
00401031 |. 51 PUSH ECX ; /Arg1 => OFFSET LOCAL.1023
00401032 |. E8 D9000000 CALL 00401110 ; \CrackMe02.00401110
00401037 |. 83C4 04 ADD ESP,4
关键call就是401032这个call的地方,我们跟进去
00401110 /[ DISCUZ_CODE_31 ]nbsp; 83EC 0C SUB ESP,0C ; CrackMe02.00401110(guessed Arg1)
00401113 |. A1 A0A14000 MOV EAX,DWORD PTR DS:[40A1A0] ; ASCII "ADGNWKQU"
00401118 |. 8B0D A4A14000 MOV ECX,DWORD PTR DS:[40A1A4] ; ASCII "WKQU"
0040111E |. 53 PUSH EBX
0040111F |. 55 PUSH EBP
00401120 |. 8B6C24 18 MOV EBP,DWORD PTR SS:[ARG.1]
00401124 |. 56 PUSH ESI
00401125 |. 57 PUSH EDI ; //Arg2 => ARG.EDI
00401126 |. 894424 10 MOV DWORD PTR SS:[LOCAL.2],EAX ; ||
0040112A |. 894C24 14 MOV DWORD PTR SS:[LOCAL.1],ECX ; ||
0040112E |. 8BFD MOV EDI,EBP ; ||
00401130 |. 83C9 FF OR ECX,FFFFFFFF ; ||
00401133 |. 33C0 XOR EAX,EAX ; ||
00401135 |. F2:AE REPNE SCAS BYTE PTR ES:[EDI] ; ||
00401137 |. 8A15 A8A14000 MOV DL,BYTE PTR DS:[40A1A8] ; ||
0040113D |. F7D1 NOT ECX ; ||
0040113F |. 49 DEC ECX ; ||
00401140 |. 885424 18 MOV BYTE PTR SS:[LOCAL.0],DL ; ||
00401144 |. 83F9 08 CMP ECX,8 ; ||
00401147 |. 75 62 JNE SHORT 004011AB ; ||
以后的汇编实现的功能就是将关键比较的字符串ADGNWKQU压入,然后在401144处进行判断你输入的长度是否为8,如果不是8则直接跳入失败的地方,所以这里说明输入的注册码的长度为8。 00401161 |> /8D741C 10 /LEA ESI,[EBX+ESP+10] ; ||
00401165 |. |B9 1A000000 |MOV ECX,1A ; ||
0040116A |. |0FBE042E |MOVSX EAX,BYTE PTR DS:[EBP+ESI] ; ||
0040116E |. |83E8 2F |SUB EAX,2F ; ||
00401171 |. |99 |CDQ ; ||
00401172 |. |F7F9 |IDIV ECX ; ||
00401174 |. |52 |PUSH EDX ; ||/Arg1
00401175 |. |E8 E6010000 |CALL 00401360 ; ||\CrackMe02.00401360
0040117A |. |8A0E |MOV CL,BYTE PTR DS:[ESI] ; ||
0040117C |. |83C4 04 |ADD ESP,4 ; ||
0040117F |. |3AC1 |CMP AL,CL ; ||
00401181 |. |75 28 |JNE SHORT 004011AB ; ||
00401183 |. |8D7C24 10 |LEA EDI,[LOCAL.2] ; ||
00401187 |. |83C9 FF |OR ECX,FFFFFFFF ; ||
0040118A |. |33C0 |XOR EAX,EAX ; ||
0040118C |. |43 |INC EBX ; ||
0040118D |. |F2:AE |REPNE SCAS BYTE PTR ES:[EDI] ; ||
0040118F |. |F7D1 |NOT ECX ; ||
00401191 |. |49 |DEC ECX ; ||
00401192 |. |3BD9 |CMP EBX,ECX ; ||
00401194 |.^\7C CB \JL SHORT 00401161 ; ||
这个代码就是关键的算法处,里面有个call我们跟进去如下面: 00401360 /[ DISCUZ_CODE_33 ]nbsp; 8B4424 04 MOV EAX,DWORD PTR SS:[ARG.1] ; CrackMe02.00401360(guessed Arg1)
00401364 |. 85C0 TEST EAX,EAX ; Switch (cases 0..1A, 2 exits)
00401366 |. 7C 08 JL SHORT 00401370
00401368 |. 83F8 1A CMP EAX,1A
0040136B |. 7F 03 JG SHORT 00401370
0040136D |. 83C0 41 ADD EAX,41 ; Cases 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 1A of switch CrackMe02.401364
00401370 \> C3 RETN ; Default case of switch CrackMe02.401364
这个call就是对每次计算出来的值进行加‘A’也就是0x41。
我们再来看看上面的算法吧,就是一个循环与str=”ADGNWKQU”比较,只要有一个计算出来不等于就跳向错误。算法是:(输入 - 0x2f)% 0x1a + 0x41 = str;
于是我们用c语言实现下算法: #include <stdio.h>
int main (void)
{
char str1[8] = "ADGNWKQU";
char str2[8];
int i = 0;
printf ("\n其中一个字符串:\n")
for (i = 0; i < 8; i++)
{
str2[i] = str1[i] - 0x41 + 0x1a + 0x2f;
printf ("%c",str2[i]);
}
printf ("\n");
return 0;
}
因为上面有取余数的操作,所以导致注册码不唯一,我就给出一组吧ILOV_SY] 得到的结果如下图:
我们发现有个问题呀,它的答案是right?说明有问题呀。我们再看看这个程序的提示有个VERYEASY还有在以下汇编我们看看: 0040101E |. 8B8424 0C1000 MOV EAX,DWORD PTR SS:[ARG.1]
00401025 |. 83C4 08 ADD ESP,8
00401028 |. 83F8 01 CMP EAX,1
0040102B |. 75 20 JNE SHORT 0040104D
0040102D |. 8D4C24 00 LEA ECX,[LOCAL.1023]
00401031 |. 51 PUSH ECX ; /Arg1 => OFFSET LOCAL.1023
00401032 |. E8 D9000000 CALL 00401110 ; \CrackMe02.00401110
00401037 |. 83C4 04 ADD ESP,4
0040103A |. 68 30A04000 PUSH OFFSET 0040A030 ; ASCII "pause"
0040103F |. E8 3C030000 CALL 00401380
00401044 |. 33C0 XOR EAX,EAX
00401046 |. 81C4 04100000 ADD ESP,1004
0040104C |. C3 RETN
0040104D |> 83F8 02 CMP EAX,2
00401050 |. 75 18 JNE SHORT 0040106A
如果这个程序不带参数运行,直接就不会跑到下面去,而下面才是真正的注册算法地方,于是我们用到OD的带参数调试,进入程序,发现40101e跳向了下面,参数是VERYEASY哦。关键算法4011c0处。 004011C0 /[ DISCUZ_CODE_36 ]nbsp; 83EC 3C sub esp, 3C
004011C3 |. 8B0D F4A14000 mov ecx, dword ptr [40A1F4]
004011C9 |. 53 push ebx
004011CA |. 55 push ebp
004011CB |. 56 push esi
004011CC |. 57 push edi
004011CD |. 894C24 1C mov dword ptr [esp+1C], ecx
004011D1 |. B9 09000000 mov ecx, 9
004011D6 |. BE C8A14000 mov esi, 0040A1C8 ; ASCII "X#duh#vr#fohyhu$#Wklv#lv#wkh#Uhdo#Nh|[ DISCUZ_CODE_6 ]quot;
004011DB |. 8D7C24 24 lea edi, dword ptr [esp+24]
004011DF |. A1 F0A14000 mov eax, dword ptr [40A1F0]
004011E4 |. F3:A5 rep movs dword ptr es:[edi], dword p>
004011E6 |. 66:8B0D C4A14>mov cx, word ptr [40A1C4]
004011ED |. 8B6C24 50 mov ebp, dword ptr [esp+50]
004011F1 |. 66:A5 movs word ptr es:[edi], word ptr [esi>
004011F3 |. 894424 18 mov dword ptr [esp+18], eax
004011F7 |. A1 C0A14000 mov eax, dword ptr [40A1C0]
004011FC |. A4 movs byte ptr es:[edi], byte ptr [esi>
004011FD |. 894424 10 mov dword ptr [esp+10], eax
00401201 |. 66:894C24 14 mov word ptr [esp+14], cx
00401206 |. 8BFD mov edi, ebp
00401208 |. 83C9 FF or ecx, FFFFFFFF
0040120B |. 33C0 xor eax, eax
0040120D |. 8A15 F8A14000 mov dl, byte ptr [40A1F8]
00401213 |. F2:AE repne scas byte ptr es:[edi]
00401215 |. F7D1 not ecx
00401217 |. 885424 20 mov byte ptr [esp+20], dl
0040121B |. 8A15 C6A14000 mov dl, byte ptr [40A1C6]
00401221 |. 49 dec ecx
00401222 |. 885424 16 mov byte ptr [esp+16], dl
00401226 |. 83F9 08 cmp ecx, 8
00401229 |. 0F85 D5000000 jnz 00401304
如同上面一样,也是先进行了判断输入的字符长度,上面401229就是个关键跳,如果长度不等于8就会跳向输入错误。
00401289 |> /8B4C24 54 /mov ecx, dword ptr [esp+54]
0040128D |. |8D741C 18 |lea esi, dword ptr [esp+ebx+18]
00401291 |. |0FBE042E |movsx eax, byte ptr [esi+ebp]
00401295 |. |0FBE11 |movsx edx, byte ptr [ecx]
00401298 |. |2BC2 |sub eax, edx
0040129A |. |B9 1A000000 |mov ecx, 1A
0040129F |. |83C0 1A |add eax, 1A
004012A2 |. |99 |cdq
004012A3 |. |F7F9 |idiv ecx
004012A5 |. |52 |push edx
004012A6 |. |E8 B5000000 |call 00401360
004012AB |. |8A0E |mov cl, byte ptr [esi]
004012AD |. |83C4 04 |add esp, 4
004012B0 |. |3AC1 |cmp al, cl
004012B2 |. |75 50 |jnz short 00401304
004012B4 |. |8D7C24 18 |lea edi, dword ptr [esp+18]
004012B8 |. |83C9 FF |or ecx, FFFFFFFF
004012BB |. |33C0 |xor eax, eax
004012BD |. |43 |inc ebx
004012BE |. |F2:AE |repne scas byte ptr es:[edi]
004012C0 |. |F7D1 |not ecx
004012C2 |. |49 |dec ecx
004012C3 |. |3BD9 |cmp ebx, ecx
004012C5 |.^\7C C2 \jl short 00401289
关键算法的地方,里面有一个call也很重要,我们跟进去,如下图:
00401360 /[ DISCUZ_CODE_38 ]nbsp; 8B4424 04 mov eax, dword ptr [esp+4]
00401364 |. 85C0 test eax, eax
00401366 |. 7C 08 jl short 00401370
00401368 |. 83F8 1A cmp eax, 1A
0040136B |. 7F 03 jg short 00401370
0040136D |. 83C0 41 add eax, 41
00401370 \> C3 retn
也就是对结果进行加1。
我们继续来看看上面的算法,其实和第一个也差不多,先定义了一个常量字符串str[]= ”ANLJSJWJ”,然后进行对输入的字符串str2进行操作:(str2 – 0x2f) % 0x1a + ‘A’ =str。
如果有一个不等于就跳向错误。
下面我简单的使用c写一个算注册码的吧,因为这里是用到了取余,同样结果不唯一
#include <stdio.h>
int main (void)
{
char str1[8] = "ANLJSJWJ";
char str2[8];
int i = 0;
printf ("\n其中一个字符串:\n");
for (i = 0; i < 8; i++)
{
str2[i] = str1[i] - 0x41 + 0x56;
printf ("%c",str2[i]);
}
printf ("\n");
return 0;
}
结果是Vca_h_1_(倒数第二个是小写的L哦) 运行成功的图例:
差不多分析这么多了吧,希望对自己有所提升,也希望和吾爱破解的众位一起学习,一起进步。 附件:
|