首页
社区
课程
招聘
[原创]消息万能断点
发表于: 2009-9-22 13:28 26956

[原创]消息万能断点

2009-9-22 13:28
26956

既然window是基于消息的系统,我们就来研究一下应用程序的消息处理,就拿最简单的窗口程序来试验下吧
下面是Iczelion写的创建简单窗口的例程
.386
.model flat,stdcall
option casemap:none
include windows.inc
include user32.inc
includelib user32.lib            ; calls to functions in user32.lib and kernel32.lib
include kernel32.inc
includelib kernel32.lib

WinMain proto :DWORD,:DWORD,:DWORD,:DWORD

.DATA                     ; initialized data
ClassName db "SimpleWinClass",0        ; the name of our window class
AppName db "Our First Window",0        ; the name of our window

.DATA?                ; Uninitialized data
hInstance HINSTANCE ?        ; Instance handle of our program
CommandLine LPSTR ?
.CODE                ; Here begins our code
start:
invoke GetModuleHandle, NULL            ; get the instance handle of our program.
                                                                       ; Under Win32, hmodule==hinstance mov hInstance,eax
mov hInstance,eax
invoke GetCommandLine                        ; get the command line. You don't have to call this function IF
                                                                       ; your program doesn't process the command line.
mov CommandLine,eax
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT        ; call the main function
invoke ExitProcess, eax                           ; quit our program. The exit code is returned in eax from WinMain.

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
    LOCAL wc:WNDCLASSEX                                            ; create local variables on stack
    LOCAL msg:MSG
    LOCAL hwnd:HWND

    mov   wc.cbSize,SIZEOF WNDCLASSEX                   ; fill values in members of wc
    mov   wc.style, CS_HREDRAW or CS_VREDRAW
    mov   wc.lpfnWndProc, OFFSET WndProc
    mov   wc.cbClsExtra,NULL
    mov   wc.cbWndExtra,NULL
    push  hInstance
    pop   wc.hInstance
    mov   wc.hbrBackground,COLOR_WINDOW+1
    mov   wc.lpszMenuName,NULL
    mov   wc.lpszClassName,OFFSET ClassName
    invoke LoadIcon,NULL,IDI_APPLICATION
    mov   wc.hIcon,eax
    mov   wc.hIconSm,eax
    invoke LoadCursor,NULL,IDC_ARROW
    mov   wc.hCursor,eax
    invoke RegisterClassEx, addr wc                       ; register our window class
    invoke CreateWindowEx,NULL,\
                ADDR ClassName,\
                ADDR AppName,\
                WS_OVERLAPPEDWINDOW,\
                CW_USEDEFAULT,\
                CW_USEDEFAULT,\
                CW_USEDEFAULT,\
                CW_USEDEFAULT,\
                NULL,\
                NULL,\
                hInst,\
                NULL
    mov   hwnd,eax
    invoke ShowWindow, hwnd,CmdShow               ; display our window on desktop
    invoke UpdateWindow, hwnd                                 ; refresh the client area
  .WHILE TRUE                                                         ; Enter message loop
                 invoke GetMessage, ADDR msg,NULL,0,0
                .BREAK .IF (!eax)
                invoke TranslateMessage, ADDR msg
                invoke DispatchMessage, ADDR msg
   .ENDW
    mov     eax,msg.wParam   
                                          ; return exit code in eax
    ret
WinMain endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
    .IF uMsg==WM_DESTROY                           ; if the user closes our window
        invoke PostQuitMessage,NULL             ; quit our application
    .ELSE
        invoke DefWindowProc,hWnd,uMsg,wParam,lParam     ; Default message processing
        ret
    .ENDIF
    xor eax,eax
    ret
WndProc endp

end start
刚开始我想原来消息处理就这么简单啊,构建一个消息循环,用GetMessage获取所有消息,用DispatchMessage分发消息给窗口过程,是这样吗?

先做第一个试验,将invoke GetMessage, ADDR msg,NULL,0,0 改成invoke GetMessage, ADDR msg,hwnd,0,0 你会发现当你关闭窗口时,程序还在进程列表里并没退出,why?这是因为现在只能接受窗口句柄为hwnd的消息,关闭窗口时,收到消息WM_DESTROY 后窗口过程里调用PostQuitMessage发送的WM_QUIT消息是给应用程序的,不是给窗口的,所以GetMessage是收不到此消息的。将invoke GetMessage语句改回来

现在第二个试验,用od载入,在
0040110B >|.  E8 42000000   |CALL <JMP.&user32.DispatchMessageA>     ; \DispatchMessageA
下个条件断点[[esp]+4]==WM_DESTORY,当我们关闭窗口时,程序并没有像我们想象的那样被断下来,而是直接退出了,why?我们改一下断点,在窗口过程的入口点00401119下个条件断点[esp+8]==WM_DESTORY,
00401119 >/.  55            PUSH EBP
0040111A  |.  8BEC          MOV EBP,ESP
0040111C  |.  837D 0C 02    CMP DWORD PTR SS:[EBP+C],2
00401120  |.  75 09         JNZ SHORT trial.0040112B
00401122  |.  6A 00         PUSH 0                                   ; /ExitCode = 0
00401124  |.  E8 41000000   CALL <JMP.&user32.PostQuitMessage>       ; \PostQuitMessage
再试试,这次被顺利断下来了,好了,现在我们开始怀疑,WM_DESTORY消息好像并不是通过我们设计的消息循环而来到窗口过程的,那它是怎么来到我们的窗口过程的呢,难道跟我们前面在系统里注册了窗口类有关?(因为定义了窗口过程mov   wc.lpfnWndProc, OFFSET WndProc),当我们把下面几个地方也下断点重新运行时,结果让我们大吃一惊
004010D0  |.  E8 71000000   CALL <JMP.&user32.CreateWindowExA>       ; \CreateWindowExA
004010DE  |.  E8 93000000   CALL <JMP.&user32.ShowWindow>            ; \ShowWindow
004010E6  |.  E8 97000000   CALL <JMP.&user32.UpdateWindow>          ; \UpdateWindow
004010F5  |.  E8 5E000000   |CALL <JMP.&user32.GetMessageA>          ; \GetMessageA
0040110B  |.  E8 42000000   |CALL <JMP.&user32.DispatchMessageA>     ; \DispatchMessageA
在调用CreateWindowExA函数里就已经使用了我们的窗口过程,处理的消息码为24h,81h,83h,01h(01h为WM_CREATE)
ShowWindow也调用了我们的窗口过程,处理了消息18h,46h,1c,86h,0dh,06h,281h,282h,7h,85h,0d,14h,47h,05h,03h
UpdateWindow也调用了我们的窗口过程,处理了消息0fh
GetMessageA也调用了我们的窗口过程,处理了消息7fh,7fh,7fh,这个可不是它返回后由DispatchMessageA分发给窗口过程的,而是它自己函数内部就调用了

有兴趣还可以试下,先将程序跑起来,将od窗口缩小点,让我们能看到程序窗口,然后在GetMessageA的返回处004010FA和窗口过程入口00401119下断点
004010F5  |.  E8 5E000000   |CALL <JMP.&user32.GetMessageA>          ; \GetMessageA
004010FA  |.  0BC0          |OR EAX,EAX
然后将鼠标移向程序窗口,这时被断在00401119,处理了消息84h,20h,然后才轮到我们的GetMessageA返回一个200h(WM_MOUSEMOVE)的消息,再由DispatchMessageA交给00401119

从上面的实验可以看出,是系统一直在管理着我们的消息处理,很多内部消息它是直接调用了我们的窗口过程函数,而交给GetMessageA带回的只是很少一部分消息,类似鼠标移动,按键,键盘等,经我试验,像恢复窗口显示时标题栏,最小化,最大化,关闭按钮,以及窗口的边框显示都是由系统自己调用我们窗口过程完成的,而交给GetMessageA带回的WM_PAINT消息也仅仅是用来显示客户区而已。

好了,现在进入我们的重点,虽然我们设计的消息循环只是处理很小一部分消息而已,但所有的消息都是由我们的窗口过程处理的,观察堆栈发现,不管是由系统调用也好,还是由我们的DispatchMessageA分发来的好,我们的窗口过程结束都返回到77d18734
0012FDEC   77D18734  返回到 user32.77D18734
好了,我们就去那里看看吧,看到什么没
77D1870C    55              PUSH EBP
77D1870D    8BEC            MOV EBP,ESP
77D1870F    56              PUSH ESI
77D18710    57              PUSH EDI
77D18711    53              PUSH EBX
77D18712    68 CDABBADC     PUSH DCBAABCD
77D18717    56              PUSH ESI
77D18718    FF75 18         PUSH DWORD PTR SS:[EBP+18]
77D1871B    FF75 14         PUSH DWORD PTR SS:[EBP+14]
77D1871E    FF75 10         PUSH DWORD PTR SS:[EBP+10]
77D18721    FF75 0C         PUSH DWORD PTR SS:[EBP+C]
77D18724    64:A1 18000000  MOV EAX,DWORD PTR FS:[18]
77D1872A    8088 B40F0000 0>OR BYTE PTR DS:[EAX+FB4],1
77D18731    FF55 08         CALL DWORD PTR SS:[EBP+8]           :[EBP+8] 这里就是我们的窗口过程函数,可能有些系统显示的是       CALL DWORD PTR [EBX+X] 但实质是一样的
77D18734    64:8B0D 1800000>MOV ECX,DWORD PTR FS:[18]

让我们重载程序,在77D1870C下个断点,观察堆栈发现什么没,所有的消息处理都经过这里,这里才是系统真正的底层消息处理函数,其中[esp+4]里放的是窗口过程函数的地址,比如这里的00401119,[esp+8]为窗口句柄,[esp+c]为消息码,77D1870C就是我们的万能消息断点了,经快雪时晴兄指点这个地址是个user32内部函数,名为
user32.InternalCallWinProc,而且在不同系统下不是个定值,例如他的为7E41870C。在此感谢快雪时晴,至于怎么找到自己系统的万能断点地址详看下面快雪时晴兄的回帖
要想找到窗口过程函数,中断后[esp+4]就是,如果想看对特定消息的处理,在这个断点加个条件,比如菜单,按钮的WM_COMMAND,[esp+c]==WM_COMMAND,也许有人会说,那如果是新控件,又不知道消息码怎么办,比如列表视图里鼠标点击选中一项,我们可以直接下条件断点,[esp+c]==WM_LBUTTONDOWN,触发消息被断下来了吧,比如我这里
0012FE00   77D18816  返回到 user32.77D18816 来自 user32.77D1870C
0012FE04   5D176E16  返回到 COMCTL32.5D176E16
可以看出是子控件自己处理了消息(子控件也是窗口,有自己的窗口过程函数),并且最后会向父窗口发送转化后的消息这里如果想直接到我们程序的窗口处理函数,直接在内存.text下断就可以了,如果想了解系统是怎么把消息处理转到我们的函数,这时将条件断点改为直接断点,按F9,会发现依次调用了
0012FB5C   77D18816  返回到 user32.77D18816 来自 user32.77D1870C
0012FB60   5D177512  COMCTL32.5D177512        子控件的窗口过程函数
0012FB64   00060D4A                                                    消息信息
0012FB68   00000407

0012FA8C   77D18816  返回到 user32.77D18816 来自 user32.77D1870C
0012FA90   5D176E16  返回到 COMCTL32.5D176E16
0012FA94   00390C24
0012FA98   00000215

0012F9F8   77D23CE4  返回到 user32.77D23CE4 来自 user32.77D1870C
0012F9FC   0040113D  返回到 import.0040113D 来自 <JMP.&comctl32.InitCommonControls>
0012FA00   00070CC0
0012FA04   0000004E

哈哈,终于来到我们的0040113D

2009.12.12
确定断点位置新方法,首先需要加载微软符号库,详见海风大大的帖子http://bbs.pediy.com/showthread.php?t=96856
然后就可以用bp _InternalCallWinProc@20即可


