【文章标题】:一个简单的CrackMe的详细分析和注册机制作
【文章作者】: 孤叶飘零
【作者邮箱】: buaa.libo@gmail.com
【软件名称】: The BigMan's CrackMe #6
【下载地址】: 论坛下载
【加壳方式】: 无壳
【使用工具】: OD
【作者声明】: 冒个泡,证明我没鬼混。。。和大家分享自己的学习经历
--------------------------------------------------------------------------------
用PEID查看文件,发现LCC Win32 1.x -> Jacob Navia [Overlay]
打开文件,输入Name:buaalibo
Serial:123456789
发现没有任何提示。
用OD载入,在这里
004011CB >/$ 64:A1 0100000>mov eax, dword ptr fs:[1]
004011D1 |. 55 push ebp
004011D2 |. 89E5 mov ebp, esp
004011D4 |. 6A FF push -1
004011D6 |. 68 1C204000 push 0040201C
004011DB |. 68 9A104000 push 0040109A
004011E0 |. 50 push eax
004011E1 |. 64:8925 00000>mov dword ptr fs:[0], esp
004011E8 |. 83EC 10 sub esp, 10
004011EB |. 53 push ebx
004011EC |. 56 push esi
004011ED |. 57 push edi
004011EE |. 8965 E8 mov dword ptr [ebp-18], esp
004011F1 |. 50 push eax
004011F2 |. D93C24 fstcw word ptr [esp]
004011F5 |. 66:810C24 000>or word ptr [esp], 300
004011FB |. D92C24 fldcw word ptr [esp]
004011FE |. 83C4 04 add esp, 4
按CTRL+N,查看输入表,发现GetDlgItemTextA,这个应该就是读取用户名和注册码的地方,在反汇编窗口中跟随,
77D6B05E > 8BFF mov edi, edi
77D6B060 55 push ebp
77D6B061 8BEC mov ebp, esp
77D6B063 FF75 0C push dword ptr [ebp+C]
77D6B066 FF75 08 push dword ptr [ebp+8]
77D6B069 E8 0093FBFF call GetDlgItem
在此F2下断点,F9运行程序,输入Name:buaalibo
Serial:123456789
点击CHECK ,断下,ALT+F9返回程序领空
00401534 |. 6A 65 push 65 ; |ControlID = 65 (101.)
00401536 |. FF75 08 push dword ptr [ebp+8] ; |hWnd
00401539 |. E8 FA010000 call <jmp.&USER32.GetDlgItemTextA> ; \GetDlgItemTextA
0040153E |. 89C3 mov ebx, eax//eax返回的是name的长度
00401540 |. 09DB or ebx, ebx//如果没有输入用户名的话会失败
00401542 |. 75 04 jnz short 00401548
00401544 |. 31C0 xor eax, eax
00401546 |. EB 50 jmp short 00401598
00401548 |> BF BC020000 mov edi, 2BC
0040154D |. BE 30000000 mov esi, 30
删除刚才的断点,在0040153E这一句下段,下次从这里开始分析。
F8一直向下走
00401548 |> \BF BC020000 mov edi, 2BC
0040154D |. BE 30000000 mov esi, 30
00401552 |. B8 48000000 mov eax, 48
00401557 |. 99 cdq ; 将EAX扩展到edx
00401558 |. F7FB idiv ebx ; 用48除以strlen(name),eax为商 edx为余数
0040155A |. 29C6 sub esi, eax ; 用30-商
0040155C |. 8D34B6 lea esi, dword ptr [esi+esi*4] ; esi=esi*5
0040155F |. 29F7 sub edi, esi ; 用edi=2BC-(esi-48/strlen(name))*5
00401561 |. 6BFF 6B imul edi, edi, 6B ; edi=edi imul 6B
00401564 |. 81EF 6CCF0000 sub edi, 0CF6C ; edi=edi-0CF6C
0040156A |. 81FF 00230000 cmp edi, 2300 ; edi必须>190<2300
这一段程序是利用用户名长度和给定的2BC,30,48算出一个值,这个值必须在190h-2300h之间,经过试验用户名长度应该在4-8之间。
继续F8
0040157E |> \8D85 00FFFFFF lea eax, dword ptr [ebp-100] ; eax指向name
00401584 |. 50 push eax ; name strlen(name)入栈
00401585 |. 53 push ebx
00401586 |. FF75 08 push dword ptr [ebp+8]
00401589 |. E8 77FDFFFF call 00401305
0040158E |. 83C4 0C add esp, 0C
00401591 |. 09C0 or eax, eax
00401593 |. 74 03 je short 00401598
00401595 |. 31C0 xor eax, eax
00401597 |. 40 inc eax
00401598 |> 5F pop edi
00401599 |. 5E pop esi
0040159A |. 5B pop ebx
0040159B |. C9 leave
0040159C \. C3 retn
这里只有一个CALL 之后程序会返回,故先在CALL 那里F7跟进
00401305 /$ 55 push ebp
00401306 |. 89E5 mov ebp, esp
00401308 |. 81EC 2C040000 sub esp, 42C ; 分配空间
0040130E |. 53 push ebx
0040130F |. 56 push esi
来到这里后一路F8,之间的rep命令一直没搞懂他在干嘛,影响不大。
F8走到
0040139E |. 68 00010000 push 100 ; /Count = 100 (256.)
004013A3 |. 8D85 E1FCFFFF lea eax, dword ptr [ebp-31F] ; |
004013A9 |. 50 push eax ; |Buffer
004013AA |. 6A 66 push 66 ; |ControlID = 66 (102.)
004013AC |. FF75 08 push dword ptr [ebp+8] ; |hWnd
004013AF |. E8 84030000 call <jmp.&USER32.GetDlgItemTextA> ; \GetDlgItemTextA
004013B4 |. 09C0 or eax, eax ; strlen(code)!=0
在此调用GetDlgItemTextA获得输入的假码,若没有输入则失败
F8
004013B4 |. 09C0 or eax, eax ; strlen(code)!=0
004013B6 |. 0F84 48010000 je 00401504
004013BC |. B8 CF110000 mov eax, 11CF
004013C1 |. 0FB68D E1FCFF>movzx ecx, byte ptr [ebp-31F] ; ecx=code[0]
004013C8 |. 99 cdq
004013C9 |. F7F9 idiv ecx ; eax=eax/code[0],edx=eax%code[0]
004013CB |. 83FA 17 cmp edx, 17 ; (11CF+17)%code[0]==0 否则会挂 程序中生成的中间码 从以后的跟踪可以得到注册码第一位为T 可以采用
004013CE |. 74 07 je short 004013D7
主要是根据JE之后的JMP一下跳过了下面所有的代码,所以这里把JE改为JMP方便调试
F8来到这里
004013DB |> /8B45 10 /mov eax, dword ptr [ebp+10]
004013DE |. |0FBE0418 |movsx eax, byte ptr [eax+ebx] ; 取name[i] i=ebx
004013E2 |. |0145 FC |add dword ptr [ebp-4], eax ; 将sum_name存入[ebp-4]
004013E5 |. |43 |inc ebx ; i++
004013E6 |> |3B5D 0C cmp ebx, dword ptr [ebp+C] ; ebx<strlen(name)?
004013E9 |.^\7C F0 \jl short 004013DB ; 小于则跳转
将name[i]每一位的ASCII码相加。
F8来到这里
004013F2 |> /8B55 10 /mov edx, dword ptr [ebp+10] ; edx指向name
004013F5 |. |0FBE3C1A |movsx edi, byte ptr [edx+ebx] ; edi=name[i] i=ebx
004013F9 |. |8B75 FC |mov esi, dword ptr [ebp-4] ; esi=sum_name
004013FC |. |89D9 |mov ecx, ebx ; ecx=i
004013FE |. |C1E1 02 |shl ecx, 2 ; ecx*4
00401401 |. |89DA |mov edx, ebx ; i=ebx j=i=ebx
00401403 |. |42 |inc edx ; j=i+1
00401404 |. |29D1 |sub ecx, edx ; i*4-i-1=i*3-1
00401406 |. |0FB68C0D E1FE>|movzx ecx, byte ptr [ebp+ecx-11F] ; ecx=存好的一组表
0040140E |. |89FA |mov edx, edi ; edx=name[i]
00401410 |. |31CA |xor edx, ecx ; edx^table[i]=0
00401412 |. |89F1 |mov ecx, esi ; ecx=sum_name
00401414 |. |0FAFCB |imul ecx, ebx ; ecx=sum_name*i
00401417 |. |29F1 |sub ecx, esi ; ecx=sum_name*i-sum_name
00401419 |. |89CE |mov esi, ecx ; esi=sum_name*i-sum_name
0040141B |. |83F6 FF |xor esi, FFFFFFFF ; esi^=FFFFFFFF
0040141E |. |8DB432 4D0100>|lea esi, dword ptr [edx+esi+14D] ; esi=esi+edx+14D
00401425 |. |8B4D 0C |mov ecx, dword ptr [ebp+C] ; ecx=strlen(name)
00401428 |. |89DA |mov edx, ebx ; edx=i
0040142A |. |83C2 03 |add edx, 3 ; i+=3
0040142D |. |0FAFCA |imul ecx, edx ; ecx=strlen(name)*(i+3)
00401430 |. |0FAFCF |imul ecx, edi ; ecx=strlen(name)*(i+3)*name[i]
00401433 |. |89F0 |mov eax, esi ; esi见前面
00401435 |. |01C8 |add eax, ecx ; eax=esi+strlen(name)*(i+3)*name[i]
00401437 |. |B9 0A000000 |mov ecx, 0A
0040143C |. |31D2 |xor edx, edx
0040143E |. |F7F1 |div ecx ; eax=eax/0A,edx=eax%0A
00401440 |. |83C2 30 |add edx, 30 ; edx=eax%0A+30
00401443 |. |88941D FCFEFF>|mov byte ptr [ebp+ebx-104], dl ; mov ebp+i-104,dl
0040144A |. |0FB6BC1D FCFE>|movzx edi, byte ptr [ebp+ebx-104] ; edi=dl
00401452 |. |81F7 ACAD0000 |xor edi, 0ADAC ; edi=dl^=0ADAC
00401458 |. |89DE |mov esi, ebx ; esi=i
0040145A |. |83C6 02 |add esi, 2 ; esi=i+2
0040145D |. |89F8 |mov eax, edi ; eax=dl^=0ADAC
0040145F |. |0FAFC6 |imul eax, esi ; eax=eax*(i+2)
00401462 |. |B9 0A000000 |mov ecx, 0A ; ecx=0A
00401467 |. |99 |cdq
00401468 |. |F7F9 |idiv ecx ; eax=eax/0A,edx=eax%0A
0040146A |. |83C2 30 |add edx, 30 ; edx+=30
0040146D |. |88941D FCFEFF>|mov byte ptr [ebp+ebx-104], dl ; dl->ebp+i-104
00401474 |. |43 |inc ebx
00401475 |> |3B5D 0C cmp ebx, dword ptr [ebp+C] ; ebx<strlen(name)?
00401478 |.^\0F8C 74FFFFFF \jl 004013F2
0040147E |. 8D85 FCFEFFFF lea eax, dword ptr [ebp-104] ; eax记录算出来的一个码cname
这段程序是利用用户名的长度和用户名本身通过一系列计算(已经写在注释中),得到一个字符串,记为cname
看00401406 |. |0FB68C0D E1FE>|movzx ecx, byte ptr [ebp+ecx-11F]
这一行 其中大的ECX为保存好的一组计算时候需要的表,可以在堆栈窗口中翻页找到这个地址中的内容,而且由上一行代码,ecx每次增加3,所以每隔三个取一个字节即可,因为用户名位数不能过多,所以姑且取10个字节吧,
00401484 |. 50 push eax ; 参数
00401485 |. 6A 54 push 54
00401487 |. 8D85 DCFBFFFF lea eax, dword ptr [ebp-424] ; 格式。。eax54
0040148D |. 50 push eax ; |Format
0040148E |. 8D85 E1FBFFFF lea eax, dword ptr [ebp-41F] ; |
00401494 |. 50 push eax ; |s
00401495 |. E8 CE020000 call <jmp.&USER32.wsprintfA> ; \wsprintfA
0040149A |. 8B7D 0C mov edi, dword ptr [ebp+C] ; strlen(name)
0040149D |. 89F8 mov eax, edi
0040149F |. 0FAF45 FC imul eax, dword ptr [ebp-4] ; eax=strlen(name)*sum(name[i])
004014A3 |. B9 64000000 mov ecx, 64
004014A8 |. 99 cdq
004014A9 |. F7F9 idiv ecx ; eax=strlen(name)*sum(name[i])/64,edx=...%64
004014AB |. 89D7 mov edi, edx
004014AD |. 83C7 30 add edi, 30 ; edi=strlen(name)*sum(name[i])%64+30
004014B0 |. 57 push edi
004014B1 |. 8DBD E1FBFFFF lea edi, dword ptr [ebp-41F] ; edi->算出的字符串
004014B7 |. 57 push edi
004014B8 |. 8DBD D6FBFFFF lea edi, dword ptr [ebp-42A] ; 格式 %s-%d
004014BE |. 57 push edi ; |Format
004014BF |. 8DBD E1FDFFFF lea edi, dword ptr [ebp-21F] ; |
004014C5 |. 57 push edi ; |s
004014C6 |. E8 9D020000 call <jmp.&USER32.wsprintfA> ; \wsprintfA
这里,是利用wsprintf为cname前面加一个T(54H),后面加"-"和一个算出来的数字
得到cname1字符串
004014D7 |> /40 /inc eax ; eax做为计数器i 循环检测算出的string 是否为有空字符
004014D8 |. |803C01 00 |cmp byte ptr [ecx+eax], 0
004014DC |.^\75 F9 \jnz short 004014D7
004014DE |. 50 push eax ; eax为字符串长度
004014DF |. 8D85 E1FCFFFF lea eax, dword ptr [ebp-31F] ; eax->code
这里得到cname1的长度
004014DE |. 50 push eax ; eax为字符串长度
004014DF |. 8D85 E1FCFFFF lea eax, dword ptr [ebp-31F] ; eax->code
004014E5 |. 50 push eax
004014E6 |. 8D85 E1FDFFFF lea eax, dword ptr [ebp-21F]
004014EC |. 50 push eax
004014ED |. E8 D0FDFFFF call 004012C2
PUSH将cname1的长度cname1和假码入栈,下面的CALL应该就是比较的CALL F7
跟进
004012C8 |. 8B5D 10 mov ebx, dword ptr [ebp+10] ; ebx=strlen(cname)
004012CB |. 31F6 xor esi, esi
004012CD |. 46 inc esi
004012CE |. EB 29 jmp short 004012F9
004012D0 |> 8B55 08 /mov edx, dword ptr [ebp+8]
004012D3 |. 0FBE3C32 |movsx edi, byte ptr [edx+esi] ; edi=cname[i] i初始值为1
004012D7 |. 89F8 |mov eax, edi
004012D9 |. 83F0 20 |xor eax, 20 ; eax=cname[i]^20
004012DC |. B9 0A000000 |mov ecx, 0A
004012E1 |. 99 |cdq
004012E2 |. F7F9 |idiv ecx
004012E4 |. 89D7 |mov edi, edx ; edx=cname[i]^20%0A
004012E6 |. 83C7 30 |add edi, 30 ; edi=cname[i]^20%0A+30
004012E9 |. 8B55 0C |mov edx, dword ptr [ebp+C]
004012EC |. 0FBE1432 |movsx edx, byte ptr [edx+esi] ; edx=code[i] i初始值为1
004012F0 |. 39D7 |cmp edi, edx
004012F2 74 04 je short 004012F8 ; 这里就是比较的关键 改为jmp即可爆破
004012F4 |. 31C0 |xor eax, eax
004012F6 |. EB 08 |jmp short 00401300
004012F8 |> 46 |inc esi
004012F9 |> 39DE cmp esi, ebx ; esi<strlen(cname)?
004012FB |.^ 7C D3 \jl short 004012D0
循环部分便是比较的算法,看得出作者是分别取cname1的第2到第倒数第二个字符经过一系列算法算出一个值然后与输入的假码的每一位比较,只要有一次不相等,便会失败。
到此算法分析完毕。
PS:将004013B6处的je改为JMP 这样只要输入的注册码不为空即可爆破。
这个可以改为JMP后跟踪来到
00401589 |. E8 77FDFFFF call 00401305 0040158E |. 83C4 0C add esp, 0C
00401591 |. 09C0 or eax, eax
00401593 |. 74 03 je short 00401598
00401595 |. 31C0 xor eax, eax
00401597 |. 40 inc eax
00401598 |> 5F pop edi
00401599 |. 5E pop esi
0040159A |. 5B pop ebx
0040159B |. C9 leave
0040159C \. C3 retn
这是算法的最后部分,上面的CALL是关键算法。只要出来的EAX不为0则可以注册成功,而现在的EAX保存的是我们输入的假码长度,只要不为0即可注册成功。换句话说 可以把
00401593改为NOP即可爆破。
下来是注册机的制作,既然已经分析出了算法,注册机的制作应该不太难了。在注释的时候我尽量用了C语言的描述。
以上貌似我已经说了很多的废话了,故只将注册机和CrackMe打包,里面有MFC的源码。算法如下
VC++下调试。。
byte table[]={0x00,0x43,0x46,0x49,0x4C,0x4F,0x52,0x55,0x58,0x00};
char name[20];
char cname[20];
char cname1[20];
char code[20];
char reg[20];
int slen,sum_name=0;
int temp1,temp2;
slen=GetDlgItemText(IDC_EDIT1,name,20);
if(slen<4||slen>8){
MessageBoxEx(NULL,TEXT("The length must no less than 4 and no more than 8!"),
TEXT("Error!"),MB_OK,0);
goto end;}
for(int i=0;name[i]!='\0';i++)
{
sum_name+=int(name[i]);
}
for(int i=0;i<slen;i++)
{
temp2=(int)name[i]^table[i];
temp1=(sum_name*i-sum_name)^0xFFFFFFFF;
temp1=temp1+temp2+0x14D+slen*(i+3)*int(name[i]);
temp1=byte(temp1%0xA+0x30)^0xADAC;
temp1*=(i+2);
cname[i]=byte((temp1%0xA)+0x30);
}
cname[slen]='\0';
int cname_d=(slen*sum_name)%0x64+0x30;
wsprintf(cname1,"%s-%d",cname,cname_d);
slen=strlen(cname1);
int i;
for( i=0;i<slen;i++)
{
reg[i]=((int)cname1[i]^0x20)%0xA+0x30;
}
reg[i]='\0';
wsprintf(code,"%c%s",'T',reg);
SetDlgItemText(IDC_EDIT2,code);
end:;
P.S. 初次分析,如果有错,请大家指证,共同进步。
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)
上传的附件: