0%
毅种循环

模块 89 - 间接系统调用 (Indirect Syscalls) - HellsHall

恶意软件开发课程 - 间接系统调用 (Indirect Syscalls) - HellsHall


模块 89 - 间接系统调用 (Indirect Syscalls) - HellsHall#

间接系统调用 (Indirect Syscalls) - HellsHall#

简介#

在上一模块中,我们更新了 Hell’s Gate 的实现 (TartarusGate),提高了它获取被挂钩 Syscall 的 SSN 的能力。然而,该实现仍然依赖于直接系统调用 (Direct Syscalls),即 syscall 指令是在本地进程的地址空间内执行的,而不是在它本该执行的地方——ntdll.dll

EDR 和其他安全解决方案可以通过回调 (Callbacks) 检测直接系统调用。当程序流在用户模式和内核模式之间转换(即执行 syscall 或返回时),这些回调会被触发。

例如,如果安全解决方案使用带有 ProcessInstrumentationCallback 标志的 NtSetProcessInformation,它可以设置一个回调函数,每当执行流从内核返回到用户模式时就会执行该回调。该回调函数可以分析执行 syscall 的指令地址是否位于 ntdll.dll 的地址空间内。

  • 问题:如果在我们自己的 .exe 或 .dll 中直接写 syscall 汇编指令,EDR 看到指令指针 (RIP) 指向我们的代码区域,就会判定为异常。
  • 解决:我们需要使用 间接系统调用 (Indirect Syscalls)
  • 使用 HellsHall 技术,我们不再直接执行 syscall,而是跳转 (JMP) 到 ntdll.dll 内部现有的 syscall 指令上。

💡 初学者提示:什么是间接系统调用?

类比理解: 想象你要进入一个守卫森严的皇宫(内核)

  • 直接系统调用:其是你在皇宫围墙外自己挖了一个地道(在自己的代码里写 syscall)钻进去。守卫(EDR 回调)看到你从地道钻出来,立刻就知道你是非法入侵者。
  • 间接系统调用:你发现皇宫大门(NTDLL)有一个合法的跳板(Trampoline)。你先跳到这个合法的跳板上,利用惯性被弹进皇宫。守卫看到你是从正门方向进来的,就会认为你是合法的访客。

下图展示了间接系统调用的原理。

图片
图片

寻找 Syscall 地址#

我们将继续使用上一模块的代码,因为仍然需要指定 Syscall 的 SSN。唯一的区别在于汇编函数:我们需要用 jmp 指令替换 syscall 指令。jmp 指令需要一个跳转地址,如前所述,这个地址必须位于 ntdll.dll 内部。

任何有效的 syscall 指令地址都可以使用,但最好使用与当前调用的 Syscall 不同的函数的 syscall 指令。这是一种混淆策略。

例如,如果要调用 NtAllocateVirtualMemory,最好跳转到内存中不属于 NtAllocateVirtualMemorysyscall 指令地址,比如跳转到 ZwWriteFileGathersyscall 指令地址。

图片
图片

更新 NT_SYSCALL 结构体#

为此,新引入的 NT_SYSCALL 结构体现在将包含一个新成员 pSyscallInstAddress。该成员保存 NTDLL 中随机 syscall 指令的地址。

typedef struct _NT_SYSCALL
{
    DWORD dwSSn;                    // 系统调用号
    DWORD dwSyscallHash;            // 系统调用哈希值
    PVOID pSyscallAddress;          // 系统调用函数的地址
    PVOID pSyscallInstAddress;      // ntdll 中随机 'syscall' 指令的地址 (跳板地址)    

} NT_SYSCALL, * PNT_SYSCALL;
c

更新 FetchNtSyscall#

下一步是修改 FetchNtSyscall 函数以搜索 syscall 指令地址。更新后的代码执行以下操作:

  1. 检查是否成功获取了 Syscall 的地址。
  2. 将 Syscall 函数地址加上 0xFF (255 字节),从此位置开始搜索 syscall 指令。原因是在远离初始 Syscall 的随机函数中搜索 syscall 指令,增加随机性。
  3. 启动一个 for 循环,搜索操作码 0x0f0x05 (代表 syscall 指令)。
  4. 找到匹配项后,将 pSyscallInstAddress 设置为检索到的 syscall 指令的地址。

更新 SetSSn 和 RunSyscall#

回忆一下用来启动 Syscall 的汇编函数。

以前,SetSSn 只需要 SSN。现在,SetSSn 还需要另一个值 qSyscallInsAdress,即要跳转到的 syscall 指令的地址。初始化这些值后,RunSyscall 将执行它们。

混淆后的汇编函数#

创建辅助宏#

为了更方便地调用 SetSSn,我们创建了 SET_SYSCALL 宏。

#define SET_SYSCALL(NtSys)(SetSSn((DWORD)NtSys.dwSSn,(PVOID)NtSys.pSyscallInstAddress))
c

演示#

  1. 在 x64dbg 中运行 HellsHall 实现。
  2. NtAllocateVirtualMemory 实际上在使用 NtReleaseMutantsyscall 指令地址 (0x00007FF8308E3F82)。
  3. 执行 NtAllocateVirtualMemory 时,触发断点,显示 syscall 指令确实是从 ntdll.dll 内部执行的。

图片
图片

总结#

最佳方法是使用 HellsHall 实现,以规避基于直接系统调用检测的安全解决方案。为了进一步增强规避能力,建议结合使用 HellsHall 和前面学到的 Unhooking 技术。

请注意,GitHub 上有一个公共版本的 HellsHall,但缺少某些功能。本模块中解释的版本包含更多功能。