天天看点

windbg调试窗口过程WindowProc(winxp 32bit)

    ollydbg在调试窗口程序方面做得很便捷,虽然windbg在这方面不如od,但通过命令的组合也能达到类似的效果。我借winxp的calc.exe为例来谈谈如何用windbg调试窗口过程。

    od在查找窗口过程(WindowProc)时相当方便,几步操作就够了,但windbg需要通过断点和函数堆栈来搜索这个用户定义的回调函数。该回调函数由RegisterClass(Ex)注册:

RegisterClassEx(
  const WNDCLASSEX* lpWndClass
);      

参数中WNDCLASSEX!lpfnWndProc域存放了注册的回调函数。因此,只要在这个函数上下断点当windbg停下后,搜索WNDCLASSEX!lpfnWndProc域的值即可。

    windbg加载calc.exe后,调试器会中断到用户,这时可以搜索符号并下断点:

0:000> x user32!RegisterClassEx*
77d1a401 USER32!RegisterClassExWOWA = <no type information>
77d1af7f USER32!RegisterClassExW = <no type information>
77d27c39 USER32!RegisterClassExA = <no type information>
77d1a164 USER32!RegisterClassExWOWW = <no type information>
0:000> bp user32!RegisterClassExA
breakpoint 1 redefined
0:000> bp user32!RegisterClassExW
breakpoint 0 redefined      

    继续运行,调试器会在calc.exe的主窗口出现之前再一次中断到用户:

0:000> .lastevent  @windbg遇到RegisterClassExW而中断
Last event: 17c.370: Hit breakpoint 0
  debugger time: Sat May  6 00:49:05.901 2017 (UTC + 8:00)
0:000> kb 
ChildEBP RetAddr  Args to Child              
0007ed94 771fb59e ->0007edac<-函数的第一个参数 0000012c 0007ee08 USER32!RegisterClassExW
0007ede0 771840cb 77180000 00000001 0007ee30 comctl32!InitScrollBarClass+0x77
0007edfc 771841f8 13700002 00000008 0000ffff comctl32!InitCommonControlsEx+0x43
0007ee10 77184277 77180000 0007ee3c 7c92118a comctl32!_ProcessAttach+0x71
0007ee1c 7c92118a 77180000 00000001 00000000 comctl32!LibMain+0x21
0007ee3c 7c93c4da 77184256 77180000 00000001 ntdll!LdrpCallInitRoutine+0x14
0007ef44 7c936351 00000000 00000000 00000000 ntdll!LdrpRunInitializeRoutines+0x344
0007f1f0 7c9364b3 00000001 000a4908 0007f4e4 ntdll!LdrpLoadDll+0x3e5
0007f498 7c801bbd 000a4908 0007f4e4 0007f4c4 ntdll!LdrLoadDll+0x230
0007f500 7c80aeec 7d5a2e50 00000000 00000000 kernel32!LoadLibraryExW+0x18e
0007f514 7d5f6740 7d5a2e50 0007fd30 7d5a2e50 kernel32!LoadLibraryW+0x11
0007f54c 7d5b7603 7d5a2e50 7d590000 7d5f66f9 SHELL32!SHFusionLoadLibrary+0x2a
0007f558 7d5f66f9 00000020 00000008 0007f7a0 SHELL32!DelayLoadCC+0x15
0007f78c 7d5f666b 0007f7a0 0000007c 00000001 SHELL32!SHFusionInitializeIDCC+0x92
0007f9ac 7d5f65a9 7d590000 0000007c 00000001 SHELL32!SHFusionInitializeFromModuleID+0x3a
0007f9c0 7d5f6566 7d590000 00000001 0007f9f0 SHELL32!_ProcessAttach+0x34
0007f9d0 7d5b751e 7d590000 00000001 0007fd30 SHELL32!DllMain+0x27
0007f9f0 7c92118a 7d590000 00000001 0007fd30 SHELL32!_DllMainCRTStartup+0x52
0007fa10 7c93c4da 7d5b74d6 7d590000 00000001 ntdll!LdrpCallInitRoutine+0x14
0007fb18 7c941194 0007fd30 7ffdf000 7ffd9000 ntdll!LdrpRunInitializeRoutines+0x344
0007fc94 7c94108f 0007fd30 7c920000 0007fce0 ntdll!LdrpInitializeProcess+0x1131
0007fd1c 7c92e437 0007fd30 7c920000 00000000 ntdll!_LdrpInitialize+0x183      

上面kb的结果显示程序向USER32!RegisterClassExW参数的WNDCLASSEX*为0x7edac,我们只要在这个地址上以WNDCLASSEX结构转储内存,就能获得WNDCLASSEX!lpfnWndProc域的值:

0:000> dt USER32!WNDCLASSEX 0007edac 
Symbol USER32!WNDCLASSEX not found.      

很可惜,windbg找不到这个结构的符号定义,那只能参考MSDN,肉眼搜索了。关于这个结构,MSDN定义为

typedef struct tagWNDCLASSEX {
  UINT      cbSize;
  UINT      style;
  WNDPROC   lpfnWndProc;
  int       cbClsExtra;
  int       cbWndExtra;
  HINSTANCE hInstance;
  HICON     hIcon;
  HCURSOR   hCursor;
  HBRUSH    hbrBackground;
  LPCTSTR   lpszMenuName;
  LPCTSTR   lpszClassName;
  HICON     hIconSm;
} WNDCLASSEX, *PWNDCLASSEX;      

