系统 : Windows xp
程序 :KeygenMe_1_by_boonz
程序下载地址 :http://www.crackmes.de/users/boonz/keygenme_1_by_boonz/download
要求 :爆破 & 注册机编写
使用工具 : IDA Pro & OD
首先我们来使用IDA加载程序,打开字符串表:
可以看到注册成功/失败的提示“Hello,Mr. Goodboy”/“Hello,Mr.Badboy”,双击进入定义,再双击交叉参考进入引用字符串的程序段:
往上翻肯定就是程序接收字符串以及判断的代码了,在0040131C处程序有比较字符串的关键代码:
打开OD,载入程序并修改00401321处程序,用jnz short 00401338替代。接着运行程序,无论输入什么序列号都可以注册成功:
接着分析注册算法,发现该程序采用的是F(用户名) = 序列号的形式。那我们就可以直接分析序列号算法,并按照册算法编写注册机:
00401208 /$ 68 F8DC4000 push 0040DCF8 ; /String = "" 0040120D |. E8 80010000 call <jmp.&kernel32.lstrlenA> ; \该函数返回指定字符串的字节长度 00401212 |. A3 86DC4000 mov dword ptr [40DC86], eax ; 长度为5,保存在40DC86 00401217 |. 833D 86DC4000>cmp dword ptr [40DC86], 4 ; 是否小于4? 0040121E |. 0F8C 29010000 jl 0040134D ; 小于4则跳转到出错函数 00401224 |. 833D 86DC4000>cmp dword ptr [40DC86], 32 ; 是否大于50(32h)? 0040122B |. 0F8F 1C010000 jg 0040134D ; 大于50则跳转到出错函数 00401231 |. 33C0 xor eax, eax ; 清空eax 00401233 |. 33DB xor ebx, ebx ; 清空ebx 00401235 |. 33C9 xor ecx, ecx ; 清空ecx 00401237 |. BF F8DC4000 mov edi, 0040DCF8 0040123C |. 8B15 86DC4000 mov edx, dword ptr [40DC86] ; 长度赋值给edx 00401242 |> 0FB60439 /movzx eax, byte ptr [ecx+edi] ; ecx作为计数器,edi则作为用户名字串首地址,eax可以简单看做str[i] 00401246 |. 83E8 19 |sub eax, 19 ; str[i]-25 00401249 |. 2BD8 |sub ebx, eax ; 变量ebx减去str[i] 0040124B |. 41 |inc ecx ; 计数器自增 0040124C |. 3BCA |cmp ecx, edx ; 字符串是否处理完? 0040124E |.^ 75 F2 \jnz short 00401242 ; 处理完毕则跳转 00401250 |. 53 push ebx ; /<%lX> 00401251 |. 68 F8DB4000 push 0040DBF8 ; |Format = "%lX" 00401256 |. 68 F8E04000 push 0040E0F8 ; |s = keygenme.0040E0F8 0040125B |. E8 38010000 call <jmp.&user32.wsprintfA> ; \wsprintfA函数,将结果ebx复制到40E0F8 00401260 |. 83C4 0C add esp, 0C ; 平衡堆栈 00401263 |. 33C0 xor eax, eax ; 清空eax,edx,ecx 00401265 |. 33D2 xor edx, edx 00401267 |. 33C9 xor ecx, ecx 00401269 |. 03C3 add eax, ebx ; 运算结果ebx赋值给eax 0040126B |. 0FAFC3 imul eax, ebx ; 两者相乘,结果放入eax 0040126E |. 03C8 add ecx, eax ; 运算结果eax赋值给ecx 00401270 |. 2BD3 sub edx, ebx ; edx - ebx 00401272 |. 33D0 xor edx, eax ; edx 和 eax 做异或运算 00401274 |. 0FAFD8 imul ebx, eax ; ebx 乘以 eax 00401277 |. 53 push ebx ; /<%lX> 00401278 |. 68 F8DB4000 push 0040DBF8 ; |Format = "%lX" 0040127D |. 68 F8E14000 push 0040E1F8 ; |s = keygenme.0040E1F8 00401282 |. E8 11010000 call <jmp.&user32.wsprintfA> ; \wsprintfA函数,将结果ebx复制到40E1F8 00401287 |. 83C4 0C add esp, 0C ; 平衡堆栈 0040128A |. 33C0 xor eax, eax ; 清空eax、ebx、edx、ecx 0040128C |. 33DB xor ebx, ebx 0040128E |. 33D2 xor edx, edx 00401290 |. 33C9 xor ecx, ecx 00401292 |. B8 F8E04000 mov eax, 0040E0F8 ; 将第一次运算保存的数据存入eax 00401297 |. 03D8 add ebx, eax 00401299 |. 33CB xor ecx, ebx ; 第一次运算结果与 0 异或 0040129B |. 0FAFCB imul ecx, ebx ; 第一次运算结果的地址 乘以 第一次运算结果的地址 0040129E |. 2BC8 sub ecx, eax ; 再将ecx的值 减去 第一次运算结果的地址 004012A0 |. 51 push ecx ; /<%lX> 004012A1 |. 68 F8DB4000 push 0040DBF8 ; |Format = "%lX" 004012A6 |. 68 F8E24000 push 0040E2F8 ; |s = keygenme.0040E2F8 004012AB |. E8 E8000000 call <jmp.&user32.wsprintfA> ; \wsprintfA 004012B0 |. 83C4 0C add esp, 0C ; 调用函数保存ecx到40E2F8,然后平衡堆栈 004012B3 |. 68 FCDB4000 push 0040DBFC ; /Format = "Bon-" 004012B8 |. 68 F8DD4000 push 0040DDF8 ; |s = keygenme.0040DDF8 004012BD |. E8 D6000000 call <jmp.&user32.wsprintfA> ; \wsprintfA 004012C2 |. 83C4 08 add esp, 8 ; 字符串"Bon-" 004012C5 |. 68 F8E04000 push 0040E0F8 ; /StringToAdd = "" 004012CA |. 68 F8DD4000 push 0040DDF8 ; |ConcatString = "" 004012CF |. E8 B2000000 call <jmp.&kernel32.lstrcatA> ; \lstrcatA 004012D4 |. 68 01DC4000 push 0040DC01 ; /StringToAdd = "-" 004012D9 |. 68 F8DD4000 push 0040DDF8 ; |ConcatString = "" 004012DE |. E8 A3000000 call <jmp.&kernel32.lstrcatA> ; \lstrcatA 004012E3 |. 68 F8E14000 push 0040E1F8 ; /StringToAdd = "" 004012E8 |. 68 F8DD4000 push 0040DDF8 ; |ConcatString = "" 004012ED |. E8 94000000 call <jmp.&kernel32.lstrcatA> ; \lstrcatA 004012F2 |. 68 01DC4000 push 0040DC01 ; /StringToAdd = "-" 004012F7 |. 68 F8DD4000 push 0040DDF8 ; |ConcatString = "" 004012FC |. E8 85000000 call <jmp.&kernel32.lstrcatA> ; \lstrcatA 00401301 |. 68 F8E24000 push 0040E2F8 ; /StringToAdd = "" 00401306 |. 68 F8DD4000 push 0040DDF8 ; |ConcatString = "" 0040130B |. E8 76000000 call <jmp.&kernel32.lstrcatA> ; \lstrcatA 00401310 |. B8 F8DD4000 mov eax, 0040DDF8 ; 将三次运算的结果连接成一个字符串,就是我们的序列号惹 00401315 |. BB F8DE4000 mov ebx, 0040DEF8 ; 用户输入的序列号入栈 0040131A |. 53 push ebx ; /String2 => "" 0040131B |. 50 push eax ; |String1 => "" 0040131C |. E8 6B000000 call <jmp.&kernel32.lstrcmpA> ; \lstrcmpA
分析完算法就可以直接将 汇编语言 翻译成高级语言来实现注册机,在本例子中,我们利用来MFC编写注册机。首先,打开VC6.0,新建一个MFC项目,框架选择对话框程序,并搭建界面如下:
为OK按钮添加消息响应函数:
OnOK函数代码如下:
void CSerialNumber_KeygenDlg::OnOK() { // TODO: Add extra validation here CString str; GetDlgItem( IDC_EDIT_NAME )->GetWindowText( str ); //获取用户名 int len = str.GetLength(); //获取长度 if ( len < 4 || len > 50 ) //当字符串长度小于4或者大于50时 MessageBox( "用户名必须长度大于4或者小于50!" ); else { int res1 = 0; //存储运算结果.变量一定要初始化! CString Temp = str; for ( int i = 0 ; i < len ; i++ ){ res1 -= ( Temp[i] - 25 ); } char* str1 = new char[50]; wsprintf( str1,"%lX",res1 ); //以十六进制存储. CString SerialNumber = "Bon-"; SerialNumber += str1; int eax = res1 * res1; //存放乘积。 int res2 = eax * res1; char* str2 = new char[50]; wsprintf( str2,"-%lX",res2 ); //以十六进制存储. SerialNumber += str2; eax = 0x40E0F8; //存储第一次运算的地址。 int res3 = eax * eax; res3 -= eax; char* str3 = new char[50]; wsprintf( str3,"-%lX",res3 ); //以十六进制存储. SerialNumber += str3; GetDlgItem( IDC_EDIT_Number )->SetWindowText( SerialNumber ); } //CDialog::OnOK(); //屏蔽基类OnOk函数 }
运行程序,随便填写一个用户名,例如hahaha,单击“解密”按钮解得注册码:
黏贴到KeygenMe_1_by_boonz程序中:
解密成功!
PS:
1.程序中运用了很多混淆的汇编指令,要注意分辨。
2.程序不能移动位置、背景音乐很嘈杂,建议关闭声音。
3.http://www.cnblogs.com/ZRBYYXDM/p/5002705.html 内含三百多个可破解的程序。一天一个,可连破三百六十五天哦~
我们一路奋战,不是为了改变世界,而是不让世界改变我们
——《熔炉》