找到一个没加壳的半免费网游(网游的名字就不说了),分析其双开方式。
大概“某人们”早就对它动过手脚了,只是没看到有人公布。
经过几天的试用,至今没有发现这种破法有问题。(由于是几天以前破的,现在回忆起来可能会有点出入,还是希望读者以实际为准,灵活应变^_^)
本文仅仅作为研究用,不可作其他用途……
///////////////////////////////////

经过简单的观察,猜测执行桌面快捷方式后打开的是一个自动Update的程序,该程序每天更新其核心。
继续观察,发现Update程序完成后直接用CreateProcess之类的方法运行那个核心文件。核心文件通过分析命令行参数来指定登陆服务器(官服,不是私的)。

于是开始下手:

首先自己写一个程序 ShowCmdLine.asm :

.386
.model flat, stdcall
option casemap :none

include windows.inc
include user32.inc
include kernel32.inc
include masm32.inc

includelib user32.lib
includelib kernel32.lib
includelib masm32.lib
include macro.asm
.data
mytitleA db 'GetCmdLine()',0
.data?
  buffer  db 100 dup(?)
  
.CODE
START:
  
  invoke GetCommandLine
  invoke MessageBox,NULL,eax,offset mytitleA,MB_OK
  invoke ExitProcess,0
  
end START

编译它,重命名成网游的核心文件名,放到网游目录下。正常步骤运行之…………
Oh,Ja!事实证明了猜想。选择任意一个服务器测试,记下那个服务器所对应的参数(我玩的服务器参数是1rag21,这个字眼熟悉吧~应该有许多人知道这是什么网游了,我也不能多说,不知道的自己google一下吧)。

打开IDA,分析原来的核心(文件满大的~要有耐心)。在等待IDA分析的时候多看看import table,找找看有什么可疑的函数……
大多人首先会找FindWindow之类的……没错!
从IDA的Imports中看到

CreateMutexA 和 FindWindowA

不管怎样,这两个不可不防。
改之!
在FindWindowA上按下'X',得到FindWindowA函数的xrefs(嘻嘻,只有一处):

..........
.....       call FindWindowA
.text:0064CFCF                 test    eax, eax
.text:0064CFD1                 jz      short loc_64CFDC
.......

如果没有FindWindow到,就注册个窗体类,再建立一个窗口。
没话说,改成 xor eax,eax (只改动了一个字节的机器码,不能改长度)。

然后处理CreateMutexA:
同样按下'x',得到xrefs
这里的CreateMutexA的调用大概有5处左右。
每处的代码类似:

.text:006517F2                 call    ebx ; CreateMutexA
.text:006517F4                 mov     esi, eax
.text:006517F6                 test    esi, esi
.text:006517F8                 jnz     .....


jnz到的每个地方(只有两个实例,其他都是重复的)一开始都是GetLastError,如:

.text:00651843                 call    ds:GetLastError
.text:00651849                 cmp     eax, ERROR_ALREADY_EXISTS

这里有个技巧,把 cmp eax, ERROR_ALREADY_EXISTS 改成比如 cmp eax,00000001 之类的其他错误代码,建议直接修改机器码,否则要注意不能改变文件长度,或者应该用nop填充……

保存修改完的结果。

为了绕过Updater,迅速启动游戏,由于那个核心文件不是.exe为后缀的。写个程序来调用它:
.386
.model flat, stdcall
option casemap :none

include windows.inc
include user32.inc
include kernel32.inc
include masm32.inc

includelib user32.lib
includelib kernel32.lib
includelib masm32.lib
include macro.asm
.data
ErrorTitle db 'Error',0
ErrorMsg db '不能运行',0
.data?
  buffer  db 100 dup(?)
.CODE
START:
  invoke StdIn,offset buffer,100
  invoke lstrlen,offset buffer
  dec eax
  mov BYTE PTR buffer[eax],0
  dec eax
  mov BYTE PTR buffer[eax],0
  invoke WinExec,offset buffer,SW_SHOW
  cmp eax,31D
  jg @f
  invoke MessageBox,NULL,offset ErrorMsg,offset ErrorMsg,MB_OK
@@:  invoke ExitProcess,0
end START

link的时候加上 /subsystem:console。

通过这个程序间接调用游戏核心程序(其实应该说成是主程序),别忘了加上参数指定服务器(否则……)。

…………
………………
……………………
……………………………
…………………………………
…………………………………………
………………汗~~还是不能双开。

别着急,来动态调试它。
我用OllyDebug,主要因为它和Tc的快捷键基本相同,以前TC用了相当长的时间……
我用的计算机速度相当慢(P3-667,15G硬盘)。
有经验的无产阶级们不必等着OllyDbg分析完整个程序,在OD进度调缓慢移动的时候按下空格,阻止OD分析。
(虽然本菜鸟没有经验,但是由于计算机配置的限制,也只能加载完不等分析把它停了。)如果实在对动态加载显示的Call来Call去摸不着头脑,可以对照着刚才IDA的分析结果。但是相信只要稍微熟悉VC6的rtl就不会有大问题。

大可在很后面的地方按下F4,因为前面的障碍在静态分析的时候都已经扫除了!

跳跃着动态F4在其中一个可疑的地方停下(似乎应该是这里,记得不是很清楚了。不过,既然本菜鸟都可以一眼看出来,相信大家……)

.text:0064E1E9                 call    ds:CreateMutexA
.text:0064E1EF                 push    eax             ; hHandle
.text:0064E1F0                 call    ds:WaitForSingleObject
.text:0064E1F6                 test    eax, eax         ;!!!!!!!!!!!
.text:0064E1F8                 jz      short loc_64E208

大概是刚才分析CreateMutexA的时候漏了这里,把这里也补成 xor eax,eax
再运行一遍……Gut!成功了。
顺便针对玩家(不是为了研究的那种)说一下,该文件每天更新,如果用旧版本则无法进入游戏,写个程序打打补丁吧。

总共Patch了4个字节,应该算是改动比较少的,其他几组核心程序改法也类似。

总结:
这个程序利用了普遍应用的CreateMutex以及FindWindow来保证程序只运行一个实例。该破解过程属于简单类型。

!!!再次声明,本文仅为讨论技术,作者不承担任何责任。

/*
跋:
 打完字后没校对,用拼音打的,若有错别字自己估摸一下。
 大一下半个没计算机课的学期马上要开学了,计算机水平上个学期里一点也没有长进,还是停留在高中阶段。趁寒假没结束,独立crack一次做个纪念。
Auf Wiedersehen!
2007年2月下旬
*/