NtCall64源码解析(一):如何用6字节模式匹配在ntoskrnl中定位KiServiceTable——Windows x64系统调用模糊测试器入门
2026/8/25 17:38:23 网站建设 项目流程

NtCall64源码解析(一):如何用6字节模式匹配在ntoskrnl中定位KiServiceTable——Windows x64系统调用模糊测试器入门

【免费下载链接】NtCall64Windows NT x64 syscall fuzzer项目地址: https://gitcode.com/gh_mirrors/nt/NtCall64

NtCall64 是一个 Windows NT x64 系统调用(syscall)模糊测试器(fuzzer),它的任务是用海量随机参数暴力调用系统服务表中的每一个入口点,从而挖出内核漏洞。而这一切的起点,是在只读的 ntoskrnl.exe 镜像里找到KiServiceTable——这篇文章带你读懂 NtCall64 如何用短短 6 字节的字节码模式匹配,完成这个"寻宝"过程。

为什么服务表是整个工具的"命门"

模糊测试 syscall 需要三样东西:

需要用途
服务表地址(KiServiceTable)第 N 个 syscall 的函数入口就在这里
条目总数(KiServiceLimit)知道要 fuzz 多少个系统调用
栈参数表(KiSystemCallStackArgumentTable)判断每个 syscall 需要推几个参数

这三个值在ntoskrnl中是私有符号——没有导出表可以直接GetProcAddress。NtCall64 因此走了一条经典的内核逆向路线:特征码扫描 + 相对跳转解引用。核心实现位于Source/NtCall64/sup.c中的supFindKiServiceTable函数(约 1279 行起)。

前置步骤:以"只读+不可执行"方式映射内核镜像

定位服务表之前,main.cFuzzInitPhase2函数(约 136 行起)先通过supMapImageNoExecute%SystemRoot%\system32\ntoskrnl.exe映射进自己的用户态进程:

  • NtCreateSection+SEC_IMAGE_NO_EXECUTE创建节对象
  • NtMapViewOfSectionPAGE_READONLY映射

💡 这一步很关键:映射是只读且不可执行的,NtCall64 只是"读"内核镜像来解析结构,绝不会执行内核代码,这是它能在用户态安全运行的前提。

6字节魔数:KiSystemService 的"指纹"

sup.c中定义了这样一个模式(约 1290 行):

const BYTE KiSystemServiceStartPattern[] = { 0x45, 0x33, 0xC9, 0x44, 0x8B, 0x05 };

拆开看,这两条指令正是 64 位KiSystemService的开头(微软编译器生成的固定序言):

字节反汇编含义
45 33 C9xor ebp, ebp函数序言第一条指令
44 8B 05mov r8, QWORD PTR [rip+disp32]RIP 相对寻址取 64 位常量

选择它的理由很朴素:

  1. 足够短——6 字节扫描快,误报概率低;
  2. 足够稳——xor ebp,ebp是现代 MSVC x64 函数几乎必然的开头,且紧跟一条rip+disp32取指指令,在KiSystemService入口处是长期稳定的结构。

扫描前还要先锁定代码段

注意 NtCall64 并不扫描整个镜像,而是先遍历 PE 节表(sup.c约 1301 行),寻找名为PAGELK的节——ntoskrnl 的可执行代码节。找到后,用RtlCompareMemory逐字节滑动比对这 6 个字节(约 1325 行),命中即得KiSystemService在节内的偏移。

连续三跳:从入口点解析出三张关键表

找到入口点只是第一步。KiSystemService的开头紧接着三条 7 字节的 RIP 相对跳转指令,分别指向三张表。supFindKiServiceTable(约 1338 行起)用同一套算术连跳三次:

目标地址 = disp32 + 7 + 当前指令地址 (7 = 该跳转指令自身长度)

每次解出地址后指针前进 7 字节,处理下一条:

第几跳解析出的符号存入结构体
1KiServiceLimitCountOfEntries
2KiSystemCallStackArgumentTableStackArgumentTable
3KiServiceTableServiceTable

解析结果写入global.h(约 58 行)定义的RAW_SERVICE_TABLE结构体,main.csupFindKiServiceTable成功后就带着这张表进入FuzzRun开始正式 fuzz。

对照组:win32k 根本不需要模式匹配 🆚

-win32k参数时,NtCall64 改为 fuzz 图形子系统的影子 SSDT。有趣的是,supFindW32pServiceTablesup.c约 1359 行)完全不用特征码——win32k.sys 直接导出了W32pServiceLimitW32pArgumentTableW32pServiceTable三个符号,通过supGetProcAddressEx(在导出一节里二分查找)一步到位即可。

这个对比恰好说明设计取舍:ntoskrnl 把服务表藏起来,win32k 却大方导出,所以同一工具对两套表用了两种定位策略。

服务表如何驱动 fuzzing

拿到表之后(fuzz.c约 375 行起):

  • StackArgumentTable[sid] / 8得出该 syscall 的栈参数个数(64 位下每参数 8 字节)
  • fuzz_data.h中精心设计的参数值(句柄模式、边界值、随机可识别值等)填充,循环syscall指令逐个轰击
  • Source/badcalls.ini则用于拉黑会关机/挂起系统的危险调用,如NtShutdownSystem

本篇小结

NtCall64 定位 KiServiceTable 的完整链路可以记为一句话:只读映射 ntoskrnl.exe → 锁定 PAGELK 代码节 → 6 字节模式匹配 KiSystemService → 三连跳解析 Limit / 参数表 / 服务表

涉及的源码文件清单:

  • 服务表定位核心:Source/NtCall64/sup.c(supFindKiServiceTable / supMapImageNoExecute)
  • 初始化与调度:Source/NtCall64/main.c(FuzzInitPhase2)
  • 数据结构定义:Source/NtCall64/global.h(RAW_SERVICE_TABLE)
  • fuzz 主循环:Source/NtCall64/fuzz.c
  • 参数素材库:Source/NtCall64/fuzz_data.h
  • 危险调用黑名单:Source/badcalls.ini

⚠️ 安全提示:NtCall64 专为受控的实验室环境设计,可能导致系统崩溃或数据丢失,请勿在存有重要数据的生产机器上运行。

(下一篇将深入 fuzz 参数构造:-h启发式模式与参数矩阵是怎么生成的。)

【免费下载链接】NtCall64Windows NT x64 syscall fuzzer项目地址: https://gitcode.com/gh_mirrors/nt/NtCall64

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询