0x00 前言今天翻开期盼已久的《加密与解密(第4版)》,正式开始学习逆向分析技术。1.3.3 Windows 消息机制
看得一头雾水,于是开始从网上寻找相关文章。本文目的是记录自己的学习过程及整理思路。 P.S.:确实应该像坛主 说的那样,学习完Win32编程之后再看《加密与解密(第4版)》。
以下关于Windows SDK的介绍来自中文维基百科 :
Microsoft Windows SDK(简称Windows SDK或者SDK)是由微软公司出品的一个软件开发包,向在微软的Windows操作系统和.NET框架上开发软件和网站的程序员提供头文件、库文件、示例代码、开发文档和开发工具。
微软每次发布一个主要版本的Windows,都会发布对应的开发工具以使得开发人员能够调用新的操作系统的应用程序开发接口(API)。在Windows 98之后,这个开发工具包被命名为为Platform SDK。在Windows Vista的SDK推出时,这个产品改名为Windows SDK。和Windows一样,微软每次更新.NET框架,也会发布一个对应的.NET框架 SDK。在Windows 2008版本的Windows SDK推出之后,.NET框架SDK被整合到Windows SDK。
截至2013年,最新版本的Windows SDK是Windows 8.1 SDK,支持面向Windows Vista/Server 2008或更高版本的Windows的程序的开发,以及兼容Visual Studio 2010或者以上的版本,包括速成版本。
0x01 消息 0x01.1 What?
程序的执行分为两种:过程驱动与事件驱动。过程驱动的程序按照编写好的代码顺序执行,而事件驱动的程序执行顺序由触发事件顺序决定。
发生事件则触发消息,比如:创建窗口会触发WM_CREATE消息,绘制窗口会触发WM_PAINT消息,单击鼠标,按下键盘上某个键都会触发相应的消息。
消息的相关信息包含在MSG
结构体中:
typedef struct tagMSG {
HWND hwnd;
UINT message;
WPARAM wParam;
LPARAM lParam;
DWORD time;
POINT pt;
DWORD lPrivate;
} MSG, *PMSG, *NPMSG, *LPMSG;
(HWND)hwnd
:接收到消息的窗口的句柄,即触发该消息的窗口的句柄;
(UINT)message
:消息ID。标识每个消息;
(WPARM)wParam/(LPARAM)lParam
:消息的相关附加信息。与消息ID有关,不同消息ID的(WPARM)wParam/(LPARAM)lParam
含义不同;
(DWORD)time
:触发该消息时间;
(POINT)pt
:触发该消息时光标在屏幕上的位置。
0x01.2 消息队列Windows系统中有两种消息队列:
发生事件时,Windows先将触发的消息放入系统消息队列,之后根据消息的hwnd
值将消息复制到相应的应用程序消息队列,接着应用程序中的消息循环在其消息队列中检索每个消息并发送给相应的窗口处理函数。
0x02 示例代码VS2019创建一个新的Windows桌面应用程序
项目,创建完成后,使用其中的模板代码进行分析(其中部分已修改):
// WindowsProject1.cpp : 定义应用程序的入口点。
#include "framework.h"
#include "WindowsProject1.h"
#define MAX_LOADSTRING 100
// 全局变量:
HINSTANCE hInst; // 当前实例
// 函数原型:
ATOM MyRegisterClass(HINSTANCE hInstance);
BOOL InitInstance(HINSTANCE, int);
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
INT_PTR CALLBACK About(HWND, UINT, WPARAM, LPARAM);
int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE hPrevInstance,
_In_ LPWSTR lpCmdLine,
_In_ int nCmdShow)
{
UNREFERENCED_PARAMETER(hPrevInstance);
UNREFERENCED_PARAMETER(lpCmdLine);
MyRegisterClass(hInstance);
// 执行应用程序初始化:
if (!InitInstance (hInstance, nCmdShow))
{
return FALSE;
}
MSG msg;
// 主消息循环:
while (GetMessage(&msg, nullptr, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return (int) msg.wParam;
}
//
// 函数: MyRegisterClass()
//
// 目标: 注册窗口类。
//
ATOM MyRegisterClass(HINSTANCE hInstance)
{
WNDCLASSEXW wcex;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_WINDOWSPROJECT1));
wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
wcex.lpszMenuName = MAKEINTRESOURCEW(IDC_WINDOWSPROJECT1);
wcex.lpszClassName = _T("MAINCLASS");
wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));
return RegisterClassExW(&wcex);
}
//
// 函数: InitInstance(HINSTANCE, int)
//
// 目标: 保存实例句柄并创建主窗口
//
// 注释:
//
// 在此函数中,我们在全局变量中保存实例句柄并
// 创建和显示主程序窗口。
//
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
hInst = hInstance; // 将实例句柄存储在全局变量中
HWND hWnd = CreateWindowW(_T("MAINCLASS"), _T("Test"), WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, nullptr, nullptr, hInstance, nullptr);
if (!hWnd)
{
return FALSE;
}
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
return TRUE;
}
//
// 窗口处理函数: WndProc(HWND, UINT, WPARAM, LPARAM)
//
// 目标: 处理主窗口的消息。
//
// WM_COMMAND - 处理应用程序菜单
// WM_PAINT - 绘制主窗口
// WM_DESTROY - 发送退出消息并返回
//
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_COMMAND:
{
int wmId = LOWORD(wParam);
// 分析菜单选择:
switch (wmId)
{
case IDM_ABOUT:
DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
break;
case IDM_EXIT:
DestroyWindow(hWnd);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
}
break;
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hWnd, &ps);
// TODO: 在此处添加使用 hdc 的任何绘图代码...
EndPaint(hWnd, &ps);
}
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
// “关于”框的消息处理程序。
INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
UNREFERENCED_PARAMETER(lParam);
switch (message)
{
case WM_INITDIALOG:
return (INT_PTR)TRUE;
case WM_COMMAND:
if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)
{
EndDialog(hDlg, LOWORD(wParam));
return (INT_PTR)TRUE;
}
break;
}
return (INT_PTR)FALSE;
}
0x03 创建窗口创建窗口并非本文重点内容,为方便理解下文,故仅作简单介绍。
0x03.1 WinMain()
控制台应用的入口函数是main
,桌面应用程序的入口函数是WinMain
(示例代码中的wWinMain
是由于使用了Unicode字符集因而得此名):
(HINSTANCE)hInstance
:当前程序实例的句柄;
(HINSTANCE)hPrevInstance
:前一程序实例的句柄,这一参数值通常为NULL;
(LPWSTR)lpCmdLine
:命令行参数,包括程序名称;
(int)nCmdShow
:窗口的显示方式。
0x03.2 RegisterClassEx()
该函数为窗口注册函数,创建窗口之前应当先向操作系统注册该窗口。函数原型如下:
ATOM RegisterClassEx(
const WNDCLASSEX *Arg1
);
传递给该函数的是一个指向WNDCLASSEX
结构的指针,该结构定义如下:
typedef struct tagWNDCLASSEX {
UINT cbSize;
UINT style;
WNDPROC lpfnWndProc;
int cbClsExtra;
int cbWndExtra;
HINSTANCE hInstance;
HICON hIcon;
HCURSOR hCursor;
HBRUSH hbrBackground;
LPCSTR lpszMenuName;
LPCSTR lpszClassName;
HICON hIconSm;
} WNDCLASSEX, *PWNDCLASSEX, *NPWNDCLASSEX, *LPWNDCLASSEX;
(UINT)cbSize
:该结构体的大小(单位:Byte)。使用sizeof(WNDCLASSEX)
给此变量赋值;
(UINT)style
:窗口类的样式。若要使用多种样式,以OR运算符(|)连接(具体样式列表请参考Microsoft Docs );
(WNDPROC)lpfnWndProc
:指向窗口处理函数的指针;
(HINSTANCE)hInstance
:当前程序实例的句柄;
(HICON)hIcon
:图标的句柄;
(HCURSOR)hCursor
:光标的句柄;
(HBRUSH)hbrBackground
:背景刷的句柄或一颜色值。若为颜色值,需强制转换为HBRUSH类型;
(LPCSTR)lpszClassName
:指向类名字符串的指针;
(HICON)hIconSm
:与窗口类关联图标的句柄。如果是NULL,则将hIcon
指定图标转换为合适大小以使用;
0x03.3 CreateWindow()
该函数为创建窗口函数,其原型如下:
HWND CreateWindow(
LPCTSTR lpClassName,
LPCTSTR lpWindowName,
DWORD dwStyle,
int x,
int y,
int nWidth,
int nHeight,
HWND hWndParent,
HMENU hMenu,
HANDLE hInstance,
PVOID lpParam
);
(LPCTSTR)lpClassName
:指向窗口类名字符串的指针;
(LPCTSTR)lpWindowName
:指向窗口名称字符串的指针;
(DWORD)dwStyle
:窗口样式。具体值参考Microsoft Docs;
(int)x
:窗口相对于父容器的水平坐标。若该值为CW_USEDEFAULT,则忽略y
参数;
(int)y
:窗口相对于父容器的垂直坐标;
(int)nWidth
:窗口宽度。若该值为CW_USEDEFAULT,则忽略nHeight
参数;
(int)nHeight
:窗口高度;
(HWND)hWndParent
:创建窗口父窗口的句柄;
(HANDLE)hInstance
:窗口实例的句柄。
0x03.4 ShowWindow()
&& UpdateWindow()
BOOL ShowWindow(
HWND hWnd,
int nCmdShow
);
设置指定窗口的显示状态:
BOOL UpdateWindow(
HWND hWnd
);
更新指定窗口区域,并发送WM__PAINT
消息:
0x04 消息循环 MSG msg;
// 主消息循环:
while (GetMessage(&msg, nullptr, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
0x04.1 GetMessage()
从调用该函数的应用程序消息队列中取回消息,函数原型如下:
BOOL GetMessage(
LPMSG lpMsg,
HWND hWnd,
UINT wMsgFilterMin,
UINT wMsgFilterMax
);
(LPMSG)lpMsg
:指向接收消息的MSG
结构的指针。关于MSG
结构可参照0x01.1
;
(HWND)hWnd
:取回消息窗口的句柄。若为NULL,则该函数为所有当前应用程序的窗口取回消息,MSG.hwnd==NULL
的消息亦会被取回;
(UINT)wMsgFilterMin
:所取回消息的最小值。每个消息都是一个具有相对应数值的宏(e.g.:WM__COMMAND
---->0x111)。
(UINT)wMsgFilterMax
:所取回消息的最大值。wMsgFilterMin
与wMsgFilterMin
对取回的消息进行"过滤"(可能称作"筛选"更合适些)。[e.g.:wMsgFilterMin
与wMsgFilterMin
分别指定为WM_KEYFIRST 、WM_KEYLAST 用以接收键盘消息]
0x04.2 TranslateMessage()
将虚拟键消息转换成字符消息,即WM_KEYDOWN 与WM_KEYUP 组合产生一个WM_CHAR 或是WM_DEADCHAR 消息、 WM_SYSKEYDOWN 与WM_SYSKEYUP 组合产生一个WM_SYSCHAR 或是WM_SYSDEADCHAR 消息,该字符消息会到加入消息队列中,等待下一次调用GetMessage()
或PeekMessage()
取回。函数原型如下:
BOOL TranslateMessage(
const MSG *lpMsg
);
(const MSG *)lpMsg
:指向MSG
结构的指针,由GetMessage()
或PeekMessage()
取回。
TranslateMessage()
在消息循环中并非必需,但程序只要接收用户输入,就应当在消息循环中调用该函数,代码示例见Microsoft Docs ,其中的说明清晰明了,笔者便不再赘述:
0x04.3 DispatchMessage()
将消息交给窗口处理函数,函数原型如下:
LRESULT DispatchMessage(
const MSG *lpMsg
);
(const MSG *)lpMsg
:指向MSG
结构的指针。
0x05 总结上文是笔者按照个人理解组织而成,其中大部分内容均来自Microsoft Docs。
Windows常用消息及其wParam
、lParam
含义还应该查询Microsoft Docs。
GetMessage与PeekMessage、SendMessage与Postmesage的区别Microsoft Docs中已详细说明,笔者不再赘述,有兴趣者可自行查阅Microsoft Docs。
0x06 参考文档
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!