本专栏上一篇文章中我们讲解了Win32程序入口识别,定位回调函数,具体事件处理的定位,这一章节中我们来讲解一下子窗口的创建,子窗口的回调函数,并且逆向分析子窗口消息处理过程。
一.子窗口按钮的创建
我在学到本课程中Win32的时候,实在是听不懂,所以我专门去学习了Win32应用程序设计,当然,我也在CSDN中分享出来了我的学习成果,大家如果不是很理解这里的子窗口的创建的话,可以到我的达内Windows/Win32编程中大致看一下:达内Windows/Win32编程专栏。
这里我们讲解子窗口:按钮的创建过程。
在windows内核中,不仅有我们注册进去的窗口类,还有一些系统已经帮我们注册好的窗口类,这里的按钮就是系统帮我们注册好的窗口类。我们在创建按钮的时候,直接调用就可以了。
我们来看看创建按钮的实操:
- 创建子窗口:
使用CreateWindow()
函数:
HWND hButton = CreateWindow( TEXT("Button"), TEXT("anniu"), WS_CHILD|WS_VISIBLE|BS_PUSHBUTTON|BS_DEFPUSHBUTTON, 10, 10, 80, 20, hwnd, (HMENU)1001, //这是一个编号,我们后面对子串口消息做处理的时候会用到 hIns, NULL);
注意这里的创建子窗口一定要在主窗口创建之后,因为我们在创建子窗口的时候需要用到父窗口句柄。
实际上这里创建了子窗口之后,正常编写显示窗口等等,按钮就被创建出来了:
// P74子窗口(按钮).cpp : 定义应用程序的入口点。 // #define _CRT_SECURE_NO_WARNINGS 1 #include "framework.h" #include "P74子窗口(按钮).h" #include <stdio.h> #define MAX_LOADSTRING 100 HANDLE g_hOUTPUT = 0; //接收标准输出句柄 HINSTANCE hIns = 0; LRESULT CALLBACK WindowProc( IN HWND hwnd, IN UINT uMsg, IN WPARAM wParam, IN LPARAM lParam ); //普通窗口回调函数 int APIENTRY wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPWSTR lpCmdLine, _In_ int nCmdShow) { hIns = hInstance; //窗口类名 TCHAR className[] = TEXT("My First Windows."); //创建窗口对象 WNDCLASS wndclass = { 0 }; wndclass.hbrBackground = (HBRUSH)0; //窗口背景颜色 wndclass.lpfnWndProc = WindowProc; //窗口的过程函数 wndclass.lpszClassName = className; //窗口的类名字 wndclass.hInstance = hInstance; //定义窗口类的应用程序的实例句柄 wndclass.lpszMenuName = NULL; //注册窗口 RegisterClass(&wndclass); //创建窗口 HWND hwnd = CreateWindow( className, //lpname类名 TEXT("我的第一个窗口。"), //窗口标题 WS_OVERLAPPEDWINDOW, //dwStyle 10, 10, 600, 300, NULL, NULL, hInstance, NULL); HWND hButton = CreateWindow( TEXT("Button"), TEXT("按钮"), WS_CHILD|WS_VISIBLE|BS_PUSHBUTTON|BS_DEFPUSHBUTTON, 10, 10, 80, 20, hwnd, (HMENU)1001, hIns, NULL); //显示窗口 ShowWindow(hwnd, SW_SHOW); // 主消息循环: MSG msg; while (1) { if (PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE)) { if (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } else { return 0; } } else { //在空闲的时候,我们想让它做什么就可以填在这里 //i++; //TCHAR std[256] = { 0 }; //sprintf((char*)std,TEXT("进程无消息....%d\n"), i++); //WriteConsole(g_hOUTPUT, std,strlen((const char*)std), 0, 0); } } return (int)msg.wParam; }; LRESULT CALLBACK WindowProc( IN HWND hwnd, IN UINT uMsg, IN WPARAM wParam, IN LPARAM lParam ) { char output[256] = { 0 }; switch (uMsg) { //常见消息 case WM_DESTROY: { PostQuitMessage(0); return 0; } case WM_CREATE: { sprintf(output, "检测到WM_CREATE消息,将创建窗口。\n"); WriteConsole(g_hOUTPUT, output, strlen(output), 0, 0); return 0; } case WM_SIZE: { sprintf(output, "lParam:窗口宽变化为:%d,窗口高变化为:%d \n", HIWORD(lParam), LOWORD(lParam)); WriteConsole(g_hOUTPUT, output, strlen(output), 0, 0); return 0; } //键盘消息 case WM_KEYDOWN: { sprintf(output, "检测到WM_KEYDOWN消息,键码值:%d.\n", wParam); WriteConsole(g_hOUTPUT, output, strlen(output), 0, 0); return 0; } case WM_KEYUP: { sprintf(output, "检测到WM_KEYUP消息,键码值:%d.该按键被放开\n", wParam); WriteConsole(g_hOUTPUT, output, strlen(output), 0, 0); return 0; } //鼠标消息 case WM_LBUTTONDOWN: { sprintf(output, "检测到WM_LBUTTONDOWN消息,鼠标左键被按下。\n"); WriteConsole(g_hOUTPUT, output, strlen(output), 0, 0); return 0; } case WM_LBUTTONUP: { sprintf(output, "检测到WM_LBUTTONUP消息,鼠标左键被放开。\n"); WriteConsole(g_hOUTPUT, output, strlen(output), 0, 0); return 0; } case WM_RBUTTONDOWN: { sprintf(output, "检测到WM_RBUTTON消息,鼠标右键被按下。\n"); WriteConsole(g_hOUTPUT, output, strlen(output), 0, 0); return 0; } case WM_RBUTTONUP: { sprintf(output, "检测到WM_RBUTTON消息,鼠标右键被放开。\n"); WriteConsole(g_hOUTPUT, output, strlen(output), 0, 0); return 0; } case WM_MOUSEMOVE: { sprintf(output, "检测到WM_MOUSEMOVE消息,鼠标移动中,鼠标位置(%d,%d).\n", LOWORD(lParam), HIWORD(lParam)); WriteConsole(g_hOUTPUT, output, strlen(output), 0, 0); return 0; break; } case WM_MOUSEWHEEL: { sprintf(output, "鼠标滚轮滚动中,偏移量:%d,鼠标当前位置(%d,%d)\n", HIWORD(wParam), LOWORD(lParam), HIWORD(lParam)); WriteConsole(g_hOUTPUT, output, strlen(output), 0, 0); return 0; } case WM_CONTEXTMENU: { TrackPopupMenu( CreatePopupMenu(), TPM_LEFTALIGN, 0, 0, 0, hwnd, 0); return 0; } } return DefWindowProc(hwnd, uMsg, wParam, lParam); }
这里我是用的很多代码都是之前在编写Windows32程序调试的代码,大家主要看一下创建按钮的过程函数就可以了,我们来看看创建效果:
获取系统注册的窗口类的信息
我们在使用按钮等系统帮我们注册的窗口类的时候,我们并不能知道类的内部成员的参数,那么既然我们是学底层的,我们就应该了解一下它内部的参数:
- 获取类名:
使用GetClassName()
函数,MSDN官方文档解释
int GetClassName( [in] HWND hWnd, [out] LPTSTR lpClassName, [in] int nMaxCount );
参数解释:
hWnd:我们已经创建了的窗口的句柄
lpClassName:这是一个OUT类型的参数,获取到的名称字符串将被存储进去
nMaxCount:最大长度
这个函数能获取已经创建了的窗口类名
- 获取窗口类的完整参数:
使用GetClassInfo()
函数,MSDN官方文档解释
BOOL GetClassInfoA( [in, optional] HINSTANCE hInstance, [in] LPCSTR lpClassName, [out] LPWNDCLASSA lpWndClass );
参数解释:
hInstance:当前应用程序实例句柄,也就是那个ImageBase,我们需要使用全局变量来取得
lpClassName:窗口类名
lpWndClass:这是一个OUT类型的参数,我们需要先定义一个WndClass类型的结构,用于存储获取到的参数,然后将这个结构的地址给这个参数,获取到的参数将会被存储到这个结构体中。
二.按钮事件处理
我们来看看按钮消息的处理过程:
按钮被点击,产生消息->操作系统WinProc->转化为WM_COMMAND类型的消息->父窗口的WinProc
这里其实子窗口的消息处理函数最后都会被传到主窗口的消息处理函数中,所以我们需要处理按钮消息的时候,只需要在主窗口的消息处理函数中处理即可:
这里给出一段伪代码,供大家理解:
主窗口WinProc(...){ switch(uMsg){ case WM_COMMAND:{ switch(LOWORD(wParam){ case(消息编号){ .......//具体处理 } } } } }
看到这里,相信大家能看出来,这里的wParam的低两字节,就存储的是消息编号,我们根据这个,就可以对按钮消息做具体处理了。
三.消息堆栈
还记得我们在上一篇文章中对具体的事件做的条件断点的条件吗?
[esp+8] == WM_KEYDOWN
可能有很多人还是不理解这里为啥这里是esp+8,这里我们来看看消息堆栈,相信大家就会恍然大悟:
WinProc函数需要四个参数,在调用函数之前,就已经压入栈中了,在进入函数的时候,就会在栈中压入返回地址,所以这里的esp+8就是uMsg了。
四.逆向定位子窗口消息处理过程
我们在逆向过程中,必须要学会定位子窗口的消息处理。
我们来看看在逆向过程中定位子窗口消息处理的过程:
- 定位入口函数
- 定位主窗口回调函数
在定位主窗口回调函数之后,结合我们之前讲解的,我们需要在回调函数中做消息断点即可,比如我们要定位的按钮编号为0x000003E9,这里我们将条件设置为[esp+8]==WM_COMMAND && [EXP+0XC]==0X00003E9
即可得到断点。 - 获取按钮编号:我们在OllyDbg中可以直接看到按钮的编号:
- 条件断点设置:
这样,当我们点击按钮的时候,程序就会断在这里了,我们就可以逆向分析出当点击按钮的时候,程序到底做了哪些处理:
今天的文章就分享到这里,希望对大家有所帮助,另外,如果大家发现了文章中的错误,还请大家指出来,我会非常虚心地学习。希望我们共同进步!!!