windows消息机制详解
字数 2030 2025-08-06 23:10:27

Windows消息机制详解

0x00 前言

Windows是一个消息驱动的系统,消息机制提供了应用程序之间、应用程序与Windows系统之间进行通信的手段。深入理解Windows消息机制对于Windows开发和安全研究都至关重要。

0x01 基础概念

消息队列的存储与访问

  1. 消息队列位置:消息队列存储在0环(内核空间),通过KTHREAD.Win32Thread可以找到
  2. GUI线程特性
    • 并非所有线程都有消息队列,只有GUI线程才有
    • 一个GUI线程对应一个消息队列
  3. 线程转换过程
    • 普通线程指向SSDT表(Thread.ServiceTable->KeServiceDescriptorTable)
    • 当线程第一次调用Win32k.sys时,会调用PsConvertToGuiThread函数将其转换为GUI线程
    • 转换过程:
      a. 扩充内核栈到64KB(普通内核栈只有12KB)
      b. 创建包含消息队列的结构体并挂到KTHREAD上(对应MessageQueue属性)
      c. 将Thread.ServiceTable指向KeServiceDescriptorTableShadow表(包含SSDT和图形函数)
      d. 将需要的内存数据映射到本进程空间

0x02 窗口与线程

窗口创建过程

  1. 创建流程
    • CreateWindowCreateWindowExA/WVerNtUserCreateWindowExNtUserCreateWindowEx(进入0环)
  2. 窗口对象
    • 每个窗口在0环有一个WINDOW_OBJECT结构
    • pti字段指向所属线程
  3. 窗口与线程关系
    • 一个线程可以对应多个窗口
    • 一个窗口只能属于一个线程(在同一程序中)
    • 一个GUI线程只有一个消息队列,线程中所有窗口共享同一个消息队列

消息接收机制

  1. 消息队列结构

    • SentMessagesListHead:接收SendMessage发来的消息
    • PostedMessagesListHead:接收PostMessage发来的消息
    • HardwareMessagesListHead:接收鼠标、键盘等硬件消息
  2. GetMessage函数

    GetMessage(
      LPMSG lpMsg,    // 返回从队列中摘下来的消息
      HWND hWnd,      // 过滤条件一:发给这个窗口的消息
      UNIT wMsgFilterMin, // 过滤条件
      UNIT wMsgFilterMax  // 过滤条件
    );
    
    • 功能:循环判断是否有指定窗口的消息,如果有则将消息存储到MSG结构并从列表中删除
    • 处理顺序:优先处理SentMessagesListHead中的消息
  3. 消息发送方式

    • SendMessage:同步发送,发送方会等待接收方处理完毕
    • PostMessage:异步发送,发送方不会等待

0x03 消息分发

消息处理流程

  1. 典型消息循环

    MSG msg;
    while(GetMessage(&msg, NULL, 0, 0)){
      TranslateMessage(&msg);
      DispatchMessage(&msg);
    }
    
  2. 关键函数

    • DispatchMessage
      • 根据窗口句柄找到窗口对象
      • 根据窗口对象得到窗口过程函数并由0环发起调用
    • TranslateMessage
      • 处理键盘输入,将WM_KEYDOWN转换为WM_CHAR等消息
  3. 默认处理

    • 对于不需要特殊处理的消息,应使用DefWindowProc让系统默认处理

0x04 内核回调机制

回调机制原理

  1. 调用窗口过程的三种情况

    • GetMessage()处理SentMessagesListHead中的消息时
    • DispatchMessage()处理其他队列中的消息时
    • 内核代码直接调用
  2. 内核调用用户模式的方式

    • APC(异步过程调用)
    • 异常处理
    • 内核回调
  3. KeUserModeCallback机制

    NTSTATUS KeUserModeCallback(
      IN ULONG ApiNumber,       // 索引值
      IN PVOID InputBuffer,     // 输入缓冲区
      IN ULONG InputLength,     // 输入长度
      OUT PVOID *OutputBuffer,  // 输出缓冲区
      IN PULONG OutputLength    // 输出长度
    );
    
    • 调用过程:
      1. nt!KeUserModeCallback
      2. nt!KiCallUserMode
      3. nt!KiServiceExit
      4. ntdll!KiUserCallbackDispatcher
      5. 回调函数
      6. int2B
      7. nt!KiCallbackReturn
      8. 返回nt!KeUserModeCallback
  4. 回调函数表

    • 位于PEB结构的0x2C偏移处(PEB+0x2C)
    • user32.dll提供
    • ApiNumber参数作为索引在表中查找对应的回调函数

