Unfairgame是一家在线游戏作弊提供商,专门为竞技游戏销售游戏作弊。这些游戏包括《彩虹六号:围攻》、《守望先锋》、《 铁锈》(https://en.wikipedia.org/wiki/Rust_(video_game%29)、《逃离塔科夫》和《Valorant》。这个作弊提供者已经存在近两年了,并且轻松赚取了超过 10 万美元的利润。一直以来,他们都通过构建不良的作弊加载系统向用户出售公开可用的代码。
我的大部分逆向工程都是在内核内部完成的,只是在最后我查看了用户模式可执行文件。让我们从购买后提供给用户的可执行映像开始。我注意到的第一件事是这个应用程序的大小。它不到四分之一兆字节,这让我相信这个应用程序是两件事之一。首先,此应用程序可能不受VMProtect或Themida保护。这些虚拟器倾向于生成更大的可执行文件。其次,该应用程序可能不包含任何嵌入式应用程序,如驱动程序、dll 等。后者可能总是错误的,但该应用程序仍然是变异的,乍一看难以理解。而不是追求静态分析,考虑到现代加壳器和变异器的异步性,在这些情况下最好结合使用动态分析和静态分析。通常,您可以通过查看应用程序如何与正在执行的操作系统交互来了解应用程序的作用。在调试器中运行此可执行文件后,很明显它会生成一个子进程 (RuntimeBroker.exe)并将自身注入该进程,通过将其在磁盘上的原始名称更改为随机名称来完成其执行。新注入的模块现在会生成一个控制台,并提示用户登录。登录后,系统会提示用户选择任何订阅,然后从其 Web 服务器下载加密模块。
尽管此 url 是根据您的订阅预先确定的,但您可以简单地将所需文件更改为任何其他订阅。换句话说,通过一点点猜测,我能够流式传输我从未订阅过的加密模块。请记住,我们只是这篇文章的几段内容,我已经能够获取每个订阅提供的每个文件。与该产品所获得的利润相比,对该产品的缺乏思考令人作呕。此外,随后下载的模块被解密并复制到堆分配中,从而允许系统的解密和转储。
此外,解密的模块不仅存储在堆分配中,而且还存储了手动映射的驱动程序。既然我已经有了可以在这里找到的所有彩虹六号、Rust(https://en.wikipedia.org/wiki/Rust_(video_game%29)和Valorant 的解密模块,让我们进入这个秘籍的内核部分,可以说它包含更多有趣的琐碎错误。
[unfairgame]base address: 0xFFFFF80353B70000 [unfairgame]driver loaded from: \??\C:\Windows\diskptex0.dat:exe [unfairgame] - driver timestamp: 0x000000005EB9B094 [unfairgame]unfair driver loaded...
该作弊提供商加载到内核中的第一个映像是一个驱动程序,该驱动程序使用他们购买的证书进行签名。
Name: 艾許工作室 (Ash Studio) Issuer: DigiCert SHA2 High Assurance Code Signing CA Valid From: 2019-10-30 00:00:00 Valid To: 2021-02-10 12:00:00 Algorithm: sha256RSA Thumbprint: C52DD640E34C86A824B881B1F80B27195B811B4A Serial Number: 0E 9F 17 96 8A C5 0F 1A FA F5 3B 19 8D BF 7F FD
这家公司似乎是在 2017 年在中国注册的,关于这家公司的更多信息可以在这里找到。
虽然这个镜像是由他们签名的,但它没有任何突变或虚拟化。这是 Unfairgame 的一个相当大的错误,考虑到他们在内核中唯一的真正防御线是一本开放的书。此外,这个驱动程序的代码构造得非常糟糕,从导入到IOCTL 的所有内容都没有经过深思熟虑,几乎无法运行,而且非常不安全。
NTSTATUS __stdcall DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath) { //... if ( (int)ZwQuerySystemInformation(0xBi64, v4, (unsigned int)NumberOfBytes, 0i64) < 0 ) { ExFreePoolWithTag(v5, 0x47536353u); return 0xC0000365; } ntoskrnlBaseAddress = v5[3]; // first entry is always ntoskrnl.exe ExFreePoolWithTag(v5, 0x47536353u); if ( !ntoskrnlBaseAddress ) return 0xC0000365; *(_QWORD *)MmMapIoSpace = GetProcAddress(ntoskrnlBaseAddress, "MmMapIoSpace"); *(_QWORD *)MmUnmapIoSpace = GetProcAddress(ntoskrnlBaseAddress, "MmUnmapIoSpace"); *(_QWORD *)IofCompleteRequest = GetProcAddress(ntoskrnlBaseAddress, "IofCompleteRequest"); IoCreateDevice = GetProcAddress(ntoskrnlBaseAddress, "IoCreateDevice"); IoCreateSymbolicLink = GetProcAddress(ntoskrnlBaseAddress, "IoCreateSymbolicLink"); IoDeleteDevice = GetProcAddress(ntoskrnlBaseAddress, "IoDeleteDevice"); *(_QWORD *)IoDeleteSymbolicLink = GetProcAddress(ntoskrnlBaseAddress, "IoDeleteSymbolicLink"); MmGetPhysicalAddress = GetProcAddress(ntoskrnlBaseAddress, "MmGetPhysicalAddress"); //... }
如上所示,假设第一个RTL_PROCESS_MODULE_INFORMATION是内核信息,该驱动程序通过使用SystemModuleInformation调用NtQuerySystemInformation来获取 ntoskrnl.exe 的基地址。开发人员对这种作弊的缺乏经验真的开始显现出来。无需进行单个函数调用即可获得内核的基址。可以简单地导入PsInitialSystemProcess,它是一个指向EPROCESS的指针,该EPROCESS包含有关进程的各种内容,包括其基地址。将 0x3C0 添加到指针,然后将其取消引用为 qword 将为您提供内核的基址。
.text:000000014006CE70 PsGetProcessSectionBaseAddress proc near .text:000000014006CE70 mov rax, [rcx+3C0h] .text:000000014006CE77 retn .text:000000014006CE77 PsGetProcessSectionBaseAddress endp
驱动程序获得内核的基地址后,它会动态解析稍后使用的导入。钩住他们的GetProcAddress实现很容易使我能够钩住他们的其余导入,例如MmMapIoSpace和MmGetPhysicalAddress。
namespace hooks { void* get_addr_hook(const void* base_addr, const char* func_name) { DBG_PRINT(""); DBG_PRINT("=============== %s ==============", __FUNCTION__); DBG_PRINT("func_name: %s", func_name); if (!strcmp(func_name, "MmMapIoSpace")) return &map_io_space; else if (!strcmp(func_name, "MmUnmapIoSpace")) return &unmap_io_space; else if (!strcmp(func_name, "MmGetPhysicalAddress")) return &get_phys_addr; else if (!strcmp(func_name, "IoCreateSymbolicLink")) return &create_sym_link; else if (!strcmp(func_name, "IoCreateDevice")) return &create_device; return driver_util::get_kmode_export("ntoskrnl.exe", func_name); } }
现在我可以通过各种各样的钩子完全控制所有外部依赖项,是时候记录每个函数调用及其后续参数了。调用和参数的大量文档允许快速和完整的逆向工程开始。 MmMapIoSpace / MmGetPhysicalAddress可能是最有趣的函数,考虑到当它们一起使用时,它们可以有效地映射虚拟地址的物理内存,从而对特定地址范围进行二次映射。更有趣的是,这两个函数都通过IOCTL暴露给用户模式。
[unfairgame]=============== hooks::get_phys_addr ============== [unfairgame]getting physical address of: 0xFFFFF588CB335660 // vtable ptr of DxgkReclaimAllocations2 (inside of win32kbase.sys) [unfairgame]base_addr value: 0xFFFFF80B2C504420 // the actual pointer to DxgkReclaimAllocations2 [unfairgame]physical address: 0x00000001D7317660 [unfairgame]=============== hooks::map_io_space ============== [unfairgame]mapping physical memory 0x00000001D7317660 of size 0x8 [unfairgame]mapped io space 0xFFFFA3818A146660, value: 0xFFFFF80B2C504420 // the actual pointer to DxgkReclaimAllocations2
仔细查看传递给MmGetPhysicalAddress的指针会发现它驻留在 win32base.sys 中。通过查看 Process Hacker 的内核模块列表及其基址,我能够得出该地址位于哪个模块中的结论。
在仔细检查 win32kbase.sys 确实是正确的模块后,我打开了位于 C:\Windows\System32\drivers\win32kbase.sys 的 win32kbase.sys 并导航到驱动程序内部的正确偏移量。我进入了模块的数据部分,特别是在一个 vtable 的条目上。这个指针恰好是一个指向 dxgkrnl.sys 导出的指针。经过进一步分析,很明显,win32kbase.sys 充当 dxgkrnl.sys 导出的巨大虚拟表,可以通过将指针更改为您想要的任何内容来轻松操作,从而允许您使用任意数量的参数调用内核中的任何函数.
.data:00000001C01A5618 qword_1C01A5618 dq ? ; DATA XREF: NtGdiDdDDIRemoveSurfaceFromSwapChain+4 .data:00000001C01A5620 qword_1C01A5620 dq ? ; DATA XREF: NtGdiDdDDIUnOrderedPresentSwapChain+4 .data:00000001C01A5628 qword_1C01A5628 dq ? ; DATA XREF: NtGdiDdDDIAcquireSwapChain+4 .data:00000001C01A5630 qword_1C01A5630 dq ? ; DATA XREF: NtGdiDdDDIReleaseSwapChain+4 .data:00000001C01A5638 qword_1C01A5638 dq ? ; DATA XREF: NtGdiDdDDIGetSetSwapChainMetadata+4 .data:00000001C01A5640 qword_1C01A5640 dq ? ; DATA XREF: NtGdiDdDDIAbandonSwapChain+4 .data:00000001C01A5648 qword_1C01A5648 dq ? ; DATA XREF: NtGdiDdDDISetDodIndirectSwapchain+4 .data:00000001C01A5650 qword_1C01A5650 dq ? ; DATA XREF: NtGdiDdDDICheckMultiPlaneOverlaySupport2+4 .data:00000001C01A5658 qword_1C01A5658 dq ? ; DATA XREF: NtGdiDdDDIPresentMultiPlaneOverlay2+4 .data:00000001C01A5660 ; __int64 (*NtGdiDdDDIReclaimAllocations2_0)(void) notice how this ends with 0x660! .data:00000001C01A5660 NtGdiDdDDIReclaimAllocations2_0 dq ? ; DATA XREF: NtGdiDdDDIReclaimAllocations2+4 .data:00000001C01A5668 qword_1C01A5668 dq ? ; DATA XREF: NtGdiDdDDIGetResourcePresentPrivateDriverData+4 .data:00000001C01A5670 qword_1C01A5670 dq ? ; DATA XREF: NtGdiDdDDISetStablePowerState+4 .data:00000001C01A5678 qword_1C01A5678 dq ? ; DATA XREF: NtGdiDdDDIQueryClockCalibration+4
检查对这个指针的 ex 引用表明它在从 win32kbase 导出的名为 DxgkReclaimAllocations2 的函数中使用。Win32kbase 有效地导出与 dxgkrnl.sys 相同的所有功能。但是,对于较新版本的 Windows 10,情况并非如此。在我运行 Windows 10 2004 的虚拟机上,win32kbase 缺少 Windows 10 旧版本中的许多导出。可能会将软件限制为仅支持特定版本的 Windows。
.text:00000001C0068330 NtGdiDdDDIReclaimAllocations2 proc near .text:00000001C0068330 sub rsp, 28h .text:00000001C0068334 mov rax, cs:NtGdiDdDDIReclaimAllocations2_0 .text:00000001C006833B call cs:__guard_dispatch_icall_fptr .text:00000001C0068341 add rsp, 28h .text:00000001C0068345 retn .text:00000001C0068345 NtGdiDdDDIReclaimAllocations2 endp
作者个人研发的在高并发场景下,提供的简单、稳定、可扩展的延迟消息队列框架,...
公司 邮箱 如何开通?企业运作已离不开 企业邮箱 ,它已经成为企业与外界、企业...
【51CTO.com原创稿件】在今年经历了疫情的冲击后,众多企业也都在加速数字化转型...
近年来,随着全球信息化和数字化程度的不断加深,人类的生产生活方式正在发生深刻...
首先值得肯定的一点是,云计算依旧是热的。让人们感觉没有那么热的原因就是因为...
如何购买新 虚拟主机 ?不同的网站,对虚拟主机的需求是不一样的。购买新虚拟主...
12月15日,2020 年 MSU 世界视频编码器大赛成绩公布,阿里巴巴自研奇点编码器首...
为了保障视频内容安全,防止视频被盗链、非法下载和传播,云点播提供了针对视频...
在存储大数据时,数据湖和数据仓库都是既定术语,但是这两个术语不是同义词。数...
作者 | 顾荣? 南京大学?PASALab (注:本文基于作者公开演讲报告内容整理完成) ...