KiSystemCall64 系统调用及钩子技术介绍
字数 1888 2025-11-21 12:56:19

KiSystemCall64 系统调用及钩子技术详解

一、KiSystemCall64 系统调用介绍

1.1 x64 架构系统调用机制

在 x64 Windows 系统中,用户态到内核态的切换通过 syscall 指令完成。该指令执行时,CPU 自动完成以下关键操作:

; syscall 指令执行流程(硬件自动完成)
mov rcx, rip ; 保存返回地址到 RCX
mov r11, rflags ; 保存标志寄存器
mov rip, IA32_LSTAR_MSR ; 跳转到系统调用处理程序
; 同时切换特权级别到 Ring 0,使用内核栈

IA32_LSTAR MSR(Model Specific Register)寄存器存储着系统调用入口点 KiSystemCall64 的地址,地址为 0xC0000082。

1.2 KiSystemCall64 执行流程

KiSystemCall64 作为系统调用的统一入口,承担着复杂的状态切换和参数处理任务:

  1. 切换 GS 段指向 KPCR(处理器控制区域)
  2. 从用户栈切换到内核栈
  3. 关闭 SMAP 以允许内核访问用户数据
  4. 缓解硬件漏洞的特殊代码
  5. 保存完整用户态上下文到 _KTRAP_FRAME
  6. 根据 EAX 计算目标系统服务地址
  7. 复制用户栈参数到内核栈
  8. 调用实际的内核服务函数
  9. 执行用户态 APC(异步过程调用)
  10. 恢复上下文并通过 sysret 返回用户态

1.3 系统服务表结构

Windows 维护两个主要系统服务表:

  • KeServiceDescriptorTable:普通系统服务表
  • KeServiceDescriptorTableShadow:包含 GUI 相关服务的扩展表

服务地址计算采用特殊的编码方式:

// 系统服务地址 = (SSDT 基地址 + (RVA 值 >> 4))
ULONG_PTR ServiceAddress = KeServiceDescriptorTable->ServiceTableBase + (RVA >> 4);

二、KiSystemCall 系统调用流程

2.1 用户态系统调用准备

在 Windows 10 21H2 中,三环应用程序通过 win32u.dll 中的存根函数发起系统调用:

; win32u.dll!NtGdiFlattenPath 实现示例
.text:0000000180006430 NtGdiFlattenPath proc near
.text:0000000180006430 mov r10, rcx ; 保存第一个参数
.text:0000000180006433 mov eax, 12A0h ; 系统调用号
.text:0000000180006438 test byte ptr ds:7FFE0308h, 1
.text:0000000180006440 jnz short loc_180006445
.text:0000000180006442 syscall ; 执行系统调用
.text:0000000180006444 retn

关键点解析:

  • eax=0x12A0:系统调用号,高 4 位决定使用哪个服务表
  • r10=rcx:保存第一个参数(符合 x64 调用约定)
  • 7FFE0308h:检测是否需要使用快速系统调用替代路径

2.2 syscall 指令硬件行为

执行 syscall 指令时,CPU 自动完成以下操作:

; syscall 指令的微码操作(硬件实现)
mov rcx, rip ; 保存返回地址到 RCX
mov r11, rflags ; 保存标志寄存器
mov rip, IA32_LSTAR_MSR ; 跳转到 KiSystemCall64
; 同时:切换到 Ring 0 特权级,使用内核栈,更新 CS/SS 段寄存器

2.3 KiSystemCall64 内核入口点分析

2.3.1 初始堆栈切换与状态保存

