Win32k.sys 加载与系统调用分发分析

发布时间:2026/7/19 3:05:45
Win32k.sys 加载与系统调用分发分析 Win32k.sys 加载与系统调用分发分析概述win32k.sys 是 ReactOS 的内核模式 Win32 子系统负责窗口管理USER和图形设备接口GDI。它由 SMSS 通过NtSetSystemInformation(SystemExtendServiceTableInformation)加载注册自己的系统服务表到内核的 SSDTSystem Service Descriptor Table使得用户态的 User32.dll / Gdi32.dll 可以通过系统调用int 0x2E/sysenter进入 win32k.sys 执行。1. 加载 win32k.sys 的完整路径1.1 SMSS 发起加载请求文件[smsubsys.c#L539](file:///d:/reactos/base/system/smss/smsubsys.c#L539)SmpLoadSubSystemsForMuSession() └─ 扫描子系统列表, 找到 Kmode 条目 └─ NtSetSystemInformation(SystemSessionCreate, ...) ← 创建会话 0 └─ NtSetSystemInformation(SystemExtendServiceTableInformation, ...) └─ → 进入内核加载 win32k.sys1.2 内核加载 win32k.sys文件[ex/sysinfo.c#L1923](file:///d:/reactos/ntoskrnl/ex/sysinfo.c#L1923)SSI_DEF(SystemExtendServiceTableInformation){// 1. 验证调用者权限 (KernelMode 或 SeLoadDriverPrivilege)if(RequestedModeUserMode){// 检查 CSRSS 权限验证路径为 \SystemRoot\System32\win32k.sys// 然后递归以 KernelMode 调用自身}// 2. MmLoadSystemImage — 加载 win32k.sys 到内核地址空间StatusMmLoadSystemImage(ImageInfo,LoaderBlock,ImageInformation);// 3. 从 PE 头获取入口点DriverInit(PDRIVER_INITIALIZE)(ImageInfo.ImageBaseImageInfo.EntryPoint);// 4. 创建伪 DRIVER_OBJECT调用 DriverEntryRtlZeroMemory(Win32k,sizeof(Win32k));Win32k.DriverStartImageInfo.ImageBase;returnDriverInit(Win32k,NULL);}关键点步骤内核函数说明加载 PE 映像MmLoadSystemImage将 win32k.sys 映射到系统空间获取入口点PE 头AddressOfEntryPoint找到DriverEntry创建伪驱动对象零初始化DRIVER_OBJECT设置DriverStart不注册任何 MajorFunction调用初始化DriverInit(Win32k, NULL)进入 win32k.sys 的DriverEntry2. win32k.sys DriverEntry 初始化文件[win32ss/user/ntuser/main.c#L981](file:///d:/reactos/win32ss/user/ntuser/main.c#L981)DriverEntry(IN PDRIVER_OBJECT DriverObject,IN PUNICODE_STRING RegistryPath){// ★ Step 1: 注册系统服务表 (SSDT Shadow)KeAddSystemServiceTable(Win32kSSDT,// 函数指针数组NULL,// Count (可选)Win32kNumberOfSysCalls,// 服务数量Win32kSSPT,// 参数大小表 (每个服务参数栈字节数)1);// Index 1 (win32k 表)// Step 2: 标记为可分页驱动MmPageEntireDriver(DriverObject-DriverStart);// ★ Step 3: 注册 Win32 回调 (进程/线程生命周期)PsEstablishWin32Callouts(CalloutData);// Step 4: 创建全局用户堆UserCreateHeap();// Step 5: 初始化各子系统InitGdiHandleTable();// GDI 句柄表InitPaletteImpl();// 调色板CreateStockObjects();// 库存对象 (画笔, 画刷, 字体等)InitBrushImpl();// 画刷InitPDEVImpl();// 物理设备InitLDEVImpl();// 逻辑设备InitDeviceImpl();// 设备InitDcImpl();// 设备上下文 (DC)InitUserImpl();// USER 子系统InitWindowStationImpl();// 窗口站InitDesktopImpl();// 桌面InitInputImpl();// 输入InitKeyboardImpl();// 键盘布局MsqInitializeImpl();// 消息队列InitTimerImpl();// 定时器InitDCEImpl();// 显示上下文扩展returnSTATUS_SUCCESS;}PsEstablishWin32Callouts注册的回调回调触发时机作用CreateProcessCallout进程创建初始化进程的 Win32 数据W32PROCESSCreateThreadCallout线程首次调用 win32k初始化线程的 Win32 数据W32THREAD、分配 GUI 内核栈ProcessDeleteCallout进程销毁清理窗口、句柄等进程级 Win32 资源ThreadDeleteCallout线程销毁清理线程级 Win32 资源3. 系统服务表注册机制3.1 数据结构文件[ke/procobj.c#L23](file:///d:/reactos/ntoskrnl/ke/procobj.c#L23)// 主表 — 仅含内核 Native APIKSERVICE_TABLE_DESCRIPTOR KeServiceDescriptorTable[SSDT_MAX_ENTRIES];// 影子表 — Index 0 Native API, Index 1 win32kKSERVICE_TABLE_DESCRIPTOR KeServiceDescriptorTableShadow[SSDT_MAX_ENTRIES];typedefstruct_KSERVICE_TABLE_DESCRIPTOR{PULONG_PTR Base;// 服务函数指针数组PULONG Count;// 可选: 调用计数数组ULONG Limit;// 服务数量PUCHAR Number;// 参数大小表 (每个服务参数占用的栈字节数)}KSERVICE_TABLE_DESCRIPTOR,*PKSERVICE_TABLE_DESCRIPTOR;3.2 KeAddSystemServiceTable文件[ke/procobj.c#L906](file:///d:/reactos/ntoskrnl/ke/procobj.c#L906)BOOLEANKeAddSystemServiceTable(IN PULONG_PTR Base,IN PULONG Count OPTIONAL,IN ULONG Limit,IN PUCHAR Number,IN ULONG Index){if(IndexSSDT_MAX_ENTRIES)returnFALSE;if(KeServiceDescriptorTableShadow[Index].Base!NULL)returnFALSE;// 填入影子表KeServiceDescriptorTableShadow[Index].BaseBase;KeServiceDescriptorTableShadow[Index].CountCount;KeServiceDescriptorTableShadow[Index].LimitLimit;KeServiceDescriptorTableShadow[Index].NumberNumber;// 初始指向影子表 (后续非 GUI 线程指向主表)KeServiceDescriptorTableShadow[0]KeServiceDescriptorTable[0];returnTRUE;}win32k 调用时Index1因此KeServiceDescriptorTableShadow[0] KeServiceDescriptorTable[0] // Native API KeServiceDescriptorTableShadow[1] { Win32kSSDT, NULL, Win32kNumberOfSysCalls, Win32kSSPT }3.3 Win32kSSDT 与 Win32kSSPT文件[win32ss/napi.h](file:///d:/reactos/win32ss/napi.h)#defineSVC_(name,argcount)(ULONG_PTR)Nt##name,ULONG_PTR Win32kSSDT[]{#includew32ksvc32.h// ← 约 750 个 NtGdi*/NtUser* 函数指针};#defineSVC_(name,argcount)argcount*sizeof(void*),UCHAR Win32kSSPT[]{#includew32ksvc32.h// ← 对应的参数大小};#defineNUMBER_OF_SYSCALLS(sizeof(Win32kSSPT)/sizeof(Win32kSSPT[0]))ULONG Win32kNumberOfSysCallsNUMBER_OF_SYSCALLS;w32ksvc32.h示例条目SVC_(GdiAbortDoc, 1) SVC_(GdiBitBlt, 11) SVC_(GdiDeleteObject, 1) ... SVC_(UserCreateWindowEx, 15) SVC_(UserSendMessage, 7) SVC_(UserRegisterClassExW, 8) ...实际函数名为NtGdiAbortDoc、NtUserCreateWindowEx等。系统调用号从0x1000开始MIN_SYSCALL_NUMBER以避免与 Native API0x0000–0x0FFF冲突。4. 系统调用号编码与分发4.1 调用号编码规则系统调用号 (32位): ┌──────────┬──────────┬──────────────┐ │ 保留 │ 表索引 │ 服务编号 │ │ 31-12 │ 11-8 │ 7-0 │ └──────────┴──────────┴──────────────┘宏值说明SERVICE_TABLE_SHIFT8表索引位移位数SERVICE_TABLE_MASK0x0F表索引掩码 (4 位, 最多 16 张表)SERVICE_NUMBER_MASK0xFF服务编号掩码 (8 位, 每张表最多 256 个服务)Native API 调用:0x0000XXXX→ 表索引 0 ( 8 0)Win32k 调用:0x1XXX→ 表索引 1 ( 8 1)4.2 KiSystemServiceHandler 分发逻辑文件[ke/i386/traphdlr.c#L1722](file:///d:/reactos/ntoskrnl/ke/i386/traphdlr.c#L1722)KiSystemServiceHandler(TrapFrame) │ ├─ 从 TrapFrame 读取系统调用号 │ ├─ Offset (SystemCallNumber SERVICE_TABLE_SHIFT) SERVICE_TABLE_MASK │ 0x1000 8 1 ← win32k 表 │ ├─ ID SystemCallNumber SERVICE_NUMBER_MASK │ 0xXXXX 0xFF ← 具体函数编号 │ ├─ DescriptorTable Thread-ServiceTable Offset │ KeServiceDescriptorTableShadow[1] ← win32k 表 │ ├─ 检查 ID DescriptorTable-Limit │ ├─ if (线程尚未是 GUI 线程 Offset SERVICE_TABLE_TEST) │ KiConvertToGuiThread() ← 转换为 GUI 线程 │ ├─ if (TEB-GdiBatchCount) │ KeGdiFlushUserBatch() ← 刷新 GDI 批处理缓冲区 │ ├─ Handler DescriptorTable-Base[ID] ← 找到处理函数 │ Win32kSSDT[ID] NtUserCreateWindowEx │ └─ KiSystemCallTrampoline(Handler, Arguments, StackBytes) ← 调用4.3 线程转换为 GUI 线程文件[ps/win32.c#L32](file:///d:/reactos/ntoskrnl/ps/win32.c#L32)NTSTATUSPsConvertToGuiThread(VOID){// 1. 分配大的内核栈 (GUI 线程需要更大的栈)NewStackMmCreateKernelStack(0,KeGetCurrentThread());// 2. 调用进程回调PspW32ProcessCallout(...);// 3. 切换到影子表Thread-ServiceTableKeServiceDescriptorTableShadow;// 4. 调用线程回调 (初始化 W32THREAD)PspW32ThreadCallout(...);returnSTATUS_SUCCESS;}4.4 汇编入口文件[ke/i386/trap.s](file:///d:/reactos/ntoskrnl/ke/i386/trap.s#L141); int 0x2E 处理入口 _KiSystemService: KiEnterTrap (KI_PUSH_FAKE_ERROR_CODE ...) KiCallHandler KiSystemServiceHandler8 ; SYSENTER 处理入口 _KiFastCallEntry: KiEnterTrap (KI_FAST_SYSTEM_CALL ...) KiCallHandler KiSystemServiceHandler85. 完整调用路径示例SendMessageW应用程序: SendMessageW(hWnd, WM_PAINT, 0, 0) │ ├─ [user32.dll: message.c#L2367] │ SendMessageW() │ ├─ 同进程同线程? → 直接调用窗口过程 (优化路径) │ └─ 跨进程/线程 → NtUserMessageCall(...) │ ├─ [win32u / ntdll] │ 填充系统调用号 (0x1XXX) → int 0x2E / sysenter │ ├─ [ntoskrnl: trap.s#L141] │ KiSystemService │ ├─ [ntoskrnl: traphdlr.c#L1722] │ KiSystemServiceHandler() │ ├─ Offset 1, ID NtUserMessageCall 编号 │ ├─ DescriptorTable Shadow[1] (win32k 表) │ └─ Handler Win32kSSDT[ID] NtUserMessageCall │ ├─ [win32k: message.c#L2527] │ NtUserMessageCall(hWnd, Msg, wParam, lParam, ResultInfo, FNID_SENDMESSAGE, Ansi) │ ├─ UserEnterExclusive() ← 获取全局 USER 锁 │ ├─ 根据 FNID 分发: │ │ FNID_SENDMESSAGE → 实际消息发送逻辑 │ │ FNID_DEFWINDOWPROC → IntDefWindowProc │ │ FNID_SCROLLBAR → ScrollBarWndProc │ │ ... │ │ │ ├─ 发送消息: │ │ ├─ 跨进程 → 排队到目标消息队列 │ │ └─ 当前线程 → co_IntSendMessage(...) │ │ │ └─ UserLeave() ← 释放全局 USER 锁 │ └─ 返回结果到应用程序6. Win32k 与 CSRSS 的交互win32k 不仅处理系统调用还需要与用户态的 CSRSS 通过 LPC 通信完成某些操作。win32k (内核模式) │ ├─ KeUserModeCallback → 回调到 user32.dll │ ├─ co_IntCallWindowProc (调用窗口过程) │ ├─ co_IntCallHookProc (调用钩子) │ ├─ co_IntCallEventProc (触发事件) │ ├─ co_IntClientLoadLibrary (加载 DLL) │ └─ co_IntSendMessage (发送消息) │ └─ CsrClientCallServer → LPC → CSRSS ├─ 创建系统线程 (RIT, 桌面线程, 鬼影窗口) ├─ 创建会话 └─ 其他需要 CSRSS 协助的操作6.1 KeUserModeCallback 机制文件[ke/i386/usercall.c#L330](file:///d:/reactos/ntoskrnl/ke/i386/usercall.c#L330)win32k 调用 KeUserModeCallback() │ ├─ 构造回调帧 (KAPC_FRAME KTRAP_FRAME) ├─ 设置 TrapFrame-Eip KeUserCallbackDispatcher ├─ 通过 KiServiceExit 返回用户模式 │ ├─ [用户模式: ntdll] │ KiUserCallbackDispatcher() │ → 调用 user32.dll 中的对应函数 │ → user32 完成后调用 NtCallbackReturn() │ └─ [内核模式: usercall.c#L386] NtCallbackReturn() → 恢复原始陷阱帧 → 继续 win32k 的执行6.2 CSR LPC 通信文件[win32ss/user/ntuser/csr.c#L72](file:///d:/reactos/win32ss/user/ntuser/csr.c#L72)NTSTATUSCsrClientCallServer(PCSR_API_MESSAGE ApiMessage,PVOID CaptureBuffer,ULONG ApiNumber){// 填入 LPC 消息头ApiMessage-h.u1.s1.DataLengthApiMessageLength;ApiMessage-h.u1.s1.TotalLengthApiMessageLength;// 调用期间释放 USER 锁 (避免死锁)UserLeaveCo();// 发送 LPC 消息到 CSRSSStatusLpcRequestWaitReplyPort(CsrApiPort,ApiMessage-h);// 重新获取 USER 锁UserEnterCo();returnStatus;}7. 完整架构图┌─────────────────────────────────────────────────────────────────┐ │ 用户模式 │ │ │ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │ │ App.exe │ │ CSRSS │ │ WINLOGON │ │ │ │ │ │ │ │ │ │ │ │ Create... │ │ CSR API │ │ WinSta0 │ │ │ │ SendMsg │ │ 循环 │ │ Desktop │ │ │ └─────┬─────┘ └────▲─────┘ └──────────┘ │ │ │ │ │ │ user32.dll │ LPC │ │ gdi32.dll │ ┌─────────────────┐ │ │ │ └──────────│ CsrClientCall.. │ │ │ │ └────────▲────────┘ │ │ ▼ │ │ │ NtUserCreateWindowEx() │ │ │ (系统调用号 0x1XXX) │ │ │ │ │ │ ├────────┼───────────────────────────────────┼─────────────────────┤ │ │ 内核模式 │ │ │ ▼ │ │ │ ┌─────────────────────────────┐ │ │ │ │ KiSystemService (int 0x2E) │ │ │ │ │ KiFastCallEntry (SYSENTER) │ │ │ │ └─────────────┬───────────────┘ │ │ │ │ │ │ │ ▼ │ │ │ ┌─────────────────────────────┐ │ │ │ │ KiSystemServiceHandler() │ │ │ │ │ Offset 1 (win32k 表) │ │ │ │ │ DescriptorTable Shadow[1]│ │ │ │ │ Handler Win32kSSDT[ID] │ │ │ │ └─────────────┬───────────────┘ │ │ │ │ │ │ ├────────────────┼────────────────────────────┼─────────────────────┤ │ │ win32k.sys │ │ │ ▼ │ │ │ ┌─────────────────────────────┐ ┌────────┴────────┐ │ │ │ NtUserCreateWindowEx() │ │ CsrClientCall.. │ │ │ │ (window.c:2699) │ │ (csr.c:72) │ │ │ │ │ │ └────────▲────────┘ │ │ │ ▼ │ │ │ │ │ IntCreateWindow() │ │ │ │ │ (window.c:1805) │ │ │ │ │ │ │ │ │ │ │ ├─ 验证参数 │ │ │ │ │ ├─ 分配 PWND │ │ │ │ │ ├─ 注册窗口 │ │ │ │ │ └─ co_IntSendMessage │ │ │ │ │ (WM_CREATE) │ │ │ │ │ │ │ │ │ │ │ └─ KeUserModeCallback ─────────┘ │ │ │ → user32!CallWindowProc │ │ │ → NtCallbackReturn │ │ │ │ │ └─────────────────────────────┘ │ └─────────────────────────────────────────────────────────────────┘8. 关键设计要点概念说明SSDT Shadowwin32k 注册到KeServiceDescriptorTableShadow[1]非 GUI 线程用主表GUI 线程用影子表系统调用号 0x1000高位编码表索引使内核分发器可区分 Native API 和 GUI 调用延迟转换线程首次调用 win32k 时才PsConvertToGuiThread分配大内核栈 初始化 W32THREAD全局 USER 锁UserEnterExclusive()/UserLeave()保护所有 USER 操作窗口、消息、输入KeUserModeCallbackwin32k 回调用户态 user32.dll 执行窗口过程、钩子等CSR LPC 通信win32k 通过CsrClientCallServer与 CSRSS 通信创建系统线程、会话GDI 批处理用户态缓存 GDI 调用系统调用前通过KeGdiFlushUserBatch批量刷新co_ 函数约定co_前缀表示需在全局 USER 锁下执行且可能回调用户态等价于 Windows 的xxx前缀