某网游双开破解
分析一个没加壳的半免费网游的双开方式。
2007-02-24

破解过程

经过简单的观察,猜测执行桌面快捷方式后打开的是一个自动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
编译并重命名成网游的核心文件名,放于其目录下。正常步骤运行之。 选择任意一个服务器测试,记下那个服务器所对应的参数(此处为1rag21)。

等待IDA分析的过程中顺便查了import table,看一看有什么可疑的函数…… 首先会找FindWindow之类…没错! 再看下去,发现引入了 CreateMutexAFindWindowA 不管怎样,这两个不可不防。 改之! 找到FindWindowA函数的xrefs(嘻嘻,只有一处):


.....       call FindWindowA
.text:0064CFCF                 test    eax, eax
.text:0064CFD1                 jz      short loc_64CFDC
如果没有FindWindow到,就注册个窗体类,再建立一个窗口。 改成 xor eax,eax (只改动了一个字节的机器码,不能改长度)。

然后处理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填充…… 保存修改完的结果。

由于那个核心文件不是.exe为后缀的,为了绕过Updater还要依赖另一个程序:


.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动态调试。大可在很后面的地方按下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。完工!

由于Updater频繁更新,还需写个程序打打每次自动补丁。共须Patch四字节。

总结

这个程序仅仅利用了CreateMutex以及FindWindow来保证程序只运行一个实例。破解过程非常简单。