【文章标题】: Haunted的crack#1的破解
【文章作者】: 烏鴉(DarkByte)
【软件名称】: Haunted的crack#1
【下载地址】:http://bbs.pediy.com/showthread.php?t=12136&page=12
初入樵新手 crackme 博物馆168楼isgod发布的
【加壳方式】: 无
【编写语言】: MASM32 / TASM32
【使用工具】: OD
【操作平台】: WINXP
【软件介绍】: 无
【作者声明】: 偶是菜鸟,愿意与大家一起交流!第一次写破文,写的不好还请见谅
--------------------------------------------------------------------------------
【详细过程】
程序很简单,进行的明码比较,主要分析下代码。
运行程序,输入用户名,密码运行,弹出对话框提示,PEID查下,无壳,OD载入
因为弹出对话框提示,所以对MessageBoxA下断点,在Command窗口输入bp MessageBoxA回车
F9运行程序,输入用户名密码,停在系统领空,Alt+F9我们返回程序领空
004012D3 . 56 push esi
004012D4 . FF15 08204000 call dword ptr [402008] ; USER32.MessageBoxA
004012DA . B9 C8000000 mov ecx, 0C8 <---我们停在这里
004012DF . B8 00000000 mov eax, 0
004012E4 . BF 0C204000 mov edi, 0040200C ; ASCII "Wrong Code",CR,LF,"-weradiyi-"
004012E9 . F2:AE repne scas byte ptr es:[edi]
004012EB . 4F dec edi
004012EC . C607 DC mov byte ptr [edi], 0DC
004012EF . 5E pop esi
004012F0 . C3 retn
这时候已经对注册码比较过了所以关键Call在上面,我们网上找
0040123E . B8 7C204000 mov eax, 0040207C ; ASCII "ssssssssssssssssss"
00401243 . E8 F0000000 call 00401338 ; 这个Call是计算注册码长度的,别当关键Call喽^_^
00401248 . 39D0 cmp eax, edx
0040124A . 0F85 5F000000 jnz 004012AF ; 跳向失败
00401250 . BE B8204000 mov esi, 004020B8 ; ASCII "HNT-3288-0304-7532-KU"
00401255 . BF 7C204000 mov edi, 0040207C ; ASCII "ssssssssssssssssss"
0040125A . 89C1 mov ecx, eax
0040125C > 8A06 mov al, byte ptr [esi]
0040125E . 8A27 mov ah, byte ptr [edi] ;这一段是把用户的注册码和计算得出的注册码对位相减
00401260 . 46 inc esi
00401261 . 47 inc edi
00401262 . 30C4 xor ah, al ; ah==al则成功
00401264 . 0F85 45000000 jnz 004012AF ; 跳向失败
0040126A . 49 dec ecx
0040126B .^ 75 EF jnz short 0040125C
由上面我们知道当计算出来注册码后,先比较长度与输入的是否相等,然后再比较每一位是否相等,有一个不相等都讲导致失败
我们继续往上找
004010C9 . 40 inc eax
004010CA . C3 retn
004010CB > B9 1E000000 mov ecx, 1E ;这里就是算法函数开始的地方了,为什么呢?看到上面的retn了吧
004010D0 . 31C0 xor eax, eax ;说明这是一个函数的开始
004010D2 . BF B8204000 mov edi, 004020B8 ; ASCII "HNT-3288-0304-7532-KU"
004010D7 . F3:AA rep stos byte ptr es:[edi]
004010D9 . 31FF xor edi, edi
004010DB . 47 inc edi
004010DC . C1E7 0B shl edi, 0B
004010DF . 6A 2D push 2D
004010E1 . 68 4F204000 push 0040204F ; ASCII "DarkByte"
004010E6 . 57 push edi
我们可以看到
004010CB > B9 1E000000 mov ecx, 1E 这句是由上面跳转下来的,我们继续往上走看到下面的代码
这里就是窗口的回调函数啦
0040106E . 89D8 mov eax, ebx
00401070 . C1E0 10 shl eax, 10
00401073 . C1E8 10 shr eax, 10
00401076 . 31FF xor edi, edi
00401078 . 47 inc edi
00401079 . 83F8 02 cmp eax, 2
0040107C . 0F84 83020000 je 00401305
00401082 . C1E7 07 shl edi, 7
00401085 . 39F8 cmp eax, edi
00401087 . 0F84 3E000000 je 004010CB 是由这里跳转下去的
0040108D . C1E7 02 shl edi, 2
00401090 . 39C7 cmp edi, eax
00401092 . 0F84 59020000 je 004012F1
再往上的这段代码就是程序的入口了
0040102D >/$ 6A 00 push 0 ; /(initial cpu selection)
0040102F |. E8 E44F0000 call <jmp.&KERNEL32.GetModuleHandleA> ; \GetModuleHandleA
00401034 |. A3 98214000 mov dword ptr [402198], eax
00401039 |. 68 6C214000 push 0040216C ; /user32.dllgetdlgitemtexta
0040103E |. E8 DB4F0000 call <jmp.&KERNEL32.LoadLibraryA> ; \LoadLibraryA
00401043 |. A3 08204000 mov dword ptr [402008], eax
00401048 |. E8 C8020000 call 00401315
0040104D |. A3 08204000 mov dword ptr [402008], eax
00401052 |. 6A 00 push 0 ; /lParam = NULL
00401054 |. 68 00104000 push 00401000 ; |DlgProc = Crackme.00401000
00401059 |. 6A 00 push 0 ; |hOwner = NULL
0040105B |. 68 E8030000 push 3E8 ; |pTemplate = 3E8
00401060 |. FF35 98214000 push dword ptr [402198] ; |hInst = 00400000
00401066 |. E8 954F0000 call <jmp.&USER32.DialogBoxParamA> ; \DialogBoxParamA
0040106B |. 31C0 xor eax, eax
0040106D \. C3 retn
这个原来的代码就类似这样:
start:
call _WinMain
invoke ExitProcess,NULL
end start
再向上的代码就是消息循环了....
- -!额,跑题了,我们回来继续分析注册码的计算
两个GetDlgItemTextA后,我们的注册信息被传入,如果不输入会直接retn掉
我们来到这里
00401111 > \B8 4F204000 mov eax, 0040204F ; ASCII "DarkByte"
00401116 . E8 1D020000 call 00401338 ;还是刚才的那个计算字符串长度的函数
0040111B . A3 A0214000 mov dword ptr [4021A0], eax ; 保存用户名长度
00401120 . BB 4F204000 mov ebx, 0040204F ; ASCII "DarkByte"
00401125 . 0FB613 movzx edx, byte ptr [ebx] ; 取出用户名的第一位
00401128 . 0FB64C03 FF movzx ecx, byte ptr [ebx+eax-1] ; 取出用户名最后一位
0040112D . 0FAFD1 imul edx, ecx ; 用户名第一位乘以最后一位
00401130 . 50 push eax
00401131 . B9 02000000 mov ecx, 2
00401136 . 52 push edx ; 保存乘积
00401137 . 31D2 xor edx, edx
00401139 . F7F9 idiv ecx ; 用户名长度/2
0040113B . 5A pop edx ; 取出乘积
0040113C . 0FB60C03 movzx ecx, byte ptr [ebx+eax] ;
00401140 . 0FAFD1 imul edx, ecx ;用刚才乘出来的那个数再乘以用户名中间的那位
00401143 . BB B8204000 mov ebx, 004020B8
00401148 . C703 484E542D mov dword ptr [ebx], 2D544E48 ; 正确注册码前三位(固定的)
0040114E . 89D0 mov eax, edx ;把刚才的乘积给eax为后面的Call做准备
00401150 . 83C3 04 add ebx, 4 ;
00401153 . E8 F7010000 call 0040134F
00401158 . C705 00204000>mov dword ptr [402000], 1E
写成C就类似
num = username[0]*username[namelen-1]*username[namelen/2]
我们跟进这个Call
这个函数是一个求余函数,而被除数正好是刚才我们的乘积
0040134F /$ 51 push ecx
00401350 |. 56 push esi
00401351 |. 4B dec ebx
00401352 |. B9 04000000 mov ecx, 4 ;循环四次
00401357 |. BE 0A000000 mov esi, 0A
0040135C |. 52 push edx
0040135D |> 31D2 xor edx, edx
0040135F |. F7FE idiv esi ; 求余
00401361 |. 80C2 30 add dl, 30 ; 余数加30h
00401364 |. 88140B mov byte ptr [ebx+ecx], dl ;把求出的数保存起来,这里就是注册码
00401367 |.^ E2 F4 loopd short 0040135D
00401369 |. 5A pop edx
0040136A |. 5E pop esi
0040136B |. 59 pop ecx
0040136C \. C3 retn
上面的代码转换成C就类似
这里产生了注册码的第一个四位数组
Rcode[i] = num3 %0x0A+0x30;
num3/=0x0A;
继续向下
00401158 . C705 00204000>mov dword ptr [402000], 1E
00401162 . 68 00204000 push 00402000 ; /pBufferSize = Crackme.00402000
00401167 . 68 9A204000 push 0040209A ; |Buffer = Crackme.0040209A
0040116C . E8 B34E0000 call <jmp.&KERNEL32.GetComputerNameA> ; \GetComputerNameA
00401171 . B8 9A204000 mov eax, 0040209A
00401176 . E8 F2010000 call 0040136D ; 将计算机名累加,我们注意这里的返回值也就是累加和在eax中,
而eax在下一个Call中被ebx覆盖了,而中间并没有保存,可见这个累加值没用
0040117B . BB 4F204000 mov ebx, 0040204F ; ASCII "DarkByte"
00401180 . E8 00020000 call 00401385 ;很麻烦的一个Call,写注册机的时候在这里卡了下
00401185 . 50 push eax
我们看看上面的那个Call
00401385 /$ 51 push ecx
00401386 |. 52 push edx
00401387 |. 89D8 mov eax, ebx
00401389 |. E8 AAFFFFFF call 00401338 ; 字符长度
0040138E |. 89C1 mov ecx, eax ;ecx为用户名长度
00401390 |. 4B dec ebx
00401391 |. 31D2 xor edx, edx
00401393 |> 8A140B /mov dl, byte ptr [ebx+ecx] ;注意这里,这里只是把edx的最低字节擦除了,而乘积保存在高位的并未擦除
00401396 |. 0FAFD1 |imul edx, ecx ; 用户名对应位乘以对应字符
00401399 |. 01D0 |add eax, edx
0040139B |. 49 |dec ecx
0040139C |.^ 75 F5 \jnz short 00401393
0040139E |. 5A pop edx
0040139F |. 59 pop ecx
004013A0 \. C3 retn
这段码循环类似C语言的
for(i=namelen;i>0;i--)
{
temp= i*((temp&0xFFFFFF00)|(int(*(username+i-1))));
num +=temp;
}
继续向下
00401180 . E8 00020000 call 00401385
00401185 . 50 push eax ;保存刚才算出来的那个数
00401186 . 31C0 xor eax, eax ; eax=0参数这是cpuid的一个参数,将返回Cpu的信息到寄存器中
00401188 . 0FA2 cpuid ; 获取cpu信息
0040118A . 58 pop eax ; 取出了上面Call的那个数
0040118B . 51 push ecx ;保存Cpu的返回信息
0040118C . 52 push edx ;保存Cpu的返回信息
0040118D . 21D8 and eax, ebx ; 刚才那个Call计算出来的值与Cpu信息的EbX
0040118F . BB B8204000 mov ebx, 004020B8 ; ASCII "HNT-3288"
00401194 . 89DF mov edi, ebx
00401196 . 50 push eax
00401197 . 66:31C0 xor ax, ax
0040119A . F2:AE repne scas byte ptr es:[edi]
0040119C . 89FB mov ebx, edi
0040119E . 58 pop eax ;这里是与出来的那个值
0040119F . 4B dec ebx
004011A0 . C603 2D mov byte ptr [ebx], 2D ; 加-
004011A3 . 43 inc ebx
004011A4 . E8 A6010000 call 0040134F ;这个Call前面分析过了,这时候被除数就上面的eax
;在这里就产生了注册码的第二个四位,同上就不分析啦
再往下
004011A9 . B8 4F204000 mov eax, 0040204F ; ASCII "DarkByte"
004011AE . E8 BA010000 call 0040136D
004011B3 . 59 pop ecx ;这里注意下与前面的push的对应关系
004011B4 . 31C8 xor eax, ecx ; 与cpuid得到的edx异或
004011B6 . 59 pop ecx
004011B7 . 09C8 or eax, ecx ; 与cpu得到的ecx或
004011B9 . 50 push eax
004011BA . BB 4F204000 mov ebx, 0040204F ; ASCII "DarkByte"
004011BF . 0FB603 movzx eax, byte ptr [ebx] ;用户名的第一位
004011C2 . 0FB64B 01 movzx ecx, byte ptr [ebx+1]
004011C6 . 0FAFC1 imul eax, ecx ; 用户名第一位乘第二位
004011C9 . 59 pop ecx
004011CA . 0FAFC1 imul eax, ecx ;第一位和第二位的成绩乘上上面或出来的那个数
这段就是把用户名的长度与刚才cpuid出来的信息的edx异或再或上ecx再和第一,第二位相乘
C语言这样
num2 = ((Count(username)^_EDX)|_ECX)*username[0]*username[1];
004011CD . BF B8204000 mov edi, 004020B8 ; ASCII "HNT-3288-0304"
004011D2 . 50 push eax
004011D3 . 30C0 xor al, al
004011D5 . F2:AE repne scas byte ptr es:[edi]
004011D7 . 58 pop eax
004011D8 . 89FB mov ebx, edi
004011DA . 4B dec ebx
004011DB . C603 2D mov byte ptr [ebx], 2D
004011DE . 43 inc ebx
004011DF . E8 6B010000 call 0040134F ;又是刚才那个函数,到这第三个4位数就诞生了
- -!我们继续,忽忽..
004011E4 . B8 4F204000 mov eax, 0040204F ; ASCII "DarkByte"
004011E9 . E8 7F010000 call 0040136D ; 计算用户名累加值
004011EE . B9 1A000000 mov ecx, 1A
004011F3 . 31D2 xor edx, edx
004011F5 . F7F9 idiv ecx ; 用户名累加值%1A
004011F7 . B8 41000000 mov eax, 41 ;
004011FC . 01D0 add eax, edx ;余数+0x41
004011FE . 89C7 mov edi, eax ;保存在edi里
00401200 . BB 4F204000 mov ebx, 0040204F ; ASCII "DarkByte"
00401205 . E8 7B010000 call 00401385 ;这里还是刚才那个奇怪乘法
0040120A . 31D2 xor edx, edx
0040120C . F7F9 idiv ecx ;eax的值,也就是刚才那个call的返回值求余1A,
0040120E . 83C2 41 add edx, 41 ;加上0x41,这里保存在edx
00401211 . BB B8204000 mov ebx, 004020B8 ; ASCII "HNT-3288-0304-7532"
00401216 . 50 push eax
00401217 . 31C0 xor eax, eax
00401219 . 57 push edi ; 保存第一次计算出来的数
0040121A . 89DF mov edi, ebx
0040121C . F2:AE repne scas byte ptr es:[edi]
0040121E . 58 pop eax ; 这里的值就是上面保存在edi里面的值
0040121F . 89FB mov ebx, edi ; Crackme.004020CB
00401221 . 5F pop edi
00401222 . 4B dec ebx
00401223 . C603 2D mov byte ptr [ebx], 2D
00401226 . 8843 01 mov byte ptr [ebx+1], al ;把第一次计算出来的树存放
00401229 . 8853 02 mov byte ptr [ebx+2], dl ;第二个数存放,edx在计算完第二个数后一直未使用
0040122C . C643 03 00 mov byte ptr [ebx+3], 0
这段类似
Rcode[19] = Count(username)%0x1A+0x41;
Rcode[20] =char(unsigned long(num5) %0x1A +0x41);
好了,所有的注册码都出来了...再往下就是注册码的比较了,文章开始已经说了就不继续了。。
貌似有点啰嗦了
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)