首页
社区
课程
招聘
[原创]Windows x64 系统调用链:从用户态到 NtCreateFile
发表于: 8小时前 153

[原创]Windows x64 系统调用链:从用户态到 NtCreateFile

8小时前
153

整体调用链概览

用户态 App
    │
    ▼
NtCreateFile (ntdll.dll)        ← 用户态存根函数
    │  MOV EAX, syscall_number
    │  SYSCALL
    ▼
═══════════════════════════════════ Ring3 → Ring0 切换 ═══════════════════
    │
    │  CPU 读取 MSR_LSTAR (0xC0000082) 获取入口地址
    ▼
KiSystemCall64 (ntoskrnl.exe)   ← 内核态系统调用入口
    │
    ▼
KiSystemServiceRepeat           ← 查 SSDT / KiServiceTable
    │
    ▼
nt!NtCreateFile (ntoskrnl.exe)  ← 真正的内核实现

1. 用户态:ntdll!NtCreateFile 存根

ntdll!NtCreateFile:
    mov   r10, rcx            ; SYSCALL 会用 rcx 保存返回地址,所以先搬走第一个参数
    mov   eax, 0x55           ; 系统调用号(不同 Windows 版本不同)
    test  byte ptr [SharedUserData+0x308], 1
    jne   use_int2e           ; 极少走这条路径
    syscall                   ; 触发系统调用
    ret

