前辈的文章链接:http://bbs.pediy.com/showthread.php?t=11222
破解声明:
在学校图书馆借到了《加密与解密3》,这是第5章练习题:pediy crackme 的第一道题。
自己先尝试做了一遍,弄一有段时间了,最后终于弄出来了。前辈用了很短的时候就做出来了,
虽然自己做的比较慢,但是也发现了前辈在分析中的一些漏洞。由于前辈对有些细节没有考虑详细
,导致写出来的注册机有部分bug,现在我来尝试完善一下。
大部分分析和前辈是一样的:
发现加了ASPack 壳,用OD加载,SFX自动脱壳之后停在了真正的入口点。
发现时用GetDlgItemTextA函数取字符串的,当然是下断点,然后F9,之后再按F2,回到了代码领空。
我输入的
账号是:zhangyudi
密码是:loong
用于前辈给的注释不太详细,我再来把代码贴出来
总共分为三大部分吧:
第一部分
00401528 |. 68 00010000 PUSH 100 ; /Count = 100 (256.)
0040152D |. 8D85 00FFFFFF LEA EAX,DWORD PTR SS:[EBP-100] ; |
00401533 |. 50 PUSH EAX ; |Buffer
00401534 |. 6A 65 PUSH 65 ; |ControlID = 65 (101.)
00401536 |. FF75 08 PUSH DWORD PTR SS:[EBP+8] ; |hWnd
00401539 |. E8 FA010000 CALL <JMP.&USER32.GetDlgItemTextA> ; \GetDlgItemTextA
0040153E |. 89C3 MOV EBX,EAX
00401540 |. 09DB OR EBX,EBX ; 判断输入的账号是否为空
00401542 |. 75 04 JNZ SHORT unpacked.00401548
00401544 |. 31C0 XOR EAX,EAX ; 最后是以eax是否为空来判断注册是否成功
00401546 |. EB 50 JMP SHORT unpacked.00401598
00401548 |> BF BC020000 MOV EDI,2BC
0040154D |. BE 30000000 MOV ESI,30
00401552 |. B8 48000000 MOV EAX,48
00401557 |. 99 CDQ
00401558 |. F7FB IDIV EBX
0040155A |. 29C6 SUB ESI,EAX ; 30减去48h除以账号长度的商
0040155C |. 8D34B6 LEA ESI,DWORD PTR DS:[ESI+ESI*4] ; 上一步的结果乘以5
0040155F |. 29F7 SUB EDI,ESI ; edi减去上一步的结果
00401561 |. 6BFF 6B IMUL EDI,EDI,6B
00401564 |. 81EF 6CCF0000 SUB EDI,0CF6C
0040156A |. 81FF 00230000 CMP EDI,2300 ; 大于就失败
00401570 |. 7F 08 JG SHORT unpacked.0040157A
00401572 |. 81FF 90010000 CMP EDI,190
00401578 |. 7D 04 JGE SHORT unpacked.0040157E ; 大于等于才行
0040157A |> 31C0 XOR EAX,EAX
0040157C |. EB 1A JMP SHORT unpacked.00401598
0040157E |> 8D85 00FFFFFF LEA EAX,DWORD PTR SS:[EBP-100]
00401584 |. 50 PUSH EAX
00401585 |. 53 PUSH EBX
00401586 |. FF75 08 PUSH DWORD PTR SS:[EBP+8]
00401589 |. E8 77FDFFFF CALL unpacked.00401305 ; 关键点,必须跟进去
0040158E |. 83C4 0C ADD ESP,0C
00401591 |. 09C0 OR EAX,EAX ; 返回值必须不是0啊
这是对输入的账号进行判断:
输入为空的话,程序失败,
kong = (0x2bc -(0x30 - 0x48 / (signed int)(strlen(账号字符串))) * 5) * 0x6b - 0x0cf6c;最后kong的范围在[0x190,0x2300)
第二部分:
0040139E |. 68 00010000 PUSH 100 ; /Count = 100 (256.)
004013A3 |. 8D85 E1FCFFFF LEA EAX,DWORD PTR SS:[EBP-31F] ; |
004013A9 |. 50 PUSH EAX ; |Buffer
004013AA |. 6A 66 PUSH 66 ; |ControlID = 66 (102.)
004013AC |. FF75 08 PUSH DWORD PTR SS:[EBP+8] ; |hWnd
004013AF |. E8 84030000 CALL <JMP.&USER32.GetDlgItemTextA> ; \GetDlgItemTextA
004013B4 |. 09C0 OR EAX,EAX
004013B6 |. 0F84 48010000 JE unpacked.00401504 ; 如果输入的serial为空,则失败
004013BC |. B8 CF110000 MOV EAX,11CF
004013C1 |. 0FB68D E1FCFF>MOVZX ECX,BYTE PTR SS:[EBP-31F] ; 密码首位的asc码
004013C8 |. 99 CDQ
004013C9 |. F7F9 IDIV ECX
004013CB |. 83FA 17 CMP EDX,17 ; 余数必须等于0x17
004013CE |. 74 07 JE SHORT unpacked.004013D7
004013D0 |. 31C0 XOR EAX,EAX
004013D2 |. E9 2D010000 JMP unpacked.00401504
004013D7 |> 31DB XOR EBX,EBX
004013D9 |. EB 0B JMP SHORT unpacked.004013E6
004013DB |> 8B45 10 /MOV EAX,DWORD PTR SS:[EBP+10]
004013DE |. 0FBE0418 |MOVSX EAX,BYTE PTR DS:[EAX+EBX]
004013E2 |. 0145 FC |ADD DWORD PTR SS:[EBP-4],EAX ; 0012F8F0最后存放账号的ASC码的和
004013E5 |. 43 |INC EBX
004013E6 |> 3B5D 0C CMP EBX,DWORD PTR SS:[EBP+C]
004013E9 |.^ 7C F0 \JL SHORT unpacked.004013DB
004013EB |. 31DB XOR EBX,EBX
004013ED |. E9 83000000 JMP unpacked.00401475
004013F2 |> 8B55 10 /MOV EDX,DWORD PTR SS:[EBP+10] ; edx始终指向账号字符串的地址
004013F5 |. 0FBE3C1A |MOVSX EDI,BYTE PTR DS:[EDX+EBX] ; edi指向每一位账号的ASC值
004013F9 |. 8B75 FC |MOV ESI,DWORD PTR SS:[EBP-4] ; 账号每位ASC的和
004013FC |. 89D9 |MOV ECX,EBX
004013FE |. C1E1 02 |SHL ECX,2 ; 账号的数组下标左移2位
00401401 |. 89DA |MOV EDX,EBX
00401403 |. 42 |INC EDX
00401404 |. 29D1 |SUB ECX,EDX ; 上一步的结果减去对应数组下标加1的和
00401406 |. 0FB68C0D E1FE>|MOVZX ECX,BYTE PTR SS:[EBP+ECX-11F] ; 0012F7D4的一串字符串
0040140E |. 89FA |MOV EDX,EDI
00401410 |. 31CA |XOR EDX,ECX ; edx为账号某些特定位的asc值与ecx对应的asc值异或
00401412 |. 89F1 |MOV ECX,ESI
00401414 |. 0FAFCB |IMUL ECX,EBX ; 账号字符asc码和乘以当前读取字符下标
00401417 |. 29F1 |SUB ECX,ESI ; 上面的结果在减去一个账号ASC的和
00401419 |. 89CE |MOV ESI,ECX
0040141B |. 83F6 FF |XOR ESI,FFFFFFFF ; 上面的结果与-1异或
0040141E |. 8DB432 4D0100>|LEA ESI,DWORD PTR DS:[EDX+ESI+14D] ; esi加上14D加上账号asc
00401425 |. 8B4D 0C |MOV ECX,DWORD PTR SS:[EBP+C] ; 账号的长度
00401428 |. 89DA |MOV EDX,EBX ; 当前下标
0040142A |. 83C2 03 |ADD EDX,3 ; 当前下标加3
0040142D |. 0FAFCA |IMUL ECX,EDX ; 长度乘以上面的结果
00401430 |. 0FAFCF |IMUL ECX,EDI ; 上面的结果乘以对应asc值
00401433 |. 89F0 |MOV EAX,ESI
00401435 |. 01C8 |ADD EAX,ECX ; 加上之前esi的结果
00401437 |. B9 0A000000 |MOV ECX,0A
0040143C |. 31D2 |XOR EDX,EDX
0040143E |. F7F1 |DIV ECX ; 每次除以10,即求模10
00401440 |. 83C2 30 |ADD EDX,30 ; 余数加30h
00401443 |. 88941D FCFEFF>|MOV BYTE PTR SS:[EBP+EBX-104],DL ; 要余数的低byte位
0040144A |. 0FB6BC1D FCFE>|MOVZX EDI,BYTE PTR SS:[EBP+EBX-104]
00401452 |. 81F7 ACAD0000 |XOR EDI,0ADAC ; 与0adac异或
00401458 |. 89DE |MOV ESI,EBX ; 当前下标
0040145A |. 83C6 02 |ADD ESI,2 ; 加2
0040145D |. 89F8 |MOV EAX,EDI
0040145F |. 0FAFC6 |IMUL EAX,ESI
00401462 |. B9 0A000000 |MOV ECX,0A
00401467 |. 99 |CDQ
00401468 |. F7F9 |IDIV ECX
0040146A |. 83C2 30 |ADD EDX,30
0040146D |. 88941D FCFEFF>|MOV BYTE PTR SS:[EBP+EBX-104],DL
00401474 |. 43 |INC EBX
00401475 |> 3B5D 0C CMP EBX,DWORD PTR SS:[EBP+C] ; ebx做指针
00401478 |.^ 0F8C 74FFFFFF \JL unpacked.004013F2
0040147E |. 8D85 FCFEFFFF LEA EAX,DWORD PTR SS:[EBP-104]
00401484 |. 50 PUSH EAX
00401485 |. 6A 54 PUSH 54
00401487 |. 8D85 DCFBFFFF LEA EAX,DWORD PTR SS:[EBP-424]
0040148D |. 50 PUSH EAX ; |Format
0040148E |. 8D85 E1FBFFFF LEA EAX,DWORD PTR SS:[EBP-41F] ; |
00401494 |. 50 PUSH EAX ; |s
00401495 |. E8 CE020000 CALL <JMP.&USER32.wsprintfA> ; \wsprintfA
0040149A |. 8B7D 0C MOV EDI,DWORD PTR SS:[EBP+C] ; zhangyudi账号得到的是T61258670(0012F4D5)
0040149D |. 89F8 MOV EAX,EDI
0040149F |. 0FAF45 FC IMUL EAX,DWORD PTR SS:[EBP-4] ; 账号的长度乘以每位账号ASC码的和
004014A3 |. B9 64000000 MOV ECX,64
004014A8 |. 99 CDQ
004014A9 |. F7F9 IDIV ECX ; 上面得到的结果除以64
004014AB |. 89D7 MOV EDI,EDX
004014AD |. 83C7 30 ADD EDI,30 ; 商加上30
004014B0 |. 57 PUSH EDI
004014B1 |. 8DBD E1FBFFFF LEA EDI,DWORD PTR SS:[EBP-41F]
004014B7 |. 57 PUSH EDI ; 上一个wsprintfA得到的结果T61258670
004014B8 |. 8DBD D6FBFFFF LEA EDI,DWORD PTR SS:[EBP-42A] ; %s-%d
004014BE |. 57 PUSH EDI ; |Format
004014BF |. 8DBD E1FDFFFF LEA EDI,DWORD PTR SS:[EBP-21F] ; |
004014C5 |. 57 PUSH EDI ; |s
004014C6 |. E8 9D020000 CALL <JMP.&USER32.wsprintfA> ; \wsprintfA
004014CB |. 83C4 20 ADD ESP,20 ; 得到序列T612582670-59,59应该是后面计算(0012F6D5)
004014CE |. 8D8D E1FDFFFF LEA ECX,DWORD PTR SS:[EBP-21F]
004014D4 |. 83C8 FF OR EAX,FFFFFFFF
004014D7 |> 40 /INC EAX
004014D8 |. 803C01 00 |CMP BYTE PTR DS:[ECX+EAX],0
004014DC |.^ 75 F9 \JNZ SHORT unpacked.004014D7 ; 得到第二次的序列长度
004014DE |. 50 PUSH EAX ; /Arg3
004014DF |. 8D85 E1FCFFFF LEA EAX,DWORD PTR SS:[EBP-31F] ; |指向输入密码的地址
004014E5 |. 50 PUSH EAX ; |Arg2
004014E6 |. 8D85 E1FDFFFF LEA EAX,DWORD PTR SS:[EBP-21F] ; |指向第二次得到的序列的地址
004014EC |. 50 PUSH EAX ; |Arg1
004014ED |. E8 D0FDFFFF CALL unpacked.004012C2 ; \unpacked.004012C2
004014F2 |. 83C4 0C ADD ESP,0C ; 上面那个函数是关键算法
004014F5 |. 83F8 00 CMP EAX,0
004014F8 |. 75 07 JNZ SHORT unpacked.00401501 ; 返回值必须不是0
004014FA |. B8 00000000 MOV EAX,0
004014FF |. EB 03 JMP SHORT unpacked.00401504
00401501 |> 31C0 XOR EAX,EAX
00401503 |. 40 INC EAX
00401504 |> 5F POP EDI
00401505 |. 5E POP ESI
00401506 |. 5B POP EBX
00401507 |. C9 LEAVE
00401508 \. C3 RETN
这部分是比较关键的代码了,它根据输入的账号进行了一系列的运算,得到了一个中间字符串,
其中还有一点是,输入的密码的第一位的asc码是有限制的,它必须被0x11cf - 0x17整除。
至于得到中间序列的算法请看注册机代码。
第三部分
004012C2 /$ 55 PUSH EBP
004012C3 |. 89E5 MOV EBP,ESP
004012C5 |. 53 PUSH EBX
004012C6 |. 56 PUSH ESI
004012C7 |. 57 PUSH EDI
004012C8 |. 8B5D 10 MOV EBX,DWORD PTR SS:[EBP+10] ; ebx为序列的长度
004012CB |. 31F6 XOR ESI,ESI
004012CD |. 46 INC ESI ; 跳过第一个字符
004012CE |. EB 29 JMP SHORT unpacked.004012F9
004012D0 |> 8B55 08 /MOV EDX,DWORD PTR SS:[EBP+8]
004012D3 |. 0FBE3C32 |MOVSX EDI,BYTE PTR DS:[EDX+ESI]
004012D7 |. 89F8 |MOV EAX,EDI
004012D9 |. 83F0 20 |XOR EAX,20 ; 序列号第二位之后的ASC码与20h异或
004012DC |. B9 0A000000 |MOV ECX,0A
004012E1 |. 99 |CDQ
004012E2 |. F7F9 |IDIV ECX ; 异或之后的结果除以A
004012E4 |. 89D7 |MOV EDI,EDX
004012E6 |. 83C7 30 |ADD EDI,30 ; 余数加30
004012E9 |. 8B55 0C |MOV EDX,DWORD PTR SS:[EBP+C]
004012EC |. 0FBE1432 |MOVSX EDX,BYTE PTR DS:[EDX+ESI]
004012F0 |. 39D7 |CMP EDI,EDX ; 与输入的账号逐位比较,必须相等才行
004012F2 |. 74 04 |JE SHORT unpacked.004012F8
004012F4 |. 31C0 |XOR EAX,EAX
004012F6 |. EB 08 |JMP SHORT unpacked.00401300
004012F8 |> 46 |INC ESI
004012F9 |> 39DE CMP ESI,EBX
004012FB |.^ 7C D3 \JL SHORT unpacked.004012D0
004012FD |. 31C0 XOR EAX,EAX
004012FF |. 40 INC EAX
00401300 |> 5F POP EDI
00401301 |. 5E POP ESI
00401302 |. 5B POP EBX
00401303 |. 5D POP EBP
00401304 \. C3 RETN
这段代码应该比较简单:
上面得到的中间字符串从第二位开始:
asc码值与0x20异或,然后余数加上0x30,得到的结果应该就是输入的密码对应的字符的asc
值。
注册机的代码如下(vc6.0通过):
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main(void)
{
int i, j;
int sum;
char tep[10];
int str;
int ch;
int kong;
int loong;
//char arr1[] = {"zhangyudi"};
char arr1[50];
char arr[] = {"ABCDEFGHIJKLMNOPQRSTUVWXYZ"};
char temp[50];
sum = 0;
j = 0;
temp[j] = 'T';
j++;
printf("请输入要注册的账号!\n");
scanf("%s", arr1);
if (strlen(arr1) == 0) //账号长度为0,则失败
{
printf("账号无效!\n");
return 0;
}
kong = (0x2bc -(0x30 - 0x48 / (signed int)(strlen(arr1))) * 5) * 0x6b - 0x0cf6c;
if (kong < 0x190 || kong >= 0x2300)
{
printf("账号无效!\n");
return 0;
}
/**********上面是对账号的验证**************/
for (i = 0; i < (signed int)strlen(arr1); i++)
{
sum = sum + arr1[i];
}
//printf("%x\n", sum); //输入的账号总和
i = 0;
ch = ((((((arr1[i] ^ 0) + ((sum * i - sum) ^ 0xffffffff) + 0x14D +
(i + 3) * strlen(arr1) * arr1[i]) % 0xA + 0x30) ^ 0x0ADAC)
* (i + 2) % 0xA) + 0x30) % 16;
//printf("%d ", ch); //首位需要单独考虑
temp[j] = ch + 48;
j++;
for (i = 1; i < (signed int)strlen(arr1); i++, j++)
{
ch = ((((((arr1[i] ^ arr[i * 4 - i - 1]) + ((sum * i - sum) ^ 0xffffffff) + 0x14D +
(i + 3) * strlen(arr1) * arr1[i]) % 0xA + 0x30) ^ 0x0ADAC)
* (i + 2) % 0xA) + 0x30) % 16;
//printf("%d ", ch);
temp[j] = ch + 48;
}
/********上面各种复杂的取得中间码的运算*******/
str = j;
printf("\n");
printf("\n中间运算变量1:\n");
for (j = 0; j < str; j++)
{
printf("%c", temp[j]);
}
temp[str] = '\0';
ultoa((sum * strlen(arr1) % 0x64 + 0x30), tep, 10);
strcat(temp, "-");
strcat(temp, tep);
temp[strlen(temp) + 1] = '\0';
printf("\n中间运算变量2:\n");
printf("\n%s", temp);
for (i = 1; i < (signed int)strlen(temp); i++)
{
temp[i] = (temp[i] ^ 0x20) % 0xA + 0x30;
}
printf("\n最终的注册码\n");
printf("\n%s", temp);
/**************以下是对密码首位的验证*************/
loong = 0x11cf - 0x17;
printf("其实密码的首位有很多种情况,考虑到用户的输入,只列出下面的情况!\n");
for (i = 2; i < (loong + 1) / 2; i++)
{
if ((loong % i) == 0 && (i >= 32) && (i <= 126) )
{
printf("%c\n", i);
}
}
return 0;
}
PS: 如果发现有bug,请指出。
经过数学计算,发现输入的的账号如果超过10位(含10位)的话,就肯定不行,因为字母AB...Z,总共只有26位, (26 + 1) / 3 = 9,这点程序设计者在之前貌似通过第一部分已经过滤掉了
(输入验证没有发现bug,但是没有从理论上证明)。
整个的过程大致如此吧,感觉做逆向非常需要静得下心来,这样才能有所突破。
谈谈算法本身吧,感觉还行,只是虽然没有直接用账号和密码进行简单的比较,但是
账号的运算毕竟产生的中间字符串明文的出现在了内存之中,貌似可以直接将那一部分的反汇编
代码内嵌的C语言中,这样好像可以不管你具体的算法,而直接跳过那一部分,弱化了算法的复杂性。
大致如此吧,菜鸟的第一次发破解贴,请大家多多指教
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课