[反汇编练习] 160个CrackMe之007.
本系列文章的目的是从一个没有任何经验的新手的角度(其实就是我自己),一步步尝试将160个CrackMe全部破解,如果可以,通过任何方式写出一个类似于注册机的东西。
其中,文章中按照如下逻辑编排(解决如下问题):
1、使用什么环境和工具
2、程序分析
3、思路分析和破解流程
4、注册机的探索
----------------------------------
提醒各位看客: 如果文章中的逻辑看不明白,那你一定是没有亲手操刀!OD中的跳转提示很强大,只要你跟踪了,不用怎么看代码就理解了!
----------------------------------
1、工具和环境:
WinXP SP3 + 52Pojie六周年纪念版OD + PEID + 汇编金手指。
160个CrackMe的打包文件。
下载地址: http://pan.baidu.com/s/1xUWOY 密码: jbnq
注:
1、Win7系统对于模块和程序开启了随机初始地址的功能,会给分析带来很大的负担,所以不建议使用Win7进行分析。
2、以上工具都是在52PoJie论坛下的原版程序,NOD32不报毒,个人承诺绝对不会进行任何和木马病毒相关内容。
2、程序分析:
想要破解一个程序,必须先了解这个程序。所以,在破解过程中,对最初程序的分析很重要,他可以帮助我们理解作者的目的和意图,特别是对于注册码的处理细节,从而方便我们反向跟踪和推导。
和上一节一样,打开CHM,选择第7个aLoNg3x.2,保存下来。运行程序,程序界面如下:
使用PEID查一下,Delphi 4.0 - 5.0。点一下About-Help,看完之后和上一个要求一样。不用想了,直接上IDR分析。
3、思路分析和破解流程
IDR打开后如图:
我们先看一下窗口信息(F5):
我们发现,除了第一个register按钮之后,还有一个again按钮。看来这个程序意思很明确点击完Reg之后还需要通过again按钮的验证。我们在树列表中查看下按钮对应事件的关系。
首先,分析Register的事件:
双击IDR最下面的列表中的RegisterClick.event,右上角会显示对应的反汇编。
根据上一个程序的经验,大概地分析一下:
Delphi字符串函数反汇编参考:http://www.cnblogs.com/bbdxf/p/3787684.html
_CrackMe200::TPrincipale.RegisterzClick
00442F28 push ebp
00442F29 mov ebp,esp
00442F2B add esp,0FFFFFFF8
00442F2E push ebx
00442F2F push esi
00442F30 xor ecx,ecx
00442F32 mov dword ptr [ebp-8],ecx
00442F35 mov ebx,eax
00442F37 xor eax,eax
00442F39 push ebp
00442F3A push 443022
00442F3F push dword ptr fs:[eax]
00442F42 mov dword ptr fs:[eax],esp
00442F45 lea edx,[ebp-8]
00442F48 mov eax,dword ptr [ebx+2DC]; TPrincipale.Codice:TEdit
00442F4E call TControl.GetText
00442F53 mov eax,dword ptr [ebp-8]
00442F56 lea edx,[ebp-4]
00442F59 call @ValLong
00442F5E mov esi,eax
00442F60 cmp dword ptr [ebp-4],0
>00442F64 je 00442F9D
00442F66 mov eax,443038; 'You MUST insert a valid Long Integer Value in the Code Editor... Thank you :)'
00442F6B call ShowMessage
00442F70 lea edx,[ebp-8]
00442F73 mov eax,dword ptr [ebx+2DC]; TPrincipale.Codice:TEdit
00442F79 call TControl.GetText
00442F7E mov eax,dword ptr [ebp-8]
00442F81 call 00442A8C
00442F86 mov [00445830],eax; gvar_00445830
00442F8B mov edx,443090; '0'
00442F90 mov eax,dword ptr [ebx+2DC]; TPrincipale.Codice:TEdit
00442F96 call TControl.SetText
>00442F9B jmp 0044300C
00442F9D test esi,esi
>00442F9F jle 00442FFB
00442FA1 lea edx,[ebp-8]
00442FA4 mov eax,dword ptr [ebx+2D8]; TPrincipale.Nome:TEdit
00442FAA call TControl.GetText
00442FAF mov ecx,dword ptr [ebp-8]
00442FB2 mov edx,esi
00442FB4 mov eax,[00445830]; 0x0 gvar_00445830
00442FB9 call 004429A8 ; // 关键CALL
00442FBE test al,al
>00442FC0 je 00442FF2 ; // 关键跳转
00442FC2 xor edx,edx ;// 将注册按钮隐藏,然后显示出来那个again按钮
00442FC4 mov eax,dword ptr [ebx+2CC]; TPrincipale.Registerz:TButton
00442FCA call TControl.SetVisible
00442FCF mov dl,1
00442FD1 mov eax,dword ptr [ebx+2E8]; TPrincipale.Again:TButton
00442FD7 call TControl.SetVisible
00442FDC xor edx,edx
00442FDE mov eax,dword ptr [ebx+2D8]; TPrincipale.Nome:TEdit
00442FE4 mov ecx,dword ptr [eax]
00442FE6 call dword ptr [ecx+60]; TControl.SetEnabled
00442FE9 xor eax,eax
00442FEB mov [00445830],eax; gvar_00445830
>00442FF0 jmp 0044300C
00442FF2 xor eax,eax
00442FF4 mov [00445830],eax; gvar_00445830
>00442FF9 jmp 0044300C
00442FFB mov eax,44309C; 'Please... The Code Must be > 0'
00443000 call ShowMessage
00443005 xor eax,eax
00443007 mov [00445830],eax; gvar_00445830
0044300C xor eax,eax
0044300E pop edx
0044300F pop ecx
00443010 pop ecx
00443011 mov dword ptr fs:[eax],edx
00443014 push 443029
00443019 lea eax,[ebp-8]
0044301C call @LStrClr
00443021 ret
<00443022 jmp @HandleFinally
<00443027 jmp 00443019
00443029 pop esi
0044302A pop ebx
0044302B pop ecx
0044302C pop ecx
0044302D pop ebp
0044302E ret
根据函数的名称,我们很容易分析出了关键Call 004429A8, 在CALL下面的JE就是爆破的关键跳转。
将exe程序拖到OD中打开,根据IDR中的地址,转到(Ctrl+G)开头地址:00442F28。在程序中输入伪码,参照IDR中的汇编,修改关键的跳转JE 00442FF2,选中JE->右键->Binary->Fill with NOPs。在程序中随意输入,点击Register按钮,发现注册按钮不见了,again按钮出来了。
我们继续在IDR中找到againClick.event,双击进去,继续分析一下:
_CrackMe200::TPrincipale.AgainClick
004430BC push ebp
004430BD mov ebp,esp
004430BF push 0
004430C1 push 0
004430C3 push 0
004430C5 push ebx
004430C6 push esi
004430C7 mov ebx,eax
004430C9 xor eax,eax
004430CB push ebp
004430CC push 44322D
004430D1 push dword ptr fs:[eax]
004430D4 mov dword ptr fs:[eax],esp
004430D7 lea edx,[ebp-0C]
004430DA mov eax,dword ptr [ebx+2DC]; TPrincipale.Codice:TEdit
004430E0 call TControl.GetText
004430E5 mov eax,dword ptr [ebp-0C]
004430E8 lea edx,[ebp-4]
004430EB call @ValLong
004430F0 mov esi,eax
004430F2 cmp dword ptr [ebp-4],0
>004430F6 je 00443132
004430F8 mov eax,443244; 'You MUST insert a valid Long Integer Value in the Code Editor... Thank you :)'
004430FD call ShowMessage
00443102 lea edx,[ebp-0C]
00443105 mov eax,dword ptr [ebx+2DC]; TPrincipale.Codice:TEdit
0044310B call TControl.GetText
00443110 mov eax,dword ptr [ebp-0C]
00443113 call 00442A8C
00443118 mov [00445830],eax; gvar_00445830
0044311D mov edx,44329C; '0'
00443122 mov eax,dword ptr [ebx+2DC]; TPrincipale.Codice:TEdit
00443128 call TControl.SetText
>0044312D jmp 0044320F
00443132 test esi,esi
>00443134 jle 004431FE
0044313A lea edx,[ebp-0C]
0044313D mov eax,dword ptr [ebx+2D8]; TPrincipale.Nome:TEdit
00443143 call TControl.GetText
00443148 mov ecx,dword ptr [ebp-0C]
0044314B mov edx,esi
0044314D mov eax,[00445830]; 0x0 gvar_00445830
00443152 call 004429A8
00443157 test al,al
>00443159 je 004431CE
0044315B xor edx,edx
0044315D mov eax,dword ptr [ebx+2E8]; TPrincipale.Again:TButton
00443163 call TControl.SetVisible
00443168 lea edx,[ebp-8]
0044316B mov eax,[0044582C]; 0x0 gvar_0044582C:TPrincipale
00443170 call TControl.GetText
00443175 lea eax,[ebp-8]
00443178 call UniqueString
0044317D mov byte ptr [eax+5],65
00443181 lea eax,[ebp-8]
00443184 call UniqueString
00443189 mov byte ptr [eax+6],64
0044318D lea eax,[ebp-8]
00443190 mov ecx,13
00443195 mov edx,0C
0044319A call @LStrDelete
0044319F lea edx,[ebp-0C]
004431A2 mov eax,dword ptr [ebx+2D8]; TPrincipale.Nome:TEdit
004431A8 call TControl.GetText
004431AD mov edx,dword ptr [ebp-0C]
004431B0 lea eax,[ebp-8]
004431B3 call @LStrCat
004431B8 mov edx,dword ptr [ebp-8]
004431BB mov eax,[0044582C]; 0x0 gvar_0044582C:TPrincipale
004431C0 call TControl.SetText
004431C5 xor eax,eax
004431C7 mov [00445830],eax; gvar_00445830
>004431CC jmp 0044320F
004431CE xor eax,eax
004431D0 mov [00445830],eax; gvar_00445830
004431D5 xor edx,edx
004431D7 mov eax,dword ptr [ebx+2E8]; TPrincipale.Again:TButton
004431DD call TControl.SetVisible
004431E2 mov dl,1
004431E4 mov eax,dword ptr [ebx+2CC]; TPrincipale.Registerz:TButton
004431EA call TControl.SetVisible
004431EF mov dl,1
004431F1 mov eax,dword ptr [ebx+2D8]; TPrincipale.Nome:TEdit
004431F7 mov ecx,dword ptr [eax]
004431F9 call dword ptr [ecx+60]; TControl.SetEnabled
>004431FC jmp 0044320F
004431FE mov eax,4432A8; 'Please... The Code Must be > 0'
00443203 call ShowMessage
00443208 xor eax,eax
0044320A mov [00445830],eax; gvar_00445830
0044320F xor eax,eax
00443211 pop edx
00443212 pop ecx
00443213 pop ecx
00443214 mov dword ptr fs:[eax],edx
00443217 push 443234
0044321C lea eax,[ebp-0C]
0044321F call @LStrClr
00443224 lea eax,[ebp-8]
00443227 call @LStrClr
0044322C ret
<0044322D jmp @HandleFinally
<00443232 jmp 0044321C
00443234 pop esi
00443235 pop ebx
00443236 mov esp,ebp
00443238 pop ebp
00443239 ret
大概地一看,是不是发现他们的逻辑基本和Register的差不多?关键Call也一样。没关系,我们继续在JE 004431CE右键,Binary->Fill with NOPs。这时,回到程序,继续点击Again,是不是两个按钮都被隐藏了?!!哈哈哈!
(多试几次,会发现第一个编辑框还是有字数限制的,需要大于4个)
小结:两个按钮调用了相同的关键CALL,即使用了同一个算法函数对注册码进行验证,这要爆破这一个函数或它的跳转就行。
4、注册机尝试
由于两个按钮的关键Call一样,所以这个算法就很省事了,直接在 OD中跳转到CALL 004429A8的地方,F8单步分析。
慢着,是不是很多CALL还不知道什么意思?是啊,先用IDR看一下嘛!
在IDR中也是用Ctrl+G,输入地址 004429A8,确定:
_CrackMe200::sub_004429A8
004429A8 push ebp
004429A9 mov ebp,esp
004429AB add esp,0FFFFFFF4
004429AE push ebx
004429AF push esi
004429B0 push edi
004429B1 mov dword ptr [ebp-8],ecx
004429B4 mov dword ptr [ebp-4],edx
004429B7 mov edi,eax
004429B9 mov eax,dword ptr [ebp-8]
004429BC call @LStrAddRef
004429C1 xor eax,eax
004429C3 push ebp
004429C4 push 442A7A
004429C9 push dword ptr fs:[eax]
004429CC mov dword ptr fs:[eax],esp
004429CF mov eax,dword ptr [ebp-8]
004429D2 call @LStrLen
004429D7 cmp eax,4
>004429DA jle 00442A62
004429E0 xor ebx,ebx
004429E2 mov eax,dword ptr [ebp-8]
004429E5 call @LStrLen
004429EA test eax,eax
>004429EC jle 00442A26
004429EE mov dword ptr [ebp-0C],eax
004429F1 mov esi,1
004429F6 mov eax,dword ptr [ebp-8]
004429F9 call @LStrLen
004429FE cmp eax,1
>00442A01 jl 00442A20
00442A03 mov edx,dword ptr [ebp-8]
00442A06 movzx edx,byte ptr [edx+esi-1]
00442A0B mov ecx,dword ptr [ebp-8]
00442A0E movzx ecx,byte ptr [ecx+eax-1]
00442A13 imul edx,ecx
00442A16 imul edx,edi
00442A19 add ebx,edx
00442A1B dec eax
00442A1C test eax,eax
<00442A1E jne 00442A03
00442A20 inc esi
00442A21 dec dword ptr [ebp-0C]
<00442A24 jne 004429F6
00442A26 mov eax,ebx
00442A28 cdq
00442A29 xor eax,edx
00442A2B sub eax,edx
00442A2D mov ecx,0A2C2A
00442A32 cdq
00442A33 idiv eax,ecx
00442A35 mov ebx,edx
00442A37 mov eax,dword ptr [ebp-4]
00442A3A mov ecx,59
00442A3F cdq
00442A40 idiv eax,ecx
00442A42 mov ecx,eax
00442A44 mov eax,dword ptr [ebp-4]
00442A47 mov esi,50
00442A4C cdq
00442A4D idiv eax,esi
00442A4F add ecx,edx
00442A51 inc ecx
00442A52 mov dword ptr [ebp-4],ecx
00442A55 cmp ebx,dword ptr [ebp-4]
>00442A58 jne 00442A5E
00442A5A mov bl,1
>00442A5C jmp 00442A64
00442A5E xor ebx,ebx
>00442A60 jmp 00442A64
00442A62 xor ebx,ebx
00442A64 xor eax,eax
00442A66 pop edx
00442A67 pop ecx
00442A68 pop ecx
00442A69 mov dword ptr fs:[eax],edx
00442A6C push 442A81
00442A71 lea eax,[ebp-8]
00442A74 call @LStrClr
00442A79 ret
<00442A7A jmp @HandleFinally
<00442A7F jmp 00442A71
00442A81 mov eax,ebx
00442A83 pop edi
00442A84 pop esi
00442A85 pop ebx
00442A86 mov esp,ebp
00442A88 pop ebp
00442A89 ret
然后在OD中F8单步调试详细分析:
004429A8 /$ 55 push ebp ; // reg和again 关键Call
004429A9 |. 8BEC mov ebp,esp
004429AB |. 83C4 F4 add esp,-0xC
004429AE |. 53 push ebx
004429AF |. 56 push esi
004429B0 |. 57 push edi
004429B1 |. 894D F8 mov [local.2],ecx
004429B4 |. 8955 FC mov [local.1],edx ; // edx=123321转整数
004429B7 |. 8BF8 mov edi,eax ; // edi=eax=0
004429B9 |. 8B45 F8 mov eax,[local.2]
004429BC |. E8 2712FCFF call 00403BE8 ; LStrAddRef
004429C1 |. 33C0 xor eax,eax
004429C3 |. 55 push ebp
004429C4 |. 68 7A2A4400 push 00442A7A
004429C9 |. 64:FF30 push dword ptr fs:[eax]
004429CC |. 64:8920 mov dword ptr fs:[eax],esp
004429CF |. 8B45 F8 mov eax,[local.2] ; bbdxf
004429D2 |. E8 5D10FCFF call 00403A34 ; @LStrLen
004429D7 |. 83F8 04 cmp eax,0x4
004429DA |. 0F8E 82000000 jle 00442A62 ; 必须 > 4
004429E0 |. 33DB xor ebx,ebx ; // ebx=0
004429E2 |. 8B45 F8 mov eax,[local.2]
004429E5 |. E8 4A10FCFF call 00403A34 ; @LStrLen
004429EA |. 85C0 test eax,eax
004429EC |. 7E 38 jle short 00442A26
004429EE |. 8945 F4 mov [local.3],eax
004429F1 |. BE 01000000 mov esi,0x1
004429F6 |> 8B45 F8 /mov eax,[local.2]
004429F9 |. E8 3610FCFF |call 00403A34 ; @LStrLen
004429FE |. 83F8 01 |cmp eax,0x1
00442A01 |. 7C 1D |jl short 00442A20
00442A03 |> 8B55 F8 |/mov edx,[local.2]
00442A06 |. 0FB65432 FF ||movzx edx,byte ptr ds:[edx+esi-0x1] ; // 从第一个字符开始
00442A0B |. 8B4D F8 ||mov ecx,[local.2]
00442A0E |. 0FB64C01 FF ||movzx ecx,byte ptr ds:[ecx+eax-0x1] ; // 最后一个字符
00442A13 |. 0FAFD1 ||imul edx,ecx ; // 第一个字符乘上最后一个--
00442A16 |. 0FAFD7 ||imul edx,edi ; // edi=0, 所以结果一直为0
00442A19 |. 03DA ||add ebx,edx ; // ebx=0, 所以结果一直为0
00442A1B |. 48 ||dec eax ; // len--
00442A1C |. 85C0 ||test eax,eax
00442A1E |.^ 75 E3 |\jnz short 00442A03
00442A20 |> 46 |inc esi
00442A21 |. FF4D F4 |dec [local.3] ; // len--
00442A24 |.^ 75 D0 \jnz short 004429F6 ; // 整个循环结束,edx=0,ebx=0
00442A26 |> 8BC3 mov eax,ebx
00442A28 |. 99 cdq
00442A29 |. 33C2 xor eax,edx ; // eax=0,edx=0
00442A2B |. 2BC2 sub eax,edx
00442A2D |. B9 2A2C0A00 mov ecx,0xA2C2A
00442A32 |. 99 cdq
00442A33 |. F7F9 idiv ecx ; // eax/ecx
00442A35 |. 8BDA mov ebx,edx ; // 这里以上的计算结果都一定是0
00442A37 |. 8B45 FC mov eax,[local.1] ; // 序列号转整数,从这里开始才是有意义的计算
00442A3A |. B9 59000000 mov ecx,0x59
00442A3F |. 99 cdq ; // edx=0
00442A40 |. F7F9 idiv ecx ; // eax=eax/0x59
00442A42 |. 8BC8 mov ecx,eax ; // 存储结果到ecx
00442A44 |. 8B45 FC mov eax,[local.1]
00442A47 |. BE 50000000 mov esi,0x50
00442A4C |. 99 cdq
00442A4D |. F7FE idiv esi ; // eax=eax/0x50, edx=eax % 0x50
00442A4F |. 03CA add ecx,edx ; // ecx=ecx+edx
00442A51 |. 41 inc ecx ; // ecx++
00442A52 |. 894D FC mov [local.1],ecx
00442A55 |. 3B5D FC cmp ebx,[local.1]
00442A58 |. 75 04 jnz short 00442A5E ; // ebx等于返回值,应该为1
00442A5A |. B3 01 mov bl,0x1
00442A5C |. EB 06 jmp short 00442A64
00442A5E |> 33DB xor ebx,ebx
00442A60 |. EB 02 jmp short 00442A64
00442A62 |> 33DB xor ebx,ebx
00442A64 |> 33C0 xor eax,eax ; // 都调到这里
00442A66 |. 5A pop edx
00442A67 |. 59 pop ecx
00442A68 |. 59 pop ecx
00442A69 |. 64:8910 mov dword ptr fs:[eax],edx
00442A6C |. 68 812A4400 push 00442A81
00442A71 |> 8D45 F8 lea eax,[local.2]
00442A74 |. E8 3F0DFCFF call 004037B8 ; @LStrClr
00442A79 \. C3 retn
00442A7A .^ E9 F907FCFF jmp 00403278
00442A7F .^ EB F0 jmp short 00442A71
00442A81 . 8BC3 mov eax,ebx ; // 返回到这里
00442A83 . 5F pop edi
00442A84 . 5E pop esi
00442A85 . 5B pop ebx
00442A86 . 8BE5 mov esp,ebp
00442A88 . 5D pop ebp
00442A89 . C3 retn
请详细看上面的汇编代码的注释:在地址00442A35以上部分的代码最终的结果一直为0,这个地址下面的代码才真正的有实际意义。
其中,有意义这一块的C/CPP代码大概如下:
int nCode=123321; // 用户输入的注册码,必须是4位以上,转换为整数
int n1 = nCode/0x59;
int n2 = nCode%0x50;
if( n1+n2+1 == 0 )
{
// 第一次隐藏Register按钮,显示again按钮,第二次隐藏again按钮
}else{
//显示register按钮,隐藏again按钮
}
为什么我没有写出一个注册机呢?
算法原理大概是一个数除以0x59的结果加上它对于0x50取模,然后结果加上1等于0。不加1还好理解,加上1就顿时感觉蛋疼了,我实在无法找到这样一个数。(当然也不排除我理解错了,求大牛指导!!)
007结束。
-----------
PS: 有人说,帖子内容很难看懂,问我能否做成视频?
我想说:
首先,我的贴子都是按照我分析的流程写的,相关的工具、参考资料、代码分析、关键算法分析流程 全部都已经很详细地写下来了。只要你拿着OD和有限的几个工具,除非你真的连F7、F8分析代码都不会,否则对照我的文章肯定能看到一些有价值的东西,至少我是这么认为的。
其次,【无论多么简单的事情,想要做好,都是不容易的!】经常混论坛的都知道,爆破虽易,算法不易!确实自己水平有限,无法做到举重若轻,几分钟分析出一个算法。这种水平我真达不到!此系列文章的重头戏不在于爆破,而在于算法部分。每天破解的80%以上的时间都是在分析算法的部分,还有10%是用来整理排版帖子,虽然做的不是很好,但我问心无愧地做到了自己的承诺,尽自己所能。帖子尚且不易,视频何其远乎!我只能说声抱歉了!
最后,谢谢大家的捧场!
BY 笨笨D幸福