windows消息机制详解
字数 2030 2025-08-06 23:10:27
Windows消息机制详解
0x00 前言
Windows是一个消息驱动的系统,消息机制提供了应用程序之间、应用程序与Windows系统之间进行通信的手段。深入理解Windows消息机制对于Windows开发和安全研究都至关重要。
0x01 基础概念
消息队列的存储与访问
- 消息队列位置:消息队列存储在0环(内核空间),通过
KTHREAD.Win32Thread可以找到 - GUI线程特性:
- 并非所有线程都有消息队列,只有GUI线程才有
- 一个GUI线程对应一个消息队列
- 线程转换过程:
- 普通线程指向SSDT表(
Thread.ServiceTable->KeServiceDescriptorTable) - 当线程第一次调用
Win32k.sys时,会调用PsConvertToGuiThread函数将其转换为GUI线程 - 转换过程:
a. 扩充内核栈到64KB(普通内核栈只有12KB)
b. 创建包含消息队列的结构体并挂到KTHREAD上(对应MessageQueue属性)
c. 将Thread.ServiceTable指向KeServiceDescriptorTableShadow表(包含SSDT和图形函数)
d. 将需要的内存数据映射到本进程空间
- 普通线程指向SSDT表(
0x02 窗口与线程
窗口创建过程
- 创建流程:
CreateWindow→CreateWindowExA/W→VerNtUserCreateWindowEx→NtUserCreateWindowEx(进入0环)
- 窗口对象:
- 每个窗口在0环有一个
WINDOW_OBJECT结构 pti字段指向所属线程
- 每个窗口在0环有一个
- 窗口与线程关系:
- 一个线程可以对应多个窗口
- 一个窗口只能属于一个线程(在同一程序中)
- 一个GUI线程只有一个消息队列,线程中所有窗口共享同一个消息队列
消息接收机制
-
消息队列结构:
SentMessagesListHead:接收SendMessage发来的消息PostedMessagesListHead:接收PostMessage发来的消息HardwareMessagesListHead:接收鼠标、键盘等硬件消息
-
GetMessage函数:
GetMessage( LPMSG lpMsg, // 返回从队列中摘下来的消息 HWND hWnd, // 过滤条件一:发给这个窗口的消息 UNIT wMsgFilterMin, // 过滤条件 UNIT wMsgFilterMax // 过滤条件 );- 功能:循环判断是否有指定窗口的消息,如果有则将消息存储到MSG结构并从列表中删除
- 处理顺序:优先处理
SentMessagesListHead中的消息
-
消息发送方式:
SendMessage:同步发送,发送方会等待接收方处理完毕PostMessage:异步发送,发送方不会等待
0x03 消息分发
消息处理流程
-
典型消息循环:
MSG msg; while(GetMessage(&msg, NULL, 0, 0)){ TranslateMessage(&msg); DispatchMessage(&msg); } -
关键函数:
DispatchMessage:- 根据窗口句柄找到窗口对象
- 根据窗口对象得到窗口过程函数并由0环发起调用
TranslateMessage:- 处理键盘输入,将
WM_KEYDOWN转换为WM_CHAR等消息
- 处理键盘输入,将
-
默认处理:
- 对于不需要特殊处理的消息,应使用
DefWindowProc让系统默认处理
- 对于不需要特殊处理的消息,应使用
0x04 内核回调机制
回调机制原理
-
调用窗口过程的三种情况:
GetMessage()处理SentMessagesListHead中的消息时DispatchMessage()处理其他队列中的消息时- 内核代码直接调用
-
内核调用用户模式的方式:
- APC(异步过程调用)
- 异常处理
- 内核回调
-
KeUserModeCallback机制:
NTSTATUS KeUserModeCallback( IN ULONG ApiNumber, // 索引值 IN PVOID InputBuffer, // 输入缓冲区 IN ULONG InputLength, // 输入长度 OUT PVOID *OutputBuffer, // 输出缓冲区 IN PULONG OutputLength // 输出长度 );- 调用过程:
nt!KeUserModeCallbacknt!KiCallUserModent!KiServiceExitntdll!KiUserCallbackDispatcher- 回调函数
int2Bnt!KiCallbackReturn- 返回
nt!KeUserModeCallback
- 调用过程:
-
回调函数表:
- 位于PEB结构的0x2C偏移处(
PEB+0x2C) - 由
user32.dll提供 ApiNumber参数作为索引在表中查找对应的回调函数
- 位于PEB结构的0x2C偏移处(
技术细节
-
窗口创建时的回调:
CreateWindow可以不通过消息队列,而是直接调用3环提供的窗口过程- 例如
WM_CREATE消息可能在窗口创建过程中直接被调用
-
查找回调表:
- 通过
fs:[0]找到TEB - TEB的0x30偏移为PEB
- PEB的0x2C偏移即为回调函数地址表
- 通过
总结
Windows消息机制是一个复杂的系统,涉及用户模式和内核模式的交互。理解以下几点至关重要:
- 消息队列存储在0环,通过GUI线程访问
- 窗口与线程的关系及消息的分发机制
SendMessage和PostMessage的区别- 内核回调机制及其实现细节
KeUserModeCallback的工作流程和回调函数表的定位
这些知识对于Windows应用程序开发、逆向工程和安全研究都有重要意义。