粘贴过来才知道语法高亮没效果了,可以看附件(mht格式的笔记,CrackMe,udd)
CRC32的CrackMe
不是原创。。自己跟了一下,写出来的笔记觉得还挺清晰的,代码高亮也好看就分享下!
多谢WindRand编写了系列密码学CrackMe
第一篇密码学CrackMe的破解笔记,虽然我依稀记得CRC32只是一个校验数据完整性的算法,还是干一下吧。
要注意的几点:
1 在贴出来的反汇编代码里你可能会见到一些变量名或者函数名,这个是我理解了之后放上去的,方法是在函数或变量的开头地方shift+分号来输入标签
2 这系列的CrackMe找注册算法定位点很简单,所以就不关注了,直接看注册算法(位于地址401282)
分析过程如下:
00401282 . 68 00010000 push 0x100 ; /Count = 100 (256.)
00401287 . 51 push ecx ; |Buffer
00401288 . 68 E8030000 push 0x3E8 ; |ControlID = 3E8 (1000.)
0040128D . 56 push esi ; |hWnd
0040128E . FFD3 call ebx ; \GetDlgItemTextA
00401290 . 8DBC24 0C010000 lea edi,dword ptr ss:[esp+0x10C]
00401297 . 83C9 FF or ecx,0xFFFFFFFF
0040129A . 33C0 xor eax,eax
0040129C . F2:AE repne scas byte ptr es:[edi]
0040129E . F7D1 not ecx
004012A0 . 49 dec ecx ; 获取szUserName长度
004012A1 . 83F9 01 cmp ecx,0x1
004012A4 . 73 1F jnb short <CRC32Cra.BadBoy> ; 长度必须大于等于1
004012A6 . 6A 40 push 0x40 ; /Style = MB_OK|MB_ICONASTERISK|MB_APPLMODAL
004012A8 . 68 8C604000 push CRC32Cra.0040608C ; |Title = "注册提示"
004012AD . 68 74604000 push CRC32Cra.00406074 ; |Text = "用户名不能为空请输入!"
004012B2 . 56 push esi ; |hOwner
004012B3 . FF15 A8504000 call dword ptr ds:[<&USER32.MessageBoxA>] ; \MessageBoxA
004012B9 . 5F pop edi
004012BA . 5E pop esi
004012BB . 33C0 xor eax,eax
004012BD . 5B pop ebx
004012BE . 81C4 00030000 add esp,0x300
004012C4 . C3 retn
004012C5 > > 8D5424 0C lea edx,dword ptr ss:[esp+0xC] ; label:BadBoy
004012C9 . 68 00010000 push 0x100
004012CE . 52 push edx
004012CF . 68 07040000 push 0x407
004012D4 . 56 push esi
004012D5 . FFD3 call ebx
004012D7 . 8D7C24 0C lea edi,dword ptr ss:[esp+0xC]
004012DB . 83C9 FF or ecx,0xFFFFFFFF
004012DE . 33C0 xor eax,eax
004012E0 . F2:AE repne scas byte ptr es:[edi]
004012E2 . F7D1 not ecx
004012E4 . 49 dec ecx ; 获取szPassword长度
004012E5 . 83F9 01 cmp ecx,0x1 ; 长度必须大于等于1
004012E8 . 73 1F jnb short <CRC32Cra.BadBoy2>
004012EA . 6A 40 push 0x40 ; /Style = MB_OK|MB_ICONASTERISK|MB_APPLMODAL
004012EC . 68 8C604000 push CRC32Cra.0040608C ; |Title = "注册提示"
004012F1 . 68 5C604000 push CRC32Cra.0040605C ; |Text = "注册码不能为空请输入!"
004012F6 . 56 push esi ; |hOwner
004012F7 . FF15 A8504000 call dword ptr ds:[<&USER32.MessageBoxA>] ; \MessageBoxA
004012FD . 5F pop edi
004012FE . 5E pop esi
004012FF . 33C0 xor eax,eax
00401301 . 5B pop ebx
00401302 . 81C4 00030000 add esp,0x300
00401308 . C3 retn
00401309 > > 8D8424 0C020000 lea eax,dword ptr ss:[esp+0x20C] ; label:BadBoy2
00401310 . 8D8C24 0C010000 lea ecx,dword ptr ss:[esp+0x10C]
00401317 . 50 push eax ; empty buffer
00401318 . 51 push ecx ; szUserName
00401319 . E8 F2FDFFFF call <CRC32Cra.CRC32>
0040131E . 8D9424 14020000 lea edx,dword ptr ss:[esp+0x214]
00401325 . 8D4424 14 lea eax,dword ptr ss:[esp+0x14]
00401329 . 52 push edx ; may be CRC32 value
0040132A . 50 push eax ; szPassword
0040132B . E8 D0FCFFFF call <CRC32Cra.Compare>
00401330 . 83C4 10 add esp,0x10
00401333 . 83F8 01 cmp eax,0x1
00401336 . 6A 40 push 0x40 ; /Style = MB_OK|MB_ICONASTERISK|MB_APPLMODAL
00401338 . 68 8C604000 push CRC32Cra.0040608C ; |Title = "注册提示"
0040133D . 75 18 jnz short CRC32Cra.00401357 ; |
0040133F . 68 48604000 push CRC32Cra.00406048 ; |Text = "恭喜你,注册码正确!"
00401344 . 56 push esi ; |hOwner
00401345 . FF15 A8504000 call dword ptr ds:[<&USER32.MessageBoxA>] ; \MessageBoxA
0040134B . 5F pop edi
0040134C . 5E pop esi
0040134D . 33C0 xor eax,eax
0040134F . 5B pop ebx
00401350 . 81C4 00030000 add esp,0x300
00401356 . C3 retn
00401357 > 68 30604000 push CRC32Cra.00406030 ; |Text = "注册码错误,继续加油!"
0040135C . 56 push esi ; |hOwner
0040135D . FF15 A8504000 call dword ptr ds:[<&USER32.MessageBoxA>] ; \MessageBoxA
00401363 . 5F pop edi
00401364 . 5E pop esi
00401365 . 33C0 xor eax,eax
00401367 . 5B pop ebx
00401368 . 81C4 00030000 add esp,0x300
0040136E . C3 retn
进入名为CRC32的函数看看:
00401110 >/$ 83EC 0C sub esp,0xC
00401113 |. 56 push esi
00401114 |. 57 push edi
00401115 |. E8 B6FFFFFF call <CRC32Cra.InitCRCTable>
0040111A |. 8B7424 18 mov esi,dword ptr ss:[esp+0x18]
0040111E |. 83C9 FF or ecx,0xFFFFFFFF
00401121 |. 8BFE mov edi,esi
00401123 |. 33C0 xor eax,eax
00401125 |. 83CA FF or edx,0xFFFFFFFF
00401128 |. F2:AE repne scas byte ptr es:[edi]
0040112A |. F7D1 not ecx
0040112C |. 49 dec ecx
0040112D |. 85C9 test ecx,ecx
第四句在初始化一个表,进去看看:
004010D0 >/$ 56 push esi
004010D1 |. 33D2 xor edx,edx
004010D3 |. B9 20854000 mov ecx,CRC32Cra.00408520
004010D8 |> 8BC2 /mov eax,edx
004010DA |. BE 08000000 |mov esi,0x8
004010DF |> A8 01 |/test al,0x1
004010E1 |. 74 09 ||je short CRC32Cra.004010EC
004010E3 |. D1E8 ||shr eax,1
004010E5 |. 35 2083B8ED ||xor eax,0xEDB88320
004010EA |. EB 02 ||jmp short CRC32Cra.004010EE
004010EC |> D1E8 ||shr eax,1
004010EE |> 4E ||dec esi
004010EF |.^ 75 EE |\jnz short CRC32Cra.004010DF
004010F1 |. 8901 |mov dword ptr ds:[ecx],eax
004010F3 |. 83C1 04 |add ecx,0x4
004010F6 |. 42 |inc edx
004010F7 |. 81F9 20894000 |cmp ecx,CRC32Cra.00408920
004010FD |.^ 7C D9 \jl short CRC32Cra.004010D8
004010FF |. 5E pop esi
00401100 \. C3 retn
代码很简单,用C语言写出来如下(初步描述,不保证无逻辑和编译错误):
void InitCRCTable()
{
DWORD *CRCTable=malloc(256*sizeof(int));
int i=0,j=0,k=0;
While(i<=256-1)
{
k=i;
j=8;
LabelJ_UnequalZero:
if(k==1)
{
k=k>>1;
j=j-1;
}
Else
{
k=k>>1;
k=k^0xEDB88320;
}
if(j!=0)
{
goto LabelJ_UnequalZero;
}
CRCTable[i++]=k;
}
}
继续Call InitCRCTable之后的:
0040111A |. 8B7424 18 mov esi,dword ptr ss:[esp+0x18]
0040111E |. 83C9 FF or ecx,0xFFFFFFFF
00401121 |. 8BFE mov edi,esi
00401123 |. 33C0 xor eax,eax
00401125 |. 83CA FF or edx,0xFFFFFFFF
00401128 |. F2:AE repne scas byte ptr es:[edi]
0040112A |. F7D1 not ecx
0040112C |. 49 dec ecx ; 获取szUserName长度
0040112D |. 85C9 test ecx,ecx
0040112F |. 7E 1F jle short CRC32Cra.00401150 ; 长度必须大于等于0
00401131 |. 53 push ebx
00401132 |> 8BC2 /mov eax,edx
00401134 |. 33DB |xor ebx,ebx
00401136 |. 8A1E |mov bl,byte ptr ds:[esi]
00401138 |. 25 FF000000 |and eax,0xFF
0040113D |. 33C3 |xor eax,ebx
0040113F |. C1EA 08 |shr edx,0x8
00401142 |. 8B0485 208540>|mov eax,dword ptr ds:[eax*4+<CRCTable>]
00401149 |. 33D0 |xor edx,eax
0040114B |. 46 |inc esi
0040114C |. 49 |dec ecx
0040114D |.^ 75 E3 \jnz short CRC32Cra.00401132
0040112F处开始的循环用C语言写出来如下(初步描述,不保证无逻辑和编译错误):
while(length==0)
{
int i,j,k,z;
i=0xFFFFFFFF;
j=i;
k=0;
z=0;
k=szUserName[z];
j=j^0xFF;
j=j^k;
i=i>>8;
j=CRCTable[j];
i=i^j;
length=length-1;
//这个循环是要计算i的值,不知道这是什么,不妨命名为magic
}
继续后面:
00401152 |. 8D4424 08 lea eax,dword ptr ss:[esp+0x8]
00401156 |. 894C24 09 mov dword ptr ss:[esp+0x9],ecx
0040115A |. 6A 10 push 0x10 ; 这是函数参数
0040115C |. F7D2 not edx ; not magic(从后面得知这个就是CRC32校验值了)
0040115E |. 894C24 11 mov dword ptr ss:[esp+0x11],ecx
00401162 |. 50 push eax ; 这是函数参数,一个buffer
00401163 |. 52 push edx ; 这是magic取反之后的,是个函数参数
00401164 |. C64424 14 00 mov byte ptr ss:[esp+0x14],0x0
00401169 |. 884C24 1D mov byte ptr ss:[esp+0x1D],cl ; 上面对一块地址的某些地方赋值为0(不知道何用)取magic相反数
0040116D |. E8 24380000 call CRC32Cra.00404996
最后有个call,进去看:
00404996 /$ 55 push ebp
00404997 |. 8BEC mov ebp,esp
00404999 |. 837D 10 0A cmp [arg.3],0xA ; 刚刚压入了0x10
0040499D |. 75 0C jnz short CRC32Cra.004049AB
0040499F |. 837D 08 00 cmp [arg.1],0x0
004049A3 |. 7D 06 jge short CRC32Cra.004049AB
004049A5 |. 6A 01 push 0x1
004049A7 |. 6A 0A push 0xA
004049A9 |. EB 05 jmp short CRC32Cra.004049B0
004049AB |> 6A 00 push 0x0
004049AD |. FF75 10 push [arg.3]
004049B0 |> FF75 0C push [arg.2] ; |Arg2
004049B3 |. FF75 08 push [arg.1] ; |Arg1
004049B6 |. E8 08000000 call CRC32Cra.004049C3 ; \CRC32Cra.004049C3
004049BB |. 8B45 0C mov eax,[arg.2] ; 发现这里的arg2变成了arg1的字符串形式
004049BE |. 83C4 10 add esp,0x10
004049C1 |. 5D pop ebp
004049C2 \. C3 retn
由于call CRC32Cra.004049C3之后Arg2变成了arg1的字符串形式,所以不用费力跟这个call了,应该是个delphi的库函数,如果OD有个识别delphi函数的插件就好了。。
继续:
00401172 |. 8D7C24 14 lea edi,dword ptr ss:[esp+0x14] ; 这个是crc32的字符串形式
00401176 |. 83C9 FF or ecx,0xFFFFFFFF
00401179 |. 33C0 xor eax,eax
0040117B |. 83C4 0C add esp,0xC
0040117E |. 33F6 xor esi,esi
00401180 |. F2:AE repne scas byte ptr es:[edi]
00401182 |. F7D1 not ecx
00401184 |. 49 dec ecx ; 获取CRC32值长度
00401185 |. 74 25 je short CRC32Cra.004011AC
发现delphi获取字符串长度的代码都很有个性的。。
继续,有个循环:
00401187 |> /0FBE4C34 08 /movsx ecx,byte ptr ss:[esp+esi+0x8]
0040118C |. |51 |push ecx
0040118D |. |E8 4E020000 |call CRC32Cra.004013E0 ; 循环多几次可以发现这个函数在把ecx转换为大写,放到eax
00401192 |. |884434 0C |mov byte ptr ss:[esp+esi+0xC],al ; 物归原主
00401196 |. |83C4 04 |add esp,0x4
00401199 |. |8D7C24 08 |lea edi,dword ptr ss:[esp+0x8]
0040119D |. |83C9 FF |or ecx,0xFFFFFFFF
004011A0 |. |33C0 |xor eax,eax
004011A2 |. |46 |inc esi ; esi++
004011A3 |. |F2:AE |repne scas byte ptr es:[edi]
004011A5 |. |F7D1 |not ecx
004011A7 |. |49 |dec ecx ; 计算大小
004011A8 |. |3BF1 |cmp esi,ecx
004011AA |.^\72 DB \jb short CRC32Cra.00401187
很明显可以看出是在把CRC字符串中的字母转换为大写了
继续:
004011AC |> \8B4424 1C mov eax,dword ptr ss:[esp+0x1C]
004011B0 |. 8D5424 08 lea edx,dword ptr ss:[esp+0x8]
004011B4 |. 52 push edx ; /String2
004011B5 |. 50 push eax ; |String1
004011B6 |. FF15 00504000 call dword ptr ds:[<&KERNEL32.lstrcpyA>] ; \lstrcpyA
004011BC |. 5F pop edi ; 0012F723
004011BD |. 5E pop esi
004011BE |. 83C4 0C add esp,0xC
004011C1 \. C3 retn
Copy一下字符串到12F91C
继续:
0040131E . 8D9424 140200>lea edx,dword ptr ss:[esp+0x214]
00401325 . 8D4424 14 lea eax,dword ptr ss:[esp+0x14]
00401329 . 52 push edx ; may be CRC32 value
0040132A . 50 push eax ; szPassword
0040132B . E8 D0FCFFFF call <CRC32Cra.Compare>
进入Compare函数:
00401000 >/$ 81EC 00020000 sub esp,0x200
00401006 |. 56 push esi
00401007 |. 57 push edi
00401008 |. B9 3F000000 mov ecx,0x3F
0040100D |. 33C0 xor eax,eax
0040100F |. 8D7C24 09 lea edi,dword ptr ss:[esp+0x9]
00401013 |. C64424 08 00 mov byte ptr ss:[esp+0x8],0x0
00401018 |. F3:AB rep stos dword ptr es:[edi] ; 初始化一段地方为0
0040101A |. 66:AB stos word ptr es:[edi]
0040101C |. AA stos byte ptr es:[edi]
0040101D |. B9 3F000000 mov ecx,0x3F
00401022 |. 33C0 xor eax,eax
00401024 |. 8DBC24 090100>lea edi,dword ptr ss:[esp+0x109]
0040102B |. C68424 080100>mov byte ptr ss:[esp+0x108],0x0
00401033 |. F3:AB rep stos dword ptr es:[edi]
00401035 |. 66:AB stos word ptr es:[edi]
00401037 |. 8B35 00504000 mov esi,dword ptr ds:[<&KERNEL32.lstrcpyA>] ; kernel32.lstrcpyA
0040103D |. 8D4C24 08 lea ecx,dword ptr ss:[esp+0x8]
00401041 |. AA stos byte ptr es:[edi]
00401042 |. 8B8424 0C0200>mov eax,dword ptr ss:[esp+0x20C]
00401049 |. 50 push eax ; /szPassword
0040104A |. 51 push ecx ; |empty buffer
0040104B |. FFD6 call esi ; \lstrcpyA
0040104D |. 8B9424 100200>mov edx,dword ptr ss:[esp+0x210]
00401054 |. 8D8424 080100>lea eax,dword ptr ss:[esp+0x108]
0040105B |. 52 push edx ; /szCRC
0040105C |. 50 push eax ; |empty buffer
0040105D |. FFD6 call esi ; \lstrcpyA
0040105F |. 8DBC24 080100>lea edi,dword ptr ss:[esp+0x108]
00401066 |. 83C9 FF or ecx,0xFFFFFFFF
00401069 |. 33C0 xor eax,eax
0040106B |. F2:AE repne scas byte ptr es:[edi]
0040106D |. F7D1 not ecx
0040106F |. 49 dec ecx ; 获取szCRC长度
00401070 |. 8D7C24 08 lea edi,dword ptr ss:[esp+0x8]
00401074 |. 8BD1 mov edx,ecx
00401076 |. 83C9 FF or ecx,0xFFFFFFFF
00401079 |. F2:AE repne scas byte ptr es:[edi]
0040107B |. F7D1 not ecx
0040107D |. 49 dec ecx ; 获取szPassword长度
0040107E |. 3BCA cmp ecx,edx
00401080 |. 75 40 jnz short CRC32Cra.004010C2 ; 首先长度得相等
00401082 |. 8D7C24 08 lea edi,dword ptr ss:[esp+0x8]
00401086 |. 83C9 FF or ecx,0xFFFFFFFF
00401089 |. 33D2 xor edx,edx
0040108B |. F2:AE repne scas byte ptr es:[edi]
0040108D |. F7D1 not ecx
0040108F |. 49 dec ecx ; 又计算szCRC长度,delphi就不能找个地方存起来?
00401090 |. 74 22 je short CRC32Cra.004010B4
00401092 |> 8A4414 08 /mov al,byte ptr ss:[esp+edx+0x8]
00401096 |. 8A8C14 080100>|mov cl,byte ptr ss:[esp+edx+0x108]
0040109D |. 3AC1 |cmp al,cl
0040109F |. 75 21 |jnz short CRC32Cra.004010C2
004010A1 |. 8D7C24 08 |lea edi,dword ptr ss:[esp+0x8]
004010A5 |. 83C9 FF |or ecx,0xFFFFFFFF
004010A8 |. 33C0 |xor eax,eax
004010AA |. 42 |inc edx
004010AB |. F2:AE |repne scas byte ptr es:[edi]
004010AD |. F7D1 |not ecx
004010AF |. 49 |dec ecx
004010B0 |. 3BD1 |cmp edx,ecx
004010B2 |.^ 72 DE \jb short CRC32Cra.00401092
004010B4 |> 5F pop edi
004010B5 |. B8 01000000 mov eax,0x1
004010BA |. 5E pop esi
004010BB |. 81C4 00020000 add esp,0x200
004010C1 |. C3 retn
最后那个循环很容易看出是比较两个字符串了;一个字符一个字符地比较
写注册机:
这个CM就是单纯地计算字符串的CRC,所以注册机也就是一个CRC算法了,拼凑刚才写好的代码,运行了一下,错误一大堆,编译的改好了也有逻辑错误导致运算结果不正确,调试了一阵,结果出来了,要注意的地方有:
1 shr跟C语言的>>运算不同,硬要在C语言实现shr的话需要给操作数加上unsigned int
2 有一个地方test al,0x1,写注册机的时候不可以直接eax==0,而是取eax的最低位判断是否为0
最终注册机完整代码如下:
#include <string.h>
#include <windows.h>
#include <stdio.h>
int *CRCTable=NULL;
void InitCRCTable()
{
int i=0,j=0;
unsigned int k=0;
while(i<=256-1)
{
//i是edx k是eax j是esi
k=i;
j=8;
LabelJ_UnequalZero:
//如果最后一位是0
if((k&1)==0)
{
k=k>>1;
j=j-1;
}
else
{
k=k>>1;
k=k^0xEDB88320;
j=j-1;
}
if(j!=0)
{
goto LabelJ_UnequalZero;
}
CRCTable[i++]=k;
}
}
void main()
{
CRCTable=(int*)malloc(256*sizeof(int));
InitCRCTable();
char szUserName[20];
char *szTmpPassword=(char*)malloc(20);
char *szFinalPassword=(char*)malloc(20);
memset(szTmpPassword,0,20);
memset(szFinalPassword,0,20);
int length=0;
unsigned int i;
unsigned int j;
unsigned int k;
unsigned int z;
scanf("%s",szUserName);
length=strlen(szUserName);
i=0xFFFFFFFF;
z=0;
//这里i是edx j是eax z对应esi k=ebx
while(length!=0)
{
j=i;
k=0;
k=szUserName[z++];
j=j&0xFF;
j=j^k;
i=i>>8;
j=CRCTable[j];
i=i^j;
length=length-1;
//这个循环是要计算i的值,不知道这是什么,不妨命名为magic
}
//再取反一下
i=~i;
//转为字符串
sprintf(szTmpPassword,"%x",i);
//转为大写
szFinalPassword=strupr(szTmpPassword);
//输出
printf("%s\n",szFinalPassword);
}
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课