【文章标题】: rFactor 1.250 破解过程详述
【文章作者】: noword
【作者主页】: bbs.srfc.com.cn
【软件名称】: rFactor
【下载地址】: http://www.rfactor.net/index.php?page=downloads
【加壳方式】: ActiveMark
【保护方式】: ActiveMark + 阴险的暗桩
【使用工具】: FileMon, OllyICE
【操作平台】: Win XP
【作者声明】: 只是感兴趣,没有其他目的。失误之处敬请诸位大侠赐教!
--------------------------------------------------------------------------------
【简介】
rFactor是ISI最新的一款模拟赛车游戏,售价34.99欧元。游戏原始引擎最初用于ISI公司99年出版的Sports Car GT (SCGT)模拟赛车游戏,在经过了近8年的不断发展,现在rF采用的是经过ISI全面改进和升级的被称为isiMotor 2.0的开发环境,这是ISI积累了多年赛车游戏经验的技术结晶。rF是这款游戏引擎的首部作品,随后FIA官方游戏GT Legends、 GTR 2也相继采用了这款游戏引擎。相比较ISI过去的游戏引擎,rF增加了真实的动力学,更加真实的操作感,更加逼真的图像,让玩家体验身临其境的感觉。
以前,rFactor一直是一个来自波兰叫Passeli的组织或个人破解的,而在1.150出来之后却迟迟不见动静,倒是有两个假档出现在emule上,于是只好亲自出马,破而解之,造福于天下。
07年1月25日,1.250的补丁出来了,拿到手后,粗看之下同1.150的加密方式没什么不同,但是事后证明这只是表相……
【保护手法】
该游戏使用了ActiveMark(http://www.trymedia.com)的保护方案,可执行文件被其加壳,启动后如果是未付费用户,则跳出付费窗口,且只能进入有一定时间限制的demo模式。
用FileMonitor可发现,rFactor.exe会读写在C:\Documents and Settings\All Users\Application Data\Trymedia下的两个目录 data 和 licenses。
data目录中有4个文件:
{51AE6369-615F-A146-E860-D9D8FF2E73D5}
{F5E9419B-BF55-B4A2-21B9-EA3C4E7E854E}
{C405FC19-D234-E01E-D4FF-F24278204F43}
{439B1256-E959-D772-E973-7835AE2AAE1C}
每次运行后文件中的内容都会变化,应该是记录了游戏运行时间等信息,这样开发商就能限制用户的试玩时间了。
而licensens目录中是空的,程序会试图打开一个名为d27248b8604abd930ed681c3033da219.lcn的文件,密匙应该就在其中。
ps:关于ActiveMark,在http://tutorials.accessroot.com/有好几篇教程,图文并茂,非常详细。
【初步破解】
用OllyICE载入rFactor.exe,停在了入口00EA9003处,按Alt-M:
代码:00400000 00001000 rFactor
00401000 0023E000 rFactor .data
0063F000 000B4000 rFactor .bss
006F3000 003FA000 rFactor .text
00AED000 00009000 rFactor .rsrc
00AF6000 0016E000 rFactor .rdata 第二层
00C64000 00004000 rFactor .idata
00C68000 0000A000 rFactor .rsrc
00C72000 00231000 rFactor .data
00EA3000 00001000 rFactor .idata
00EA4000 00005000 rFactor .rsrc
00EA9000 00018000 rFactor .bss 第一层
00EC1000 00001000 rFactor .text
00EC2000 0000A000 rFactor .rdata
00ECC000 00001000 rFactor .idata
AcitveMark的壳分为两层,基本的流程是这样的:
第一层:检测Debug,释放数据,完成后进入第二层
第二层:验证keyfile,如通过则进入真正的OEP,否则跳出付费窗口
rFactor比较特殊,在第二层中不管是否通过keyfile验证,都会进入真正的OEP,然后会调用第二层中的函数,再次检测keyfile。
先要做的当然是让它乖乖的在Debug环境中运行起来:
1.找出第二层的OEP (00C407B3)和真正的OEP (00623A88),并在上面下硬件执行断点(可以在程序运行起来后Attach,找GetVerison)
2.有2处int3陷阱需要跳过,在ODBG的调试选项中忽略所有的异常就可以了。
3.第一层运行时会检查一些关键的WinApi是否被下了断点,不要激活中断。
4.第一层运行时会清硬件中断,所以必须钩上“事件”->“中断于新模块”,一直到成功到达第二层的OEP为止。原理就是ODBG每次中断下来后,会把中断地址重新写回到DR寄存器。
5.第一层会创建4个AntiDebug的线程,第二层会创建1个。应对方法很简单,在进入第二层后挂起那4个线程,进入真正的OEP后挂起剩下的一个。
如此这般之后,便能来到OEP,也就意味着可以DUMP了。但是,我并没有采用脱壳的破解方法,而是写了一个loader。为什么呢?
ActiveMark会随机的替换一些DLL中的函数,将其指向第二层的某个地址,第二层在转到真正的DLL函数之前或之后,会进行一轮甚至若干轮的检测。
例如:
代码:00636B03 |> \68 04010000 push 104 ; /BufSize = 104 (260.)
00636B08 |. BE 60B3AE00 mov esi, 00AEB360 ; |
00636B0D |. 56 push esi ; |PathBuffer => rFactor.00AEB360
00636B0E |. 53 push ebx ; |hModule
00636B0F |. 881D 64B4AE00 mov byte ptr [AEB464], bl ; |
00636B15 |. 90 nop ; \GetModuleFileNameA
00636B16 |. E8 E24E5400 call 00B7B9FD
00636B1B |. A1 68C9AE00 mov eax, dword ptr [AEC968]
00636B16的“call 00B7B9FD”,就是一处被替换的DLL函数调用,执行到这里后,在00636B1B下断点,然后“跟踪步入”:
代码:地址 线程 命令 ; 寄存器和注释
00B7BA02 主 jmp short 00B7BA07
00B7BA07 主 call 00C40794 ; ESP=0012FF0C
00C40794 主 push -1 ; ESP=0012FF08
00C40796 主 push eax ; ESP=0012FF04
00C40797 主 mov eax, dword ptr fs:[0] ; EAX=0012FFB0
00C4079D 主 push eax ; ESP=0012FF00
00C4079E 主 mov eax, dword ptr [esp+C] ; EAX=00B7BA0C
00C407A2 主 mov dword ptr fs:[0], esp
00C407A9 主 mov dword ptr [esp+C], ebp
00C407AD 主 lea ebp, dword ptr [esp+C] ; EBP=0012FF0C
00C407B1 主 push eax ; ESP=0012FEFC
00C407B2 主 retn ; ESP=0012FF00
00B7BA0C 主 jmp short 00B7BA10
00B7BA10 主 sub esp, 20 ; FL=0, ESP=0012FEE0
00B7BA13 主 jmp short 00B7BA17
00B7BA17 主 mov al, byte ptr [ebp+13] ; EAX=00B7BA00
00B7BA1A 主 push ebx ; ESP=0012FEDC
00B7BA1B 主 push esi ; ESP=0012FED8
00B7BA1C 主 jmp short 00B7BA20
00B7BA20 主 jmp 00B7AD3E
00B7AD3E 主 xor esi, esi ; FL=PZ, ESI=00000000
00B7AD40 主 jmp short 00B7AD44
00B7AD44 主 push edi ; ESP=0012FED4
00B7AD45 主 jmp short 00B7AD49
00B7AD49 主 push esi ; ESP=0012FED0
00B7AD4A 主 jmp 00B7B6B9
00B7B6B9 主 lea ecx, dword ptr [ebp-2C] ; ECX=0012FEE0
00B7B6BC 主 push 00B7A2E8 ; ESP=0012FECC
00B7B6C1 主 retn ; ESP=0012FED0
00B7A2E8 主 mov byte ptr [ebp-2C], al
00B7A2EB 主 push 00B7A8A4 ; ESP=0012FECC
00B7A2F0 主 retn ; ESP=0012FED0
00B7A8A4 主 jmp short 00B7A8A8
00B7A8A8 主 jmp 00B7BBDC
00B7BBDC 主 call 00B66F53 ; ESP=0012FECC
00B66F53 主 cmp byte ptr [esp+4], 0
00B66F58 主 push esi ; ESP=0012FEC8
00B66F59 主 mov esi, ecx ; ESI=0012FEE0
00B66F5B 主 je short 00B66F7F
00B66F7F 主 and dword ptr [esi+4], 0
00B66F83 主 and dword ptr [esi+8], 0
00B66F87 主 and dword ptr [esi+C], 0
00B66F8B 主 pop esi ; ESP=0012FECC, ESI=00000000
00B66F8C 主 retn 4 ; ESP=0012FED4
00B7BBE1 主 jmp short 00B7BBE5
00B7BBE5 主 push 00B7B05E ; ESP=0012FED0
00B7BBEA 主 retn ; ESP=0012FED4
00B7B05E 主 push dword ptr [ebp+10] ; ESP=0012FED0
00B7B061 主 jmp short 00B7B065
00B7B065 主 mov ebx, dword ptr [ebp+C] ; EBX=00AEB360
00B7B068 主 jmp 00B7A3CB
00B7A3CB 主 mov dword ptr [ebp-4], esi
00B7A3CE 主 push ebx ; ESP=0012FECC
00B7A3CF 主 jmp short 00B7A3D3
00B7A3D3 主 jmp dword ptr [B7A3DB]
00B7B804 主 push dword ptr [ebp+8] ; ESP=0012FEC8
00B7B807 主 call dword ptr [kernel32.GetModuleFileNameA] ; EAX=0000001B, ECX=7C93056D, EDX=098A0000, ESP=0012FED4
...
00C423DE 主 call dword ptr [ntdll.RtlAllocateHeap] ; FL=PZ, EAX=01C44698, ECX=7C9306EB, EDX=01220006, ESP=0012FE34
...
00C423DE 主 call dword ptr [ntdll.RtlAllocateHeap] ; FL=PZ, EAX=01C446D0, ECX=7C9306EB, EDX=01220005, ESP=0012FC6C
...
00C423DE 主 call dword ptr [ntdll.RtlAllocateHeap] ; FL=PZ, EAX=01C4A598, ECX=7C9306EB, EDX=01220004, ESP=0012FCA4
...
00C0D080 主 call dword ptr [kernel32.FindFirstFileA] ; FL=PZ, EAX=00152820, ECX=000000D9, EDX=0012FAC0, ESP=0012FD38
...
00C0D0E5 主 call dword ptr [kernel32.FindClose] ; FL=PS, EAX=00000001, ECX=7C80EE67, EDX=003B0000, ESP=0012FD38
...
00C0D080 主 call dword ptr [kernel32.FindFirstFileA]] ; FL=PZ, EAX=00152820, ECX=000000D9, EDX=0012FAC0, ESP=0012FD38
...
00C0D0E5 主 call dword ptr [kernel32.FindClose] ; FL=PS, EAX=00000001, ECX=7C80EE67, EDX=003C0000, ESP=0012FD38
...
00C0D080 主 call dword ptr [kernel32.FindFirstFileA]] ; FL=PZ, EAX=00152820, ECX=000000D9, EDX=0012FAC0, ESP=0012FD38
...
00C0D0E5 主 call dword ptr [kernel32.FindClose] ; FL=PS, EAX=00000001, ECX=7C80EE67, EDX=003D0000, ESP=0012FD38
...
00C422A4 主 call dword ptr [ntdll.RtlFreeHeap] ; FL=PZ, EAX=0278E201, ECX=7C93056D, EDX=01220003, ESP=0012FCE4
...
00C422A4 主 call dword ptr [ntdll.RtlFreeHeap] ; FL=PZ, EAX=01C4A501, ECX=7C93056D, EDX=01230004, ESP=0012FE80
...
00C422A4 主 call dword ptr [ntdll.RtlFreeHeap] ; FL=PZ, EAX=01C44601, ECX=7C93056D, EDX=01240005, ESP=0012FE80
...
00B7AF89 主 jmp 00B7B0EB
00B7B0EB 主 leave ; ESP=0012FF10, EBP=0012FF38
00B7B0EC 主 retn 0C ; ESP=0012FF20
断点位于 rFactor.00636B1B
00636B1B 主 mov eax, dword ptr [AEC968]
RUN 跟踪已关闭
可以看到,在执行完GetModuleFileNameA后,又执行了一堆垃圾代码。如果想在真正的OEP处dump,就要修复所有被替换的DLL函数调用。而且除了手工查找之外,我想不出有其他的方法。(accessroot的教程是在第二层进行dump的,这样就不用修复了,但是又多出来一个exe文件的校验问题。)
事实上,我花了半天时间,修复了所有被替换的DLL函数。但是,这会有另外一个问题。
ActiveMark提供了一套文件加密机制,用16进制编辑器打开一些游戏中的文件,会发现其中一些是{0x0,"TMSAMVOH"}开头的,这就是被加密的文件。读取这些文件时,会在第二层自动解密。因此,必须把与文件相关的一组API保留下来,分别是CreateFile,SetFilePointer,CloseHandle,ReadFile,WriteFile。
接下来,要跳过keyfile的验证。
在修复DLL函数时,发现一处特别的地方:
代码:0053BAD0 56 push esi
0053BAD1 8BF1 mov esi, ecx
0053BAD3 807E 70 00 cmp byte ptr [esi+70], 0
0053BAD7 . 75 12 jnz short 0053BAEB
0053BAD9 . 6A 00 push 0
0053BADB . 90 nop
0053BADC . E8 518F6300 call 00B74A32
0053BAE1 . 24 01 and al, 1
0053BAE3 . 83C4 04 add esp, 4
0053BAE6 . 04 01 add al, 1
0053BAE8 . 8846 70 mov byte ptr [esi+70], al
0053BAEB > 807E 70 02 cmp byte ptr [esi+70], 2
0053BAEF . 5E pop esi
0053BAF0 . 0F94C0 sete al
0053BAF3 . C3 retn
运行call 00B74A32时,filemon发现程序会试图读取d27248b8604abd930ed681c3033da219.lcn,且在0053BAF3处返回时,al为0。暴力修改成这样:
代码:
0053BAD0 33C0 xor eax, eax
0053BAD2 40 inc eax
0053BAD3 C3 retn
返回值为1后,不再弹出付费窗口,能够直接进入游戏。
【Loader编写】
我写的Loader不同于accessroot的教程,不是使用busy loop不断的检查某个关键点的值,符合后再写入patch数据;而是使用WaitForDebugEvent、ContinueDebugEvent,这样的好处是patch的时机更加精确。主要的循环体如下:
代码:do
{
WaitForDebugEvent (&DBEvent, INFINITE);
GetThreadContext (pi.hThread, &Regs);
ContinueStatus = DBG_CONTINUE;
switch (DBEvent.dwDebugEventCode)
{
case EXCEPTION_DEBUG_EVENT:
{
switch (DBEvent.u.Exception.ExceptionRecord.ExceptionCode)
{
case EXCEPTION_ACCESS_VIOLATION:
case EXCEPTION_FLT_DENORMAL_OPERAND:
case EXCEPTION_FLT_DIVIDE_BY_ZERO:
case EXCEPTION_FLT_INEXACT_RESULT:
case EXCEPTION_FLT_INVALID_OPERATION:
case EXCEPTION_FLT_OVERFLOW:
case EXCEPTION_FLT_STACK_CHECK:
case EXCEPTION_FLT_UNDERFLOW:
{
ContinueStatus = DBG_EXCEPTION_NOT_HANDLED;
break;
}
case EXCEPTION_SINGLE_STEP:
if (Regs.Eip == OEP1) //到达第二层的OEP
{
OEPStatus++;
Regs.Dr0 = 0;
Regs.Dr7 = 0x404;
SetThreadContext (pi.hThread, &Regs);
SuspendOtherThread (pi); //挂起除主线程之外的其他线程
ApplyPatch (pi.hProcess, patch); //写入补丁数据
}
else if (Regs.Eip == OEP2) //到达真正的OEP
{
OEPStatus++;
Regs.Dr1 = Regs.Dr7 = 0;
SetThreadContext (pi.hThread, &Regs);
SuspendOtherThread (pi); //挂起除主线程之外的其他线程
ContinueDebugEvent (DBEvent.dwProcessId,
DBEvent.dwThreadId, ContinueStatus);
STOP = TRUE;
}
else
ContinueStatus = DBG_EXCEPTION_NOT_HANDLED;
break;
case EXCEPTION_BREAKPOINT:
//是否在第一层或第二层
if ((Regs.Eip >= 0xEA9000 && Regs.Eip <= 0xEC1000)
|| (Regs.Eip >= 0xAED000 && Regs.Eip <= 0xC64000))
ContinueStatus = DBG_EXCEPTION_NOT_HANDLED;
//两处int3陷阱,必须将EAX设置为-1
if (Regs.Eip == 0xEAAD18 || Regs.Eip == 0xEB61CF)
{
Regs.Eax = -1;
SetThreadContext(pi.hThread, &Regs);
}
break;
}
break;
}
case LOAD_DLL_DEBUG_EVENT:
//每次载入新的DLL时,都在OEP处下一次硬件断点
if (OEPStatus == 0)
{
Regs.Dr0 = OEP1;
Regs.Dr1 = OEP2;
Regs.Dr7 = 0x505;
SetThreadContext (pi.hThread, &Regs);
}
else if (OEPStatus == 1)
{
Regs.Dr1 = OEP2;
Regs.Dr7 = 0x404;
SetThreadContext (pi.hThread, &Regs);
}
break;
case EXIT_PROCESS_DEBUG_EVENT:
STOP = TRUE;
ExitProcess (-1);
break;
}
if (!STOP)
{
ContinueDebugEvent (DBEvent.dwProcessId, DBEvent.dwThreadId,
ContinueStatus);
}
}
while (!STOP);
其中,ApplyPatch里做的就是WriteProcessMemory;SuspendOtherThread用来挂起除主线程之外的其他线程,代码如下:
代码:
void
SuspendOtherThread (PROCESS_INFORMATION pi)
{
THREADENTRY32 te32 = { 0 };
te32.dwSize = sizeof (THREADENTRY32);
HANDLE hThreadSnap = CreateToolhelp32Snapshot (TH32CS_SNAPTHREAD, 0);
if (Thread32First (hThreadSnap, &te32))
{
do
{
if (te32.th32OwnerProcessID == pi.dwProcessId
&& te32.th32ThreadID != pi.dwThreadId)
{
HANDLE hThread = OpenThread (THREAD_SUSPEND_RESUME, FALSE,
te32.th32ThreadID);
SuspendThread (hThread);
}
}
while (Thread32Next (hThreadSnap, &te32));
}
CloseHandle (hThreadSnap);
}
【清除暗桩】
以上的破解过程和1.150的一模一样,由于有之前的经验,loader很快就出来了。把loader发给几个朋友草草测试过后,就发布在了srfc的论坛上。
可是,很快就有人报告说,进入赛车画面后,会被弹回到主菜单,且弹回的时间不定,从几秒到若干分钟都有。可以肯定,这是游戏开发者留下的一个暗桩。
寻找暗桩无疑是非常头痛的一件事情,因为整个程序处于正常运行状态,没有出错对话框,很难找到一个突破口。
一开始,我使用自上而下的方法,企图在源头解决问题。我猜测,程序执行到第二层,验证keyfile失败之后,会在某处做一个标记,之后在游戏中再验证此标记,如不符合,就偷偷的使坏。这是一个非常符合逻辑的想法,于是接下来的两天,我就在繁复的算法和缭乱的花指令中彻底迷失了方向。
两天之后,山穷水尽,只好另寻出路。既然自上而下不行,那就只好自下而上了——直接找出致使游戏弹回主菜单的代码。
没想到,这要容易的多。进入赛车画面后,在ODBG中暂停,层层retn,很快就摸清了游戏的流程。
游戏的框架是这样的:
代码:004860F0 |> /B9 A4309800 /mov ecx, 009830A4
004860F5 |. |E8 56AEFBFF |call 00440F50 ; loading
004860FA |. |85C0 |test eax, eax
004860FC |. |75 36 |jnz short 00486134
004860FE |. |8BFF |mov edi, edi
00486100 |> |B9 A4309800 |/mov ecx, 009830A4
00486105 |. |E8 F6AFFBFF ||call 00441100
0048610A |. |85C0 ||test eax, eax
0048610C |. |B9 A4309800 ||mov ecx, 009830A4
00486111 |. |75 26 ||jnz short 00486139
00486113 |. |E8 08B1FBFF ||call 00441220 ; 进入赛车画面
00486118 |. |85C0 ||test eax, eax
0048611A |.^|74 E4 |\je short 00486100
0048611C |. |B9 A4309800 |mov ecx, 009830A4
00486121 |. |E8 CAB7FBFF |call 004418F0
00486126 |. |B9 A4309800 |mov ecx, 009830A4
0048612B |. |E8 30ADFBFF |call 00440E60 ; 主菜单
00486130 |. |85C0 |test eax, eax
00486132 |.^\74 BC \je short 004860F0
进入赛车画面后:
代码:00441220 /$ 8379 04 05 cmp dword ptr [ecx+4], 5
00441224 |. 74 07 je short 0044122D
00441226 |. 6A 05 push 5
00441228 |. E8 53F7FFFF call 00440980
0044122D |> 56 push esi
0044122E |. 8BFF mov edi, edi
00441230 |> B9 1465AB00 /mov ecx, 00AB6514
00441235 |. E8 46F91100 |call 00560B80
0044123A |. 833D 2065AB00 00 |cmp dword ptr [AB6520], 0
00441241 |. 74 14 |je short 00441257
00441243 |. 6A 61 |push 61
00441245 |. 68 A4816500 |push 006581A4 ; ASCII "%c"
0044124A |. 68 1465AB00 |push 00AB6514
0044124F |. E8 FCF91100 |call 00560C50
00441254 |. 83C4 0C |add esp, 0C
00441257 |> B9 5859A900 |mov ecx, 00A95958
0044125C |. E8 9FB60D00 |call 0051C900
00441261 |. 833D 2065AB00 00 |cmp dword ptr [AB6520], 0
00441268 |. 74 14 |je short 0044127E
0044126A |. 6A 62 |push 62
0044126C |. 68 A4816500 |push 006581A4 ; ASCII "%c"
.
.
.
00441271 |. 68 1465AB00 |push 00AB6514
00441276 |. E8 D5F91100 |call 00560C50
0044127B |. 83C4 0C |add esp, 0C
004417E3 |. 833D 2065AB00 00 |cmp dword ptr [AB6520], 0
004417EA |. 74 14 |je short 00441800
004417EC |. 6A 45 |push 45
004417EE |. 68 A4816500 |push 006581A4 ; ASCII "%c"
004417F3 |. 68 1465AB00 |push 00AB6514
004417F8 |. E8 53F41100 |call 00560C50
004417FD |. 83C4 0C |add esp, 0C
00441800 |> B9 D8309800 |mov ecx, 009830D8
00441805 |. E8 D6390000 |call 004451E0
0044180A |. 833D 2065AB00 00 |cmp dword ptr [AB6520], 0
00441811 |. 74 14 |je short 00441827
00441813 |. 6A 46 |push 46
00441815 |. 68 A4816500 |push 006581A4 ; ASCII "%c"
0044181A |. 68 1465AB00 |push 00AB6514
0044181F |. E8 2CF41100 |call 00560C50
00441824 |. 83C4 0C |add esp, 0C
00441827 |> 83FE 05 |cmp esi, 5 ; Switch (cases 4..6)
0044182A |. /75 7F |jnz short 004418AB ; 正常退出点
0044182C |. |833D 8C309800 01 |cmp dword ptr [98308C], 1 ; Case 5 of switch 00441827
00441833 |.^|0F85 F7F9FFFF |jnz 00441230 ; 继续循环
00441839 |. |B9 7050A900 |mov ecx, 00A95070
0044183E |. |E8 7D460D00 |call 00515EC0 ; 生成随机数
00441843 |. |A9 FF0F0000 |test eax, 0FFF
00441848 |.^|0F85 E2F9FFFF \jnz 00441230 ; 继续循环
.
.
.
0044183E处的“call 00515EC0”,是用GetTickCount来生成随机数,当返回的EAX的末尾24位为0时,就被强制弹回到主菜单。
在执行这个call前,会先“cmp dword ptr [98308C], 1”。问题就出在[98308C]。
在代码段搜索98308C,找到这里:
代码:004410B0 |. E8 6B721000 call 00548320
004410B5 |. B9 7858A900 mov ecx, 00A95878
004410BA A3 8C309800 mov dword ptr [98308C], eax
进入548320:
代码:
00548320 /$ 51 push ecx
00548321 |. 833D 1465AB00 02 cmp dword ptr [AB6514], 2
00548328 |. 56 push esi
00548329 |. 8BF1 mov esi, ecx
0054832B |. 7C 26 jl short 00548353
0054832D |. 68 EA020000 push 2EA
00548332 |. 68 5C7D6A00 push 006A7D5C ; ASCII "..\Source\sound.cpp"
00548337 |. B9 1465AB00 mov ecx, 00AB6514
0054833C |. E8 AF890100 call 00560CF0
00548341 |. 68 FC7D6A00 push 006A7DFC ; ASCII "Entered Sound::Init()"
00548346 |. 68 1465AB00 push 00AB6514
0054834B |. E8 D0880100 call 00560C20
00548350 |. 83C4 08 add esp, 8
00548353 |> B9 D446AB00 mov ecx, 00AB46D4
00548358 |. E8 33FEFFFF call 00548190
0054835D |. B9 20A17000 mov ecx, 0070A120
00548362 |. E8 9982EDFF call 00420600
00548367 |. 8BCE mov ecx, esi
00548369 |. E8 A2F2FFFF call 00547610
0054836E |. 8D4C24 04 lea ecx, dword ptr [esp+4]
00548372 |. E8 59F10A00 call 005F74D0
00548377 |. 6A 00 push 0
00548379 |. 6A 00 push 0
0054837B |. 6A 00 push 0
0054837D |. 68 E47D6A00 push 006A7DE4 ; ASCII "rfactor.*.loader.exe"
00548382 |. 8D4C24 14 lea ecx, dword ptr [esp+14]
00548386 |. E8 85ED0A00 call 005F7110
0054838B |. 8D4C24 04 lea ecx, dword ptr [esp+4]
0054838F |. E8 2CEA0A00 call 005F6DC0
00548394 |. 85C0 test eax, eax
00548396 |. 5E pop esi
00548397 |. 8D0C24 lea ecx, dword ptr [esp]
0054839A |. 7E 0C jle short 005483A8
0054839C |. E8 8FF10A00 call 005F7530
005483A1 |. B8 01000000 mov eax, 1
005483A6 |. 59 pop ecx
005483A7 |. C3 retn
005483A8 |> E8 83F10A00 call 005F7530
005483AD |. 33C0 xor eax, eax
005483AF |. 59 pop ecx
005483B0 \. C3 retn
这应该是一个初始化声音的函数,但是在最后却偷偷的检查游戏目录下,有没有“rfactor.*.loader.exe”。 我以前发布的1.150的loader的文件名是rFactor.1.150.loader.exe,看来这个暗桩完全是游戏开发者针对我来的。
把004410BA处的“mov dword ptr [98308C], eax”nop掉,就能在赛车画面里想呆多就呆多久了。
成功清除暗桩,发布新loader。
【再战暗桩】
约一个星期后,大家发现1.250的车辆非常容易损坏,一开始还以为是新补丁对损坏的判定更加严格,但是渐渐的发现不对劲。这种损坏是突发的、毫无预兆的,而且只在正赛中才会发生。在练习模式下跑十几圈都没问题,但是在比赛模式中4,5圈车就暴了。看来这又是一处暗桩。
测试下来,基本一场比赛,在赛程的90%左右,车辆会突然损坏,共分4种随机的损坏:暴右后胎,暴刹车,暴最高档和暴缸。
面对这样的暗桩,我全无头绪。茫然中,又走起了以前的老路,试图分析keyfile的验证过程。若干天后,无果。
如果一条路异常艰难且走不到头,很可能是一开始就选错了路。穷途末路之际,我尝试从其他角度来解决问题。
还记得在解决前一个暗桩时,那个生成随机数的“call 00515EC0”吗?既然损坏的的种类是随机的,那么应该就会用到这个函数。
进入比赛后,在00515ec0处下断点,马上断了下来,返回到这里:
代码:00515F60 /$ 51 push ecx
00515F61 |. 56 push esi
00515F62 |. 57 push edi
00515F63 |. 8BF1 mov esi, ecx
00515F65 |. E8 56FFFFFF call 00515EC0 ; 生成随机数
00515F6A |. 33D2 xor edx, edx
00515F6C |. 33C9 xor ecx, ecx
00515F6E |. 8BFF mov edi, edi
00515F70 |> 8BF8 mov edi, eax
00515F72 |. D3FF sar edi, cl
00515F74 |. 83C1 01 add ecx, 1
00515F77 |. 83E7 01 and edi, 1
00515F7A |. 03D7 add edx, edi
00515F7C |. 83F9 20 cmp ecx, 20
00515F7F |.^ 7C EF jl short 00515F70
00515F81 |. 8B06 mov eax, dword ptr [esi]
00515F83 |. 8BC8 mov ecx, eax
00515F85 |. 81E1 FF000000 and ecx, 0FF
00515F8B |. D9848E 04040000 fld dword ptr [esi+ecx*4+404]
00515F92 |. 83C0 01 add eax, 1
00515F95 |. D80D 30F86300 fmul dword ptr [63F830]
00515F9B |. 83C2 F0 add edx, -10
00515F9E |. 895424 08 mov dword ptr [esp+8], edx
00515FA2 |. 5F pop edi
00515FA3 |. DA4424 04 fiadd dword ptr [esp+4]
00515FA7 |. 8906 mov dword ptr [esi], eax
00515FA9 |. 5E pop esi
00515FAA |. D80D 24B26900 fmul dword ptr [69B224]
00515FB0 |. 59 pop ecx
00515FB1 \. C3 retn
继续返回,来到了一个庞大的函数中
代码:.
.
.
0046A4F1 |. /0F8A AA020000 jpe 0046A7A1 ; 结束
0046A4F7 |. |807F 2D 00 cmp byte ptr [edi+2D], 0 ; edi+2d = 009853b5
0046A4FB |. |0F84 A0020000 je 0046A7A1 ; 结束
0046A501 |. |B9 7050A900 mov ecx, 00A95070
0046A506 |. |E8 55BA0A00 call 00515F60 ; 随机数
0046A50B |. |D95424 28 fst dword ptr [esp+28] ; 返回到这里
0046A50F |. |D81D 14E36500 fcomp dword ptr [65E314]
0046A515 |. |DFE0 fstsw ax
0046A517 |. |F6C4 05 test ah, 5
0046A51A |. |0F8A 81020000 jpe 0046A7A1
0046A520 |. |6A 00 push 0
0046A522 |. |B9 6853AB00 mov ecx, 00AB5368 ; ASCII "D k"
0046A527 |. |E8 B4980E00 call 00553DE0
0046A52C |. |8BF0 mov esi, eax
0046A52E |. |69F6 485F0000 imul esi, esi, 5F48
0046A534 |. |81C6 001C7100 add esi, 00711C00
0046A53A |. |83BE B41F0000 FF cmp dword ptr [esi+1FB4], -1
0046A541 |. |0F84 5A020000 je 0046A7A1
0046A547 |. |DD05 70A9AB00 fld qword ptr [ABA970]
0046A54D |. |8D4E 04 lea ecx, dword ptr [esi+4]
0046A550 |. |D82D 2454AB00 fsubr dword ptr [AB5424]
0046A556 |. |D80D 10E36500 fmul dword ptr [65E310]
0046A55C |. |D95C24 24 fstp dword ptr [esp+24]
0046A560 |. |D905 6C9E7000 fld dword ptr [709E6C]
0046A566 |. |D95C24 18 fstp dword ptr [esp+18]
0046A56A |. |E8 61F81000 call 00579DD0
0046A56F |. |D87424 18 fdiv dword ptr [esp+18]
0046A573 |. |DB05 1854AB00 fild dword ptr [AB5418]
0046A579 |. |DB86 F83C0000 fild dword ptr [esi+3CF8]
0046A57F |. |DEC2 faddp st(2), st
0046A581 |. |D9C0 fld st
0046A583 |. |DEE2 fsubrp st(2), st
0046A585 |. |D9C9 fxch st(1)
0046A587 |. |D80D 34026400 fmul dword ptr [640234]
0046A58D |. |D905 AC2C6500 fld dword ptr [652CAC]
0046A593 |. |D85C24 28 fcomp dword ptr [esp+28]
0046A597 |. |DFE0 fstsw ax
0046A599 |. |F6C4 41 test ah, 41
0046A59C |. |75 07 jnz short 0046A5A5
0046A59E |. |8305 7050A900 01 add dword ptr [A95070], 1
0046A5A5 |> |D9EE fldz
0046A5A7 |. |D94424 24 fld dword ptr [esp+24]
0046A5AB |. |D8D1 fcom st(1)
0046A5AD |. |DFE0 fstsw ax
0046A5AF |. |F6C4 41 test ah, 41
0046A5B2 |. |0F85 18010000 jnz 0046A6D0
0046A5B8 |. |A1 7050A900 mov eax, dword ptr [A95070]
0046A5BD |. |8BD0 mov edx, eax
0046A5BF |. |81E2 FF000000 and edx, 0FF
0046A5C5 |. |D90495 7450A900 fld dword ptr [edx*4+A95074]
0046A5CC |. |83C0 01 add eax, 1
0046A5CF |. |DED9 fcompp
0046A5D1 |. |A3 7050A900 mov dword ptr [A95070], eax
0046A5D6 |. |DFE0 fstsw ax
0046A5D8 |. |F6C4 41 test ah, 41
0046A5DB |. |0F85 F1000000 jnz 0046A6D2
0046A5E1 |. |DDD9 fstp st(1)
0046A5E3 |. |DDD8 fstp st
0046A5E5 |> |8B87 241E0000 mov eax, dword ptr [edi+1E24]
0046A5EB |. |DD40 74 fld qword ptr [eax+74]
0046A5EE |. |83C0 64 add eax, 64
0046A5F1 |. |DD40 08 fld qword ptr [eax+8]
0046A5F4 |. |DD00 fld qword ptr [eax]
0046A5F6 |. |DCC8 fmul st, st
0046A5F8 |. |D9C1 fld st(1)
0046A5FA |. |DECA fmulp st(2), st
0046A5FC |. |DEC1 faddp st(1), st
0046A5FE |. |D9C1 fld st(1)
0046A600 |. |DECA fmulp st(2), st
0046A602 |. |DEC1 faddp st(1), st
0046A604 |. |DC1D 08E36500 fcomp qword ptr [65E308]
0046A60A |. |DFE0 fstsw ax
0046A60C |. |F6C4 41 test ah, 41
0046A60F |. |0F85 8A010000 jnz 0046A79F
0046A615 |. |80BF 9D000000 00 cmp byte ptr [edi+9D], 0
0046A61C |. |0F85 7D010000 jnz 0046A79F
0046A622 |. |80BF 9E000000 00 cmp byte ptr [edi+9E], 0
0046A629 |. |0F85 70010000 jnz 0046A79F
0046A62F |. |DD05 68A9AB00 fld qword ptr [ABA968]
0046A635 |. |D81D 48546400 fcomp dword ptr [645448]
0046A63B |. |DFE0 fstsw ax
0046A63D |. |F6C4 05 test ah, 5
0046A640 |. |0F8A 59010000 jpe 0046A79F
0046A646 |. |391D B0309800 cmp dword ptr [9830B0], ebx
0046A64C |. |0F85 4D010000 jnz 0046A79F
0046A652 |. |833D B4309800 05 cmp dword ptr [9830B4], 5
0046A659 |. |0F85 40010000 jnz 0046A79F
0046A65F |. |D80D 6C9E7000 fmul dword ptr [709E6C]
0046A665 |. |D81D 04E36500 fcomp dword ptr [65E304]
0046A66B |. |DFE0 fstsw ax
0046A66D |. |F6C4 41 test ah, 41
0046A670 |. |0F85 2B010000 jnz 0046A7A1
0046A676 |. |D905 00E36500 fld dword ptr [65E300]
0046A67C |. |D81D 2854AB00 fcomp dword ptr [AB5428]
0046A682 |. |DFE0 fstsw ax
0046A684 |. |F6C4 05 test ah, 5
0046A687 |. |0F8A 14010000 jpe 0046A7A1
0046A68D |. |D905 94FD6300 fld dword ptr [63FD94]
0046A693 |. |D85F 28 fcomp dword ptr [edi+28]
0046A696 |. |DFE0 fstsw ax
0046A698 |. |F6C4 41 test ah, 41
0046A69B |. |0F85 00010000 jnz 0046A7A1
0046A6A1 |. |807F 2C 00 cmp byte ptr [edi+2C], 0
0046A6A5 |. |0F85 F6000000 jnz 0046A7A1
0046A6AB |. |C647 2C 01 mov byte ptr [edi+2C], 1
0046A6AF |. |E8 0E241B00 call 0061CAC2
0046A6B4 |. |25 03000080 and eax, 80000003
0046A6B9 |. |79 05 jns short 0046A6C0
0046A6BB |. |48 dec eax
0046A6BC |. |83C8 FC or eax, FFFFFFFC
0046A6BF |. |40 inc eax
0046A6C0 |> |83F8 03 cmp eax, 3 ; Switch (cases 0..3)
0046A6C3 |. |0F87 D8000000 ja 0046A7A1
0046A6C9 |. |FF2485 BCA74600 jmp dword ptr [eax*4+46A7BC]
0046A6D0 |> |DDD8 fstp st
0046A6D2 |> |D8D9 fcomp st(1)
0046A6D4 |. |DFE0 fstsw ax
0046A6D6 |. |F6C4 05 test ah, 5
0046A6D9 |. |0F8A BE000000 jpe 0046A79D
0046A6DF |. |A1 7050A900 mov eax, dword ptr [A95070]
0046A6E4 |. |8BC8 mov ecx, eax
0046A6E6 |. |81E1 FF000000 and ecx, 0FF
0046A6EC |. |D9048D 7450A900 fld dword ptr [ecx*4+A95074]
0046A6F3 |. |83C0 01 add eax, 1
0046A6F6 |. |DED9 fcompp
0046A6F8 |. |A3 7050A900 mov dword ptr [A95070], eax
0046A6FD |. |DFE0 fstsw ax
0046A6FF |. |F6C4 41 test ah, 41
0046A702 |. |0F85 97000000 jnz 0046A79F
0046A708 |.^|E9 D8FEFFFF jmp 0046A5E5
0046A70D |> |DD05 F8E26500 fld qword ptr [65E2F8] ; Case 0 of switch 0046A6C0
0046A713 |. |DD9F C4290000 fstp qword ptr [edi+29C4]
0046A719 |. |E9 83000000 jmp 0046A7A1
0046A71E |> |DD87 40100000 fld qword ptr [edi+1040] ; Case 1 of switch 0046A6C0
0046A724 |. |DC0D F0E26500 fmul qword ptr [65E2F0]
0046A72A |. |DD9F 28100000 fstp qword ptr [edi+1028]
0046A730 |. |DD87 38100000 fld qword ptr [edi+1038]
0046A736 |. |D9E8 fld1
0046A738 |. |D8D1 fcom st(1)
0046A73A |. |DFE0 fstsw ax
0046A73C |. |DDD9 fstp st(1)
0046A73E |. |F6C4 41 test ah, 41
0046A741 |. |74 08 je short 0046A74B
0046A743 |. |DDD8 fstp st
0046A745 |. |DD87 38100000 fld qword ptr [edi+1038]
0046A74B |> |DD9F 38100000 fstp qword ptr [edi+1038]
0046A751 |. |EB 4E jmp short 0046A7A1
0046A753 |> |DD05 E8E26500 fld qword ptr [65E2E8] ; Case 2 of switch 0046A6C0
0046A759 |. |DD9F 9C1A0000 fstp qword ptr [edi+1A9C]
0046A75F |. |DD87 AC1A0000 fld qword ptr [edi+1AAC]
0046A765 |. |D9E8 fld1
0046A767 |. |D8D1 fcom st(1)
0046A769 |. |DFE0 fstsw ax
0046A76B |. |DDD9 fstp st(1)
0046A76D |. |F6C4 41 test ah, 41
0046A770 |. |74 08 je short 0046A77A
0046A772 |. |DDD8 fstp st
0046A774 |. |DD87 AC1A0000 fld qword ptr [edi+1AAC]
0046A77A |> |DD9F AC1A0000 fstp qword ptr [edi+1AAC]
0046A780 |. |EB 1F jmp short 0046A7A1
0046A782 |> |8B87 542B0000 mov eax, dword ptr [edi+2B54] ; Case 3 of switch 0046A6C0
0046A788 |. |83C0 FF add eax, -1 ; 暴最高档
0046A78B |. |83F8 03 cmp eax, 3
0046A78E |. |7D 05 jge short 0046A795
0046A790 |. |B8 03000000 mov eax, 3
0046A795 |> |8987 542B0000 mov dword ptr [edi+2B54], eax
0046A79B |. |EB 04 jmp short 0046A7A1
0046A79D |> |DDD8 fstp st
0046A79F |> |DDD8 fstp st
0046A7A1 |> \8B4C24 54 mov ecx, dword ptr [esp+54] ; Default case of switch 0046A6C0
0046A7A5 |. 81F9 00020000 cmp ecx, 200
0046A7AB |. 74 05 je short 0046A7B2
0046A7AD |. E8 2EBF1800 call 005F66E0
0046A7B2 |> 5F pop edi
0046A7B3 |. 5E pop esi
0046A7B4 |. 5B pop ebx
0046A7B5 |. 8BE5 mov esp, ebp
0046A7B7 |. 5D pop ebp
0046A7B8 \. C2 0800 retn 8
那个Switch (cases 0..3)很有意思,因为损坏的种类正好也是4种。
取消00515ec0处的断点,在case 0到3处分别下断点,继续比赛。跑了几圈后,突然中断在了case 3处。继续运行,果然赛车的最高档已经没了。
显然,问题出在0046A4F7的“cmp byte ptr [edi+2D], 0”,edi+2d的地址是009853b5,下写入断点,再次进入比赛,断在了00468235:
代码:
00468216 |. A1 F8949B00 mov eax, dword ptr [9B94F8]
0046821B |. 81EC F4000000 sub esp, 0F4
00468221 |. 3B05 28939B00 cmp eax, dword ptr [9B9328]
00468227 |. 53 push ebx
00468228 |. 56 push esi
00468229 |. 57 push edi
0046822A |. 8BD9 mov ebx, ecx
0046822C |. 75 07 jnz short 00468235
0046822E |. 3D E0322900 cmp eax, 2932E0 ; UNICODE "anama"
00468233 |. 73 04 jnb short 00468239
00468235 |. C643 2D 01 mov byte ptr [ebx+2D], 1
程序比较[9B94F8]和[9B9328]的值,如不同,则把[009853b5]置1。
重新启动程序,发现009B94F8在这里写入:
此时,写patch,把[9B94F8]和[9B9328]置为相同就可以了。代码:00512287 . E8 14650E00 call 005F87A0
0051228C . 8BCE mov ecx, esi
0051228E 8986 30010000 mov dword ptr [esi+130], eax ; esi+130=009B94F8
后来我发现,如果程序执行过0053BADC处的“call 00B74A32”——那个验证keyfile的函数,那么这两处的值就是相同的。看来,对方也研究过我的1.150版的loader。
【尾声】
发布了新版loader,谢天谢地,这次再也没有什么暗桩了,不然真的要崩溃了。
--------------------------------------------------------------------------------
【版权声明】: 本文原创于看雪技术论坛, 转载请注明作者并保持文章的完整, 谢谢!
2007年02月15日 22:40:57