SyscallRevenant 是一种执行干净间接系统调用的新方法。
主要组件动态解析系统服务号(SSN)和 syscall 指令地址。
这是通过在执行期间启用陷阱标志来实现的。
次要组件是在 syscall 之前通过参数替换进行篡改。
这种组合旨在在随后的系统调用调用期间绕过用户态钩子,同时替换最初呈现的参数。
免责声明:此项目的用途仅限教育目的和测试。 请勿在您没有权限使用的机器上使用。 请勿使用此项目来利用和与您无权使用的机器进行通信。
间接系统调用是一种在受信任的用户态空间中执行 syscall 指令的方法。
如果用户找到了一条 syscall 指令,他们可以间接跳转到它。
在跟踪会话期间启用的事件可以揭示指令之后立即的返回地址。
这并未考虑端点检测与响应(EDR)用户态钩子。
EDR 可能会在 ntdll.dll 中插入钩子,从而使 SSN 和 syscall 指令的查找过程失效。
进入钩子将有利于所属线程,而不会提高恶意评分。
观察进入函数和观察结果操作是不同的事情。
RevenantSyscalls 研究了这一差距。
以下各节概述了该项目的两个主要组件。
第一个组件从进程加载的 ntdll.dll 中解析函数地址。
如果需要系统调用信息,则可以执行陷阱遍历。
第二个组件对保存的函数信息执行间接跳转。
从段寄存器读取可以获取进程环境块(PEB)。
LoaderData 是 PEB 中的一个字段,包含随每个进程加载的数据。
LoaderData 包含有关随每个进程一起加载的模块的信息。
模块名称和基地址存在于模块条目中。
动态链接库(DLL)的导出信息描述了它导出的符号。 导出元数据支持定位导出的符号。 函数的索引仅适用于名称和名称序号。 对函数名进行哈希处理是低内存环境下的最佳选择。
向量化异常处理程序是一种异常处理机制,可以捕获进程内任何位置的异常。
启用陷阱标志会导致下一条执行的指令上出现 EXCEPTION_SINGLE_STEP 异常。
自定义向量化异常处理程序可以检查 RIP 并检查是否为 syscall。
通过反复重置陷阱标志,将发现 syscall 地址并保存 SSN。
系统调用查找
|
系统调用执行
|
每个系统调用都有初始的系统调用信息查找; 将进入钩子以检索系统调用信息。 继续,将进行间接跳转到系统调用。 将有一个关联的 RIP 欺骗(来自原始入口)。 重新进入时,初始参数已被篡改。
从 Windows 事件跟踪(ETW)的角度来看,可能发出的事件是:SysEnter、SysExit 以及两者的 StackWalk。
如果 EDR 构建类似、可比较的事件,这些事件看起来几乎相同。
唯一的区别将在于截然不同的查询性能计数器(QPC)时间戳。
src/AsmRevenant.asm REAL_RTN: DQ 0
FAKE_RTN: DQ 0
; If a syscall information lookup is in progress, the
; initial entry return address needs to be preserved.
MOV RAX, QWORD PTR [RSP]
MOV QWORD PTR [FAKE_RTN], RAX
; Once the re-entry is made, the real return is saved,
; and the current is spoofed with initial return.
MOV RAX, QWORD PTR [RSP]
MOV QWORD PTR [REAL_RTN], RAX
MOV RAX, QWORD PTR [FAKE_RTN]
MOV QWORD PTR [RSP], RAX
src/Revenant.cLONG VectoredExceptionHandler(EXCEPTION_POINTERS *BreakPoint) {
...
// If a syscall information lookup is being made, the
// syscall instruction address is saved, the SSN is
// saved, and the return is rerouted.
if (LookupInProgress && *(USHORT*)BreakPoint->Address == 0x050F) {
SyscallInstruction = BreakPoint->Address;
SyscallSSN = BreakPoint->Context->Rax;
BreakPoint->Context->Rip = SyscallExit;
}
// When re-entered, if the previous instruction was
// a syscall, reset return address, and exit.
if (!LookupInProgress && *(USHORT*)(BreakPoint->Address - 2) == 0x050F) {
*(VOID**)BreakPoint->Context->Rsp = REAL_RTN;
BreakPoint->Context->Rip = SyscallExit;
}
...
}