[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

收藏
免费 7
支持
分享
最新回复 (29)
雪    币: 201
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
沙发支持~~
2009-9-22 23:25
0
雪    币: 235
活跃值: (10)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
3
最近正在学习windows的消息机制。要好好学习一下
2009-9-23 14:41
0
雪    币: 163
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
好东西,学习中。谢谢分享
2009-9-25 19:15
0
雪    币: 370
活跃值: (15)
能力值: ( LV9,RANK:170 )
在线值:
发帖
回帖
粉丝
5
楼主思路很不错,消息断点一直是个弱区,经试验,修正一下LZ的提法:
那个77D1870C在不同系统下不是个定值,例如我的为7E41870C。
其实这个地址是个user32内部函数,
user32.InternalCallWinProc

Names in user32, item 1914
Address=7E41870C
Section=.text
Type=Library<----只在启用了Symbol时才可见
Name=InternalCallWinProc

7E41870C > 55 PUSH EBP
7E41870D 8BEC MOV EBP,ESP
7E41870F 56 PUSH ESI ; user32.DefWindowProcW
7E418710 57 PUSH EDI
7E418711 53 PUSH EBX
7E418712 68 CDABBADC PUSH DCBAABCD
7E418717 56 PUSH ESI ; user32.DefWindowProcW
7E418718 FF75 18 PUSH DWORD PTR SS:[EBP+18]
7E41871B FF75 14 PUSH DWORD PTR SS:[EBP+14]
7E41871E FF75 10 PUSH DWORD PTR SS:[EBP+10]
7E418721 FF75 0C PUSH DWORD PTR SS:[EBP+C] ; user32.DefWindowProcW


因此在运用的时候,可先通过在user32空间搜索二进制:

55 8B EC 56 57 53 68 CD AB BA DC 56 FF 75 18 FF 75 14 FF 75 10 FF 75 0C

获得该万能地址。

另外提醒一点,千万要下条件断点!
2009-9-25 21:21
0
雪    币: 211
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
为什么我测试回不到程序的领空?
2009-9-25 21:54
0
雪    币: 95
活跃值: (419)
能力值: ( LV9,RANK:310 )
在线值:
发帖
回帖
粉丝
7
呵呵,谢谢指点和修正哈
2009-9-25 23:01
0
雪    币: 95
活跃值: (419)
能力值: ( LV9,RANK:310 )
在线值:
发帖
回帖
粉丝
8
能说详细点吗?不然很难判断是什么原因
2009-9-25 23:05
0
雪    币: 159
活跃值: (89)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
9
谢谢,很好,很实用
2009-9-27 17:17
0
雪    币: 231
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
10
"另外提醒一点,千万要下条件断点! ",直接下估计系统就没法动了
2009-9-30 16:32
0
雪    币: 210
活跃值: (20)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
11
坐下来慢慢学习
2009-9-30 17:04
0
雪    币: 245
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
12
好文章,鼓掌!!
2009-10-2 21:04
0
雪    币: 200
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
13
好文章。。。不错
2009-10-2 21:31
0
雪    币: 242
活跃值: (1664)
能力值: ( LV9,RANK:410 )
在线值:
发帖
回帖
粉丝
14
不错的文章,支持一下
2009-10-4 20:26
0
雪    币: 214
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
15
文章很好,分析的比较深入,对理解windows消息机制很有帮助
2009-10-15 20:10
0
雪    币: 253
活跃值: (89)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
16
细细地看了两遍,体会其中老大的思路。谢谢!
2009-10-16 08:43
0
雪    币: 100
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
17
好文章 学习了
2009-10-16 17:04
0
雪    币: 247
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
18
很不错的文章啊,拿了,谢谢
2009-10-18 14:49
0
雪    币: 205
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
19
楼主的观点很有启发性,学习了。
关于那个函数,在user32领空直接查push 0xDCBAABCD也可以,个人感觉比较好记;^_^
2010-2-3 12:36
0
雪    币: 348
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
20
好文章 很有启发性
2010-2-6 23:51
0
雪    币: 166
活跃值: (392)
能力值: ( LV13,RANK:357 )
在线值:
发帖
回帖
粉丝
21
文章中的 WM_DESTORY   需要修改成 WM_DESTROY
2010-6-24 15:12
0
雪    币: 334
活跃值: (213)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
22
好好学习一下。
2010-6-24 19:07
0
雪    币: 95
活跃值: (419)
能力值: ( LV9,RANK:310 )
在线值:
发帖
回帖
粉丝
23
谢谢fonilye兄弟的指点和瞧红尘兄弟的纠错,呵呵
2010-6-24 23:33
0
雪    币: 156
活跃值: (190)
能力值: ( LV7,RANK:110 )
在线值:
发帖
回帖
粉丝
24
不错的文章,不过有点小毛病,后来的人注意哦!

=============================================================
将文章中的[[esp]+4]==WM_DESTORY  
                                改成  
          [[esp]+4]==WM_DESTROY
2010-8-5 23:25
0
雪    币: 3003
活跃值: (479)
能力值: ( LV15,RANK:1395 )
在线值:
发帖
回帖
粉丝
25
不错的文章,delphi获取文本框内容的CallWindowProcA在系统底层不知是不是也是这个函数处理的
2010-8-6 08:36
0
游客
登录 | 注册 方可回帖
返回
//