关于游戏外挂检测和反检测(真正的防封技术)
在网上找到篇关于游戏外挂检测和反检测的文章拿来跟断点的朋友分享。详细文章见附件,这里写些简介。
一:内存探测法
服务器发送个Paket检测游戏内存,然后返回服务器。这对游戏公开的挂威胁大。
反侦测基本思想是拦截Peket,返回伪装Peket。
二:DLL扫描
游戏反外挂系统(Module32First/Module32Next)扫描游戏中的DLL,返回。
反侦测思想是DLL隐藏。
三:进程扫描
游戏反外挂系统(Process32First/Process32Next)扫描用户进程,返回。
反侦测思想也是进程隐藏(如将外挂进程注入到游戏进程)
四:窗口扫描
游戏反外挂系统(EnumWindows)扫描进程窗口,返回。这主要针对有GUI界面的外挂(外挂都有)
反侦测思想是随机产生窗口类名和窗口名。(现在很多外挂都能做到这点)
暴雪和黑客的战争(游戏外挂与反外挂)BZ加亮
[ sell=5][/sell][ post]如前一篇文章所说,D2X中hacks的发展大约可以分为三个阶段,即前1.10的发展成熟期,1.10的过渡期以及1.11的衰落期。
一直到1.09d(1.10前的最后一个版本)为止,D2X中几乎没有作弊检测机制,这一时期是hacker们最幸福的时期。说没有是因为它没有专门的检测代码,而说几乎没有是因为它有些机制还是可以用来做作弊检测用途的。
一处是它的自动升级机制。在战网上玩过的玩家都知道,每次连到战网的时候,会有一个对话框提示正在检查游戏版本,如果用户本机和服务器端额版本不一致的话,自动进行升级。Diablo的自动升级功能在游戏业界可能是首创,这大大降低了游戏的上手难度。我接触过不少外国玩家,跟国内玩家不同的是,他们中很多人都是一些15岁不到的小孩,让他们自己从网上下载补丁包升级几乎是不可能的事。自动升级的过程如下:
1,玩家连到战网;
2,服务器端发送一个专门用于版本检查的DLL到客户端;
3,客户端在本机保存该DLL;
4,客户端调用LoadLibrary加载DLL
5,客户端调用该DLL导出的一个函数。该函数通过计算几个重要的客户端游戏文件的校验判断版本是否匹配,不匹配则做自动升级。
6,客户端版本检测完毕,调用FreeLibrary卸载DLL并删除文件。
在这个过程中,由于版本检查DLL保存在服务器端,因此显然它可能会被随时修改,增加一些其他功能,如作弊检测。从版本检测相关代码(见文末)调用的Win32 API,LoadLibraryA/GetProcAddress/FreeLibrary/DeleteFileA,可以大致看出这一过程。
另外一处不为人知的机制是,在玩家连上战网后,服务器端有时(不是一定会发,而且发送时机也不确定)会发送一个DLL (Extrawork.dll)到客户端运行,然后把结果返回给服务器端,其工作原理和版本检测机制的工作原理非常类似。显然这个机制也可以用于作弊检测。不过根据很多hacker观测的结果,extrawork.dll一般用于收集玩家的系统配置信息,包括CPU主频、内存容量、操作系统版本等。
虽然这两点机制都可能用于作弊检测,但在1.10以前的时间里,没有迹象表明暴雪利用了这点,因此在这一时期出现的hacks也都没有相应的反检测措施。
版本检测的相关代码:
XXXX45A3 lea ecx, [esp+124h]
XXXX45AA push ecx ; IX86ver0.dll
XXXX45AB call ds:LoadLibraryA
XXXX45B1 mov ebp, eax
XXXX45B3 test ebp, ebp
XXXX45B5 jz loc_6FF046F1
XXXX45BB push offset aCheckrevision ; "CheckRevision"
XXXX45C0 push ebp ; hModule
XXXX45C1 call ds:GetProcAddress
XXXX45C7 mov esi, eax
XXXX45C9 test esi, esi
XXXX45CB jnz short loc_6FF045DF
XXXX45CD push offset aErrorFailedT_0 ; "
XXXX45D7 add esp, 4
XXXX45DA jmp loc_6FF046EA
XXXX45DF loc_XXXX45DF:
;......
XXXX46E6 call esi ; CheckRevision
XXXX46E8 mov ebx, eax
XXXX46EA
XXXX46EA loc_XXXX46EA: ; CODE XREF: DownloadAndRunVersioningDLL+15A j
XXXX46EA push ebp ; hLibModule
XXXX46EB call ds:FreeLibrary
XXXX46F1
XXXX46F1 loc_XXXX46F1: ; CODE XREF: DownloadAndRunVersioningDLL+F3 j
XXXX46F1 ; DownloadAndRunVersioningDLL+11E j ...
XXXX46F1 mov eax, [esp+430h+hArchive]
XXXX46F5 pop ebp
XXXX46F6 test eax, eax
XXXX46F8 jz short loc_XXXX4700
XXXX46FA push eax ; hArchive
XXXX46FB call Storm_252_SFileCloseArchive
XXXX4700
XXXX4700 loc_XXXX4700: ; CODE XREF: DownloadAndRunVersioningDLL+278 j
XXXX4700 push 32h ; dwMilliseconds
XXXX4702 call ds:Sleep
XXXX4708 mov esi, ds:DeleteFileA
XXXX470E push offset g_szVersionDLLName ; lpFileName
XXXX4713 call esi ; DeleteFileA
XXXX4715 mov al, [esp+42Ch+FileName]
XXXX471C test al, al
XXXX471E jz short loc_XXXX472A
XXXX4720 lea eax, [esp+42Ch+FileName]
XXXX4727 push eax ; lpFileName
XXXX4728 call esi ; DeleteFileA
暴雪在WOW开发的后期,终于能够腾出人手来升级持续了2年之久的D2X 1.09d。由于1.09d时期hacks泛滥,暴雪觉得有必要打击一下这种嚣张的气焰,于是加入了hacks检测机制,这就是在1.10时期经常提起的packet 64/65检测。
何谓packet?packet即网络数据包,D2中服务器端和客户端之间的交互是通过互相发送packet进行的。D2中的packet又分为out-of-game(进入游戏前)packet和in-game(游戏内)packet两种,这里提到的都是in-game packet。in-game packet的第一个字节为packet ID,指示该packet的含义,接着的是相应的(可变长)参数。比如ID 01代表walk命令,长度为5字节,ID后面跟两个16位参数,指示walk的目的坐标,因此它的格式为:01 [WORD x] [WORD y]。需要注意的是D2中不同patch版本的packet ID含义是不一样的,不能通用。1.10中的一个比较完整的in-game packet列表可以在这里找到:http://www.edgeofnowhere.cc/viewtopic.php?t=303771
跟hacks检测有关的是packet 64和65。packet 64长度是9字节,格式为:64 [DWORD address 1] [DWORD address 2],后面的两个DWORD是服务器端想检测的两个内存地址;packet 65长度为1字节(没有参数),检查4个最有可能被patch的地址。packet 64/65的检查结果经过简单的混淆处理(增加sniffer抓包分析的难度)后发送回服务器端,如果被检测地址里的指令或数据被改过,检测结果自然就和原先的不符,因此暴雪就知道你在用hack。这种检测方法就是所谓的memory probe,即内存探测法。那暴雪怎么知道应该检测哪些地址呢?hack的detour patch(旁路点)是固定的,像maphack和d2jsp这种著名的公开发行的hack,暴雪当然会拿来研究因此也会知道它们patch了哪些地方。至于那些自己开发自娱自乐的,暴雪是没法知道的,因此相对安全点儿。但是如果你的patch点正好和maphack、d2jsp这些相同,那还是有可能不幸中标。
以下为packet 64检测中的相关代码片断,其中eax和ecx分别为两个待检测的内存地址,检测结果分别存入局部变量var_result1和var_result2中随后发送回服务器端:
.text:XXXXF362 $CHECK_RESULT1: ; CODE XREF: CheckDetectionResult+87 j
.text:XXXXF362 cmp eax, esi ; not zero
.text:XXXXF364 jz short $CLEAR_RESULT1 ; Jump if Zero (ZF=1)
.text:XXXXF366 mov [ebp+arg1], esi
.text:XXXXF369 mov eax, [eax]
.text:XXXXF36B mov [ebp+var_result1], eax
.text:XXXXF36E mov [ebp+arg1], -1
.text:XXXXF375 jmp short $CHECK_RESULT2 ; Jump
.text:XXXXF39B $CHECK_RESULT2: ; CODE XREF: CheckDetectionResult+A5 j
.text:XXXXF39B ; CheckDetectionResult+C4 j
.text:XXXXF39B cmp ecx, esi ; Compare Two Operands
.text:XXXXF39D jz short $CLEAR_RESULT2 ; Jump if Zero (ZF=1)
.text:XXXXF39F mov [ebp+arg1], 1
.text:XXXXF3A6 mov ecx, [ecx]
.text:XXXXF3A8 mov [ebp+var_result2], ecx
.text:XXXXF3AB mov [ebp+arg1], -1
.text:XXXXF3B2 jmp short $SEND_DETECT_RESULT ; Jump
packet 65的检测代码和packet 64类似,除了它检测的是几个固定地址。
packet 64/65的memory probe机制,结合前一篇介绍过的已有的version-checking.dll和extrawork.dll,就构成了暴雪在Diablo II 1.10 patch中采用的hacks检测机制。
下图显示了d2jsp 1.2.0中使用的部分旁路点(d2jsp 1.2.0用于Diablo II 1.11b,但意思是一样的)。
暴雪在1.10补丁中加入hack检测机制,在某种程度上直接导致了原本和谐的D2X游戏黑客社群的分裂。一部分出于对检测机制的顾虑,停止更 新自己的作品,如d2hackit;另一部分则把他们的hack具有的反检测功能当成卖点开始收费,如d2maphack和d2jsp;还有一部分黑客出 于不满开始制作这些收费hacks的替代品,如d2hackmap,C3PO,d2bs等;甚至有些黑客出来破解这些收费hacks。
如前一篇文章所说,暴雪在1.10中加入packet 64/65检测。最早公布packet 64被用作hack检测的是jhj。当然Mousepad在jhj之前就已知道这一点,但是他当时正打算对maphack收费,反检测是一大卖点,因此一 直没有公布。在jhj公布了他的发现后,我检查了相关代码,又发现了packet 65也被用作hack检测。
如前文的分析,packet 64/65检测用的都是memory probe方法,那么memory probe该怎么对付呢?一种简单的想法是在客户端截获packet 64/65检测,不让它返回检测结果。截获packet 64/65检测思路是对的,但不返回检测结果其实也是一种信息,暴雪完全可能根据这点判断你在使用hacks,最不济也会把你踢下线,显然不是好的做法。 对付memory probe,更好的做法是伪造检测结果。这需要截获packet 64/65检测,然后根据要检测的内存地址返回该地址被修改前的数据(如果已经被修改了的话),这样无论检测哪个地址,检测结果都和没有使用hacks时 的一样。
具体到实现方法,大约又有三种。
一种是hack在安装旁路点时,先保存原先的数据,这样在遇到检测时就能知道patch前的数据。使用这种方法的有d2maphack、d2jsp等。这种方法最简单,实现起来也容易,占用额外内存也不大。缺点只能保护自己,不能保护其他hacks。
第二种方法由jhj实现,其原理是在加载任何hack之前,先对游戏中用到的重要的dll做备份,这样就获得了这些dll干净的副本。然后截获 packet 64/65检测入口,根据检测地址,从干净的副本中返回相应的数据。这就是jhj在 1.10时期发布的antidetection.dll。这种方法最大的优点是通用,可以保护其它hacks-其他hack不需要有任何反检测措施就能避 开检测。但是这种实现也有不小的缺陷。其一是它必须抢在其他所有hack之前加载,否则无法获得干净副本-如果无法获得干净副本这种方法就完全失去意义- 这在有些情况下是不容易做到的,比如无人职守的BOT。其二是所有重要的dll都要备份,这会额外消耗不少内存,对机器配置差或者多开BOT的玩家有很大 影响。其三是antidection.dll只备份了它认为重要的dll,而不是所有dll,这样如果有的hack修改的dll不在它的保护范围,还是有 可能被抓到。
第三种方法由我实现,称之为模块重建法(module re-construction),首先在d2hackmap中实现,后来ABin升级d2hackit时请我帮忙实现anti-detection模 块,因此我又把它集成进了d2hackit 2.00版本中。这种方法的思路是截获packet 64/65检测入口(显然所有反检测方法都需要这一步),根据检测地址判断出目标模块名称和其在硬盘文件路径,然后从该模块的硬盘文件开始重建一份干净的 副本,最后从干净的副本中返回相应的数据。这种方法在没有检测活动时(其实1.10时期packet 64/65检测很少出现)不会消耗额外内存,可保护所有dll,也无需抢先加载。我个人觉得是一种比较理想的方法。当然,模块重建法的难点在于如何从一个 dll文件重建一份和已加载模块被修改前完全一样的副本(其实是代码段和只读数据段完全一样,读写数据段无所谓),这在以后的文章中应该有机会介绍到。
另外,除了packet 64/65检测,别忘了1.10以前一直就有的version-checking.dll和extrawork.dll机制。虽然它们在前1.10时期从 未被用作hack检测,但是显然暴雪在1.10时期开始重视打击hacks,因此也不得不防。回想一下这两处机制,由于这两个dll都存放在服务器端,必 要时发送到客户端运行并返回结果。不让它们运行显然是不行的。并且显然它们的检测手段也是无限、未知的,伪造检测结果也不可行。那该怎么办呢?
对付这种手段的一种比较有效的方法是,截取并保存尽可能多的从服务器端传过来的dll,逐一分析,标出安全的和不安全的,并对每个dll建立签名 (signature)。这样在每次客户端接收到dll时,先计算出其签名然后和已事先分析过的所有dll签名比较,这样就可知道该dll安不安全(即能 否检测出我的hack),如果安全则让它执行,如果不安全则hack会自己卸载,然后再让该dll执行。这样就不会被它抓到。另外对于未知模块(即该 dll的签名不在列表里),应该把它保存下来以供事后分析,同时为保安全hack自己卸载,在以后分析后再把它标识为安全或不安全。当然对于不安全的模块 还应进一步研究反检测方法然后把它标为安全模块。d2maphack、d2jsp采取的都是这种策略。d2hackmap由于我后来没有太多精力人工分析 这些模块,因此只在配置文件里设置开关变量,指示d2hackmap遇到这些模块时如何处理(有忽略、卸载自身、保存模块文件并卸载自身三种选择)。至于 jhj的AntiDetection.dll是没有这方面的反检测保护的。事实证明这种策略是比较有效的。一个可能的原因是由于设计所限,这种发送dll 检测机制尚不具备快速变形、频繁运行的能力。