第二十二课:恶意代码分析实战(三)——Rootkit样本分析

好,咱们今天聊点硬核的。Rootkit,这玩意儿在安全圈里,属于那种「听着就头疼」的东西。它不像普通木马那样,藏个文件、改个注册表就完事了。Rootkit 的目标是——让你根本看不到它。

我早年做应急响应时,遇到过一台服务器,CPU 跑满,网络连接异常,但用 Process Explorer 看,干干净净。后来用 WinDbg 一挂,才发现内核层被人动了手脚。嗯,那就是个典型的 Rootkit。

Rootkit 的核心能力

说白了,Rootkit 就是一套「内核级隐身术」。它通过加载驱动进入内核态,然后对系统进行各种 Hook,让你用常规手段根本查不到它。

常见的功能包括:

  • 驱动加载:通过服务管理器或手动映射,把恶意驱动加载到内核
  • 隐藏进程:从 EPROCESS 链表中摘除自己的进程节点
  • 隐藏文件:Hook 文件系统相关 API,过滤目录查询结果
  • 内核 Hook:修改 SSDT、IDT 或直接 Patch 内核函数

关键认知:Rootkit 的可怕之处不在于它做了什么,而在于你「看不到它做了什么」。分析 Rootkit,本质上是在跟一个「会躲猫猫的对手」博弈。

驱动加载:Rootkit 的入口

Rootkit 要进内核,最正统的方式是加载一个驱动程序。Windows 下驱动加载的路径主要有两条:

  1. 服务管理器(SCM):通过 CreateService + StartService,这是常规驱动加载方式,容易被监控
  2. 手动映射(Manual Map):自己解析 PE 文件,分配内核内存,修复导入表,然后调用 DriverEntry。这种方式更隐蔽,但实现复杂

我见过一个样本,它用的是第二种方式。驱动文件本身是加密的,运行时才解密,然后通过一个合法的签名驱动做跳板,把恶意驱动映射进内核。嗯,这种手法在 APT 攻击中很常见。

隐藏进程:从内核链表中「消失」

Windows 内核维护着一个双向链表,把所有进程的 EPROCESS 结构串起来。任务管理器、Process Explorer 这些工具,都是遍历这个链表来获取进程列表的。

Rootkit 的隐藏手法很简单——把自己的 EPROCESS 节点从链表中摘除。代码大概长这样:

// 伪代码:从进程链表中摘除自己
VOID HideProcess(PEPROCESS EProcess) {
    PLIST_ENTRY ListEntry = &EProcess->ActiveProcessLinks;
    
    // 摘除节点
    ListEntry->Flink->Blink = ListEntry->Blink;
    ListEntry->Blink->Flink = ListEntry->Flink;
    
    // 把自己指回自己,防止遍历时崩溃
    ListEntry->Flink = ListEntry;
    ListEntry->Blink = ListEntry;
}

避坑指南:我曾经调试一个样本时,发现进程虽然隐藏了,但句柄表里还有残留。后来才意识到,摘除链表只是第一步,还得清理句柄表中的引用。否则,某些内核组件依然能通过句柄找到它。

隐藏文件:Hook 文件系统

文件隐藏比进程隐藏更复杂。因为文件系统涉及多层:应用层 API(NtQueryDirectoryFile)、内核层 IRP 分发、以及底层文件系统驱动。

常见的做法是 Hook NtQueryDirectoryFile 这个系统调用。每次查询目录时,Rootkit 会检查返回的文件名,如果匹配自己的隐藏列表,就从结果中移除。

我见过一个比较狡猾的实现——它不直接修改返回数据,而是修改 IRP 的完成例程。这样,在数据返回给调用者之前,它有机会「过滤」一遍。嗯,这种手法更难检测,因为 Hook 点更深。

内核 Hook:SSDT 与 Inline Hook

内核 Hook 是 Rootkit 的「核心技能」。主要有两种方式:

