标签:lap date mamicode turn call 不能 鼠标键盘 mes orm
Windows内核分析索引目录:https://www.cnblogs.com/onetrainee/p/11675224.html
消息机制
1.最基本的窗口创建
#include <windows.h> LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); //声明用来处理消息的函数 int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) { static TCHAR szAppName[] = TEXT("MyWindow"); HWND hwnd; MSG msg; WNDCLASS wndclass; //声明一个窗口类对象 //以下为窗口类对象wndclass的属性 wndclass.style = CS_HREDRAW | CS_VREDRAW; //窗口样式 wndclass.lpszClassName = szAppName; //窗口类名 wndclass.lpszMenuName = NULL; //窗口菜单:无 wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); //窗口背景颜色 wndclass.lpfnWndProc = WndProc; //窗口处理函数 wndclass.cbWndExtra = 0; //窗口实例扩展:无 wndclass.cbClsExtra = 0; //窗口类扩展:无 wndclass.hInstance = hInstance; //窗口实例句柄 wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION); //窗口最小化图标:使用缺省图标 wndclass.hCursor = LoadCursor(NULL, IDC_ARROW); //窗口采用箭头光标 if (!RegisterClass(&wndclass)) { //注册窗口类, 如果注册失败弹出错误提示 MessageBox(NULL, TEXT("窗口注册失败!"), TEXT("错误"), MB_OK | MB_ICONERROR); return 0; } hwnd = CreateWindow( //创建窗口 szAppName, //窗口类名 TEXT("我的窗口"), //窗口标题 WS_OVERLAPPEDWINDOW, //窗口的风格 CW_USEDEFAULT, //窗口初始显示位置x:使用缺省值 CW_USEDEFAULT, //窗口初始显示位置y:使用缺省值 CW_USEDEFAULT, //窗口的宽度:使用缺省值 CW_USEDEFAULT, //窗口的高度:使用缺省值 NULL, //父窗口:无 NULL, //子菜单:无 hInstance, //该窗口应用程序的实例句柄 NULL // ); ShowWindow(hwnd, iCmdShow); //显示窗口 UpdateWindow(hwnd); //更新窗口 while (GetMessage(&msg, NULL, 0, 0)) //从消息队列中获取消息 { TranslateMessage(&msg); //将虚拟键消息转换为字符消息 DispatchMessage(&msg); //分发到回调函数(过程函数) } return msg.wParam; } LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { HDC hdc; //设备环境句柄 PAINTSTRUCT ps; //绘制结构 RECT rect; //矩形结构 switch (message) //处理得到的消息 { case WM_CREATE: //窗口创建完成时发来的消息 MessageBox(hwnd, TEXT("窗口已创建完成!"), TEXT("我的窗口"), MB_OK | MB_ICONINFORMATION); return 0; case WM_PAINT: //处理窗口区域无效时发来的消息 hdc = BeginPaint(hwnd, &ps); GetClientRect(hwnd, &rect); DrawText(hdc, TEXT("Hello, 这是我自己的窗口!"), -1, &rect, DT_SINGLELINE | DT_CENTER | DT_VCENTER); EndPaint(hwnd, &ps); return 0; case WM_LBUTTONDOWN: //处理鼠标左键被按下的消息 MessageBox(hwnd, TEXT("鼠标左键被按下。"), TEXT("单击"), MB_OK | MB_ICONINFORMATION); return 0; case WM_DESTROY: //处理窗口关闭时的消息 MessageBox(hwnd, TEXT("关闭程序!"), TEXT("结束"), MB_OK | MB_ICONINFORMATION); PostQuitMessage(0); return 0; } return DefWindowProc(hwnd, message, wParam, lParam); //DefWindowProc处理我们自定义的消息处理函数没有处理到的消息 }
2.消息队列在哪里
微软将消息队列放入零环,在_Kthread+0x130 Win32Thread中,里面指向GUI线程。
1) 当线程刚创建的时候,都是普通线程
Thread.serviceTable->KeServiceDescriptorTable。
2)当线程第一次调用win32k.sys时,会调用一个函数:PsConvertToGuiThread
其主要做以下几件事情:
① 扩充内核栈,将原来内核栈12KB换成64KB大小。
② 创建一个包含消息队列的结构体,并挂到KTHREAD上。
③ Thread.ServiceTable->KeServiceDescriptorTableShadow。
④ 把需要的内存数据映射到本进程空间中。
因此,对于消息队列,在 _Kthread.Win32Thread+0xxx,里面存在一个USER_MESSAGE_QUEUE。
3.窗口与线程的关系
1)消息从哪里来,消息到哪儿去?
win32k.sys创建两个线程,一个监控鼠标线程,一个监控键盘线程。
桌面存在有关焦点,其很容易获取当前是存在哪个窗口,将其存储到窗口对应的线程的消息队列中。
2)窗口对象 WINDOW_OBJECT
每一个窗口控件,其本质是一个窗口,其对应在内核存在一个窗口对象。
注意:并不是外面一个大窗口才是窗口,其按钮也是一个窗口,都对应一个父窗口。
GUI线程与窗口对象的关系如下,消息队列存储在线程对象中。
窗口句柄表是全局的,在三环都可以查询得到该表,因此我们在三环通过窗口回调函数来遍历全部窗口。
3)窗口过程与窗口对象之间的关系
窗口过程与窗口对象并不是一一对应的,窗口过程是消息的处理函数。
不同的窗口对象可以使用一个处理函数,注册窗口时会声明该函数,不同窗口可以共用一个窗口回调,也可以使用自己单独的回调。
4.消息队列的接收
1)消息结构
其并不是一组消息队列,而是七组消息队列,其消息结构如下:
SendMessageListHead接收SendMessage发过来的消息,Post···接收PostMessage发过来的消息。
其中鼠标和键盘在<3>组中,键盘发过来的消息。
2)GetMessage函数分析
GetMessage(LPMSG lpMsg,HWND hWnd,UINT wMsgFilterMin,UINT wMsgFilterMax)
该函数远不止获取过滤消息这么点事情,其还应该做很多事情
① 对SendMessageListHead中的消息进行处理
其内核调用win32k.NtUserGetMessage函数,其处理过程如下:
其会判断SendMessageListHead的消息,然后调用KeUserModeCallBack返回三环执行窗口回调函数。
而其他的消息我们必须手动通过DispatchMessage来派发消息,但SendMessage发过来的在内核中就进行处理。
do{
······
do{
KeUserModeCallBack(USER32_CALLBACK_WINDORPROC,xxx); // 返回三转直接回调
}while(SendMessageListHead!=NULL);
······
}while(其他队列!=NULL);
② SendMessage 与 PostMessage区别
之前,我们在看API有关文档时,总会看到SendMessage同步,PostMessage异步。
如果你理解上面所讲,则很好理解,SendMessage投递过去,立刻在内核中就会处理,因此SendMessage发过去,就会等待直到处理完成。
但是PostMessage,发过去就立刻返回,因为无法确保立刻进行处理,这是非常重要的一部分。
5. 消息的分发
如上面消息队列中所描述的,硬件(鼠标键盘)发过来的消息被存储到Hardxxx的消息队列中,其不是SendMessageListHead中,因此无法自行取出。
所以,在GetMessage中获取消息,之后通过DispatchMessage来手动分发消息。
1)为何GetMessage获取后无法直接调用函数而是要调用DispatchMessage。
因为你在三环无法确定其到底是哪个窗口过程,只能得到窗口句柄,必须调用DispatchMessage回到零环。
故DispatchMessage中的作用就是获取窗口句柄,回到内核找到对应的WINDOW_OBJECT,得到对应的窗口过程,来进行处理。
2)TranslateMessage函数
该函数的目的是将键盘的消息来进行转换,加工键盘消息,比如将键盘的65转为‘A‘。
6. 内核回调机制
1)谁还调用了窗口过程:
三类:① GetMessage处理SendMessage的;② DispatchMessage分发窗口过程; ③ 内核回调。
我们之前已经介绍过①②,其还存在一个内核回调③,下面我们就来介绍这个。
2)内核回调:
在创建窗口过程中,CreateWindow内核创建时,其并不会通过①②途径来调用内核回调函数,而是直接在零环回调三环函数。
KeUserModeCallBack函数 : 其所有的界面内核函数(win32k.sys)回三环都通过该函数来进行分析。
因为其回到三环落脚点很多,所有不能通过APC和异常的方式来,其通过一张表来获取。
3)内核回调表 PEB+0x2c:
KeUserModeCallBack从零环返回三环时,落脚点不固定,因为全部界面内核函数返回三环都使用这张表。
这张表位于_PEB+KernelCallBackTable +0x2c 处,我们通过OD就可以查看该表。
如果我们想Hook这个函数,就直接来修改这张表即可。
标签:lap date mamicode turn call 不能 鼠标键盘 mes orm
原文地址:https://www.cnblogs.com/onetrainee/p/12638823.html