很好,第三个DWORD所在的地址保存了WNDCLASSEX!lpfnWndProc域的值,那就直接用dd命令转储:

0:000> dd 0007edac L4
0007edac  00000030 0000408b 771fad26 00000000
0:000> ln 771fad26 @windbg显示地址0x771fad26保存了消息回调函数,我查看一下符号确认一下
(771fad26)   comctl32!CUxScrollBarCtl::WndProc   |  (771fb527)   comctl32!InitScrollBarClass
Exact matches:
    comctl32!CUxScrollBarCtl::WndProc = <no type information> @从符号的结果来看,还有点像窗口过程      

    地址0x771fad26保存了消息回调函数,那就接着在这个地址上下断点,当窗口过程响应消息时使windbg中断下来:

0:000> bp 771fad26      

当我让windbg继续运行后,调试器多次在RegisterClassExA(W)中断但直到calc.exe的主界面出现,windbg都没有在窗口过程0x771fad26处中断:

0:000> g
Breakpoint 0 hit
USER32!RegisterClassExW:
77d1af7f 8bff            mov     edi,edi
0:000> g
ModLoad: 74680000 746cc000   C:\WINDOWS\system32\MSCTF.dll
Breakpoint 1 hit
USER32!RegisterClassExA:
77d27c39 8bff            mov     edi,edi
0:000> g
ModLoad: 73640000 7366e000   C:\WINDOWS\system32\msctfime.ime
Breakpoint 0 hit
USER32!RegisterClassExW:
77d1af7f 8bff            mov     edi,edi
0:000> g
Breakpoint 0 hit
USER32!RegisterClassExW:
77d1af7f 8bff            mov     edi,edi
0:000> g      

这让我意识到calc.exe存在不止一个窗口,而我只给其中一个窗口过程下了断点......看来,我还得参考od调试窗口程序:先搜索主窗口,然后在对应的WindowProc上下断点。

下图是在ollydbg中点击查看-窗口后得到的calc.exe程序的窗口列表,其中包含了各个窗口的句柄,类名等信息。

windbg调试窗口过程WindowProc(winxp 32bit)

为了准确定位主窗口注册的WindowProc回调函数,需要先判断WNDCLASSEX!lpszClassName域值是否与ollydbg获得的窗口类名相同。一旦找到目标窗口类,再在WNDCLASSEX!lpfnWndProc域值上下断点。

0:000> x USER32!RegisterClassExW
77d1af7f USER32!RegisterClassExW = <no type information>
0:000> bp 77d1af7f ".if(1){du poi(poi(esp+4)+0xa*4)}"
breakpoint 0 defined
0:000> g
01014018  "SciCalc"
USER32!RegisterClassExW:      

解释一下上面条件断点的作用:这个条件断点用于打印字符串WNDCLASSEX!lpszClassName的内容。当windbg在断点USER32!RegisterClassExW处中断后,esp+4是函数的第一个参数,这个参数的类型是WNDCLASSEX*。poi(esp+4)取得指针指向的结构变量,poi(esp+4)+0x0a*4相当于取WNDCLASSEX!lpszClassName域并用du命令输出。

    现在,已经成功获得了主窗口的WNDCLASSEX结构,我们顺藤摸瓜,即可获得主窗口的窗口过程的地址:

0:000> dd poi(esp+4) L8 @取RegisterClassEx的参数,可以通过kb命令验证一下取出的参数是否正确
0007fdd8  00000030 00000000 01006118 00000000
0007fde8  0000001e 01000000 03900147 00010011
0:000> kb
ChildEBP RetAddr  Args to Child              
0007fdc8 0100183c 0007fdd8 <----传递给函数的结构的地址,和上面dd poi(esp+4)相同 000a1efc 00000030 USER32!RegisterClassExW
0007fe08 01001fc0 00000000 7c80b731 000a1efc calc!InitializeWindowClass+0x75
0007ff1c 010125e9 01000000 00000000 000a1efc calc!WinMain+0x6f
0007ffc0 7c817067 0176f6ee 0176f742 7ffd8000 calc!WinMainCRTStartup+0x174
0007fff0 00000000 01012475 00000000 78746341 kernel32!BaseProcessStart+0x23
0:000> ln 01006118 @取WNDCLASSEX!lpfnWndProc的域值,查看其符号信息
(01006118)   calc!CalcWndProc   |  (0100652a)   calc!SubDispEditProc
Exact matches:    @该地址正好是calc的窗口过程CalcWndProc,可以在这个位置下断点,用以处理消息
    calc!CalcWndProc = <no type information>      

上面获得的主窗口过程calc!CalcWndProc的值为0x1006118,这和ollydbg分析得到的值一致,这说明目前为止,我的分析还算靠谱~

    接下来给窗口过程下断点,并在收到左键按下消息时往调试窗口打印一串字符串:

0:000> bc *
0:000> bp 01006118 ".if(dwo(esp+8)==202){.echo LButton};gc"
breakpoint 0 redefined      

解释一下上面条件断点的作用,根据MSDN对WindowProc的接口定义:

LRESULT CALLBACK WindowProc(
  HWND hwnd, 
  UINT uMsg, 
  WPARAM wParam, 
  LPARAM lParam 
);      

其中第二个参数为消息值。当windbg在断点处中断后esp+8处保存了第二个参数,通过dwo命令将其值转储出来并与WM_LBUTTON(值为202)比较,如果相同则往windbg输出窗口打印输出字符串"LButton"

最后,展示一下效果截图,如下:

windbg调试窗口过程WindowProc(winxp 32bit)