Hook 类型 原理 检测难度
SSDT Hook 修改系统服务描述表中的函数指针 中等
Inline Hook 直接修改函数开头的字节码,跳转到恶意代码 较高

SSDT Hook 比较「文明」,只是改个指针。但 Windows 有 PatchGuard(内核保护),会定期检查 SSDT 是否被篡改。所以现代 Rootkit 更倾向于 Inline Hook——直接修改函数的前几个字节,比如:

// Inline Hook 示例:修改 NtCreateFile 开头
// 原始字节:4C 8B D1 48 8B 49 F8 (mov r10, rcx; mov rcx, [rcx-8])
// 修改后:E9 xx xx xx xx (jmp MyHookFunction)

BYTE JmpCode[] = {
    0xE9,           // jmp 指令
    0x00, 0x00, 0x00, 0x00  // 相对偏移,运行时计算
};

// 计算跳转偏移
ULONG_PTR TargetAddr = (ULONG_PTR)MyHookFunction;
ULONG_PTR SourceAddr = (ULONG_PTR)NtCreateFile;
*(ULONG*)(JmpCode + 1) = (ULONG)(TargetAddr - SourceAddr - 5);

// 写入 Hook
RtlCopyMemory(NtCreateFile, JmpCode, 5);

注意:Inline Hook 有个坑——如果你只修改了前 5 个字节,但原始指令长度超过 5 字节,那被覆盖的指令就不完整了。我见过一个样本因此导致系统蓝屏,因为它没处理好指令对齐。嗯,写 Rootkit 也是个技术活,写不好就自爆了。

使用 WinDbg 进行内核调试

分析 Rootkit,WinDbg 是必备工具。我个人习惯用双机调试:一台被调试机(跑样本),一台调试机(跑 WinDbg)。通过串口或网络连接。

常用命令:

  • !process 0 0:列出所有进程,看有没有隐藏的
  • !drvobj:查看驱动对象,检查异常驱动
  • !ssdt:查看 SSDT 表,检查函数指针是否被篡改
  • lm:列出已加载模块,看有没有可疑驱动
  • !chkimg:检查内核模块是否被 Patch

我记得有一次,我用 !process 0 0 发现进程数比任务管理器里多了两个。嗯,那就是被隐藏的进程。然后我用 !thread 查看它们的线程栈,顺藤摸瓜找到了恶意驱动。

知识体系结构图

下面这张图,帮你理清 Rootkit 分析的核心脉络:

Rootkit 分析知识体系 Rootkit 核心能力 驱动加载 隐藏进程 隐藏文件 内核 Hook SCM / 手动映射 摘除 EPROCESS 链表 Hook NtQueryDirectoryFile SSDT / Inline Hook 分析工具:WinDbg 内核调试 !process 0 0 !ssdt !chkimg lm / !drvobj

实战分析流程

拿到一个 Rootkit 样本,我一般按这个步骤来:

  1. 静态分析驱动文件:看导入表、字符串、是否有数字签名
  2. 加载到虚拟机:用 WinDbg 双机调试,设置内核断点
  3. 监控驱动加载:用 !drvobjlm 检查新加载的驱动
  4. 检查 SSDT 和内核模块:用 !ssdt!chkimg 看有没有被 Hook
  5. 对比进程列表:用 !process 0 0 和任务管理器对比,找隐藏进程
  6. 跟踪文件操作:在文件系统相关函数上下断点,看有没有过滤行为

个人经验:我建议你在分析前,先拍一张「快照」——记录下干净的 SSDT 表、进程列表、驱动列表。这样样本加载后,对比起来一目了然。我曾经因为没拍快照,浪费了半天时间在找「到底哪个函数被 Hook 了」。

Rootkit 分析,说白了就是一场「猫鼠游戏」。你越了解内核的工作机制,就越能发现那些「不自然」的地方。嗯,这也是逆向工程的魅力所在——你永远不知道下一个样本会用什么新花样。


公众号:蓝海资料掘金营,微信 deep3321