副标题:精准型消息断点
引言1.
前文作为系列的开篇,我们站在Notepad.exe的视角,看它接过系统传来的消息,交由Notepad的窗口处理函数(WndProc)进行处理的过程。User32.dll!DispatchMessage API是前面"系统传来"4个字中的一环,也是最靠近应用层的一环。本文从该API切入,逐渐远离熟悉的应用层。开始前先回顾1个User32.dll导出的API(也是本文的主角):
1. User32.dll!DispatchMessage。 当Notepad.exe接受到消息后,通过该API调用RegisterClassEx注册的窗口过程。
引言2.
【原本本文我想结合Ollydbg的消息断点做佐证,奈何消息断点实在有各种限制,因此我在DispatchMessage上下条件断点,更精准的达到相同目的。】
用过Ollydbg的读者一定对Ollydbg中的条件断点并不陌生,我先演示在CrackMe中下条件断点的标准流程。
2.1.查找RegisterClassEx并下断
2.2.Notepad被断点中断后,分析调用RegisterClassExW时传入的窗口类:
上图堆栈区ESP指向函数返回地址(指向notepad.exe) ;ESP+4存放传入RegisterClassExW的WNDCLASSEX结构的地址。WNDCLASS结构定义如下,域变量lpfnWndProc位于结构体中的Offset 8 Byte处:
typedef struct tagWNDCLASSEXW {
UINT cbSize; //4 Byte
/* Win 3.x */
UINT style; //4 Byte
WNDPROC lpfnWndProc; //窗口过程地址
int cbClsExtra;
int cbWndExtra;
HINSTANCE hInstance;
HICON hIcon;
HCURSOR hCursor;
HBRUSH hbrBackground;
LPCWSTR lpszMenuName;
LPCWSTR lpszClassName;
/* Win 4.0 */
HICON hIconSm;
} WNDCLASSEXW, *PWNDCLASSEXW, NEAR *NPWNDCLASSEXW, FAR *LPWNDCLASSEXW;
在ESP+4处右键"Follow in dump",即可在数据区查看WNDCLASSEX各个域变量的值。图中0x19FB64(WNDCLASSEX + 0x08)处存有Notepad窗口过程(WndProc)地址:
数据区0x19FB64处右键"Follow DWORD in Disassembler",即可在指令窗口显示Notepad窗口过程的反汇编。(我用LoadMapEx加载了Notepad的map文件,因此Comment区域会显示窗口过程的符号名)
Notepad的窗口过程位于0x0401B90(很明显该过程位于Notepad.exe 代码段内),先记录这个地址并在窗口过程入口处下断点:
2.3.给Notepad.exe窗口下条件断点.
原本想给Notepad.exe下消息断点,奈何消息断点无效(百度搜索"消息断点失效",提问者不少解答者寥寥),于是变通为给DispatchMessageA/W下条件断点。DispatchMessage的唯一参数为MSG,结构如下:
typedef struct tagMSG {
HWND hwnd;
UINT message;
WPARAM wParam;
LPARAM lParam;
DWORD time;
POINT pt;
#ifdef _MAC
DWORD lPrivate;
#endif
} MSG, *PMSG, NEAR *NPMSG, FAR *LPMSG;
先给DispatchMessage下个普通断点,断下后结合堆栈来拼凑成条件断点:
上图CommandBar中用于给 DispatchMessageW下断点;
堆栈区:ESP ==>指向返回地址;ESP+4指向参数MSG的地址。
在ESP+4处右键"Follow in Dump"将在数据区解析MSG各个成员:
MSG+0x00 (0x19FCD0):0x00307E4,为窗口句柄
MSG+0x04 (0x19FCD4):0x60,为消息值
这是最终条件断点的表达式:
2.4.F9运行Ollydbg
当Notepad收到按键抬起消息时,ollydbg会中断在条件断点处。一般为了追踪处理消息的窗口过程中,Cracker此时会在Ollydbg中下内存访问断点,对于本文就是对Notepad的代码段下内存访问断点。
点击Ollydbg工具栏的"M",显示模块窗口:
再次运行ollydbg,程序马上会暂定在Notepad的代码段中。当然也包含窗口过程所在的地址。
等等别走,还没完。此处,我提出2个调试过程中遇到的值得深思的问题:
1.如果Notepad.exe的代码量极大,窗口过程恰好位于其他dll中,那么通过下内存访问断点来定位窗口过程的方式是不是失效了?
2.有别于练手的CrackMe程序,对于多线程程序,就如Notepad.exe,设置内存访问断点后,其他线程也会访问代码段,如何从中挑选出窗口过程?这无异于引入了大量的噪声,增加的分析的难度。(简单如Notepad.exe也有15个线程)
如何解决上述2个问题?让我们深入DispatchMessage函数。
1.USER32._InternalCallWinProc
借助前面给窗口过程下的断点,继续运行Ollydbg,Ollydbg中断。点击Ollydbg工具栏"K",查看函数调用堆栈:
第一第四栈帧有点眼熟,它显示了窗口程序在User32模块中从DispatchMessage API进入窗口过程的全过程。借助IDA为User32.dll生成map文件/Ollydbg LoadMap插件加载新生成的Map文件,可以获得相对友好的调用堆栈:
1.1. IDA生成User32.Map
IDA加载User32.dll,点击File--Produce file--Create MAP file,生成User32.map:
1.2. Ollydbg加载Map
Ollydbg--Plugins--LoadMapEx--LoadMapEx加载IDA生成的MAP:
(注:一定要在User32.dll模块的地址空间中加载User32.map,否则会干扰ollydbg的分析功能.一旦干扰ollydbg的分析功能,只能通过移除分析结果来恢复)
再次打开调用堆栈,得到较为友好的调用堆栈:
虽然图中有部分函数地址没有解析出来,但是通过单步跟踪可以得到如下调用链:
DispatchMessageW
|-->DispatchMessageWorker
|-->UserCallWinProcCheckWow
|-->InternalCallWinProc
同时借助泄露的win xp源码一探究竟:
UserCallWinProcCheckWow的实现位于NT\windows\core\ntuser\client\clmsg.c
LRESULT
UserCallWinProcCheckWow(
PACTIVATION_CONTEXT pActCtx,
WNDPROC pfn,
HWND hwnd,
UINT msg,
WPARAM wParam,
LPARAM lParam,
PVOID pww,
BOOL fEnableLiteHooks)
{
BOOL fInsideHook;
LRESULT lRet = 0;
BEGIN_CALLWINPROC(fInsideHook, lRet)
BOOL fOverride = fInsideHook && fEnableLiteHooks && IsMsgOverride(msg, &guah.uoiWnd.mm);
pfn = MapKernelClientFnToClientFn(pfn);
if (fOverride) {
/*
* NOTE: It is important that the same lRet is passed to all three
* calls, allowing the Before and After OWP's to examine the value.
*/
void * pvCookie = NULL;
if (guah.uoiWnd.pfnBeforeOWP(hwnd, msg, wParam, lParam, &lRet, &pvCookie)) {
goto DoneCalls;
}
lRet = (IsWOWProc(pfn) ? (*pfnWowWndProcEx)(hwnd, msg, wParam, lParam, PtrToUlong(pfn), KPVOID_TO_PVOID(pww)) :
InternalCallWinProc((WNDPROC)KPVOID_TO_PVOID(pfn), hwnd, msg, wParam, lParam));
if (guah.uoiWnd.pfnAfterOWP(hwnd, msg, wParam, lParam, &lRet, &pvCookie)) {
// Fall through and exit normally
}
DoneCalls:
;
} else {
lRet = (IsWOWProc(pfn) ? (*pfnWowWndProcEx)(hwnd, msg, wParam, lParam, PtrToUlong(pfn), KPVOID_TO_PVOID(pww)) :
InternalCallWinProc((WNDPROC)KPVOID_TO_PVOID(pfn), hwnd, msg, wParam, lParam));
}
END_CALLWINPROC(fInsideHook)
return lRet;
#ifdef _WIN64
UNREFERENCED_PARAMETER(pww);
#endif // _WIN64
}
InternalCallWinProc是宏,定义于NT\windows\core\ntuser\client\callproc.h
#define InternalCallWinProc(winproc, hwnd, message, wParam, lParam) \
(winproc)(hwnd, message, wParam, lParam)
其中winproc是函数指针,随之猜想,winproc可能会有机会指向Notepad.exe代码段中的某个地址。
2.精准型消息断点
先给前面猜想的结论,当UserCallWinProcCheckWow函数调用InternalCallWinProc时,winproc有机会(winproc还会指向ntdll中的回调函数)会指向Notepad的窗口过程:
如上图,调用InternalCallWinProc,EBX指向notepad.NPWndProc。既然如此,我可以在调用InternalCallWinProc时下条件断点(我就称它为精准型消息断点)
图中ebx的取值需要根据代码段的起始/结束位置做调整。
当ollydbg中断时,结合InternalCallWinProc的函数原型可知:
EBX指向窗口过程
ESI指向接受消息的窗口 ;
EDI指向消息类型
当然,我们还能进一步编辑条件断点的条件,过滤出特定的窗口消息WM_KEYUP消息。