一直以来,反作弊程序是维护游戏公平最重要的工具。不过我早在7月才知道我玩的某个游戏里面发反作弊程序驱动有很大的权限漏洞。到现在这么久应该被许多大佬玩坏了。不过就这样的好奇我打开了久违的IDA,去看看里面有什么。
开搞
一上来直接就是有被吓到,这么多的Instruction,我一时间不知道是什么。不过我大概看了一下,这大部分Instruction的代码在一个名叫.upx0
的字段。不过很明显,现在是可以修改字段名称去忽悠很多的人。而且,基于占了这么多,很大怀疑是VMP的虚拟保护壳,对几十个重要的函数进行了虚拟保护。我们可以去查看入口代码去验证。
我们直接去看驱动的入口函数DriverEntry
,可以看见很明显的WDF的加载过程,所以这个函数应该是 FxDriverEntryWorker
,WDF加载先会判断DriverObject
是否为空,然后再去直接或间接去执行入口函数。所以说我们干脆直接进去。
我们可以看到他跳转到了.upx0
下的代码。
好家伙,还真是VMP的虚拟壳,你可以看见很明显的push 563A851E
和call sub_FFFFF80351349ECD
的虚拟入口特征。
好了,到这里几乎就是玩个锤子了。
可以看到,连最基本的驱动加载的过程全部被保护掉,而且会复制整个IAT加载进虚拟壳,避免被跟踪。不然也不至于连个 IoCreateDevice
都无法追踪调用。
本来想使用大佬们写的unicorn_pe去dump整个驱动,结果无论如何都出现了堆栈爆炸的错误。
不过没关系,我们可以分析其他的一些函数。最重要的我注意到了导出表有几个熟悉的函数,比如说 ZwTerminateProcess
。很明显,这一般用来杀掉作弊程序,所以说我们跟踪他。
经典的结束进程过程。被两个函数所调用,我们选第一个继续跟踪。
草,搁这套娃呢?
而且一上来就可以看见。
很明显的虚拟入口调用的特征,跟我们刚刚讲的一样。
到这里,基本IDA的F5功能基本失效,因为IDA无法分析VMP虚拟入口传递的参数个数,破坏了IDA的栈线分析。
没事不用慌,我们看看是谁调用了TerminateProcessByProcessId
。
我们可以看见这个部分,先进行了判断某个变量是0x81034000
,再执行TerminateProcessByProcessId
,然后再进行下个判断,也是比较这个函数是否为0x81044000
,如果是执行对应的函数。
估计熟悉驱动开发的估计很明显就知道这个变量和函数是干嘛了。很明显,这是使用 switch
循环在比较 IoControlCode
,即控制代码。这也说明了一点,这就是 DispatchDeviceControl
,即驱动控制的入口之一,为什么要说之一,因为在这函数前还有个函数调用它。
很好,有了个很重要的突破——我们可以从中挖掘用户层会跟驱动层进行了什么通信,做了什么。
不过讲道理,里面很多都重要的函数被保护了,不愧是你。
DispatchDeviceControl的内容
我能在DispatchDeviceControl
里面看到的内容。
- 结束进程
- 检查调试
- 获取进程路径
- 获取线程退出时间
- 读取虚拟内存
- 获取进程创建时间
- 枚举进程模块列表
- 获取进程EROCESS
- 获取进程Wow64
- 剥离句柄(剥离父进程创建进程的句柄)
- 检查回调是否注册、是否被调试等
- 设置保护进程
- 初始化保护
至少还有5个函数已经被虚拟,无法理解.jpg
一个比一个高级,一个比一个猛.cpp
我看不懂,但我大受震撼.jpg
那些函数有本事别虚拟给我康康啊。
听话!让我康康!
听话!让我康康!
接下来,我们先回分析最简单的实现,至于那些复杂的实现我回继续单独开一个文章去讲。
获取进程路径
通过进程ID获取进程路径
调用 PsLookupProcessByProcessId
获取EPROCESS。
调用PsReferenceProcessFilePointer
获取文件指针传入IoQueryFileDosDeviceName
获取Dos路径。
最后还要记得使用 ObDereferenceObject
函数来释放对象。
获取线程退出时间
通过线程ID获取退出时间
调用 PsLookupThreadByThreadId
获取ETHREAD。
因为ETHREAD结构未公开,随意访问不正确的地址会导致报错,所以访问前使用 MmIsAddressValid
验证地址是否能正常访问。
其中 qword_FFFFF803512AA728
记录偏移。经过跟踪,该全局变量在一个函数上统一赋值(这个函数以后再说),而且该全局变量仅对Version为61的版本系统有效(即Windows7系统)。
经过查询,ETHREAD在Version为61的0x0368偏移的结构如下。
union {
LARGE_INTEGER ExitTime;
LIST_ENTRY KeyedWaitChain;
};
所以我直接盲猜是获取ExitTime。
其实也好猜,就直接判断那个使用的多,一般来说 KeyedWaitChain
使用范围几乎没有。而且这个函数仅在Windows7上获取这个变量,确实让我这个小白百思不得其解。
施工中
最后编写日期为 2021-12-03 09:06:23
如有错误欢迎大佬指正