关键点:

  • EAX = 系统调用号(syscall number),是 SSDT 中的索引
  • SYSCALL 指令是 x64 唯一的快速系统调用机制(替代了 x86 的 SYSENTER/INT 0x2E

2. SYSCALL 指令与 MSR_LSTAR

MSR_LSTAR 的作用

MSR 寄存器 地址 作用
MSR_LSTAR 0xC0000082 存储 SYSCALL 的 Ring0 入口点(RIP 目标)
MSR_STAR 0xC0000081 存储 CS/SS 选择子(高32位=用户态, 低32位=内核态)
MSR_SFMASK 0xC0000084 SYSCALL 时对 RFLAGS 的掩码(关中断等)

CPU 在执行 SYSCALL 时硬件自动完成:

1. RCX ← RIP(保存用户态返回地址)
2. R11 ← RFLAGS(保存用户态标志)
3. RFLAGS &= ~MSR_SFMASK(通常关闭 IF,即关中断)
4. CS  ← MSR_STAR[47:32](内核代码段选择子)
5. SS  ← MSR_STAR[47:32] + 8(内核栈段选择子)
6. RIP ← MSR_LSTAR(跳转到 KiSystemCall64)

注意: SYSCALL 不切换 RSP。栈的切换由 KiSystemCall64 代码自行完成。

内核初始化时设置 MSR_LSTAR

// 在 KiInitializeBootStructures 或类似函数中
wrmsr(MSR_LSTAR, (ULONG64)KiSystemCall64);

3. KiSystemCall64:内核入口处理

nt!KiSystemCall64:
    swapgs                        ; GS 切换到内核 PCR(KPCR)
    mov   gs:[UserRsp], rsp       ; 保存用户态 RSP
    mov   rsp, gs:[KernelStack]   ; 切换到内核栈

    ; 构造 KTRAP_FRAME
    push  UmSS                    ; 用户态 SS
    push  gs:[UserRsp]            ; 用户态 RSP
    push  r11                     ; 用户态 RFLAGS
    push  UmCS                    ; 用户态 CS
    push  rcx                     ; 用户态 RIP(返回地址)
    ; ... 保存其余寄存器 ...

    sti                           ; 重新开中断

    ; 参数复制:将用户态栈上的参数复制到内核栈
    ; (x64 前4个参数在寄存器,第5个起在用户态栈上)

    ; ── 进入分发逻辑 ──
    ; 落入 KiSystemServiceRepeat

4. SSDT 与 KiServiceTable(服务分发)

4.1 数据结构

// SSDT = System Service Descriptor Table
// 实际结构:KeServiceDescriptorTable
typedef struct _KSERVICE_TABLE_DESCRIPTOR {
    PLONG   Base;           // KiServiceTable — 函数偏移数组
    PULONG  Count;          // 调用计数(调试用,可能为 NULL)
    ULONG   Limit;          // 表中最大服务号
    PUCHAR  Number;         // 参数字节数表(KiArgumentTable)
} KSERVICE_TABLE_DESCRIPTOR;

// 系统中有两张表:
// [0] KeServiceDescriptorTable       → nt 系统调用 (KiServiceTable)
// [1] KeServiceDescriptorTableShadow → 包含 win32k 的扩展表 (W32pServiceTable)

4.2 KiServiceTable 的编码方式(x64 特有)

在 x64 Windows 中,KiServiceTable 不是直接存储函数指针,而是存储 相对偏移(编码后的 4 字节值)

// KiServiceTable[i] 的编码:
//   高28位 = 函数相对于 KiServiceTable 基址的字节偏移(带符号右移4位)
//   低 4位 = 该系统调用在栈上的额外参数个数

offset = (INT32)KiServiceTable[syscall_number] >> 4;
target_function = (ULONG_PTR)KiServiceTable + offset;
stack_params    = KiServiceTable[syscall_number] & 0xF;

这种编码节省空间(4字节 vs 8字节指针),并且可在单次查表中同时获得目标地址和参数信息。

4.3 分发逻辑(KiSystemServiceRepeat)

KiSystemServiceRepeat:
    ; EAX = 系统调用号
    ; 位[12] 区分是 nt 表(0) 还是 win32k 表(1)
  
    mov   edi, eax
    shr   edi, 7                   ; 取出表索引位
    and   edi, 0x20                ; 0 或 0x20(选 nt 表或 shadow 表)
    and   eax, 0xFFF               ; 低12位 = 表内索引

    ; 边界检查
    lea   r10, [KeServiceDescriptorTable + rdi]
    cmp   eax, [r10+LIMIT_OFFSET]
    jae   invalid_syscall

    ; 查 KiServiceTable
    lea   r11, [r10+BASE_OFFSET]   ; r11 = KiServiceTable 基址
    movsxd r11, dword ptr [r11 + rax*4]  ; 取编码后的偏移
    mov   ecx, r11
    shr   ecx, (not implemented)   ; 取参数数信息用于栈复制
    sar   r11, 4                   ; 算术右移,得到真实偏移
    add   r11, [r10+BASE_OFFSET]   ; 加基址 = 目标函数地址

    ; 复制用户态栈参数到内核栈(如果有超过4个参数)
    ; ...

    call  r11                      ; 调用 nt!NtCreateFile

5. nt!NtCreateFile 执行

NTSTATUS NtCreateFile(
    PHANDLE            FileHandle,            // rcx (实际已在r10)
    ACCESS_MASK        DesiredAccess,         // rdx
    POBJECT_ATTRIBUTES ObjectAttributes,      // r8
    PIO_STATUS_BLOCK   IoStatusBlock,         // r9
    PLARGE_INTEGER     AllocationSize,        // 栈参数[0]
    ULONG              FileAttributes,        // 栈参数[1]
    ULONG              ShareAccess,           // 栈参数[2]
    ULONG              CreateDisposition,     // 栈参数[3]
    ULONG              CreateOptions,         // 栈参数[4]
    PVOID              EaBuffer,              // 栈参数[5]
    ULONG              EaLength               // 栈参数[6]
);
// → 调用 IoCreateFile → IopCreateFile → 对象管理器 → 文件系统驱动

6. 返回路径

    ; NtCreateFile 返回值在 RAX(NTSTATUS)

    ; 恢复寄存器,准备返回
    mov   rcx, [TrapFrame.Rip]     ; 用户态返回地址 → RCX
    mov   r11, [TrapFrame.EFlags]  ; 用户态 RFLAGS → R11
    mov   rsp, [TrapFrame.Rsp]     ; 恢复用户态 RSP

    swapgs                          ; GS 切回用户态 TEB
    sysretq                        ; 返回 Ring3
    ; CPU: RIP←RCX, RFLAGS←R11, CS←STAR[63:48]+16, SS←STAR[63:48]+8

总结图

┌─────────────────────────────────────────────────────────────────────┐
│  User Mode                                                          │
│                                                                     │
│  CreateFileW (kernel32/kernelbase)                                  │
│       ↓                                                             │
│  NtCreateFile (ntdll.dll)                                           │
│       │  mov eax, 0x55    ← syscall number                         │
│       │  syscall          ← CPU reads MSR_LSTAR                    │
│       ↓                                                             │
├═══════════════════════ RING 3 → RING 0 ════════════════════════════╡
│  Kernel Mode                                                        │
│                                                                     │
│  MSR_LSTAR (0xC0000082) → KiSystemCall64 地址                      │
│       ↓                                                             │
│  KiSystemCall64:                                                    │
│       • swapgs, 切换内核栈, 构建 TRAP_FRAME                        │
│       ↓                                                             │
│  KiSystemServiceRepeat:                                             │
│       • EAX[12] 选表 → SSDT (KeServiceDescriptorTable)             │
│       • EAX[11:0] 作为索引 → KiServiceTable[index]                 │
│       • 解码偏移 → 得到 nt!NtCreateFile 地址                       │
│       ↓                                                             │
│  nt!NtCreateFile                                                    │
│       → IoCreateFile → 文件系统驱动                                 │
│       ↓                                                             │
│  返回 NTSTATUS → sysretq → 回到用户态                              │
└─────────────────────────────────────────────────────────────────────┘

各组件核心职责

组件 职责
MSR_LSTAR 告诉 CPU "SYSCALL 应该跳到哪" — 硬件级别的入口地址寄存器
KiSystemCall64 系统调用的软件入口:切栈、保存上下文、分发、返回
SSDT 服务描述符表,描述每张服务表的基址、上限、参数表
KiServiceTable SSDT 中"Base"指向的实际函数偏移数组,通过 syscall number 索引得到目标函数

[招生]科锐逆向工程师培训(2026年7月3日实地,远程教学同时开班, 第56期)!

收藏
免费 1
打赏
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回