0%
毅种循环

模块 88 - 更新 Hell's Gate (TartarusGate)

恶意软件开发课程 - 更新 Hell's Gate (TartarusGate)


模块 88 - 更新 Hell’s Gate (TartarusGate)#

更新 Hell’s Gate (TartarusGate)#

简介#

Syscalls - Hell’s Gate 模块介绍了 Hell’s Gate 技术,该技术通过在挂钩字节中搜索系统调用号(SSN)来绕过用户态挂钩,以便稍后直接调用。本模块将对该模块中演示的原始 Hell’s Gate 实现进行更新。

更新后的实现将更加定制化,从而提隐蔽性并减少基于签名的检测。此外,更新后的代码将采用 TartarusGate 的方法来检索 Syscall 的 SSN,这是一种更强大的动态检索技术。

如果您需要重温原始的 Hell’s Gate 实现,请访问 Hell’s Gate GitHub 仓库

💡 初学者提示:为什么需要 TartarusGate?

类比理解: 想象 Syscall 表是一条街道,每户人家都有一个门牌号(SSN)。

  • Hell’s Gate 试图直接看你家大门上的门牌号。如果 EDR 在你家门口贴了大字报(挂钩),挡住了门牌号,Hell’s Gate 就不知道你是几号了。
  • TartarusGate 则更加聪明。如果你家的门牌号被遮住了,它会去看你的邻居
    • 如果左边邻居是 50 号,那你就是 51 号。
    • 如果右边邻居是 52 号,那你还是 51 号。

只要街上还有没被遮挡的门牌号,TartarusGate 就能推算出被遮挡住的号码。

更新字符串哈希算法#

原始的 Hell’s Gate 实现使用了 DJB2 字符串哈希算法。更新字符串哈希算法并不影响 Hell’s Gate 的核心逻辑,但修改哈希算法可以降低被签名检测的可能性。我们将 djb2 函数替换为以下的 crc32h 函数。

crc32h 函数是 循环冗余校验 (CRC) 字符串哈希算法的一种实现,将在本模块中使用。为了提高代码的可读性和可维护性,我们将通过以下宏来调用 crc32h 函数。

#define HASH(API) crc32h((char*)API)
c

其中 API 变量是要使用 crc32h 进行哈希处理的字符串。

更新 GetVxTableEntry#

创建 NTDLL_CONFIG 结构体#

回顾一下,GetVxTableEntry 是用于通过哈希值检索指定 Syscall 的地址和 SSN 的函数。GetVxTableEntry 函数计算搜索指定哈希所需的 RVA,并接受两个额外的参数 pModuleBasepImageExportDirectory。为了提高效率,我们创建了 NTDLL_CONFIG 结构体,如下所示。

这相当于把所有关于 NTDLL 导出表的信息打包在一起,避免重复解析。

typedef struct _NTDLL_CONFIG
{
    PDWORD      pdwArrayOfAddresses; // ntdll 导出函数地址数组的 VA
    PDWORD      pdwArrayOfNames;     // ntdll 导出函数名称数组的 VA
    PWORD       pwArrayOfOrdinals;   // ntdll 导出函数序号数组的 VA
    DWORD       dwNumberOfNames;     // ntdll.dll 导出函数的数量
    ULONG_PTR   uModule;             // ntdll 的基址 - 用于计算未来的 RVA

} NTDLL_CONFIG, *PNTDLL_CONFIG;

// 全局变量
NTDLL_CONFIG g_NtdllConf = { 0 };
c

创建 InitNtdllConfigStructure#

此外,创建了一个私有函数 InitNtdllConfigStructure,并由 GetVxTableEntry 调用,用于初始化 g_NtdllConf 全局结构体。这允许 GetVxTableEntry 访问 NTDLL 头部的数值,而无需每次都传递额外的参数或进行重复计算。

重命名 & 更新 GetVxTableEntry#

GetVxTableEntry 被重命名为 FetchNtSyscall,它将有两个参数:dwSysHash(要获取 SSN 的指定 Syscall 的哈希值)和 pNtSys(一个指向 NT_SYSCALL 结构体的指针),该结构体包含执行直接 Syscall 所需的所有信息。

typedef struct _NT_SYSCALL
{
	DWORD dwSSn;                    // 系统调用号 (SSN)
	DWORD dwSyscallHash;            // 系统调用哈希值
	PVOID pSyscallAddress;          // 系统调用内存地址
    
} NT_SYSCALL, *PNT_SYSCALL;
c

FetchNtSyscall 函数执行以下操作:

  1. 检查全局 g_NtdllConf 结构体是否已初始化。如果没有,则调用 InitNtdllConfigStructure 进行初始化。
  2. 检查用户是否指定了哈希值。
  3. 启动一个循环,使用哈希值搜索指定的 Syscall。
  4. 找到 Syscall 后,将其地址保存到 pNtSys 结构体中。
  5. 启动一个 while 循环 (Hell’s Gate 逻辑) 或升级后的逻辑 (TartarusGate) 来搜索 SSN。

增强 SSN 检索逻辑 (TartarusGate)#

原理#

Hell’s Gate 在搜索 SSN 时,会检查 syscall (0x0f, 0x05) 或 ret (0xc3) 指令。一旦遇到这些指令但仍未找到 SSN(例如因为被 Hook 了),搜索就会失败。

TartarusGate 利用了相邻的 Syscall 来计算指定 Syscall 的 SSN。

  • 如果向搜索(低地址方向),Syscall B 的 SSN = Syscall A 的 SSN + 1
  • 如果向搜索(高地址方向),Syscall B 的 SSN = Syscall C 的 SSN - 1

甚至可以跨越多个函数: Syscall C 的 SSN =

  • A 的 SSN + 2
  • B 的 SSN + 1
  • D 的 SSN - 1
  • E 的 SSN - 2

如下图所示,idx 是需要加减的偏移量。

图片
图片

更新 FetchNtSyscall 实现#

了解了 TartarusGate 的工作原理后,我们更新 FetchNtSyscall 函数以使用该搜索逻辑。

  • RANGE 设置为 255,表示向上或向下搜索内存的最大 Syscall 数量。
  • UP 等于 32,这是通常一个 Syscall Stub 的大小。用于向上搜索 (地址增加)。
  • DOWN 等于 -32。用于向下搜索 (地址减少)。

更新汇编函数 (混淆)#

HellsGateHellDescent 函数被替换为 SetSSnRunSyscall。这里添加了一些不会执行的垃圾代码 (Junk Code) 来干扰静态分析。

混淆后的汇编函数#

总结#

TartarusGate 是 Hell’s Gate 的重要升级,它解决了原始版本在遇到已被 EDR 挂钩的函数时无法获取 SSN 的痛点。通过“查询邻居”的策略,只要 NTDLL 中还有未被挂钩的函数,我们就能推算出目标函数的 SSN。结合 CRC32 哈希和简单的汇编混淆,这构成了一个强大的直接系统调用执行框架。