【文章标题】: 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在这里写入:
代码:

  00512287   .  E8 14650E00        call    005F87A0
  0051228C   .  8BCE               mov     ecx, esi
  0051228E      8986 30010000      mov     dword ptr [esi+130], eax         ;  esi+130=009B94F8

  此时,写patch,把[9B94F8]和[9B9328]置为相同就可以了。
  
  后来我发现,如果程序执行过0053BADC处的“call    00B74A32”——那个验证keyfile的函数,那么这两处的值就是相同的。看来,对方也研究过我的1.150版的loader。
  
  【尾声】
  发布了新版loader,谢天谢地,这次再也没有什么暗桩了,不然真的要崩溃了。

  
--------------------------------------------------------------------------------
【版权声明】: 本文原创于看雪技术论坛, 转载请注明作者并保持文章的完整, 谢谢!

                                                       2007年02月15日 22:40:57