avatar

Catalog
消息队列

前言

在初级班时曾学习过Win32相关API的用法,可以自己编写一些窗口界面,向窗口发送消息实现交互。但仍然有些问题是无法回答的:

  • 什么是窗口句柄?在哪里?有什么用?
  • 什么是消息?什么是消息队列?消息队列在哪?
  • 什么是窗口过程?窗口过程是谁调用的?没有消息循环窗口过程会执行吗?
  • 为什么要有w32k.sys这个模块?
  • 为什么只有使用图形界面的程序才可以访问KeServiceDescriptorTableShadow?
  • 界面”卡死”的时候为什么鼠标还可以动?

为了弄清楚这些问题,就必须进入0环,从底层弄清楚消息机制的本质,本篇从消息队列开始介绍。

什么是消息队列

先来看一个小实验,编写运行如下代码(环境:Windows XP,编译器:VC++6.0):

c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
// MessageQueue.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#define _WIN32_WINNT 0x0500
#include

typedef struct _Color
{
DWORD r,g,b;
}Color;

typedef struct _WindowClass
{
DWORD x;
DWORD y;
DWORD width;
DWORD hight;
Color color;
}WindowClass;

//画窗口
void PaintWindows(HDC hdc,WindowClass* p)
{
//取图形对象
HBRUSH hBrush = (HBRUSH)GetStockObject(DC_BRUSH);

//画刷
SelectObject(hdc,hBrush);
SetDCBrushColor(hdc,RGB(p->color.r, p->color.g, p->color.b));

MoveToEx(hdc, p->x, p->y,NULL);
LineTo(hdc, p->x+p->width, p->y);
LineTo(hdc, p->x+p->width, p->y+p->hight);
LineTo(hdc, p->x, p->y+p->hight);
LineTo(hdc, p->x, p->y);
Rectangle(hdc, p->x, p->y, p->x+p->width, p->y+p->hight+1);

DeleteObject(hBrush);
}

void main()
{
char cMessage;
HWND hwnd;
HDC hdc;

//设置窗口参数
WindowClass wClass;
wClass.x = 0;
wClass.y = 0;
wClass.width = 800;
wClass.hight = 400;
wClass.color.r = 0xEF;
wClass.color.g = 0xEB;
wClass.color.b = 0xDE;

//画在哪
hwnd = GetDesktopWindow();

//获取DC设备句柄:可以把DC理解成显卡缓存
hdc = GetWindowDC(hwnd);

for (;;)
{
//画窗口
PaintWindows(hdc, &wClass);

//接收消息
cMessage = getchar();
switch (cMessage)
{
case 'a':
wClass.color.r += 0x10;
wClass.color.g += 0x10;
wClass.color.b += 0x10;
break;

case 'b':
wClass.color.r -= 0x10;
wClass.color.g -= 0x10;
wClass.color.b -= 0x10;
break;
}

}

getchar();
}

代码逻辑不在此展开,默认是有Win32基础的,实验运行结果如下:

运行程序后,会在桌面的左上角画上一个窗口,接着我们可以通过发送消息来与窗口互动,当敲下“a”后,窗口的颜色会变深,敲下“b”后,窗口的颜色会变浅。

这是一个非常简单的交互程序,通过发送消息与窗口进行交互,但是这个代码有一个弊端,就是只能接收键盘发来的消息,对于鼠标亦或是其它进程发来的消息则无能为力。

想要能够接收并处理所有类型的消息,就必须有一个容器,而这个容器,就是消息队列

消息队列放哪

前面提到,得有一个消息队列,用来接收所有的消息,并分别处理,那么问题来了,这个消息队列放在什么位置?

专用进程

有一种思路是每个进程中放置一个消息队列,通过一个专用进程对不同类型的消息进行分发,如下图所示:

Linux操作系统采用的就是这种方式,另起一个专用进程负责接收消息,并将消息发送至不同的进程中去处理。这种方法需要有一个专用进程对消息进行处理,避免不了造成过多的跨进程通信,显然会使效率有所降低。

内核存储

微软使用了另一种策略,由于在0环中,不同进程的地址空间往往是相同的,利用这一点,就可以省去专用进程处理消息。

前面的实验中,我们通过手动画了一个窗口,Windows也提供了一部分图形界面API,尽管那些API的底层实现也是手动画图形界面。

在KThread+0x130处有一个成员Win32Thread

平时这个Win32Thread指向的值为空,一旦线程调用了图形界面API,它就会指向一个叫做_THREADINFO的结构体,消息队列就位于这个结构体中。根据ReactOS,THREADINFO结构如下:

GUI线程

当线程调用了图形界面API后,该线程的KTHREAD.Win32Thread就会指向一个叫做THREADINFO的结构,这个结构体中就包含了消息队列。此时这个线程不再是普通线程了,而是GUI线程。

  • 当线程刚创建的时候,都是普通线程:Thread.ServiceTable -> KeServiceDescriptorTable(只有一张表可见)
  • 当线程第一次调用Win32k.sys(图形界面API在0环的实现)时,会调用一个函数:PsConvertToGuiThread,这个函数主要做几件事:
    • 扩充内核栈,必须换成64KB的大内核栈,因为普通内核栈只有12KB大小。
    • 创建一个包含消息队列的结构体,并挂到KTHREAD上
    • Thread.ServiceTable -> KeServiceDescriptorTableShadow(此时两张表均可见)
    • 把需要的内存数据映射到本进程空间。

总结

  1. 消息队列存储在0环,通过KTHREAD.Win32Thread可以找到。
  2. 并不是所有线程都要消息队列,只有GUI线程才有消息队列
  3. 1个GUI线程对应1个消息队列。

参考资料

参考教程:

  • 海哥逆向中级预习班

参考链接:

Author: cataLoc
Link: http://cataloc.gitee.io/blog/2020/09/06/%E6%B6%88%E6%81%AF%E9%98%9F%E5%88%97/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.
Donate
  • 微信
    微信
  • 支付寶
    支付寶