本系列文章的目的是从一个没有任何经验的新手的角度(其实就是我自己),一步步尝试将160个CrackMe全部破解,如果可以,通过任何方式写出一个类似于注册机的东西。
其中,文章中按照如下逻辑编排(解决如下问题):
1、使用什么环境和工具
2、程序分析
3、思路分析和破解流程
4、注册机的探索
1、工具和环境:
WinXP SP3 + 52Pojie六周年纪念版OD + PEID + 汇编金手指。
160个CrackMe的打包文件。
下载地址: http://pan.baidu.com/s/1xUWOY 密码: jbnq
注:
1、Win7系统对于模块和程序开启了随机初始地址的功能,会给分析带来很大的负担,所以不建议使用Win7进行分析。
2、以上工具都是在52PoJie论坛下的原版程序,NOD32不报毒,个人承诺绝对不会进行任何和木马病毒相关内容。
2、程序分析:
想要破解一个程序,必须先了解这个程序。所以,在破解过程中,对最初程序的分析很重要,他可以帮助我们理解作者的目的和意图,特别是对于注册码的处理细节,从而方便我们反向跟踪和推导。
打开CHM文件,将第一个文件 Acid burn.exe 保存下来,新建一个01的文件夹,将exe放到这里,同时将以后的分析文件也存放这里。打开Acid burn.exe,随意输入和点击,熟悉程序流程。
我们发现,这个软件分为了两个部分,一个是Serial/Name,需要输入用户名和注册码才能通过,另外一个Serial只需要输入一个注册码一类的东西。我们随意选一个开始,比如,先进行第一个。
我们随意输入一个用户名和序列号(伪码):
伪码:
Name:112233
Serial:44556677
点击Check it Baby! 它会弹出一个对话框提示: Sorry, The Serial is incorrect !
再换几个随意试试,发现就这一种情况。
OK,出现了对话框这就很好办,说明作者在校验注册码之后发现如果错误了就直接弹窗,我们只要找到弹出对话框的地方,向上跟踪,就可以找出判断是否正确的地方了,jmp或者Nop就算爆破了。
3、具体步骤如下:
我们随意输入一个用户名和序列号(伪码):
Name:112233
Serial:44556677
点击Check it Baby! 它会弹出一个对话框提示: Sorry, The Serial is incorrect !
此时不要点击确定按钮,返回OD暂停(F12),点击堆栈-K小图标(Ctrl+K) ,如下图:
这里有两个MessageBox的地址,第一个地址为77D5082F这个地址明显太大,不在模块的领空,不是的。第二个地址为0042A1AE,和00400100地址非常接近,十有八九就是它了。
右键 show call, 在Call上面设置断点。
查看附近代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | 0042A170 /$ 55 push ebp 0042A171 |. 8BEC mov ebp,esp 0042A173 |. 83C4 F4 add esp,-0xC 0042A176 |. 53 push ebx 0042A177 |. 56 push esi 0042A178 |. 57 push edi 0042A179 |. 8BF9 mov edi,ecx 0042A17B |. 8BF2 mov esi,edx 0042A17D |. 8BD8 mov ebx,eax 0042A17F |. E8 7CB4FDFF call 0042A184 |. 8945 F8 mov [local.2],eax 0042A187 |. 33C0 xor eax,eax 0042A189 |. E8 12A0FFFF call 004241A0 0042A18E |. 8945 F4 mov [local.3],eax 0042A191 |. 33C0 xor eax,eax 0042A193 |. 55 push ebp 0042A194 |. 68 D0A14200 push 0042A1D0 0042A199 |. 64:FF30 push dword ptr fs:[eax] 0042A19C |. 64:8920 mov dword ptr fs:[eax],esp 0042A19F |. 8B45 08 mov eax,[arg.1] 0042A1A2 |. 50 push eax ; /Style 0042A1A3 |. 57 push edi ; |Title 0042A1A4 |. 56 push esi ; |Text 0042A1A5 |. 8B43 24 mov eax,dword ptr ds:[ebx+0x24] ; | 0042A1A8 |. 50 push eax ; |hOwner 0042A1A9 |. E8 FAB5FDFF call |
发现,没有跳转语句,逻辑很简单,在之上几行处就有retn,在头部push ebp下断,重新点击Check it baby 按钮,在右下角堆栈处找到最近一条Return语句:
0012F974 0042FB37 RETURN to Acid_bur.0042FB37 from Acid_bur.0042A170
右键 Follow in Disassm..(反汇编跟随),这里直接连接了一个跳转,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | 0042FAD5 |. 68 C8FB4200 push 0042FBC8 ; UNICODE "-" 0042FADA |. FF75 F8 push [local.2] 0042FADD |. 8D45 F4 lea eax,[local.3] 0042FAE0 |. BA 05000000 mov edx,0x5 0042FAE5 |. E8 C23EFDFF call 004039AC 0042FAEA |. 8D55 F0 lea edx,[local.4] 0042FAED |. 8B83 E0010000 mov eax,dword ptr ds:[ebx+0x1E0] 0042FAF3 |. E8 60AFFEFF call 0041AA58 0042FAF8 |. 8B55 F0 mov edx,[local.4] 0042FAFB |. 8B45 F4 mov eax,[local.3] 0042FAFE |. E8 F93EFDFF call 004039FC 0042FB03 75 1A jnz short 0042FB1F ; // 这个JNZ条件判断很关键? 0042FB05 |. 6A 00 push 0x0 0042FB07 |. B9 CCFB4200 mov ecx,0042FBCC 0042FB0C |. BA D8FB4200 mov edx,0042FBD8 0042FB11 |. A1 480A4300 mov eax,dword ptr ds:[0x430A48] 0042FB16 |. 8B00 mov eax,dword ptr ds:[eax] 0042FB18 |. E8 53A6FFFF call 0042A170 0042FB1D |. EB 18 jmp short 0042FB37 ; // 这个跳转是不是很可疑? 0042FB1F |> 6A 00 push 0x0 0042FB21 |. B9 74FB4200 mov ecx,0042FB74 ; ASCII 54, "ry Again!" 0042FB26 |. BA 80FB4200 mov edx,0042FB80 ; ASCII 53, "orry , The serial is incorect !" 0042FB2B |. A1 480A4300 mov eax,dword ptr ds:[0x430A48] 0042FB30 |. 8B00 mov eax,dword ptr ds:[eax] 0042FB32 |. E8 39A6FFFF call 0042A170 ; 这个CALL是导致跳转的语句 0042FB37 |> 33C0 xor eax,eax ; 返回到了这里 |
看到和提示框一样的文本,是不是感到很亲切?OK,我们大概浏览下代码,最近部分有两个可疑跳转JNZ 和JMP, JNZ会通过它上面的call 004039FC 判断我们的伪码是否正确,判断的结果存在EAX中,如果EAX不等于就跳转到错误提示信息框那里。我们的目的是无论伪码是否正确都通过验证,所以最简单的 办法就是将jnz这句使用NOP填充,我们尝试一下:选择JNZ这句,右键Binary->Fill with NOPS.回到原始程序,再次点击Check it baby!
哈哈,提示Good Job!通过了!
再次比对MessageBox和堆栈(Ctrl+K)窗口最后一个调用,是不是发现什么特殊的地方?对啦!那个CALL就是调用MessageBox的地方,所以,下次我们就不用在MessageBox处下断跟踪了,直接最后一个地址,show call。
4、注册机部分
以上部分是爆破分析,我们看看能否分析出注册机算法。
在原JNZ上面一行CALL下断:点击Ckeck it Baby!按钮,程序断下:
1 2 3 | 0042FAF8 |. 8B55 F0 mov edx,[local.4] ; // EDX=44556677 0042FAFB |. 8B45 F4 mov eax,[local.3] ; // EAX=CW-4018-CRACKED 0042FAFE |. E8 F93EFDFF call 004039FC |
我们发现,EDX存储的是我们的假序列号,EAX看上去像是一个正确的序列号,也有可能是对应的用户名也说不定,我们可疑尝试一下。
现将我们之前修改的代码恢复:选中那两行NOP,右键Undo... 。取消断点。先尝试是否是Name,Name输入EAX的值,下一行继续44556677,结果继续弹错。然后尝试作为序列号,Name为 112233,Serial为CW-4018-CRACKED,再尝试,OK!完全正确!
小结一下:在这个CALL之前程序已经将用户名对应的序列号算出来了,然后和我们输入的Serial通过这个CALL对比,最终给出提示信息。我们要的算法不再这个CALL中。
下一步的思路就是继续在这个CALL之上的CALL下断,分析出产生出这个序列号的CALL。由于CALL的返回值一般都存在EAX中,所以我们可以查看附近的CALL之后EAX的值,从而判断出正确的注册码生成函数。
附近的两个CALL:
1 2 3 4 5 6 7 8 9 | 0042FADD |. 8D45 F4 lea eax,[local.3] 0042FAE0 |. BA 05000000 mov edx,0x5 0042FAE5 |. E8 C23EFDFF call 004039AC ; // 最近的CALL2 0042FAEA |. 8D55 F0 lea edx,[local.4] 0042FAED |. 8B83 E0010000 mov eax,dword ptr ds:[ebx+0x1E0] 0042FAF3 |. E8 60AFFEFF call 0041AA58 ; // 最近的CALL 1 0042FAF8 |. 8B55 F0 mov edx,[local.4] ; // EDX=44556677 0042FAFB |. 8B45 F4 mov eax,[local.3] ; // EAX=CW-4018-CRACKED 0042FAFE |. E8 F93EFDFF call 004039FC |
分别在CALL之后的那一句下断,点击按钮,F8步过,发现在 附近的CALL 1 处,EAX出现正确的注册码,说明他可能是关键的注册码CALL,下面对它的进行分析:
重新在call 0041AA58 下断,
1 2 3 | 0042FAEA |. 8D55 F0 lea edx,[local.4] ; edx=0012F998 0042FAED |. 8B83 E0010000 mov eax,dword ptr ds:[ebx+0x1E0] ; eax=00A85E4C 0042FAF3 |. E8 60AFFEFF call 0041AA58 ; // 最近的CALL 1,注册码CALL |
其中edx和eax都没有特殊信息出现,我们就可以更加断定注册码都是在call中生成的。
这时我们就有两种方案了,一种分析CALL的参数,直接远程调用CALL生成正确的注册码,另一种是分析CALL的内容,根据他的算法自己写一个这个生成流程。这两个无明显的好坏之分,只看用在什么地方了。
按照我的理解,第一种,内存调用适合算法复杂或者更新不频繁的程序,这么做可以节省时间和复杂度。第 二种,适合算法不是特别复杂,或者软件更新频繁的软件,算法的复杂度就不说了,软件无论更新多么频繁,一般注册码算法是不会变的,这样生成的注册机就可以 适用所有的版本,大家都省事。
好了,啰嗦了这么多,开始正式分析:
(由于已经在这里CALL被调用了很多次了,各个寄存器参数很乱,建议从新加载一次程序,方便分析)
进入CALL 0041AA58,代码如下:
(由于这个CALL被很多地方一直调用,所以我们必须每次从上层CALL单步F7跟踪,直到找到正确的CALL)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 | 0041AA58 /$ 53 push ebx 0041AA59 |. 56 push esi 0041AA5A |. 57 push edi 0041AA5B |. 8BFA mov edi,edx 0041AA5D |. 8BF0 mov esi,eax 0041AA5F |. 8BC6 mov eax,esi 0041AA61 |. E8 A2FFFFFF call 0041AA08 ; // 这里直接跳出去了,所以需要继续跟踪 0041AA66 |. 8BD8 mov ebx,eax 0041AA68 |. 8BC7 mov eax,edi 0041AA6A |. 8BCB mov ecx,ebx 0041AA6C |. 33D2 xor edx,edx 0041AA6E |. E8 E18CFEFF call 00403754 0041AA73 |. 85DB test ebx,ebx 0041AA75 |. 74 0C je short 0041AA83 0041AA77 |. 8D4B 01 lea ecx,dword ptr ds:[ebx+0x1] 0041AA7A |. 8B17 mov edx,dword ptr ds:[edi] 0041AA7C |. 8BC6 mov eax,esi 0041AA7E |. E8 95FFFFFF call 0041AA18 0041AA83 |> 5F pop edi 0041AA84 |. 5E pop esi 0041AA85 |. 5B pop ebx 0041AA86 . C3 retn 继续跟踪到这里: 0041AA08 /$ 6A 00 push 0x0 ; /Arg1 = 00000000 0041AA0A |. 33C9 xor ecx,ecx ; | 0041AA0C |. BA 0E000000 mov edx,0xE ; | 0041AA11 |. E8 F6070000 call 0041B20C ; Acid_bur.0041B20C 0041AA16 . C3 retn 继续: 0041B20C /$ 55 push ebp 0041B20D |. 8BEC mov ebp,esp 0041B20F |. 83C4 F0 add esp,-0x10 0041B212 |. 53 push ebx 0041B213 |. 8955 F0 mov [local.4],edx 0041B216 |. 894D F4 mov [local.3],ecx 0041B219 |. 8B55 08 mov edx,[arg.1] 0041B21C |. 8955 F8 mov [local.2],edx 0041B21F |. 33D2 xor edx,edx 0041B221 |. 8955 FC mov [local.1],edx 0041B224 |. 85C0 test eax,eax 0041B226 |. 74 0B je short 0041B233 0041B228 |. 8D55 F0 lea edx,[local.4] 0041B22B |. 8BD8 mov ebx,eax 0041B22D |. 8B43 2C mov eax,dword ptr ds:[ebx+0x2C] 0041B230 |. FF53 28 call dword ptr ds:[ebx+0x28] 0041B233 |> 8B45 FC mov eax,[local.1] 0041B236 |. 5B pop ebx 0041B237 |. 8BE5 mov esp,ebp 0041B239 |. 5D pop ebp 0041B23A . C2 0400 retn 0x4 继续F7步入: 0041CB64 /$ 53 push ebx 0041CB65 |. 56 push esi 0041CB66 |. 57 push edi 0041CB67 |. 83C4 F0 add esp,-0x10 0041CB6A |. 8BF2 mov esi,edx 0041CB6C |. 8BD8 mov ebx,eax 0041CB6E |. 8B06 mov eax,dword ptr ds:[esi] 0041CB70 |. 3D 84000000 cmp eax,0x84 ; Switch (cases 7..20A) 0041CB75 |. 7F 18 jg short 0041CB8F 0041CB77 |. 74 69 je short 0041CBE2 ... |
跟了一大堆,我们发现没有地方可产生注册码,并且所有的CALL都是不断地在被调用,所以,初步判定 注册码不是在这里生成的。但是这么想就与之前的猜测完全推翻了,说明我们的想法有问题,即思路有问题。既然他的码不是及时算出来的,那肯定就是事先算好 的,我们再次回到产生注册码的CALL那里,向上查找,F8单步进行查看。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 | 0042FA4D |. A1 6C174300 mov eax,dword ptr ds:[0x43176C] ; // name/Tag 0042FA52 |. E8 D96EFDFF call 00406930 ; // 关键CALL 0042FA57 |. 83F8 04 cmp eax,0x4 ; // 判断tag/serial 是否合格 0042FA5A |. 7D 1D jge short 0042FA79 0042FA5C |. 6A 00 push 0x0 0042FA5E |. B9 74FB4200 mov ecx,0042FB74 ; ASCII 54, "ry Again!" 0042FA63 |. BA 80FB4200 mov edx,0042FB80 ; ASCII 53, "orry , The serial is incorect !" 0042FA68 |. A1 480A4300 mov eax,dword ptr ds:[0x430A48] 0042FA6D |. 8B00 mov eax,dword ptr ds:[eax] 0042FA6F |. E8 FCA6FFFF call 0042A170 0042FA74 |. E9 BE000000 jmp 0042FB37 0042FA79 |> 8D55 F0 lea edx,[local.4] 0042FA7C |. 8B83 DC010000 mov eax,dword ptr ds:[ebx+0x1DC] 0042FA82 |. E8 D1AFFEFF call 0041AA58 0042FA87 |. 8B45 F0 mov eax,[local.4] ; EAX=112233的地址 0042FA8A |. 0FB600 movzx eax,byte ptr ds:[eax] ; 第一个字节1=0x31 0042FA8D |. F72D 50174300 imul dword ptr ds:[0x431750] ; *0x29 0042FA93 |. A3 50174300 mov dword ptr ds:[0x431750],eax ; eax=0x7d9=0x29*0x31 0042FA98 |. A1 50174300 mov eax,dword ptr ds:[0x431750] 0042FA9D |. 0105 50174300 add dword ptr ds:[0x431750],eax ; 加一倍 0042FAA3 |. 8D45 FC lea eax,[local.1] 0042FAA6 |. BA ACFB4200 mov edx,0042FBAC 0042FAAB |. E8 583CFDFF call 00403708 0042FAB0 |. 8D45 F8 lea eax,[local.2] 0042FAB3 |. BA B8FB4200 mov edx,0042FBB8 0042FAB8 |. E8 4B3CFDFF call 00403708 0042FABD |. FF75 FC push [local.1] 0042FAC0 |. 68 C8FB4200 push 0042FBC8 ; UNICODE "-" 0042FAC5 |. 8D55 E8 lea edx,[local.6] ; 12F990 0042FAC8 |. A1 50174300 mov eax,dword ptr ds:[0x431750] ; 4018 0042FACD |. E8 466CFDFF call 00406718 0042FAD2 |. FF75 E8 push [local.6] ; // 注册码中间的值 0042FAD5 |. 68 C8FB4200 push 0042FBC8 ; UNICODE "-" 0042FADA |. FF75 F8 push [local.2] 0042FADD |. 8D45 F4 lea eax,[local.3] 0042FAE0 |. BA 05000000 mov edx,0x5 0042FAE5 |. E8 C23EFDFF call 004039AC 0042FAEA |. 8D55 F0 lea edx,[local.4] ; edx=0012F998 0042FAED |. 8B83 E0010000 mov eax,dword ptr ds:[ebx+0x1E0] ; eax=00A85E4C 0042FAF3 |. E8 60AFFEFF call 0041AA58 ; // 注册码CALL 0042FAF8 |. 8B55 F0 mov edx,[local.4] ; // EDX=44556677 0042FAFB |. 8B45 F4 mov eax,[local.3] ; // EAX=CW-4018-CRACKED 0042FAFE |. E8 F93EFDFF call 004039FC 0042FB03 75 1A jnz short 0042FB1F ; // 这个JNZ条件判断很关键? 0042FB05 |. 6A 00 push 0x0 0042FB07 |. B9 CCFB4200 mov ecx,0042FBCC 0042FB0C |. BA D8FB4200 mov edx,0042FBD8 0042FB11 |. A1 480A4300 mov eax,dword ptr ds:[0x430A48] 0042FB16 |. 8B00 mov eax,dword ptr ds:[eax] 0042FB18 |. E8 53A6FFFF call 0042A170 0042FB1D |. EB 18 jmp short 0042FB37 ; // 这个跳转是不是很可疑? 0042FB1F |> 6A 00 push 0x0 0042FB21 |. B9 74FB4200 mov ecx,0042FB74 ; ASCII 54, "ry Again!" 0042FB26 |. BA 80FB4200 mov edx,0042FB80 ; ASCII 53, "orry , The serial is incorect !" |
总结:取第一个字母的ASNI的数字,如112233中第一个字符1对应数字0x31,然后用它乘以0x29,结果再自增一倍(即x2),将得到的数字转为10进制的字符串,在前加上”CW-”,后加上”-CRACKED”,就组成了用户名对应的注册码。
C/CPP程序:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | // CrackMe160.cpp : 定义控制台应用程序的入口点。 // NameSerial部分 #include "stdafx.h" #include "iostream" int _tmain( int argc, _TCHAR* argv[]) { printf ( "Input Name:
" ); // 取第一个字符值 int cName = getchar (); if ( cName > 0x21) // 只处理可见字符 { cName *= 0x29; // 乘法 cName *= 2; // 自增一倍 printf ( "Serial: CW-%4d-CRACKED
" ,cName); } else { printf ( "input error!
" ); } system ( "pause" ); return 0; } |
第二个单独Serial
流程同第一个,
但是这个很简单,直接在调用CALL那里向上F8走一遍就基本明白了。
核心代码:
CALL之前的那个JNZ是爆破的关键,直接NOP就OK了。
JNZ之前的那个CALL是进行Serial判断的关键,通过单步跟踪发现他就是一个固定值。
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | 0042F470 /. 55 push ebp 0042F471 |. 8BEC mov ebp,esp 0042F473 |. 33C9 xor ecx,ecx 0042F475 |. 51 push ecx 0042F476 |. 51 push ecx 0042F477 |. 51 push ecx 0042F478 |. 51 push ecx 0042F479 |. 53 push ebx 0042F47A |. 8BD8 mov ebx,eax 0042F47C |. 33C0 xor eax,eax 0042F47E |. 55 push ebp 0042F47F |. 68 2CF54200 push 0042F52C 0042F484 |. 64:FF30 push dword ptr fs:[eax] 0042F487 |. 64:8920 mov dword ptr fs:[eax],esp 0042F48A |. 8D45 FC lea eax,[local.1] 0042F48D |. BA 40F54200 mov edx,0042F540 ; ASCII 48, "ello" 0042F492 |. E8 7142FDFF call 00403708 0042F497 |. 8D45 F8 lea eax,[local.2] 0042F49A |. BA 50F54200 mov edx,0042F550 ; ASCII 44, "ude!" 0042F49F |. E8 6442FDFF call 00403708 0042F4A4 |. FF75 FC push [local.1] 0042F4A7 |. 68 60F54200 push 0042F560 ; UNICODE " " 0042F4AC |. FF75 F8 push [local.2] 0042F4AF |. 8D45 F4 lea eax,[local.3] 0042F4B2 |. BA 03000000 mov edx,0x3 0042F4B7 |. E8 F044FDFF call 004039AC 0042F4BC |. 8D55 F0 lea edx,[local.4] 0042F4BF |. 8B83 E0010000 mov eax,dword ptr ds:[ebx+0x1E0] 0042F4C5 |. E8 8EB5FEFF call 0041AA58 0042F4CA |. 8B45 F0 mov eax,[local.4] ; // eax=112233 0042F4CD |. 8B55 F4 mov edx,[local.3] ; // edx=Hello Dude! 0042F4D0 |. E8 2745FDFF call 004039FC 0042F4D5 75 1A jnz short 0042F4F1 ; // 爆破的关键 0042F4D7 |. 6A 00 push 0x0 0042F4D9 |. B9 64F54200 mov ecx,0042F564 0042F4DE |. BA 70F54200 mov edx,0042F570 0042F4E3 |. A1 480A4300 mov eax,dword ptr ds:[0x430A48] 0042F4E8 |. 8B00 mov eax,dword ptr ds:[eax] 0042F4EA |. E8 81ACFFFF call 0042A170 0042F4EF |. EB 18 jmp short 0042F509 0042F4F1 |> 6A 00 push 0x0 0042F4F3 |. B9 84F54200 mov ecx,0042F584 0042F4F8 |. BA 8CF54200 mov edx,0042F58C 0042F4FD |. A1 480A4300 mov eax,dword ptr ds:[0x430A48] 0042F502 |. 8B00 mov eax,dword ptr ds:[eax] 0042F504 |. E8 67ACFFFF call 0042A170 0042F509 |> 33C0 xor eax,eax |
这个没有动态生成的注册码,是一个固定的:Hello Dude!
摘自:http://www.cnblogs.com/bbdxf/p/3777711.html