概述
在第二部分中,我们探讨了通过篡改PspCidTable来隐藏Rootkit线程。在这三部曲的最后一部分,我们将移除Rootkit中的主线程,实现一种"无线程"的用户到内核通信。
用户到内核通信
既不能使用IOCTL,也不能使用循环内核线程——那么我们如何从用户态调用内核代码?
每次执行Nt*系统调用时,你都在做这件事——设置参数并进入系统调用中断,将控制权转移到内核。因此我们可以对内核代码进行跳板钩子。
但直接在.text段做钩子和驱动踩踏一样嘈杂。如果驱动程序从.data段调用函数指针呢?
.DATA指针交换
.data段包含所有静态变量,在运行时可能发生变化。可以从用户态发起并调用存储在.data段中函数指针的函数随处可见。会话驱动程序Win32k.sys、win32kbase.sys和win32kfull.sys包含许多此类函数。
利用kCFG寻找指针
内核控制流保护(kCFG)实际上为我们提供了一个寻找指针的方便方法。__guard_dispatch_icall_fptr_是kCFG插入的调用,通过交叉引用对该函数的调用,我们就得到了驱动程序中所有.data指针的列表。
如果VBS被禁用,kCFG检查基本上被绕过——guard_icall_bitmap和retpoline_image_bitmap都为NULL。
综合运用
将此技术与第一部分中的共享内存技术结合,我们可以使用任何指针,无论参数数量如何。我们只需将命令写入共享内存区域,让被钩住的指针触发Rootkit从缓冲区轮询命令。
选择目标函数
以NtUserCreateWindowStation为例,它包装了ApiSetEditionCreateWindowStationEntryPoint。该函数中的.data指针在一个简单检查后被调用,任何对该例程的调用都会触发我们的Rootkit代码。
实现步骤
- 签名扫描找到
NtUserCreateWindowStation - 在其附近搜索
.data指针模式 - 用
_InterlockedExchangePointer交换指针 - 在钩子函数中轮询共享内存对象中的命令负载
- 用户态客户端写入命令到共享内存,触发钩子
检测向量
检测这些技术的直观方法是查找标准驱动程序中所有可能的.data指针,检查它们是否指向由驱动程序支持的内存。然而检查所有指针是一项艰巨任务,因为可以使用指针链。
所有来自第一部分的基于调用栈的检测方法(尤其是基于NMI的方法)仍然有效。但我们确实减少了驱动程序的检测面——指向我们内存的系统线程仅在Rootkit代码实际需要执行时才运行。
总结
这结束了关于反反Rootkit措施的三部曲。通过交换良性驱动.data段中的指针实现无线程通信,大大减少了被检测的可能性。仍有更多可以探讨和发现的内容。