.text:00000001404088C0 KiSystemCall64 proc near
.text:00000001404088C0 ; __unwind { // KiSystemServiceHandler
.text:00000001404088C0 swapgs ; 切换 GS 指向 KPCR
.text:00000001404088C3 mov gs:10h, rsp ; 保存用户栈到 KPCR.UserRsp
.text:00000001404088CC mov rsp, gs:1A8h ; 加载内核栈从 KPCR.KernelRsp
.text:00000001404088D5 push 2Bh ; 推送用户 SS 选择子
.text:00000001404088D7 push gs:10h ; 推送用户 RSP
.text:00000001404088E0 push r11 ; 推送 RFLAGS
.text:00000001404088E2 push 33h ; 推送用户 CS 选择子
.text:00000001404088E4 push rcx ; 推送返回地址
.text:00000001404088E5 sub rsp, 8 ; 对齐栈
.text:00000001404088E9 push rbp ; 保存非易失寄存器
.text:00000001404088EA mov rbp, rsp
.text:00000001404088ED push rbx
.text:00000001404088EE push rdi
.text:00000001404088EF push rsi

堆栈布局构建:

+-----------------+
| RSI             | ← rsp + 0x00
+-----------------+
| RDI             | ← rsp + 0x08 
+-----------------+
| RBX             | ← rsp + 0x10
+-----------------+
| RBP             | ← rsp + 0x18
+-----------------+
| 对齐填充        | ← rsp + 0x20
+-----------------+
| 返回地址(RCX)   | ← rsp + 0x28
+-----------------+
| 用户CS(0x33)    | ← rsp + 0x30
+-----------------+
| RFLAGS(R11)     | ← rsp + 0x38
+-----------------+
| 用户RSP         | ← rsp + 0x40
+-----------------+
| 用户SS(0x2B)    | ← rsp + 0x48
+-----------------+

2.3.2 系统服务分发逻辑

.text:00000001404088F0 mov rbx, gs:188h ; 获取当前线程(KTHREAD)
.text:00000001404088F9 mov [rbx+1E0h], rsp ; 保存栈指针到 Thread.TrapFrame
.text:0000000140408900 mov rdi, gs:900h ; 加载当前进程(EPROCESS)
.text:0000000140408909 mov [rbx+1F0h], rdi ; 保存到 Thread.ApcState.Process

; 系统调用号处理
.text:0000000140408C20 KiSystemServiceStart:
.text:0000000140408C20 mov [rbx+90h], rsp ; Thread.SystemCallSp
.text:0000000140408C27 mov edi, eax ; 保存系统调用号
.text:0000000140408C29 shr edi, 7 ; 计算服务表索引
.text:0000000140408C2C and edi, 20h ; 提取第 12 位(Shadow SSDT 标志)
.text:0000000140408C2F and eax, 0FFFh ; 提取 12 位服务索引

系统调用号解码:

系统调用号格式:0x12A0
┌┌─ 0x1 ──┐┐ ┌┌─ 0x2A0 ──┐┐
  高4位       低12位
   │           │
   ▼           ▼
服务表选择器  服务索引
(0=SSDT, 1=Shadow SSDT)

2.3.3 服务表选择与地址计算

.text:0000000140408C32 mov r10, [rbx+78h] ; 加载服务描述符表
.text:0000000140408C36 test edi, edi ; 检查是否使用 Shadow SSDT
.text:0000000140408C38 jz short loc_140408C42

; 使用 Shadow SSDT (GUI 相关服务)
.text:0000000140408C3A mov r10, [r10+20h] ; 指向 Shadow 表
.text:0000000140408C3E mov r11, [r10+8] ; 加载参数表
.text:0000000140408C42 jmp short loc_140408C4C

; 使用普通 SSDT
.text:0000000140408C44 loc_140408C44:
.text:0000000140408C44 mov r11, [r10+18h] ; 加载参数表
.text:0000000140408C48 loc_140408C48:
.text:0000000140408C48 mov r10, [r10] ; 加载服务表基址
.text:0000000140408C4B nop

.text:0000000140408C4C loc_140408C4C:
.text:0000000140408C4C cmp eax, [r10+rdi+10h] ; 检查服务号是否有效
.text:0000000140408C51 jnb loc_140409195 ; 跳转到无效服务处理
.text:0000000140408C57 mov r10, [r10+rdi] ; 重新加载服务表基址
.text:0000000140408C5B movsxd r11, dword ptr [r10+rax*4] ; 获取服务 RVA 偏移

服务地址计算算法:

// 实际服务地址 = 服务表基址 + (RVA >> 4)
ULONG_PTR ServiceAddress = ServiceTableBase + (RVA >> 4);

2.3.4 参数复制与调用准备

; 参数复制逻辑
.text:0000000140408C71 movzx ecx, byte ptr [r11+rax] ; 获取参数大小(字节)
.text:0000000140408C76 mov rsi, gs:10h ; 获取用户栈指针
.text:0000000140408C7F add rsi, 8 ; 跳过返回地址
.text:0000000140408C83 mov rdi, rsp ; 目标:内核栈
.text:0000000140408C86 shr ecx, 3 ; 计算参数个数(8 字节为单位)
.text:0000000140408C89 rep movsq ; 复制参数到内核栈

; 调用目标服务
.text:0000000140408D90 KiSystemServiceCopyEnd:
.text:0000000140408D90 test cs:KiDynamicTraceMask, 1
.text:0000000140408D9A jnz loc_140409233 ; 跳转到 ETW 跟踪路径
.text:0000000140408DA0 test dword ptr cs:PerfGlobalGroupMask+8, 40h
.text:0000000140408DAA jnz loc_1404092A7 ; 跳转到性能监控路径
.text:0000000140408DB0 mov rax, r10 ; 准备服务函数地址
.text:0000000140408DB3 call rax ; 调用实际系统服务

2.4 实际服务调用路径分析

2.4.1 控制流防护绕行机制

由于启用了控制流防护(CFG),直接跳转到系统服务需要经过特殊处理:

; 实际调用路径(CFG 保护)
call nt!_guard_retpoline_indirect_rax (fffff803`36c1b2e0)

; _guard_retpoline_indirect_rax 内部:
fffff803`36c1b2e0 call $+5
fffff803`36c1b2e5 pop rcx
fffff803`36c1b2e6 mov [rsp+8], rcx
fffff803`36c1b2eb ret

; 继续执行:
fffff803`36c1b2ff call nt!_guard_retpoline_indirect_rax+0x40 (fffff803`36c1b320)

CFG 绕行原理:

  • 使用 retpoline 技术防止分支预测攻击
  • 通过间接调用和返回序列确保控制流完整性
  • 避免直接跳转到动态计算的目标地址

2.4.2 到达实际服务函数

经过 CFG 保护后,最终到达实际的系统服务实现:

; 到达 win32kfull!NtGdiFlattenPath
win32kfull!NtGdiFlattenPath:
ffffd004`238dde70 4053           push rbx
ffffd004`238dde72 4881ecb0000000 sub rsp, 0B0h
ffffd004`238dde79 488bd1         mov rdx, rcx
ffffd004`238dde7c 488d4c2420     lea rcx, [rsp+20h]
ffffd004`238dde81 e88e85dcff     call win32kfull!DCOBJ::DCOBJ (ffffd004`236a6414)
ffffd004`238dde86 488b4c2420     mov rcx, qword ptr [rsp+20h]
ffffd004`238dde8b 4885c9         test rcx, rcx
ffffd004`238dde8e 7507           jne win32kfull!NtGdiFlattenPath+0x27 (ffffd004`238dde97)

2.5 系统调用返回路径

2.5.1 服务执行完成处理

; 系统服务执行完成后
.text:0000000140408DB5 nop dword ptr [rax]
.text:0000000140408DB8 mov [rsp+1D8h+var_A8], rax ; 保存返回值
.text:0000000140408DC0 mov rcx, gs:188h ; 获取当前线程
.text:0000000140408DC9 mov r10, [rcx+0B8h] ; 获取 TrapFrame

; 检查用户 APC 交付
.text:0000000140408DD0 test dword ptr [rcx+74h], 40000h
.text:0000000140408DD7 jnz loc_1404090C5 ; 跳转到 APC 交付路径

2.5.2 上下文恢复与返回用户态

; 快速返回路径(无 APC 需要交付)
.text:0000000140408F30 KiServiceExit:
.text:0000000140408F30 mov rcx, [rsp+1D8h+var_A8] ; 恢复返回值
.text:0000000140408F38 mov rsp, rbp ; 恢复栈指针
.text:0000000140408F3B pop rbp
.text:0000000140408F3C add rsp, 28h ; 跳过保存的上下文
.text:0000000140408F40 pop rcx ; 恢复返回地址
.text:0000000140408F41 pop r11 ; 恢复 RFLAGS
.text:0000000140408F43 pop rsp ; 切换回用户栈
.text:0000000140408F44 swapgs ; 恢复用户 GS
.text:0000000140408F47 sysret ; 返回用户态

2.6 调试技巧与注意事项

2.6.1 有效断点设置

由于 KiSystemCall64 开头涉及关键状态切换,直接下断点可能导致系统不稳定:

; 错误方式 - 可能导致崩溃
bp KiSystemCall64

; 正确方式 - 在安全位置下断点
ba e1 KiSystemCall64+15 ; 跳过初始 swapgs 指令
bp KiSystemServiceStart ; 在服务分发逻辑开始处
ba e1 KiSystemServiceCopyEnd+0x20 ; 在服务调用前

2.6.2 符号加载与调试环境

; 加载必要符号
.reload
.load win32kfull
!process 0 0 ; 查看进程信息
!thread ; 查看当前线程信息

; 设置 GUI 服务断点
bp win32kfull!NtGdiFlattenPath

三、KiSystemCall64 钩子技术

3.1 KiSystemCall 钩子完整设计

3.1.1 驱动入口与初始化

NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegisterPath)
{
    DriverObject->DriverUnload = DriverUnload;
    
    // 核心:重构建 KiSystemCall64
    __FakeKiSystemCall64 = ReloadKiSystemCall64();
    
    if (__FakeKiSystemCall64) {
        // 保存原始地址用于恢复
        KiSystemCall64 = __readmsr(0xC0000082);
        
        // 多处理器支持:在所有 CPU 核心上安装钩子
        for (ULONG i = 0; i < KeNumberProcessors; i++) {
            KeSetSystemAffinityThread((KAFFINITY)((ULONG64)1 << i));
            __writemsr(0xC0000082, __FakeKiSystemCall64);
            KeRevertToUserAffinityThread();
        }
        return STATUS_SUCCESS;
    }
    return STATUS_UNSUCCESSFUL;
}

3.1.2 内存重构建核心流程

定位原始系统调用处理器:

PUCHAR ReloadKiSystemCall64()
{
    // 通过 MSR 寄存器获取原始 KiSystemCall64 地址
    PUCHAR KiSystemCall64 = (PUCHAR)__readmsr(0xC0000082);
    
    // 搜索函数边界:查找跳转指令确定函数大小
    ULONG ViewSize = 0;
    for (ULONG i = 0; i < 0x1000; i++) {
        if (*(KiSystemCall64 + i) == 0xE9) { // JMP 指令
            if (*(KiSystemCall64 + i + 1) == 0x59 && ...) {
                ViewSize = i + 5;
                break;
            }
        }
    }
    // 继续处理...
}

内存布局规划:
分配 0x13000 字节非分页内存,精心规划内存布局:

+-------------------+ 0x0000
| 复制的原始代码    | ← 完整的 KiSystemCall64 副本
+-------------------+ 0x1000 
| 伪造描述符表结构  | ← 伪造的 SSDT 和 Shadow SSDT
+-------------------+ 0x2000
| 地址指针数组      | ← 重定位后的函数指针
+-------------------+ 0x3000
| 伪造 SSDT RVA 表  | ← 服务号到 RVA 的映射
+-------------------+ 0x4000
| SSDT 跳板代码     | ← 每个系统服务的跳转代码
+-------------------+ 0x5000
| SSDT 函数地址数组 | ← 实际调用的函数地址
+-------------------+ 0x6000
| 伪造 Shadow RVA 表| ← GUI 服务的 RVA 映射
+-------------------+ 0x7500
| Shadow 跳板代码   | ← GUI 服务的跳转代码
+-------------------+ 0x9500
| Shadow 函数地址数组| ← GUI 服务函数地址
+-------------------+ 0x13000

3.1.3 指令修复与重定位

修复相对调用指令:

void FixBaseRelocation(ULONG64 VirtualAddress, PUCHAR KiSystemCall64, ULONG ViewSize)
{
    // 复制原始代码
    memcpy((PVOID)VirtualAddress, KiSystemCall64, ViewSize);
    
    // 修复所有地址相关指令
    for (ULONG i = 0; i < ViewSize - 15; i++) {
        PUCHAR Current = (PUCHAR)VirtualAddress + i;
        
        // 修复直接调用 (E8 opcode)
        if (*Current == 0xE8) {
            ULONG OriginalOffset = *(PULONG)(Current + 1);
            ULONG_PTR OriginalTarget = (ULONG_PTR)(Current + 5 + OriginalOffset);
            ULONG_PTR NewTarget = FindNewAddress(OriginalTarget);
            ULONG NewOffset = (ULONG)(NewTarget - (ULONG_PTR)Current - 5);
            *(PULONG)(Current + 1) = NewOffset;
        }
        
        // 修复间接调用 (FF 15 opcode)
        if (Current[0] == 0xFF && Current[1] == 0x15) {
            // 重定向到地址指针数组
            // ...
        }
    }
}

关键指令重定向:
最核心的修改是重定向 SSDT 加载指令:

; 原始代码:
lea r10, [KeServiceDescriptorTable] ; 4C 8D 15 XX XX XX XX

; 修复后:
jmp qword ptr [__Address] ; FF 25 XX XX XX XX

这样,所有对原始 SSDT 的引用都被重定向到我们伪造的表结构。

3.1.4 伪造 SSDT 表构造

构建伪造服务描述符表:

void FixFakeKeServiceDescriptorTable(PSYSTEM_SERVICE_DESCRIPTOR_TABLE OriginalTable)
{
    // 复制原始表结构
    __FakeKeServiceDescriptorTable->ServiceTableBase = __FakeServiceTableBase;
    __FakeKeServiceDescriptorTable->NumberOfServices = OriginalTable->NumberOfServices;
    __FakeKeServiceDescriptorTable->ArgumentTableBase = OriginalTable->ArgumentTableBase;
    
    // 为每个系统服务构建跳板
    for (ULONG i = 0; i < OriginalTable->NumberOfServices; i++) {
        // 计算原始函数地址
        ULONG OriginalRVA = OriginalTable->ServiceTableBase[i];
        ULONG_PTR OriginalFunction = (ULONG_PTR)OriginalTable->ServiceTableBase + (OriginalRVA >> 4);
        
        // 保存原始函数指针
        __FakeSsdtService[i] = OriginalFunction;
        
        // 安装特定钩子(以 NtTerminateProcess 为例)
        if (i == 41) { // NtTerminateProcess 的服务号
            __NtTerminateProcess = (LPFN_TERMINATEPROCESS)__FakeSsdtService[i];
            __FakeSsdtService[i] = (ULONG_PTR)FakeNtTerminateProcess;
        }
        
        // 构建跳板代码
        BuildTrampoline(__SsdtTrampoline, &__FakeSsdtService[i]);
        
        // 计算新的 RVA 值
        ULONG NewRVA = (ULONG)((__SsdtTrampoline - __FakeServiceTableBase) << 4) | (OriginalRVA & 0xF);
        __FakeServiceTableBase[i] = NewRVA;
        
        __SsdtTrampoline += 6; // 每个跳板占用 6 字节
    }
}

跳板代码构造:

; 跳板代码结构:
; FF 25 XX XX XX XX JMP [相对地址]
; 后跟 4 字节相对偏移量

; 实际生成的跳板:
__SsdtTrampoline:
    jmp qword ptr [__FakeSsdtService + index * 8]

这种设计使得我们可以动态修改 __FakeSsdtService 数组中的函数指针,实现灵活的挂钩。

3.1.5 Shadow SSDT 特殊处理

由于 win32k.sys 加载在会话空间,处理 Shadow SSDT 需要特殊技巧:

void FixFakeKeServiceDescriptorTableShadow()
{
    // 必须附加到 GUI 进程上下文
    PEPROCESS GuiProcess = FindGuiProcess();
    KAPC_STATE ApcState;
    
    KeStackAttachProcess(GuiProcess, &ApcState);
    
    // 构建过程与普通 SSDT 类似,但需要会话空间地址
    // ...
    
    KeUnstackDetachProcess(&ApcState);
}

3.2 钩子函数实现示例

3.2.1 NtTerminateProcess 钩子

NTSTATUS FakeNtTerminateProcess(HANDLE ProcessHandle, NTSTATUS ExitStatus)
{
    // 前置处理:记录日志、权限检查等
    DbgPrint("NtTerminateProcess called: PID=%d, Status=0x%X\n", 
             PsGetProcessId(ProcessHandle), ExitStatus);
    
    // 可以阻止特定进程被终止
    if (IsProtectedProcess(ProcessHandle)) {
        DbgPrint("Blocked termination of protected process\n");
        return STATUS_ACCESS_DENIED;
    }
    
    // 调用原始函数
    return __NtTerminateProcess(ProcessHandle, ExitStatus);
}

3.3 多处理器同步机制

3.3.1 跨 CPU 安装技术

// 确保所有处理器核心都应用钩子
for (ULONG i = 0; i < KeNumberProcessors; i++) {
    // 设置线程关联性到指定 CPU
    KeSetSystemAffinityThread((KAFFINITY)((ULONG64)1 << i));
    
    // 修改该 CPU 的 LSTAR MSR
    __writemsr(0xC0000082, __FakeKiSystemCall64);
    
    // 恢复线程关联性
    KeRevertToUserAffinityThread();
}

3.4 驱动卸载与清理

3.4.1 安全恢复机制

VOID DriverUnload(PDRIVER_OBJECT DriverObject)
{
    // 在所有 CPU 上恢复原始 KiSystemCall64
    for (ULONG i = 0; i < KeNumberProcessors; i++) {
        KeSetSystemAffinityThread((KAFFINITY)((ULONG64)1 << i));
        __writemsr(0xC0000082, KiSystemCall64);
        KeRevertToUserAffinityThread();
    }
    
    // 释放分配的内存
    if (__FakeKiSystemCall64) {
        ExFreePoolWithTag((PVOID)__FakeKiSystemCall64, POOL_TAG);
    }
    
    DbgPrint("KiSystemCall64 hook unloaded successfully\n");
}

3.5 技术难点与解决方案

3.5.1 地址重定位挑战

问题: 复制代码中的相对地址和绝对地址需要重新计算。

解决方案:

  • 解析所有相对跳转和调用指令
  • 维护地址映射表进行转换
  • 使用间接跳转处理绝对地址

3.5.2 并发访问处理

问题: 系统调用可能在不同 CPU 上同时执行。

解决方案:

  • 使用
KiSystemCall64 系统调用及钩子技术详解 一、KiSystemCall64 系统调用介绍 1.1 x64 架构系统调用机制 在 x64 Windows 系统中,用户态到内核态的切换通过 syscall 指令完成。该指令执行时,CPU 自动完成以下关键操作: IA32_ LSTAR MSR(Model Specific Register)寄存器存储着系统调用入口点 KiSystemCall64 的地址,地址为 0xC0000082。 1.2 KiSystemCall64 执行流程 KiSystemCall64 作为系统调用的统一入口,承担着复杂的状态切换和参数处理任务: 切换 GS 段指向 KPCR(处理器控制区域) 从用户栈切换到内核栈 关闭 SMAP 以允许内核访问用户数据 缓解硬件漏洞的特殊代码 保存完整用户态上下文到 _ KTRAP_ FRAME 根据 EAX 计算目标系统服务地址 复制用户栈参数到内核栈 调用实际的内核服务函数 执行用户态 APC(异步过程调用) 恢复上下文并通过 sysret 返回用户态 1.3 系统服务表结构 Windows 维护两个主要系统服务表: KeServiceDescriptorTable:普通系统服务表 KeServiceDescriptorTableShadow:包含 GUI 相关服务的扩展表 服务地址计算采用特殊的编码方式: 二、KiSystemCall 系统调用流程 2.1 用户态系统调用准备 在 Windows 10 21H2 中,三环应用程序通过 win32u.dll 中的存根函数发起系统调用: 关键点解析: eax=0x12A0:系统调用号,高 4 位决定使用哪个服务表 r10=rcx:保存第一个参数(符合 x64 调用约定) 7FFE0308h:检测是否需要使用快速系统调用替代路径 2.2 syscall 指令硬件行为 执行 syscall 指令时,CPU 自动完成以下操作: 2.3 KiSystemCall64 内核入口点分析 2.3.1 初始堆栈切换与状态保存 堆栈布局构建: 2.3.2 系统服务分发逻辑 系统调用号解码: 2.3.3 服务表选择与地址计算 服务地址计算算法: 2.3.4 参数复制与调用准备 2.4 实际服务调用路径分析 2.4.1 控制流防护绕行机制 由于启用了控制流防护(CFG),直接跳转到系统服务需要经过特殊处理: CFG 绕行原理: 使用 retpoline 技术防止分支预测攻击 通过间接调用和返回序列确保控制流完整性 避免直接跳转到动态计算的目标地址 2.4.2 到达实际服务函数 经过 CFG 保护后,最终到达实际的系统服务实现: 2.5 系统调用返回路径 2.5.1 服务执行完成处理 2.5.2 上下文恢复与返回用户态 2.6 调试技巧与注意事项 2.6.1 有效断点设置 由于 KiSystemCall64 开头涉及关键状态切换,直接下断点可能导致系统不稳定: 2.6.2 符号加载与调试环境 三、KiSystemCall64 钩子技术 3.1 KiSystemCall 钩子完整设计 3.1.1 驱动入口与初始化 3.1.2 内存重构建核心流程 定位原始系统调用处理器: 内存布局规划: 分配 0x13000 字节非分页内存,精心规划内存布局: 3.1.3 指令修复与重定位 修复相对调用指令: 关键指令重定向: 最核心的修改是重定向 SSDT 加载指令: 这样,所有对原始 SSDT 的引用都被重定向到我们伪造的表结构。 3.1.4 伪造 SSDT 表构造 构建伪造服务描述符表: 跳板代码构造: 这种设计使得我们可以动态修改 __ FakeSsdtService 数组中的函数指针,实现灵活的挂钩。 3.1.5 Shadow SSDT 特殊处理 由于 win32k.sys 加载在会话空间,处理 Shadow SSDT 需要特殊技巧: 3.2 钩子函数实现示例 3.2.1 NtTerminateProcess 钩子 3.3 多处理器同步机制 3.3.1 跨 CPU 安装技术 3.4 驱动卸载与清理 3.4.1 安全恢复机制 3.5 技术难点与解决方案 3.5.1 地址重定位挑战 问题: 复制代码中的相对地址和绝对地址需要重新计算。 解决方案: 解析所有相对跳转和调用指令 维护地址映射表进行转换 使用间接跳转处理绝对地址 3.5.2 并发访问处理 问题: 系统调用可能在不同 CPU 上同时执行。 解决方案: 使用