当今的作弊行为主要是使用内部Directx挂钩或窗口覆盖图来可视化隐藏的游戏信息。这两种方法已被广泛记录,但其他更不起眼的方法包括在Windows内核中挂接图形例程,正如我们将在本文中演示的那样。没有公开发布使用与此类似的方法,这很可惜,因为与普通的Directx钩子相比,它实际上非常易于使用并且几乎没有痕迹。
dxgkrnl
在dxgkrnl.sys中实现的Microsoft DirectX图形内核子系统是DirectX图形基础结构(DXGI)设备驱动程序接口的一部分。该驱动程序充当各个显示驱动程序的抽象层,公开各种接口,并充当用户模式实现和图形卡的中介。这是一个非常广泛的子系统,并且具有许多令人感兴趣的功能。我们决定专注于D3DKMTSubmitCommand
gdi32!D3DKMTSubmitCommand用于将命令缓冲区提交给支持虚拟寻址的图形驱动程序。这些命令完全在用户模式下生成,仅通过图形内核子系统传递给图形驱动程序。它的前身DxgkDdiRenderKm仅用于“旧版”图形驱动程序,但看起来也很有趣,因为它很可能产生相同的结果。所述D3DKMTSubmitCommand函数传递一个参数:实例D3DKMT_SUBMITCOMMAND结构。该结构包含GPU命令,提交标志和上下文数据,对我们没有任何用处,除非我们要修改实际的gpu命令。
当!GDI32 D3DKMTSubmitCommand被调用,它通过系统呼叫路由NtGdiDdDDISubmitCommand,这是在任何Win32驱动程序实现的(一些Windows版本已经在它实施win32kbase,一些win32kfull)为:
图1-win32kbase!NtGdiDdDDISubmitCommand
数据成员的这种异常函数调用实际上是整个dxgkrnl抽象层的较大函数表的一部分,该函数表既未记录在符号中,也未导出到二进制文件中,这可能解释了作弊中这种图形使用类型的异常疏忽。该特定数据成员指向dxgkrnl!DxgkSubmitCommand,表的其余部分说明了这一点(请参阅x-refs):
图2-win32kbase函数指针表
这意味着我们可以通过简单地更改此数据成员RW(!)来控制所有执行流程,从而使自动完整性检查更加困难。覆盖指针后,就可以绘制到屏幕缓冲区了。
为什么?
通过截获此特定的gpu调用,我们可以与实际的屏幕更新完全同步,从而允许我们使用GDI函数来操纵中间屏幕缓冲区。我们绘制到游戏缓冲区的唯一痕迹是模糊的指针交换,实际上没有反作弊检查。请注意,这是基于cpu的,这意味着存在很大的性能开销,但是可以使用gpu绘制相同的钩子。
要进行实际绘制,我们可以直接在内核中使用任何Gdi函数,而不会出现任何问题!这是一个使用位模式复制操作NtGdiPatBlt绘制一个简单盒子的示例:
代码语言:txt复制int64_t __fastcall dxgkrnl_hook::submit_command_hook(D3DKMT_SUBMITCOMMAND* data)
{
const auto current_process = IoGetCurrentProcess();
const auto process_name = PsGetProcessImageFileName(current_process);
if (std::memeq(process_name, dxgkrnl_hook::target_name))
{
// GET CONTEXT
const auto ctx = NtUserGetDc(0x00);
// DRAW TO GAME WINDOW BUFFER
NtGdiPatBlt(ctx, 15, 15, 5, 5, PATCOPY);
}
return dxgkrnl_hook::original_submit_command(data);
}
示范
我已经组装了一个概念验证的Github存储库,该存储库是从一个较大的项目中提取的,这就是为什么某些引用的符号未定义的原因-找到它们非常琐碎,因此对于读者。
如果您不想自己尝试使用该方法,则此视频对播放器盒使用了完全相同的方法,这演示了我们前面提到的内核挂钩的完美同步。