技术细节

  1. 窗口创建时的回调

    • CreateWindow可以不通过消息队列,而是直接调用3环提供的窗口过程
    • 例如WM_CREATE消息可能在窗口创建过程中直接被调用
  2. 查找回调表

    • 通过fs:[0]找到TEB
    • TEB的0x30偏移为PEB
    • PEB的0x2C偏移即为回调函数地址表

总结

Windows消息机制是一个复杂的系统,涉及用户模式和内核模式的交互。理解以下几点至关重要:

  1. 消息队列存储在0环,通过GUI线程访问
  2. 窗口与线程的关系及消息的分发机制
  3. SendMessagePostMessage的区别
  4. 内核回调机制及其实现细节
  5. KeUserModeCallback的工作流程和回调函数表的定位

这些知识对于Windows应用程序开发、逆向工程和安全研究都有重要意义。

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. 将需要的内存数据映射到本进程空间 0x02 窗口与线程 窗口创建过程 创建流程 : CreateWindow → CreateWindowExA/W → VerNtUserCreateWindowEx → NtUserCreateWindowEx (进入0环) 窗口对象 : 每个窗口在0环有一个 WINDOW_OBJECT 结构 pti 字段指向所属线程 窗口与线程关系 : 一个线程可以对应多个窗口 一个窗口只能属于一个线程(在同一程序中) 一个GUI线程只有一个消息队列,线程中所有窗口共享同一个消息队列 消息接收机制 消息队列结构 : SentMessagesListHead :接收 SendMessage 发来的消息 PostedMessagesListHead :接收 PostMessage 发来的消息 HardwareMessagesListHead :接收鼠标、键盘等硬件消息 GetMessage函数 : 功能:循环判断是否有指定窗口的消息,如果有则将消息存储到MSG结构并从列表中删除 处理顺序:优先处理 SentMessagesListHead 中的消息 消息发送方式 : SendMessage :同步发送,发送方会等待接收方处理完毕 PostMessage :异步发送,发送方不会等待 0x03 消息分发 消息处理流程 典型消息循环 : 关键函数 : DispatchMessage : 根据窗口句柄找到窗口对象 根据窗口对象得到窗口过程函数并由0环发起调用 TranslateMessage : 处理键盘输入,将 WM_KEYDOWN 转换为 WM_CHAR 等消息 默认处理 : 对于不需要特殊处理的消息,应使用 DefWindowProc 让系统默认处理 0x04 内核回调机制 回调机制原理 调用窗口过程的三种情况 : GetMessage() 处理 SentMessagesListHead 中的消息时 DispatchMessage() 处理其他队列中的消息时 内核代码直接调用 内核调用用户模式的方式 : APC(异步过程调用) 异常处理 内核回调 KeUserModeCallback机制 : 调用过程: nt!KeUserModeCallback nt!KiCallUserMode nt!KiServiceExit ntdll!KiUserCallbackDispatcher 回调函数 int2B nt!KiCallbackReturn 返回 nt!KeUserModeCallback 回调函数表 : 位于PEB结构的0x2C偏移处( PEB+0x2C ) 由 user32.dll 提供 ApiNumber 参数作为索引在表中查找对应的回调函数 技术细节 窗口创建时的回调 : CreateWindow 可以不通过消息队列,而是直接调用3环提供的窗口过程 例如 WM_CREATE 消息可能在窗口创建过程中直接被调用 查找回调表 : 通过 fs:[0] 找到TEB TEB的0x30偏移为PEB PEB的0x2C偏移即为回调函数地址表 总结 Windows消息机制是一个复杂的系统,涉及用户模式和内核模式的交互。理解以下几点至关重要: 消息队列存储在0环,通过GUI线程访问 窗口与线程的关系及消息的分发机制 SendMessage 和 PostMessage 的区别 内核回调机制及其实现细节 KeUserModeCallback 的工作流程和回调函数表的定位 这些知识对于Windows应用程序开发、逆向工程和安全研究都有重要意义。