手写包装器与复制ntdll机器码实现Windows系统调用的差异
Windows系统调用两种实现方法的差异
方法一:自定义汇编硬编码系统调用
该方法通过手动编写包含系统调用核心Opcode的汇编文件,在C代码中声明函数原型后直接调用,完全不依赖系统自带的ntdll.dll函数实现。
示例代码
syscall.asm
.code SysNtCreateFile proc mov r10, rcx mov eax, 55h syscall ret SysNtCreateFile endp end
main.c
// 头文件及初始化代码省略 EXTERN_C NTSTATUS SysNtCreateFile( PHANDLE FileHandle, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes, PIO_STATUS_BLOCK IoStatusBlock, PLARGE_INTEGER AllocationSize, ULONG FileAttributes, ULONG ShareAccess, ULONG CreateDisposition, ULONG CreateOptions, PVOID EaBuffer, ULONG EaLength); // 声明外部函数后直接调用 SysNtCreateFile(....); // 调用会执行对应的汇编指令触发系统调用
方法二:提取ntdll.dll原生函数字节码执行
该方法从系统的ntdll.dll中提取目标系统调用函数的起始字节码,将其复制到标记为可执行的内存缓冲区后,转换为函数指针执行,本质是复用系统原生的系统调用入口逻辑。
示例代码
// 1. 打开并读取系统中的ntdll.dll文件 // 2. 解析导出表定位到SysNtCreateFile函数的内存地址 char sysntcreatefilebytes[20] = {0}; // 将缓冲区设置为可读可写可执行(RWE)权限 // 复制SysNtCreateFile函数起始位置的20字节到缓冲区 memcpy(sysntcreatefilebytes, GetProcAddress(ntdllHandle, "NtCreateFile"), 20); // 定义函数指针类型 typedef NTSTATUS (*SysNtCreateFile)(PHANDLE, ACCESS_MASK, ...); // 将缓冲区地址转换为函数指针 SysNtCreateFile myfunc = (SysNtCreateFile)sysntcreatefilebytes; // 调用函数 myfunc(..., ..., ...);
核心差异对比
依赖与实现逻辑
- 方法一:硬编码系统调用指令和调用号,完全独立于ntdll.dll,属于手动实现系统调用的底层逻辑。
- 方法二:依赖系统自带的ntdll.dll,直接复用其内部的系统调用入口代码,本质是对原生函数的"内存拷贝复用"。
跨版本兼容性
- 方法一:系统调用号(如示例中的
55h)是固定值,不同Windows版本(Win10/Win11/Server系列)的系统调用号可能不同,跨版本运行容易出错。 - 方法二:提取的是当前系统ntdll中的原生字节码,自动适配当前系统的调用号和指令序列,跨版本兼容性更强。
- 方法一:系统调用号(如示例中的
安全检测风险
- 方法一:自定义的汇编指令序列与系统原生的ntdll函数入口存在差异,容易被EDR、杀毒软件识别为异常行为,检测风险高。
- 方法二:完全复制系统原生的函数字节码,调用行为与正常程序一致,被检测的概率相对更低。
灵活性
- 方法一:需要提前知晓目标系统调用的编号,且每个系统调用都需要单独编写汇编代码,灵活性差。
- 方法二:只需解析ntdll的导出表即可提取任意系统调用的字节码,无需提前了解调用号,灵活性更高。
实现复杂度
- 方法一:仅需编写少量汇编代码,C侧只需声明函数原型即可调用,实现简单。
- 方法二:需要处理ntdll加载、导出表解析、内存权限修改、字节拷贝等多个步骤,实现相对复杂。
内容的提问来源于stack exchange,提问作者nahjoh44
相关产品推荐
相关产品推荐

