2019-5-30 01:29 /
本来还在打算写一个通用补丁,不过看到汇编就可以很快地改完了,而且非常简单,就懒得动手了。
CIRCUS游戏从2001到2019都没有变过它的验证方法,可能会在反汇编的时候显示有些不一样(比如某个API要经过某个寄存器来调用,几个jmp连在一块去了等等),不过总体思路还是很清晰的。
一、CD/DVD验证:
来想想程序一般会靠什么来验证有没有经过CD/DVD安装游戏?存档,这个想法不错。
对于初次运行的程序,如果不通过CD/DVD的验证是无法运行的。
怎么办?改存档?(我还是劝你善良)
最简单的办法是直接爆破掉验证本身,使其不管在什么时候都可以认为是经过CD/DVD安装的。
那么我们下断检测驱动器的API:GetDriveTypeA
有些有经验的人可能会冲动去改在0040D3C6的cmp eax,0x5,因为在没检测到驱动器的时候eax的值是0x00000003,。我建议这里不用改,原因请看下面:
有人说我把前面的cmp eax,0x5改了就可以了啊,因为返回为true啊。
所以这里就是最麻烦的地方,注意一下第二个跳转前面有一个cmp eax,0x1,这里又是一个判定。所以你只改一处肯定是没办法过检测的。
解决办法有二:
1、跟踪eax的变化,调整两个cmp的比较值。
2、nop掉第一个je,把第二个je改成jmp。
(不管怎么说这还是比较简单的)
二、序列号检测:
我建议最好在crack掉CD/DVD检测之后保存一个文件,因为序列号检测你很容易就跟丢或者跟错。
我们启动游戏后发现游戏会提示我们输入序列号……(智障CIRCUS)
然后我们要去找一下是哪一个函数(API)会让游戏提示我们要输入序列号的(也就是说,至少要找到显示对话框的那个函数)。
来想想有哪些函数可以显示对话框的?
MessageBox?SendMessage?
CIRCUS用的是SendMessageA来实现显示对话框的。
注意一下,“OK”的控件是灰色的,所以那一块代码里面肯定不是只有SendMessageA这个函数的:
(retn 0x10是返回到user32.dll里面)
你在这里面发现了很多函数(的确非常多,还有不少是重复的),而且有各种跳转,很难去理清楚关系(说实话我还是在看IDA反编译的伪C代码才看懂的,具体的可以去看我上一篇blog)。
那么有没有办法去回避读取这个函数?还是有的。
想想这个函数的跳转源是哪里?好吧没有。
想想00405370从哪里开始调用的?说实话还是挺难找的……
CIRCUS的这个调用离SendMessageA不远,应该就在附近。
事实证明这样想是对的:
00405476那里就会直接跳到00405370(注意不是跳转),然后这里去调用的一个DialogBoxParamA的API。这个API的用处是干嘛的自己去百度。
但是注意一下,这个调用不会跳到00405370上,会直接跳进user32.dll,很多人不理解为什么没有跳到00405370上。
这个时候你在user32里面跟一下,看一下行为,或许能找到答案。
注意一下下面两个retn,返回的都是同一个地址,所以我们直接让程序运行到retn(至于为什么不去管那个SendMessage的原因是因为点了“終了”后程序没有马上结束,会继续执行0040547C,因此不用担心程序会跑飞的问题)。
来看看retn返回到哪里了:
它返回到00414EBE,而上面的那个call就是跳到00405460去的。
从00414EB9到00414ED7是一个循环,也就是说每循环一次就会调用那个call提示我们输入序列号。
那么有没有办法不让它检测?或者说在检测之前就跳过这个循环?
注意我在00414EBE还多贴了一段,上面有一个明显的jge,还有一个test。
说明test返回的结果和jge有关。那么就很简单了,直接把jge改成jmp,不管test是什么值,到这里总会跳过这个循环检测。
好了,现在你可以开始放心游戏了。
PS:不是所有的DialogBoxParamA的调用都会跳到user32.dll上,有些是可以直接返回到SendMessageA的开头的,具体情况具体分析。这里我只是举了个例子。
PS2:关于为什么DialogBoxParamA会调用到user32.dll并且在SendMessageA的开头的指令下断点能够断的到,这个我理解成SendMessageA是一个子指令,user32.dll里面跟到最底的时候对代码进行分析可以看得出在一堆的跳转里面有SendMessage的参与了,并且在一个大跳转之后就是call。
PS3:为什么我用中文版系统运行,但是序列号认证界面没有乱码?原因在于user32.dll里面并没有用A而是用的W(也就是Unicode),具体情况请自己去查看。
PS4:破解这个只需要3分钟,写这个教程花了我两小时w
CIRCUS游戏从2001到2019都没有变过它的验证方法,可能会在反汇编的时候显示有些不一样(比如某个API要经过某个寄存器来调用,几个jmp连在一块去了等等),不过总体思路还是很清晰的。
一、CD/DVD验证:
来想想程序一般会靠什么来验证有没有经过CD/DVD安装游戏?存档,这个想法不错。
对于初次运行的程序,如果不通过CD/DVD的验证是无法运行的。
怎么办?改存档?(我还是劝你善良)
最简单的办法是直接爆破掉验证本身,使其不管在什么时候都可以认为是经过CD/DVD安装的。
那么我们下断检测驱动器的API:GetDriveTypeA
0040D3A0 /$ 8B4424 04 mov eax,dword ptr ss:[esp+0x4] ; tyakata1.0040D468
0040D3A4 |. 8D5424 04 lea edx,dword ptr ss:[esp+0x4]
0040D3A8 |. 52 push edx ; /RootPathName = "E:\"
0040D3A9 |. C64424 09 3A mov byte ptr ss:[esp+0x9],0x3A ; |
0040D3AE |. 8A08 mov cl,byte ptr ds:[eax] ; |
0040D3B0 |. C64424 0A 5C mov byte ptr ss:[esp+0xA],0x5C ; |
0040D3B5 |. 884C24 08 mov byte ptr ss:[esp+0x8],cl ; |
0040D3B9 |. C64424 0B 00 mov byte ptr ss:[esp+0xB],0x0 ; |
0040D3BE |. FF15 A0204200 call dword ptr ds:[<&KERNEL32.GetDriveTy>; \GetDriveTypeA
0040D3C4 |. 33C9 xor ecx,ecx
0040D3C6 |. 83F8 05 cmp eax,0x5
0040D3C9 |. 0f94c1 sete cl
0040D3CC |. 8BC1 mov eax,ecx
0040D3CE \. C3 retn
有些有经验的人可能会冲动去改在0040D3C6的cmp eax,0x5,因为在没检测到驱动器的时候eax的值是0x00000003,。我建议这里不用改,原因请看下面:
0040D45E |> /8D4C24 0B /lea ecx,dword ptr ss:[esp+0xB]
0040D462 |. |51 |push ecx
0040D463 |. |E8 38FFFFFF |call tyakata1.0040D3A0
0040D468 |. |83C4 04 |add esp,0x4 ;retn返回到这里
0040D46B |. |85C0 |test eax,eax
0040D46D |74 13 je short tyakata1.0040D482 ;false则跳转
0040D46F |. |8D5424 0B |lea edx,dword ptr ss:[esp+0xB]
0040D473 |. |56 |push esi ; tyakata1.0042CE5C
0040D474 |. |52 |push edx
0040D475 |. |E8 56FFFFFF |call tyakata1.0040D3D0
0040D47A |. |83C4 08 |add esp,0x8
0040D47D |. |83F8 01 |cmp eax,0x1
0040D480 |74 50 je short tyakata1.0040D4D2 ;true则跳出
0040D482 |> |8A4424 0B |mov al,byte ptr ss:[esp+0xB]
0040D486 |. |FEC0 |inc al
0040D488 |. |3C 5A |cmp al,0x5A
0040D48A |. |884424 0B |mov byte ptr ss:[esp+0xB],al
0040D48E |.^\7E CE \jle short tyakata1.0040D45E
有人说我把前面的cmp eax,0x5改了就可以了啊,因为返回为true啊。
所以这里就是最麻烦的地方,注意一下第二个跳转前面有一个cmp eax,0x1,这里又是一个判定。所以你只改一处肯定是没办法过检测的。
解决办法有二:
1、跟踪eax的变化,调整两个cmp的比较值。
2、nop掉第一个je,把第二个je改成jmp。
(不管怎么说这还是比较简单的)
二、序列号检测:
我建议最好在crack掉CD/DVD检测之后保存一个文件,因为序列号检测你很容易就跟丢或者跟错。
我们启动游戏后发现游戏会提示我们输入序列号……(智障CIRCUS)
然后我们要去找一下是哪一个函数(API)会让游戏提示我们要输入序列号的(也就是说,至少要找到显示对话框的那个函数)。
来想想有哪些函数可以显示对话框的?
MessageBox?SendMessage?
CIRCUS用的是SendMessageA来实现显示对话框的。
注意一下,“OK”的控件是灰色的,所以那一块代码里面肯定不是只有SendMessageA这个函数的:
00405370 . 8B4424 08 mov eax,dword ptr ss:[esp+0x8]
00405374 . 56 push esi ; tyakata1.00405370
00405375 . 2D 10010000 sub eax,0x110 ; Switch (cases 110..111)
0040537A . 57 push edi
0040537B . 0F84 93000000 je tyakata1.00405414
00405381 . 48 dec eax ; tyakata1.00405370
00405382 . 75 73 jnz short tyakata1.004053F7
00405384 . 8B4424 14 mov eax,dword ptr ss:[esp+0x14] ; tyakata1.00405370; Case 111 of switch 00405375
00405388 . 83F8 01 cmp eax,0x1
0040538B . 74 71 je short tyakata1.004053FE
0040538D . 83F8 02 cmp eax,0x2
00405390 . 74 6C je short tyakata1.004053FE
00405392 . 66:3D E803 cmp ax,0x3E8
00405396 . 75 5F jnz short tyakata1.004053F7
00405398 . 8B7424 0C mov esi,dword ptr ss:[esp+0xC]
0040539C . 8B3D 18224200 mov edi,dword ptr ds:[<&USER32.GetDlgIte>; user32.GetDlgItem
004053A2 . 68 74ECC600 push tyakata1.00C6EC74 ; /lParam = 0xC6EC74
004053A7 . 68 04010000 push 0x104 ; |wParam = 0x104
004053AC . 6A 0D push 0xD ; |Message = WM_GETTEXT
004053AE . 68 E8030000 push 0x3E8 ; |/ControlID = 3E8 (1000.)
004053B3 . 56 push esi ; ||hWnd = 00405370
004053B4 . FFD7 call edi ; |\GetDlgItem
004053B6 . 50 push eax ; |hWnd = 0x405370
004053B7 . FF15 28224200 call dword ptr ds:[<&USER32.SendMessageA>; \SendMessageA
004053BD . A1 480CCA00 mov eax,dword ptr ds:[0xCA0C48]
004053C2 . 50 push eax ; tyakata1.00405370
004053C3 . 68 74ECC600 push tyakata1.00C6EC74
004053C8 . E8 331A0100 call tyakata1.00416E00
004053CD . 83C4 08 add esp,0x8
004053D0 . 85C0 test eax,eax ; tyakata1.00405370
004053D2 7C 15 jl short tyakata1.004053E9
004053D4 . 6A 01 push 0x1
004053D6 . 6A 01 push 0x1
004053D8 . 56 push esi ; tyakata1.00405370
004053D9 . FFD7 call edi
004053DB . 50 push eax ; |hWnd = 00405370
004053DC . FF15 0C224200 call dword ptr ds:[<&USER32.EnableWindow>; \EnableWindow
004053E2 . 5F pop edi ; user32.778C62FA
004053E3 . 33C0 xor eax,eax ; tyakata1.00405370
004053E5 . 5E pop esi ; user32.778C62FA
004053E6 . C2 1000 retn 0x10
004053E9 > 6A 00 push 0x0
004053EB . 6A 01 push 0x1
004053ED . 56 push esi ; tyakata1.00405370
004053EE . FFD7 call edi
004053F0 . 50 push eax ; |hWnd = 00405370
004053F1 . FF15 0C224200 call dword ptr ds:[<&USER32.EnableWindow>; \EnableWindow
004053F7 > 5F pop edi ; user32.778C62FA; Default case of switch 00405375
004053F8 . 33C0 xor eax,eax ; tyakata1.00405370
004053FA . 5E pop esi ; user32.778C62FA
004053FB . C2 1000 retn 0x10
004053FE > 8B4C24 0C mov ecx,dword ptr ss:[esp+0xC]
00405402 . 50 push eax ; /Result = 405370 (4215664.)
00405403 . 51 push ecx ; |hWnd = 00E60D68
00405404 . FF15 20224200 call dword ptr ds:[<&USER32.EndDialog>] ; \EndDialog
0040540A . 5F pop edi ; user32.778C62FA
0040540B . B8 01000000 mov eax,0x1
00405410 . 5E pop esi ; user32.778C62FA
00405411 . C2 1000 retn 0x10
00405414 > 8B7424 0C mov esi,dword ptr ss:[esp+0xC] ; Case 110 of switch 00405375
00405418 . 8B3D 18224200 mov edi,dword ptr ds:[<&USER32.GetDlgIte>; user32.GetDlgItem
0040541E . 6A 00 push 0x0 ; /Enable = FALSE
00405420 . 6A 01 push 0x1 ; |/ControlID = 0x1
00405422 . 56 push esi ; ||hWnd = 00405370
00405423 . FFD7 call edi ; |\GetDlgItem
00405425 . 50 push eax ; |hWnd = 00405370
00405426 . FF15 0C224200 call dword ptr ds:[<&USER32.EnableWindow>; \EnableWindow
0040542C . 68 0017C500 push tyakata1.00C51700 ; /lParam = 0xC51700
00405431 . 68 04010000 push 0x104 ; |wParam = 0x104
00405436 . 6A 0C push 0xC ; |Message = WM_SETTEXT
00405438 . 68 E8030000 push 0x3E8 ; |/ControlID = 3E8 (1000.)
0040543D . 56 push esi ; ||hWnd = 00405370
0040543E . FFD7 call edi ; |\GetDlgItem
00405440 . 50 push eax ; |hWnd = 0x405370
00405441 . FF15 28224200 call dword ptr ds:[<&USER32.SendMessageA>; \SendMessageA
00405447 . 56 push esi ; tyakata1.00405370
00405448 . E8 A3FCFFFF call tyakata1.004050F0
0040544D . 83C4 04 add esp,0x4
00405450 . B8 01000000 mov eax,0x1
00405455 . 5F pop edi ; user32.778C62FA
00405456 . 5E pop esi ; user32.778C62FA
00405457 . C2 1000 retn 0x10
(retn 0x10是返回到user32.dll里面)
你在这里面发现了很多函数(的确非常多,还有不少是重复的),而且有各种跳转,很难去理清楚关系(说实话我还是在看IDA反编译的伪C代码才看懂的,具体的可以去看我上一篇blog)。
那么有没有办法去回避读取这个函数?还是有的。
想想这个函数的跳转源是哪里?好吧没有。
想想00405370从哪里开始调用的?说实话还是挺难找的……
CIRCUS的这个调用离SendMessageA不远,应该就在附近。
事实证明这样想是对的:
00405460 /$ A1 0021C500 mov eax,dword ptr ds:[0xC52100]
00405465 |. 8B0D DC1DC500 mov ecx,dword ptr ds:[0xC51DDC] ; tyakata1.00400000
0040546B |. 6A 00 push 0x0 ; /lParam = NULL
0040546D |. 68 70534000 push tyakata1.00405370 ; |DlgProc = tyakata1.00405370
00405472 |. 50 push eax ; |hOwner = NULL
00405473 |. 6A 6B push 0x6B ; |pTemplate = 0x6B
00405475 |. 51 push ecx ; |hInst = NULL
00405476 |. FF15 14224200 call dword ptr ds:[<&USER32.DialogBoxPar>; \DialogBoxParamA
0040547C |. 83F8 01 cmp eax,0x1
0040547F |. 75 0D jnz short tyakata1.0040548E
00405481 |. 6A 00 push 0x0
00405483 |. E8 58780000 call tyakata1.0040CCE0
00405488 |. 83C4 04 add esp,0x4
0040548B |. 33C0 xor eax,eax
0040548D |. C3 retn
0040548E |> 83C8 FF or eax,-0x1
00405491 \. C3 retn
00405476那里就会直接跳到00405370(注意不是跳转),然后这里去调用的一个DialogBoxParamA的API。这个API的用处是干嘛的自己去百度。
但是注意一下,这个调用不会跳到00405370上,会直接跳进user32.dll,很多人不理解为什么没有跳到00405370上。
这个时候你在user32里面跟一下,看一下行为,或许能找到答案。
注意一下下面两个retn,返回的都是同一个地址,所以我们直接让程序运行到retn(至于为什么不去管那个SendMessage的原因是因为点了“終了”后程序没有马上结束,会继续执行0040547C,因此不用担心程序会跑飞的问题)。
来看看retn返回到哪里了:
00414EA1 |> \8B15 480CCA00 mov edx,dword ptr ds:[0xCA0C48] ; Case 29 of switch 00414C67
00414EA7 |. 52 push edx
00414EA8 |. 68 74ECC600 push tyakata1.00C6EC74
00414EAD |. E8 4E1F0000 call tyakata1.00416E00
00414EB2 |. 83C4 08 add esp,0x8
00414EB5 |. 85C0 test eax,eax
00414EB7 |. 7D 57 jge short tyakata1.00414F10
00414EB9 |> E8 A205FFFF /call tyakata1.00405460
00414EBE |. 3BC6 |cmp eax,esi ;retn返回到这里
00414EC0 |. 74 21 |je short tyakata1.00414EE3
00414EC2 |. A1 480CCA00 |mov eax,dword ptr ds:[0xCA0C48]
00414EC7 |. 50 |push eax
00414EC8 |. 68 74ECC600 |push tyakata1.00C6EC74
00414ECD |. E8 2E1F0000 |call tyakata1.00416E00
00414ED2 |. 83C4 08 |add esp,0x8
00414ED5 |. 85C0 |test eax,eax
00414ED7 |.^ 7C E0 \jl short tyakata1.00414EB9
它返回到00414EBE,而上面的那个call就是跳到00405460去的。
从00414EB9到00414ED7是一个循环,也就是说每循环一次就会调用那个call提示我们输入序列号。
那么有没有办法不让它检测?或者说在检测之前就跳过这个循环?
注意我在00414EBE还多贴了一段,上面有一个明显的jge,还有一个test。
说明test返回的结果和jge有关。那么就很简单了,直接把jge改成jmp,不管test是什么值,到这里总会跳过这个循环检测。
好了,现在你可以开始放心游戏了。
PS:不是所有的DialogBoxParamA的调用都会跳到user32.dll上,有些是可以直接返回到SendMessageA的开头的,具体情况具体分析。这里我只是举了个例子。
PS2:关于为什么DialogBoxParamA会调用到user32.dll并且在SendMessageA的开头的指令下断点能够断的到,这个我理解成SendMessageA是一个子指令,user32.dll里面跟到最底的时候对代码进行分析可以看得出在一堆的跳转里面有SendMessage的参与了,并且在一个大跳转之后就是call。
PS3:为什么我用中文版系统运行,但是序列号认证界面没有乱码?原因在于user32.dll里面并没有用A而是用的W(也就是Unicode),具体情况请自己去查看。
PS4:破解这个只需要3分钟,写这个教程花了我两小时w
#1 - 2019-5-30 07:52
AyamiKaze (Sleeping)