avatar

Catalog
消息的接收

要点回顾

前两篇关于消息机制的学习主要介绍了以下2点:

  • 一个GUI线程有一个消息队列

    Code
    1
    普通线程 -> GUI线程 -> THREAD.Win32Thread -> THREADINFO -> 消息队列
  • 一个线程可以有多个窗口,所有窗口共享一个消息队列

    Code
    1
    2
    _WINDOW_OBJECT -> PTHREADINFO pti	//所属线程
    -> WNDPROC lpfnWndProc //窗口过程(窗口回调函数)

窗口的创建过程

在3环创建窗口时,需要先创建并注册一个窗口类的对象,并注册窗口的样式,过程函数等。然后调用CreateWindow创建一个窗口。

而本质上,CreateWindow只不过是一个3环的接口,最终调用的是位于win32k.sys中的0环函数,并在0环给窗口创建一个_WINDOW_OBJECT结构体,每个窗口在0环都有这样一个结构体。

综上可以得出如下的结论:创建类对象与创建窗口的过程,本质上就是为创建一个_WINDOW_OBJECT结构作准备。

消息队列的结构

消息队列一篇中提到过,一旦线程调用win32k.sys提供的图形界面函数后,线程结构体中的成员Win32Thread就会指向一个结构体THREADINFO,该结构体中有一个成员MessageQueue就是消息队列,消息队列中包含7组队列(旧版ReactOS才有),用于处理不同类型的消息。

其中3个是比较常见的消息队列:

  • SentMessagesListHead:接到SendMessage发来的消息。
  • PostedMessagesListHead:接到PostMessage发来的消息。
  • HardwareMessagesListHead:接到鼠标、键盘的消息。

GetMessage的功能

在编写3环的窗口程序时,总会写如下一段代码:

c
1
2
3
4
5
6
MSG msg;
while(GetMessage(&msg,NULL,0,0))
{
TranslateMessage(&msg); //翻译消息
DispatchMessage(&msg); //分发消息
}

其中GetMessage函数负责从7个队列中取消息,并让TranslateMessage与DispatchMessage翻译并分发消息。但GetMessage函数真的只负责取消息吗?

本篇研究消息的接收,我们知道消息会先进入7个队列中,而TranslateMessage与DispatchMessage又是处理消息的,所以研究的重点就在GetMessage上。先来看函数原型:

c
1
2
3
4
5
6
GetMessage( 	
LPMSG lpMsg, //返回从队列中摘下来的消息
HWND hWnd, //过滤条件一:指定接收消息的窗口
UNIT wMsgFilterMin, //过滤条件
UNIT wMsgFilterMax //过滤条件
);

GetMessage有4个参数,其中3个都是过滤条件,包括指定接收消息的窗口;另一个就从队列中取得的消息。所以表面上,GetMessage通过循环判断是否有该窗口的消息,如果有,将消息存储到MSG结构中,并将消息从原先的消息队列中(7个队列中的某个)删除。接下来,将消息交给TranslateMessage与DispatchMessage去处理。

DispatchMessage通常就是将消息转发至窗口过程函数,从而使得过程函数被调用。但实际上,GetMessage也会消息进行处理。

(实验:测试只保留GetMessage的情况下是否能够执行窗口回调函数。此处省略,以后补上……)

为什么说GetMessage也会对消息进行处理呢?这里没有实验,就直接说明一下好了,GetMessage调用的是win32k.sys中的NtUserGetMessage函数,在NtUserGetMessage函数内部有如下逻辑(ReactOS版本不对没找到,IDA中跟了一下也没找到,所以就直接按着海哥分析的结果来):

c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
do
{
//先判断SentMessagesListHead是否有消息 如果有处理掉
do
{
....
KeUserModeCallback(USER32_CALLBACK_WINDOWPROC,
Arguments,
ArgumentLength,
&ResultPointer,
&ResultLength);
....
}while(SentMessagesListHead != NULL)
//依次判断其他的6个队列,里面如果有消息 返回 没有继续
}while(其他队列!=NULL)

在一个内部的do…while循环中,NtUserGetMesssage会先判断SentMessagesListHead中是否有消息,如果有的话,就调用窗口回调函数处理掉。接着直到处理完SentMessagesListHead中的所有消息后,才会判断其它6个队列中的消息,此时就不会对这些消息作处理了,而是直接将消息返回。所以,GetMessage也是会对消息进行处理的,但是只对SentMessagesListHead中的消息作处理。

SendMessage与PostMessage

这两个函数都是像窗口发送消息的函数,区别是SendMessage是同步的,而PostMessage是异步的。

(实验:分别发送SendMessage与PostMessage到一个注释掉DispatchMessage函数的窗口。此处省略,以后补上……)

这里实验省略,直接说结论:

  • SendMessage发送消息,GetMessage接收时会进入0环遍历SentMessagesListHead是否有消息,有就处理,没有就返回。有消息就必须处理完才返回,SendMessage要接收到对方执行完并返回处理结果才会结束,否则会一直堵塞在这。
  • PostMessage发送消息,GetMessage只会接收它的消息,不会处理,它的消息由TranslateMessage与DispatchMessage来处理。PostMessage不会等待对方返回处理结果,发完就立马结束。

总结

  1. GetMessage除了接收消息外,还会处理SentMessagesListHead队列中的消息。
  2. SendMessage与消息处理是同步的,会等待处理结果。PostMessage与消息处理是异步的,发完就结束执行。

参考资料

参考教程:

  • 海哥逆向中级预习班

参考链接:

Author: cataLoc
Link: http://cata1oc.github.io/2020/09/08/%E6%B6%88%E6%81%AF%E7%9A%84%E6%8E%A5%E6%94%B6/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.
Donate
  • 微信
    微信
  • 支付寶
    支付寶