驱动程序映射
现在我们了解了这种挂钩/通信方法的基础知识,所有其他对 MmGetPhysicalAddress 的调用的意图变得更加清晰。下次调用 MmGetPhysicalAddress 时,将传递驻留在 ntoskrnl 内部的指针。这个地址就是 ExAllocatePool 的地址。通常,ExAllocatePool 的这种用法用于为未签名的驱动程序分配空间。
代码语言:javascript复制[unfairgame]=============== hooks::get_phys_addr ==============
[unfairgame]getting physical address of: 0xFFFFF588CB335660 // address of vtable in win32kbase.sys
[unfairgame]base_addr value: 0xFFFFF80046968650 // address of ExAllocatePool
[unfairgame]physical address: 0x00000001D7317660
代码语言:javascript复制.text:00000001400D1650 ; =============== S U B R O U T I N E =======================================
.text:00000001400D1650 ; NOTICE HOW THIS ENDS WITH 0x650! :)
.text:00000001400D1650
.text:00000001400D1650 ; PVOID __stdcall ExAllocatePool(POOL_TYPE PoolType, SIZE_T NumberOfBytes)
.text:00000001400D1650 public ExAllocatePool
.text:00000001400D1650 ExAllocatePool proc near
.text:00000001400D1650 sub rsp, 28h
.text:00000001400D1654 mov r8d, 656E6F4Eh ; Tag
.text:00000001400D165A call ExAllocatePoolWithTag
.text:00000001400D165F add rsp, 28h
.text:00000001400D1663 retn
.text:00000001400D1663 ExAllocatePool endp
对这种糟糕的代码设计一无所知,这对为此产品付费的人来说是一种耻辱。使用这种技术将 win32kbase 的 vtable 内的指针交换到内核中另一个函数的地址在当前上下文中没有意义,考虑到可以简单地通过添加能力来完成将这样的函数暴露给用户模式进程将此类例程调用到新的 IOCTL 开关案例。与其无缘无故地交换指针和使代码复杂化,不如简单地执行以下操作。请记住,我这么说的唯一原因是他们泄露的证书签名驱动程序仍然加载在内核中!他们正在做的任何事情都没有任何意义,也没有让任何事情变得更安全!
代码语言:javascript复制switch (IOCTL_CODE)
{
case ALLOCATE_MEMORY:
// allocate memory
break;
case CALL_DRIVER_ENTRY:
// ...
break;
default:
//...
}
最后,对MmGetPhysicalAddress的最后一次调用传递了一个不在任何合法模块内部的指针,而是在由前一个指针交换和函数调用 ( ExAllocatePool )创建的分配池内部。该指针位于分配深处的 0x2004 处,这使得人们很容易假设这可能是一个指向手动映射驱动程序入口点的指针。正如之前在这篇文章的 CPL3 部分所述,手动映射的驱动程序很容易获得,因为它位于堆分配内部。比较堆分配中驱动程序的入口点和内核池分配中的偏移量显示这些偏移量是相等的。
代码语言:javascript复制[unfairgame]base_addr value: 0xFFFF800506202004 // probably driver entry
[unfairgame]physical address: 0x0000000199516660
[unfairgame]mapping physical memory 0x0000000199516660 of size 0x8
[unfairgame]mapped io space 0xFFFF9D008C958660, value: 0xFFFF800506202004 // probably driver entry
代码语言:javascript复制.text:0000000140002004 ; As you can see this entry point is 0x2004 into this module. Same as the IOCTL log data.
.text:0000000140002004
.text:0000000140002004 ; NTSTATUS __stdcall DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
.text:0000000140002004 public DriverEntry
.text:0000000140002004 DriverEntry proc near
.text:0000000140002004
.text:0000000140002004 arg_0 = qword ptr 8
.text:0000000140002004 arg_8 = qword ptr 10h
.text:0000000140002004 arg_10 = qword ptr 18h
.text:0000000140002004 arg_18 = qword ptr 20h
.text:0000000140002004
.text:0000000140002004 call get_proc_addr
.text:0000000140002009 push rax
.text:000000014000200A rcr ecx, 1
.text:000000014000200C fdiv dword ptr [rbx 38EE64BAh]
.text:0000000140002012 db 36h
.text:0000000140002012 mov edx, 555B13A0h
.text:0000000140002018 mov rbp, rsp
.text:000000014000201B sub rsp, 50h
.text:000000014000201F mov rax, cs:qword_140004000
沟通
现在我们已经深入了解了未签名驱动程序如何映射到内核中,让我们看看这个驱动程序到底做了什么,同样重要的是,这个驱动程序如何与用户模式应用程序进行通信。首先,这个手动映射的驱动程序只不过是modmap的清晰副本,这是btbd制作的流行模块扩展程序。这可以通过查看通信方法、通信数据以及已加载到游戏中并进行扩展的模块来最终证明。
首先,这个手动映射驱动程序的通信方法与modmap 中使用的相同,只需更改 xKdEnumerateDebuggingDevices(位于 ntoskrnl.exe 数据部分内的指针),就可以调用看似无害的函数和港口它作为一种交流手段。这种交流方式最初是Can在这个游戏黑客论坛上讨论的。
代码语言:javascript复制__int64 __fastcall NtConvertBetweenAuxiliaryCounterAndPerformanceCounter(char a1, unsigned __int64 a2, _QWORD *a3, _QWORD *a4)
{
_QWORD *v4; // rbx
_QWORD *v5; // rdi
char v6; // si
__int64 v7; // r14
__int64 (__fastcall *v8)(); // rax
unsigned int v9; // ecx
__int64 (__fastcall *v10)(); // rax
__int64 v12; // [rsp 20h] [rbp-28h]
__int64 v13; // [rsp 28h] [rbp-20h]
__int64 v14; // [rsp 30h] [rbp-18h]
v4 = a4;
v5 = a3;
v6 = a1;
if ( KeGetCurrentThread()->PreviousMode )
{
if ( a2 & 3 )
ExRaiseDatatypeMisalignment();
if ( a2 8 > 0x7FFFFFFF0000i64 || a2 8 < a2 )
MEMORY[0x7FFFFFFF0000] = 0;
v7 = *(_QWORD *)a2;
v14 = *(_QWORD *)a2;
ProbeForWrite(a3, 8ui64, 4u);
if ( v4 )
ProbeForWrite(v4, 8ui64, 4u);
v8 = off_140398A08[0];
if ( !v6 )
v8 = off_140398A00[0]; // this pointer gets swapped to the address of the manually mapped function hook handler.
v9 = ((__int64 (__fastcall *)(__int64, __int64 *, __int64 *))v8)(v7, &v12, &v13);
if ( (v9 & 0x80000000) == 0 )
{
*v5 = v12;
if ( v4 )
*v4 = v13;
}
}
else
{
v10 = off_140398A08[0];
if ( !a1 )
v10 = off_140398A00[0];
v9 = ((__int64 (__fastcall *)(_QWORD, _QWORD *, _QWORD *))v10)(*(_QWORD *)a2, a3, a4);
}
return v9;
}
如您所见,此函数允许攻击者轻松交换函数指针,然后调用此函数,同时不仅允许攻击者传递数据,还可以为攻击者验证数据。如上所示,传递给此函数的第二个参数应视为指向指针的指针。附加调试器并在位于 ntdll.dll 内部的NtConvertBetweenAuxiliaryCounterAndPerformanceCounter上设置断点,应该会显示必要的信息,以最终证明这整个过程不仅是复制和粘贴的作弊,而且是构造不佳的复制和粘贴。
果然这个函数确实被调用了。在这一点上,不需要水晶球就可以预见 RDX 将成为指向包含与modmap相同结构的指针的指针。
事实上,数据确实与modmap 中看到的结构相匹配。此外,上面屏幕截图中看到的 dll 不是 Windows 原生的。它由 MSI 签名,不附带标准 Windows 10。虽然有些同名模块末尾缺少“64”,但这些模块没有签名。另外,如果我要删除这个模块,它大概会被第二阶段加载器重新创建。此外,这个模块通常不会加载到 Rust 或彩虹六号中,当使用进程黑客卸载时,它正在执行的进程会崩溃。这是因为它可以使用您的订阅提供的任何模块进行扩展。
结论
总而言之,这个作弊只不过是在游戏黑客论坛上找到的公共代码和偏移量。这种作弊的开发者和经销商甚至不了解他们自己的产品是如何工作的,也不了解它是多么令人厌恶的不安全。这些人的无知加上他们惊人的六位数利润只会让我想知道 Easy Anti Cheat 或 BattlEye 怎么能让这种下滑,更不用说支付这些反作弊提供商的公司了。