[前言]
Crack016.exe,这是《加密与解密》(第二版)中的一个动态跟踪调试示例,我没有这本书,是看到有人上传了这个附件,就下来看看,正好有人要求写篇面向初学者的文章,于是写了篇破解分析,我尽量写清楚自己的思路和想法,希望便于初学者理解。
[作者]HillCat
[日期]2005-12-8
[分析]
破解的一般步骤是:
1.查壳,如果有壳看是否能快速搞定;壳有点麻烦的话,我一般是先放一边。
2.了解编写语言,了解程序注册方式,形成破解思路。(这里分类展开可以写成长篇了,我就省了)
3.由简到易,查看字符参考,设置API断点,使用内存断点,使用万能断点,其他方法。
4.破解形式包括,找出明码,修改跳转爆破,跟踪算法分析出注册码。
好了,按照上面的步骤,进行实际操练。
PEID查看无壳,VC++编写;序列号注册方式,注册错误有提示。
于是想到可以尝试:MessageBoxA,GetDlgItemTextA
OD载入程序,不管怎样,首先还是查看一下超级字符参考,发现:
004010DD MOV ESI,CrackMe0.00405060 你输入字符要大于四个!
0040110F MOV ESI,CrackMe0.00405038 序列号错误,再来一次!
没有成功的提示,看来是对注册成功的字符串进行了处理,或者注册成功后没有消息提示。
从字符参考返回来到:
004010D6 . 56 PUSH ESI
004010D7 . 57 PUSH EDI
004010D8 . B9 05000000 MOV ECX,5
004010DD . BE 60504000 MOV ESI,CrackMe0.00405060 ; 你输入字符要大于四个!
004010E2 . 8D7C24 18 LEA EDI,DWORD PTR SS:[ESP+18]
004010E6 . A1 50504000 MOV EAX,DWORD PTR DS:[405050]
004010EB . F3:A5 REP MOVS DWORD PTR ES:[EDI],DWORD PTR DS:[ESI]
004010ED . 8B0D 54504000 MOV ECX,DWORD PTR DS:[405054]
004010F3 . 8B15 58504000 MOV EDX,DWORD PTR DS:[405058]
004010F9 . 66:A5 MOVS WORD PTR ES:[EDI],WORD PTR DS:[ESI]
004010FB . 894C24 0C MOV DWORD PTR SS:[ESP+C],ECX
004010FF . 8A0D 5E504000 MOV CL,BYTE PTR DS:[40505E]
00401105 . A4 MOVS BYTE PTR ES:[EDI],BYTE PTR DS:[ESI]
00401106 . 884C24 16 MOV BYTE PTR SS:[ESP+16],CL
0040110A . B9 05000000 MOV ECX,5
0040110F . BE 38504000 MOV ESI,CrackMe0.00405038 ; 序列号错误,再来一次!
00401114 . 8D7C24 30 LEA EDI,DWORD PTR SS:[ESP+30]
00401118 . F3:A5 REP MOVS DWORD PTR ES:[EDI],DWORD PTR DS:[ESI]
0040111A . 894424 08 MOV DWORD PTR SS:[ESP+8],EAX
0040111E . 66:A1 5C50400>MOV AX,WORD PTR DS:[40505C]
00401124 . 66:A5 MOVS WORD PTR ES:[EDI],WORD PTR DS:[ESI]
00401126 . 66:894424 14 MOV WORD PTR SS:[ESP+14],AX
0040112B . 8B8424 040100>MOV EAX,DWORD PTR SS:[ESP+104]
看这些代码几乎都是MOV,我不知道是什么意思,但不像是算法分析。如果你不放心的话,就在这里设断运行看看,设断后按多次F9都没有出来输入信息的窗口,决定放弃!
重新载入。既然有消息提示,不妨BP MessageBoxA;结果没断下来,可能使用了别的函数,我不知道用的什么,试下别的办法先!
重新载入。既然有输入信息的对话框,我们就 BP GetDlgItemTextA;
F9,输入注册信息:
HillCat
7123456
确定,断在这里:
77E04CC5 > 55 PUSH EBP
77E04CC6 8BEC MOV EBP,ESP
77E04CC8 FF75 0C PUSH DWORD PTR SS:[EBP+C]
77E04CCB FF75 08 PUSH DWORD PTR SS:[EBP+8]
77E04CCE E8 201CFFFF CALL USER32.GetDlgItem
77E04CD3 85C0 TEST EAX,EAX
77E04CD5 0F84 8F880300 JE USER32.77E3D56A
77E04CDB FF75 14 PUSH DWORD PTR SS:[EBP+14]
77E04CDE FF75 10 PUSH DWORD PTR SS:[EBP+10]
77E04CE1 50 PUSH EAX
77E04CE2 E8 8323FFFF CALL USER32.GetWindowTextA
77E04CE7 5D POP EBP
77E04CE8 C2 1000 RETN 10
看OD标题,是模块-USER32,这不是我们要分析的,
于是alt+F9,返回到程序空间,OD标题显示模块-CrackMe016:
======================================================
004011AE . 6A 51 PUSH 51 ; /Count = 51 (81.)
004011B0 . 50 PUSH EAX ; |Buffer
004011B1 . 6A 6E PUSH 6E ; |ControlID = 6E (110.)
004011B3 . 56 PUSH ESI ; |hWnd
004011B4 . FFD7 CALL EDI ; \GetDlgItemTextA
004011B6 . 8D8C24 9C0000>LEA ECX,DWORD PTR SS:[ESP+9C]
004011BD . 6A 65 PUSH 65 ; /Count = 65 (101.)
004011BF . 51 PUSH ECX ; |Buffer
004011C0 . 68 E8030000 PUSH 3E8 ; |ControlID = 3E8 (1000.)
004011C5 . 56 PUSH ESI ; |hWnd
004011C6 . 8BD8 MOV EBX,EAX ; |
004011C8 . FFD7 CALL EDI ; \GetDlgItemTextA
004011CA . 8A4424 4C MOV AL,BYTE PTR SS:[ESP+4C]
004011CE . 84C0 TEST AL,AL
004011D0 . 74 76 JE SHORT CrackMe0.00401248
004011D2 . 83FB 05 CMP EBX,5
004011D5 . 7C 71 JL SHORT CrackMe0.00401248
004011D7 . 8D5424 4C LEA EDX,DWORD PTR SS:[ESP+4C]
看看这里的代码,F8向下粗略走,可以看到输入的注册信息,看来这里应该是我们要找的地方。
好,下面我们就refresh(什么意思?自己想^_^)。
重新载入,在函数开头F2下断,F9,输入注册信息,断下:
=================================================================
004011AE . 6A 51 PUSH 51 ; /Count = 51 (81.)
004011B0 . 50 PUSH EAX ; |Buffer
004011B1 . 6A 6E PUSH 6E ; |ControlID = 6E (110.)
004011B3 . 56 PUSH ESI ; |hWnd
004011B4 . FFD7 CALL EDI ; \GetDlgItemTextA
004011B6 . 8D8C24 9C0000>LEA ECX,DWORD PTR SS:[ESP+9C]
004011BD . 6A 65 PUSH 65 ; /Count = 65 (101.)
004011BF . 51 PUSH ECX ; |Buffer
004011C0 . 68 E8030000 PUSH 3E8 ; |ControlID = 3E8 (1000.)
004011C5 . 56 PUSH ESI ; |hWnd
004011C6 . 8BD8 MOV EBX,EAX ; |
004011C8 . FFD7 CALL EDI ; \GetDlgItemTextA
004011CA . 8A4424 4C MOV AL,BYTE PTR SS:[ESP+4C]
004011CE . 84C0 TEST AL,AL
004011D0 . 74 76 JE SHORT CrackMe0.00401248 ; //判断用户名是否为空
004011D2 . 83FB 05 CMP EBX,5
004011D5 . 7C 71 JL SHORT CrackMe0.00401248 ; //用户名位数不能小于5
004011D7 . 8D5424 4C LEA EDX,DWORD PTR SS:[ESP+4C] ; //用户名地址入EDX
004011DB . 53 PUSH EBX ; //EBX保存着用户名位数
004011DC . 8D8424 A00000>LEA EAX,DWORD PTR SS:[ESP+A0] ; //输入的假码地址入EAX
004011E3 . 52 PUSH EDX ; //用户名入栈
004011E4 . 50 PUSH EAX ; //输入的假码入栈
004011E5 . E8 56010000 CALL CrackMe0.00401340 ; //算法CALL,F7跟进!转Lable1
(为什么要跟入这里?前面是push输入信息,后面是进行比较判断,这里应该是信息处理的地方哈。如果你看不出来,你就F7进去再F8看看,事实证明这个CALL就是计算注册码的。)
004011EA . 8B3D BC404000 MOV EDI,DWORD PTR DS:[<&USER32.GetDlgIte>; USER32.GetDlgItem
004011F0 . 83C4 0C ADD ESP,0C
004011F3 . 85C0 TEST EAX,EAX ; //标志位判断是否为0
004011F5 . 74 37 JE SHORT CrackMe0.0040122E ; //跳向出错,爆破的话就NOP掉这里!
004011F7 . 8D4C24 0C LEA ECX,DWORD PTR SS:[ESP+C]
004011FB . 51 PUSH ECX ; |String2
004011FC . 68 E4544000 PUSH CrackMe0.004054E4 ; |String1 = CrackMe0.004054E4
00401201 . FF15 60404000 CALL DWORD PTR DS:[<&KERNEL32.lstrcpyA>] ; \lstrcpyA
00401207 . 6A 00 PUSH 0 ; /Enable = FALSE
00401209 . 6A 6E PUSH 6E ; |/ControlID = 6E (110.)
0040120B . 56 PUSH ESI ; ||hWnd
0040120C . FFD7 CALL EDI ; |\GetDlgItem
0040120E . 8B1D A4404000 MOV EBX,DWORD PTR DS:[<&USER32.EnableWin>; |USER32.EnableWindow
00401214 . 50 PUSH EAX ; |hWnd
00401215 . FFD3 CALL EBX ; \EnableWindow
00401217 . 6A 00 PUSH 0 ; /Enable = FALSE
00401219 . 68 E8030000 PUSH 3E8 ; |/ControlID = 3E8 (1000.)
0040121E . 56 PUSH ESI ; ||hWnd
0040121F . FFD7 CALL EDI ; |\GetDlgItem
00401221 . 50 PUSH EAX ; |hWnd
00401222 . FFD3 CALL EBX ; \EnableWindow
00401224 . 68 E8030000 PUSH 3E8 ; /ControlID = 3E8 (1000.)
00401229 . 56 PUSH ESI ; |hWnd
0040122A . FFD7 CALL EDI ; \GetDlgItem
0040122C . EB 33 JMP SHORT CrackMe0.00401261 //JMP跳过错误
0040122E > 8D5424 34 LEA EDX,DWORD PTR SS:[ESP+34]
00401232 . 52 PUSH EDX ; //String2 = "序列号错误,再来一次!"
00401233 . 68 E4544000 PUSH CrackMe0.004054E4 ; |String1 = CrackMe0.004054E4
00401238 . FF15 60404000 CALL DWORD PTR DS:[<&KERNEL32.lstrcpyA>] ; \lstrcpyA
0040123E . 68 E8030000 PUSH 3E8
00401243 . 56 PUSH ESI
00401244 . FFD7 CALL EDI
00401246 . EB 19 JMP SHORT CrackMe0.00401261
00401248 > 8D4424 1C LEA EAX,DWORD PTR SS:[ESP+1C] ; //“你输入字符要大于四个”
0040124C . 50 PUSH EAX ; /String2
0040124D . 68 E4544000 PUSH CrackMe0.004054E4 ; |String1 = CrackMe0.004054E4
00401252 . FF15 60404000 CALL DWORD PTR DS:[<&KERNEL32.lstrcpyA>] ; \lstrcpyA
00401258 . 6A 6E PUSH 6E ; /ControlID = 6E (110.)
0040125A . 56 PUSH ESI ; |hWnd
0040125B . FF15 BC404000 CALL DWORD PTR DS:[<&USER32.GetDlgItem>] ; \GetDlgItem
00401261 > 50 PUSH EAX ; /hWnd
00401262 . FF15 A8404000 CALL DWORD PTR DS:[<&USER32.SetFocus>] ; \SetFocus
00401268 . 6A 00 PUSH 0 ; /BeepType = MB_OK
0040126A . FF15 AC404000 CALL DWORD PTR DS:[<&USER32.MessageBeep>>; \MessageBeep
00401270 . 8B0D E0544000 MOV ECX,DWORD PTR DS:[4054E0] ; CrackMe0.00400000
00401276 . 6A 00 PUSH 0 ; /lParam = NULL
00401278 . 68 60104000 PUSH CrackMe0.00401060 ; |DlgProc = CrackMe0.00401060
0040127D . 56 PUSH ESI ; |hOwner
0040127E . 6A 79 PUSH 79 ; |pTemplate = 79
00401280 . 51 PUSH ECX ; |hInst => 00400000
00401281 . FF15 C8404000 CALL DWORD PTR DS:[<&USER32.DialogBoxPar>; //“恭喜你!成功!”
00401287 . 5B POP EBX
00401288 . 5F POP EDI
00401289 . 33C0 XOR EAX,EAX
0040128B . 5E POP ESI
0040128C . 81C4 F4000000 ADD ESP,0F4
00401292 . C2 1000 RETN 10
上面004011E5处的CALL来到这里:
Lable1
==================================================
00401340 /$ 55 PUSH EBP
00401341 |. 8B6C24 0C MOV EBP,DWORD PTR SS:[ESP+C] ; //用户名入EBP
00401345 |. 56 PUSH ESI
00401346 |. 57 PUSH EDI
00401347 |. 8B7C24 18 MOV EDI,DWORD PTR SS:[ESP+18] ; //用户名位数入EDI
0040134B |. B9 03000000 MOV ECX,3
00401350 |. 33F6 XOR ESI,ESI
00401352 |. 33C0 XOR EAX,EAX
00401354 |. 3BF9 CMP EDI,ECX ; //比较用户名位数是否大于3
00401356 |. 7E 21 JLE SHORT CrackMe0.00401379 ; //小于等于3就跳
00401358 |. 53 PUSH EBX
00401359 |> 83F8 07 /CMP EAX,7 ; //EAX作计数器
0040135C |. 7E 02 |JLE SHORT CrackMe0.00401360 ; //循环条件判断
0040135E |. 33C0 |XOR EAX,EAX //当EAX大于7时清零
00401360 |> 33D2 |XOR EDX,EDX ; //EDX清空
00401362 |. 33DB |XOR EBX,EBX ; //EBX清空
00401364 |. 8A1429 |MOV DL,BYTE PTR DS:[ECX+EBP] ; //DL<=从用户名第4位起取字
00401367 |. 8A98 30504000 |MOV BL,BYTE PTR DS:[EAX+405030] ; //BL<=从405030处开始取字
0040136D |. 0FAFD3 |IMUL EDX,EBX ; //EDX=EDX*EBX
00401370 |. 03F2 |ADD ESI,EDX ; //ESI=ESI+EDX
00401372 |. 41 |INC ECX ; //ECX++
00401373 |. 40 |INC EAX ; //EAX++
00401374 |. 3BCF |CMP ECX,EDI ; //循环条件
00401376 |.^ 7C E1 \JL SHORT CrackMe0.00401359 //直到取完用户名最后一位
00401378 |. 5B POP EBX
00401379 |> 56 PUSH ESI ; //ESI保存着上面的计算结果
0040137A |. 68 78504000 PUSH CrackMe0.00405078 ; |Format = "%ld"
0040137F |. 55 PUSH EBP ; //EBP入栈
00401380 |. FF15 9C404000 CALL DWORD PTR DS:[<&USER32.wsprintfA>] ; //这个Call的作用是把结果转成十进制,以字符输出
00401386 |. 8B4424 1C MOV EAX,DWORD PTR SS:[ESP+1C] ; //输入的假码入EAX
0040138A |. 83C4 0C ADD ESP,0C
0040138D |. 55 PUSH EBP ; //String2="注册码"
0040138E |. 50 PUSH EAX ; //String1="输入码"
0040138F |. FF15 04404000 CALL DWORD PTR DS:[<&KERNEL32.lstrcmpA>] ; //lstrcmpA,字符串比较
00401395 |. F7D8 NEG EAX
00401397 |. 1BC0 SBB EAX,EAX
00401399 |. 5F POP EDI
0040139A |. 5E POP ESI
0040139B |. 40 INC EAX
0040139C |. 5D POP EBP
0040139D \. C3 RETN ; //返回到004011EA
上面的代码,跟踪到下面的语句时看到注册码明码:
0040138D |. 55 PUSH EBP ; //String2="注册码"
总结:
========================================
该注册码计算方法是:
从用户名第四位起,逐位与一固定字符串对应位相乘,将乘积求和。
F8到相应语句时(00401367),查看转存,在命令行 d 405030,
看到固定的数列(作为乘数)为:0C 0A 13 09 0C 0B 0A
取7位,如果该数列对应于用户名的位数不够,就返回到第一位循环。
下面来看看我的用户名计算过程:
输入的用户名:HillCat,从第四位取,即lCat,对应ASCII值为:6C 43 61 74
与固定的数列0C 0A 13 09 0C 0B 0A对位相乘,再求和,也就是:
6C 43 61 74
0C 0A 13 09 0C 0B 0A
ESI=6C*0C+43*0A+61*13+74*09=12F5
得到的结果12F5转换为十进制是4853,即为注册码。
附注:
其实,因为是VC++的程序,直接Ctrl+N查找lstrcmpA函数就可以到达Lable1下断点分析了。
(要是每个C++的程序都这么简单就好了)
注册机:
=======================================
string str1=textBox1.Text;
str1=str1.SubString(3);
char[] arr1=str1.ToCharArray();
int[] arr0={12,10,19,9,12,11,10};
int sum=0;
for(int i=0;i<arr1.Length;i++)
{
int flag=i%7;
sum=arr1[i]*arr0[flag];
}
textBox2.Text=sum.ToString();
附件:crackme016.rar 附件:crackme016.rar
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课