前言
当我们想要给某种消息下断点的时候,首先要了解的是消息的处理流程,比如消息怎么产生、消息怎么存储、消息怎么处理等等
事件与消息
事件:通俗来说就是动作,可以是用户触发的,也可以是Windows系统自身触发的;
消息:通俗来说是事件的描述信息,比如一个鼠标左键单击的事件,消息记录了这个事件产生的时间、鼠标箭头的坐标等信息,为了精确描述一个事件,Windows系统定义了一个MSG的结构体来记录消息内容
typedef struct tagMSG { HWND hwnd; //窗口句柄,相当于一个编号,用于标识窗口 UINT message; //消息类型 WPARAM wParam; //事件的描述信息 LPARAM lParam; //事件的描述信息 DWORD time; //事件产生时间 POINT pt; //坐标结构体 } MSG; typedef struct tagPOINT { LONG x; //x坐标 LONG y; //y坐标 } POINT;
消息处理
消息处理过程可以参考以下流程图:
根据流程图,我们可以简单书写一个windows窗口进行验证:
- 定义一个用户窗口的WNDCLASS类
WNDCLASS类的结构如下:
typedef struct _WNDCLASS { UINT style; WNDPROC lpfnWndProc; int cbClsExtra; int cbWndExtra; HINSTANCE hInstance; HICON hIcon; HCURSOR hCursor; HBRUSH hbrBackground; LPCTSTR lpszMenuName; LPCTSTR lpszClassName; } WNDCLASS, *PWNDCLASS;
1. 用户定义的类变量如下:
//窗口的类名 用于标识窗口 TCHAR className[] = TEXT("My First Window"); // 创建窗口类的对象 WNDCLASS wndclass = { 0 }; //一定要先将所有值赋值 wndclass.hbrBackground = (HBRUSH)COLOR_MENU; //窗口的背景色 wndclass.lpfnWndProc = WindowProc; //窗口过程函数 wndclass.lpszClassName = className; //窗口类的名字 wndclass.hInstance = hInstance; //定义窗口类的应用程序的实例句柄
这里需要提及的地方有两个:一是每个属性都要赋值,否则会报错;二是如果结构体一开始没有初始化为0,那属性的值在内存中全用0xCCCCCCCC填充
2. 注册用户定义的类
RegisterClass(&wndclass);
RegisterClass这个函数是寻找回调函数(窗口过程函数)的其中一个点,下面简单演示一下在OD种寻找回调函数:
寻找程序入口函数,相当于C语言函数中的main函数,这个函数有四个参数,F2下断点
int CALLBACK WinMain( _In_ HINSTANCE hInstance, //实例句柄,程序的ImageBase _In_ HINSTANCE hPrevInstance, //无意义 _In_ LPSTR lpCmdLine, //程序在命令行执行的附加参数的指针 _In_ int nCmdShow //表示窗口是最大化、最小化还是正常 ) ;
F7进入"main"函数内部寻找一个参数的RegisterClass,F2下断点,同时可以看到使用寄存器EAX传参
在堆栈中跟随EAX
根据前面提及WNDCLASS结构,可知第二个就是回调函数地址
查看回调函数的反汇编
3. 创建窗口与显示窗口
CreateWindow函数参数如下: HWND CreateWindow( LPCTSTR lpClassName, // registered class name LPCTSTR lpWindowName, // window name DWORD dwStyle, // window style int x, // horizontal position of window int y, // vertical position of window int nWidth, // window width int nHeight, // window height HWND hWndParent, // handle to parent or owner window HMENU hMenu, // menu handle or child identifier HINSTANCE hInstance, // handle to application instance LPVOID lpParam // window-creation data );
创建、显示窗口
//创建窗口 HWND hwnd = CreateWindow( className, //类名 TEXT("my first windows"), //窗口标题 WS_OVERLAPPEDWINDOW, //窗口外观样式 100, //相对于父窗口的X坐标 100, //相对于父窗口的Y坐标 600, //窗口的宽度 300, //窗口的高度 NULL, //父窗口句柄,为NULL NULL, //菜单句柄,为NULL hInstance, //当前应用程序的句柄 NULL); //附加数据一般为NULL if (hwnd == NULL){ return 0; //是否创建成功 } // 显示窗口 ShowWindow(hwnd, SW_SHOW);
4. 消息循环
//消息循环 MSG msg; while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); }
GetMessage将取出来的消息放到msg结构体中
5、回调函数
//回调函数 LRESULT CALLBACK WindowProc( IN HWND hwnd, //窗口句柄,相当于一个编号,用于标识窗口 IN UINT uMsg, //消息ID IN WPARAM wParam, //消息详细信息 IN LPARAM lParam //消息详细信息 ) { //窗口函数执行成功返回0,switch结构更高效 switch (uMsg) { //窗口消息 case WM_CREATE: { DbgPrintf("WM_CREATE %d %d\n", wParam, lParam); CREATESTRUCT* createst = (CREATESTRUCT*)lParam; DbgPrintf("CREATESTRUCT %s\n", createst->lpszClass); return 0; } case WM_MOVE: { DbgPrintf("WM_MOVE %d %d\n", wParam, lParam); POINTS points = MAKEPOINTS(lParam); DbgPrintf("X Y %d %d\n", points.x, points.y); return 0; } case WM_SIZE: { DbgPrintf("WM_SIZE %d %d\n", wParam, lParam); int newWidth = (int)(short)LOWORD(lParam); int newHeight = (int)(short)HIWORD(lParam); DbgPrintf("WM_SIZE %d %d\n", newWidth, newHeight); return 0; } case WM_DESTROY: { DbgPrintf("WM_DESTROY %d %d\n", wParam, lParam); PostQuitMessage(0); return 0; } //键盘消息 case WM_KEYUP: { DbgPrintf("WM_KEYUP %d %d\n", wParam, lParam); return 0; } case WM_KEYDOWN: { DbgPrintf("WM_KEYDOWN %d %d\n", wParam, lParam); return 0; } //鼠标消息 case WM_LBUTTONDOWN: { DbgPrintf("WM_LBUTTONDOWN %d %d\n", wParam, lParam); POINTS points = MAKEPOINTS(lParam); DbgPrintf("WM_LBUTTONDOWN %d %d\n", points.x, points.y); return 0; } } //返回消息给Windows处理 return DefWindowProc(hwnd, uMsg, wParam, lParam); }
6. 结果展示
父窗口消息断点
我们从父窗口的回调函数反汇编进行分析
这是典型的高效的switch语句,EAX就是消息类型的编号,之所以[ESP + 8 ]是消息类型编号,是因为WindowProc(回调函数)的调用约定属于stdcall,传参的时候是按照lParam、wParam、uMsg、hwnd的顺序,我们下消息断点的时候只需要设置[ESP + 8] == WM_LBUTTONDOWN (WM_LBUTTONDOWN可以切换为其他的),这时候我们可以过滤掉我们不要的消息类型
子窗口消息断点
子窗口(例如按钮)想要下消息断点,按照父窗口下断点的方式就行不通了,这就涉及到子窗口消息处理方式的差异。按钮属于一种特殊的窗体,并不需要像父窗口那样提供独立的回调函数,当按钮有事件产生时,其会向父窗口消息处理程序发送一个WM_COMMAND消息,因此我们只需要在父窗口回调函数处添加一个WM_COMMAND消息的处理即可
case WM_COMMAND: { switch (LOWORD(wParam)) { case 1001: MessageBox(hwnd, TEXT("Hello Button 1"), TEXT("Demo"), MB_OK); return 0; case 1002: MessageBox(hwnd, TEXT("Hello Button 2"), TEXT("Demo"), MB_OK); return 0; case 1003: MessageBox(hwnd, TEXT("Hello Button 3"), TEXT("Demo"), MB_OK); return 0; } return DefWindowProc(hwnd, uMsg, wParam, lParam); }
LOWORD()函数作用是取数值的低两位,case 1001、case 1002、case 1003中的数字就是HMENU hMenu(子窗口的ID),用于区分按钮
那我们如果想要下子窗口消息的断点,需要换一下条件,如[ESP + 8] == WM_COMMAND
简单实战寻找回调函数
分析回调函数功能
父窗口回调函数初始处下断点,并添加条件
点击按钮得知WM_COMMAND 值为0x111
取消0x00401100处的断点,在0x00401114处下断点,[esp + 0xc]就是wParam,子窗口ID,这样就可以查看每个按钮的ID
往下拉查看整个case(WM_COMMAND )逻辑,很容易理解
00401114 . 8B4424 0C MOV EAX,DWORD PTR SS:[ESP+C] 00401118 . 75 7E JNZ SHORT ReverseT.00401198 0040111A . 3D E9030000 CMP EAX,3E9 ; Switch (cases 3E9..3EB) 0040111F . 75 23 JNZ SHORT ReverseT.00401144 00401121 . 6A 00 PUSH 0 ; /Style = MB_OK|MB_APPLMODAL; Case 3E9 of switch 0040111A 00401123 . 68 74604000 PUSH ReverseT.00406074 ; |Title = "Demo" 00401128 . 68 68604000 PUSH ReverseT.00406068 ; |Text = "Find Me 1" 0040112D . 6A 00 PUSH 0 ; |hOwner = NULL 0040112F . C705 14854000>MOV DWORD PTR DS:[408514],1 ; | 00401139 . FF15 9C504000 CALL NEAR DWORD PTR DS:[40509C] ; \MessageBoxA 0040113F . 33C0 XOR EAX,EAX 00401141 . C2 1000 RETN 10 00401144 > 3D EA030000 CMP EAX,3EA 00401149 . 75 23 JNZ SHORT ReverseT.0040116E 0040114B . 6A 00 PUSH 0 ; /Style = MB_OK|MB_APPLMODAL; Case 3EA of switch 0040111A 0040114D . 68 74604000 PUSH ReverseT.00406074 ; |Title = "Demo" 00401152 . 68 5C604000 PUSH ReverseT.0040605C ; |Text = "Find Me 2" 00401157 . 6A 00 PUSH 0 ; |hOwner = NULL 00401159 . C705 14854000>MOV DWORD PTR DS:[408514],2 ; | 00401163 . FF15 9C504000 CALL NEAR DWORD PTR DS:[40509C] ; \MessageBoxA 00401169 . 33C0 XOR EAX,EAX 0040116B . C2 1000 RETN 10 0040116E > 3D EB030000 CMP EAX,3EB 00401173 . 75 23 JNZ SHORT ReverseT.00401198 00401175 . 6A 00 PUSH 0 ; /Style = MB_OK|MB_APPLMODAL; Case 3EB of switch 0040111A 00401177 . 68 74604000 PUSH ReverseT.00406074 ; |Title = "Demo" 0040117C . 68 50604000 PUSH ReverseT.00406050 ; |Text = "Find Me 3" 00401181 . 6A 00 PUSH 0 ; |hOwner = NULL 00401183 . C705 14854000>MOV DWORD PTR DS:[408514],3 ; | 0040118D . FF15 9C504000 CALL NEAR DWORD PTR DS:[40509C] ; \MessageBoxA 00401193 . 33C0 XOR EAX,EAX 00401195 . C2 1000 RETN 10 00401198 > 8B5424 10 MOV EDX,DWORD PTR SS:[ESP+10] ; Default case of switch 0040111A
按钮回调函数功能:弹出messagebox并且修改窗口的样式