从零开始分析VAC
一、资料收集
国内:
【CSGO游戏分析】VAC反作弊系统分析 - 哔哩哔哩 (bilibili.com)
国外:
How To Bypass VAC Valve Anti Cheat Info (guidedhacking.com)
代码:
二、分析笔记
1 | 实验目标:CSGO |
2.1 ARK分析
根据Inline钩子扫描猜测当前检测功能有:
禁止第三方DLL注入
LdrLoadDll、LoadLibraryA、LoadLibraryExA、LoadLibraryExW、LoadLibraryW、NtOpenFile
申请内存、修改内存属性、写内存
VirtualAlloc、VirtualProtect、VirtualAllocEx、VirtualProtectEx
经过火绒剑分析,发现除了NtOpenFile
和LoadLibraryExW
被跳转到csgo.exe
外,其他的所有的钩子全部跳转到gameoverlayrenderer.dll
模块中,将对应模块dump后,ida分析。
2.1.1 gameoverlayrenderer.dll分析
LdrLoadDll
首先会进行一个判断,猜测可能是反作弊或者是hook初始化之类的。
其中会执行一个函数sub_7ADBDE50,该函数主要是收集模块信息,其中疑似有白名单检测。
this[0xF126]和this[0xF124]都为byte类型,作用为开启模块收集。
1 | char __thiscall RecordModuleInfos_7ADBEE50(int this, int moduleName, DWORD arg_4) |
收集的模块结构如下:
1 | struct ModuleInfo{ |
除此之外,游戏还会以另一种方式收集模块信息。在调用原LdrLoadDll后会将拉起的模块句柄、函数返回值、上下文返回的上层地址进行保存在另一个数组中。
LoadLibrary系列
挂钩代码基本上与LdrLoadDll相同,但是LoadLibrayA中,存在对d3d9.dll
和OPENGL32
的判断,用来进行对应hook来实现steam面板的绘制。
另外还会判断下图中的模块是否加载完毕,然后给全局变量赋值1,可能为反作弊的核心模块。
VirtualAlloc检测
首先会判断申请的内存类型是否为PAGE_EXECUTE_READWRITE。
然后进行记录到AllocMemInfos中。
记录的内存块有如下结构:
1 | struct AllocMemInfo |
VirtualProtect检测
首先会判断欲修改内存类型是否为PAGE_EXECUTE_READWRITE。
然后进行记录到ProtectInfos中。
记录的内存块有如下结构:
1 | struct ProtectInfo |
总结
游戏会进行如下记录:
1、记录游戏加载的模块(模块入口、模块所属的线程ID、模块句柄、加载时返回到的地址)。
2、记录申请内存类型为PAGE_EXECUTE_READWRITE的内存。
3、记录修改内存类型为PAGE_EXECUTE_READWRITE的内存。
4、记录后作用未知,猜测在某些地方会进行扫描这些表来寻找游戏作弊。
2.1.2 csgo.exe分析
NtOpenFile
从ObjAttr拿到文件名后判断文件名是否存在白名单,如果不存在白名单则返回错误码0xC0000034,存在则正常打开文件。
LoadLibraryExW
同样会首先判读是否存在白名单。
存在则正常加载DLL。
如果Load的DLL不在白名单内,则进行记录到表中,但记录的方式有坑。记录前会去查找一个句柄表,表中存放着一些dll的句柄、dll名、dll文件大小。游戏通过判断文件大小和文件名来查找句柄表是否存放对应的dll句柄,如果存在则返回该句柄。
不存在则进行记录。
PeekMessage
根据CE查看得知该函数是跳转到模块GameOverlayrenderer.dll
中,猜测可能是与steam面板有关,故不分析。
initHook
对hkNtOpenFile
函数继续交叉引用,可找到初始化HOOK的位置。
可以看到分别对三个函数进行了HOOK,其中Hook NtOpenFile中有一个中转函数sub_4E5980
.
函数首先是判断当前的获取到的NtOpenFile函数地址是否合法。
hookTable为一个map表,里边存放着NtOpenFile、LoadLibraryExW、PeekMessageA的原始地址,该表在较早时期进行初始化,每次HOOK都会判断GetProcAddress得到的地址与表中是否一致,防止一些IAT类型的HOOK。之后检查函数头是否存在int3、int2、nop等指令覆盖,如果没覆盖则进行Hook。
总结
1、hook了NtOpenFile和LoadLibraryExW进行白名单判断,并且在LoadLirary中,如果dll不为白名单中则尝试查找句柄表中是否有该dll句柄,然后返回;否则就插入一个记录节点,记录该dll的信息。
2、其中游戏初期会初始化一个hookTable表,里边存放着三个函数的地址;在HOOK时会重新判断地址是否一致,然后进行HOOK,防止一些IAT HOOK。
3、对于记录了白名单外的dll,猜测可能是会有遍历文件,然后后台上传。
2.2 API分析
2.2.1 tier0.dll分析
IsDebuggerPresent
将CE调成VEH调试器后再IsDebuggerPresent
函数下断.
断下后F8单步走出。
发现模块返回到了tier0.dll
。
通过火绒剑查看进程模块发现有两个,由于是返回到tier0.dll中,所有这里先dump下该模块后进行分析下。
对IsDebuggerPresent
交叉引用后得到的结果基本都是判断在调试,如果调试就抛出一个int3
。
Dr寄存器清空
通过导入表发现该模块还调用了GetThreadContext
和SetThreadContext
,疑似为清空Dr寄存器。
但在游戏中对这两个函数下断并为断下。
2.2.2 steamclient.dll分析
检测顶层窗口
在对CreateToolhelp32Snapshot
下断点时游戏断下,查看堆栈信息来自steamclient.dll
。
将该模块dump后,跳到返回到地址进行分析。
模块首先会枚举每一个进程的PID。
然后会遍历枚举到的进程ID,获取他们的文件信息保存到数组中。
接着通过遍历进程,其中会寻找csgo.exe的PID。
通过对遍历进程的函数进行交叉引用后,发现会获取最顶层窗口并且检查是否为csgo的窗口。
并且会根据顶层窗口是否为csgo.exe来对时间数据赋值。
猜测是检测是否有窗口一直覆盖再游戏上。
总结
1、会通过IsDebuggerPresent检测是否在调试,如果调试则抛出int3异常。
2、可能会清空Dr寄存器。
3、会检测顶层窗口
4、会枚举电脑上所有的进程,并收集进程文件的信息。