
拆解57行汇编NtCall64如何用syscall指令绕过ntdll发起任意Windows系统调用【免费下载链接】NtCall64Windows NT x64 syscall fuzzer项目地址: https://gitcode.com/gh_mirrors/nt/NtCall64一句话导读NtCall64是一个 Windows NT x64 系统调用模糊测试syscall fuzzer工具它不依赖 ntdll.dll 的 API 封装而是通过一段仅 57 行的 x64 汇编ntSyscallGate直接以syscall指令向内核发起任意编号的系统调用。今天带你逐段读懂这 57 行代码背后的设计。为什么必须绕过 ntdll日常开发中你调用NtClose、NtCreateFile等系统服务走的是ntdll.dll导出函数。但 ntdll 对每个 API 都做了打包固定参数个数你无法给NtClose塞进 8 个参数内置参数校验与预处理某些调用会被改写后才进内核覆盖不全ntdll 并未导出内核服务表里的所有条目。而模糊测试fuzzing恰恰需要随便挑一个服务号、随便给多少参数、参数值任意。这些在 ntdll 的封装层都做不到。因此 NtCall64 选择最原始的路径——自己写syscall指令直接对话内核服务表。整段 syscall 网关就藏在 syscall.asm 这个 57 行的小文件里。参数约定三个寄存器搞定一切; param 1 (rcx) service ID ; param 2 (rdx) service arguments count ; param 3 (r8) pointer to array of arguments按 x64 Windows 调用约定函数前 4 个参数依次放在rcx / rdx / r8 / r9。ntSyscallGate约定了极简的三参数契约寄存器含义rcx系统调用服务号service ID将写入rax供内核查表rdx该调用要传的参数个数r8指向参数数组的指针args[0]是第 1 个参数……第一段超过 4 个参数的栈填充循环x64 系统调用约定中前 4 个参数走寄存器rcx, rdx, r8, r9第 5 个起的参数必须压在用户栈上内核入口按固定偏移去读。所以参数超过 4 个时汇编要先手动往栈里填数据ntSyscallGate PROC cmp rdx, 5 ; 参数个数 ≤ 4不需要栈 jl nostack sub rdx, 4 ; 还要往栈里放几个参数 xor r9, r9 fillstack: mov rax,[r9*8r8020h] ; 从参数数组取 mov [r9*8rsp028h], rax ; 写到栈上 inc r9 dec rdx jne fillstack几个细节值得注意cmp rdx, 5 / jl参数个数 5直接跳过不进循环零开销循环从r8020h即args[4]第 5 个参数开始取恰好跳过前 4 个寄存器参数写入目标[rsp028h]这是按syscall指令执行瞬间的栈顶计算的固定偏移。因为 x64 的ret只弹 8 字节而这里全程用[rsp固定偏移]寻址、从不增减rsp所以栈帧稳定偏移算一次即可每次循环r9自增 1、rdx自减 1直到剩余参数填完跳回nostack。第二段寄存器装载与真正的 syscallnostack: mov r10, r8 ; 记住参数数组基址 mov rax, rcx ; 服务号 → rax内核靠它查服务表 mov rcx, [r10] ; 参数 1 mov rdx, [r1008h] ; 参数 2 mov r8, [r1010h] ; 参数 3 mov r9, [r1018h] ; 参数 4 mov r10, rcx syscall ret逐行解读mov rax, rcx—— 这是整个任意系统调用的核心内核入口从rax读取服务号再拿它去查KiServiceTable找到对应的内核处理函数。rax填什么就跑哪个服务——任意二字由此而来四条mov把参数数组的前 4 项装进rcx/rdx/r8/r9完成寄存器传参mov r10, rcx—— 第 5 条指令执行syscall时会压栈寄存器传参协议要求r10保持第一个参数值供内核入口在换栈后取用这是 Windows x64 调用约定的硬性规定一行都不能省syscall—— CPU 陷入内核控制权交给KiSystemCall64入口。内核用第 5 个参数起读栈上[rsp028h]之后的位置正好对上前面循环填好的地址返回后rax里就是系统调用的NTSTATUS返回值ret结束函数。前后不足 10 行实现了一个万能系统调用发射器。与 C 层 fuzzer 如何配合汇编函数被 C 代码当作普通函数调用。在 fuzz.c 中声明原型NTSTATUS ntSyscallGate(ULONG ServiceId, ULONG ArgumentCount, ULONG_PTR* Arguments);核心流程是发现服务表sup.c 中的supFindKiServiceTable把ntoskrnl.exe以不可执行方式映射进内存扫描出KiServiceTable服务函数表、KiServiceLimit服务数量以及KiArgumentTable每个服务期望的参数个数填入 global.h 定义的RAW_SERVICE_TABLE结构生成参数DoSystemCall按参数表计算要传几个参数用模糊参数生成器可选启发式模式填充args[]数组发射调用一行status ntSyscallGate(ServiceId, paramCount, args)完成整个系统调用返回的 NTSTATUS 计入成功/失败统计用-win32k参数时则切换去模糊测试图形子系统的 Shadow SSDTW32pServiceTable。这就是fuzzer syscall 网关的完整闭环服务号任意 → 参数任意 → 一次 syscall 直达内核。写在最后为什么值得读这 57 行它浓缩了 x64 Windows 系统调用约定的全部要点rax 服务号、四寄存器传参、r10 规则、栈参数偏移它解释了为什么模糊测试必须自己下场ntdll 的封装对攻击面探索是障碍不是桥梁代码极短但每一行都有用途没有一条冗余指令是研读系统级汇编的好样本。⚠️安全提示NtCall64 会向内核投递大量畸形参数可能导致系统蓝屏或数据丢失请务必在虚拟机等受控环境中使用。【免费下载链接】NtCall64Windows NT x64 syscall fuzzer项目地址: https://gitcode.com/gh_mirrors/nt/NtCall64创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考