Window user32子動态庫控件封裝和消息分發淺析 這篇文章提到視窗程式在分發消息時會依次調用:UserCallWinProcCheckWow--->_InternalCallWinProc-->各個視窗程式的WndProc。各類控件的視窗程式不同,比如Button控件用的是Button_WndProc,程式通過_InternalCallWinProc最終進入Button_WndProc對Button事件處理。受到這篇文章的啟示,我嘗試用windbg修改calc.exe的視窗程式,并記錄于此。
網上一些文章提及萬能消息斷點時,無不例外的都會帶到_InternalCallWinProc這個函數,綜合各家所言和自己調試的過程,我得到如下結論:
1._InternalCallWinProc的函數接口可能為:
_InternalCallWinProc(WndProc*,Hwnd,UINT,WPARAM,LPARAM);
參數1是程式在調用RegisterClass(Ex)時注冊的視窗過程,參數2是接收消息的視窗的句柄,參數3是消息号。從參數2到參數5,類似于傳遞給WndProc接口的參數。
2.在_InternalCallWinProc函數中會以參數1傳入的函數指針作為消息的視窗過程,将參數2-5壓棧傳給它并跳轉到該視窗過程。可能的實作如下(xp):
_InternalCallWinProc()
{
(*WndProc)(Hwnd,UINT,WPARAM,LPARAM);
}
(win7)
_InternalCallWinProc()
{
push LPARAM
push WPARAM
push UINT
push Hwnd
jmp WndProc
}
回到我的正題上,我要做的是偷換視窗過程。xp到win7都沒有檢測函數指針WndProc的合法性(至少在windbg中是這樣),是以,我們可以把這個指針值偷換為其他代碼(可以通過注入代碼的形式實作)。作為示範的目的,我把calc.exe按鍵7的WndProc替換為ExitProcess函數,以下為實作步驟:
1.用spy++定位按鍵7的句柄:
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiI0gTMx81dsQWZ4lmZf1GLlpXazVmcvwFciV2dsQXYtJ3bm9CX9s2RkBnVHFmb1clWvB3MaVnRtp1XlBXe0xCMy81dvRWYoNHLwEzX5xCMx8FesU2cfdGLwMzX0xiRGZkRGZ0Xy9GbvNGLpZTY1EmMZVDUSFTU4VFRR9Fd4VGdsYTMfVmepNHLrJXYtJXZ0F2dvwVZnFWbp1zczV2YvJHctM3cv1Ce-cmbw5iN4kzN3cDOxQGZ3QTNlhjNzYzXzIDMwkTM3AzLcFTMyIDMy8CXn9Gbi9CXzV2Zh1WavwVbvNmLvR3YxUjLyM3Lc9CX6MHc0RHaiojIsJye.png)
2.windbg attach到calc.exe,設定符号并查找符号_InternalCallWinProc
0:001> x *!*internalcall* @查找符号InternalCallWinProc位址
77d1870c USER32!InternalCallWinProc = <no type information>
0:001> x *!Button_* @查找Button控件符号位址
771a7725 comctl32!Button_CalcRect = <no type information>
0:001> bp USER32!InternalCallWinProc
0:001> g
Breakpoint 0 hit
USER32!InternalCallWinProc:
77d1870c 55 push ebp
0:000> kb
ChildEBP RetAddr Args to Child
00>0007fd30 77d18816 7365912a 000101b2 00000219 USER32!InternalCallWinProc
01>0007fd98 77d189cd 00000000 7365912a 000101b2 USER32!UserCallWinProcCheckWow+0x150
02>0007fdf8 77d18a10 0007fee8 00000000 0007ff1c USER32!DispatchMessageWorker+0x306
03>0007fe08 010021a7 0007fee8 7c80b731 000a2348 USER32!DispatchMessageW+0xf
...
0007fff0 00000000 01012475 00000000 78746341 kernel32!BaseProcessStart+0x23
@棧回溯00>處的0x7365912a是計算機視窗過程的函數指針
0:000> ln 7365912a
(7365912a) msctfime!UIWndProc | (7365913a) msctfime!CtfImeDestroyInputContext
Exact matches:
msctfime!UIWndProc (<no parameter info>)
@檢視位址0x7365912a附近的符号是msctfime!UIWndProc,有點像一個視窗過程
3.準備下條件斷點。來分析一下斷點要滿足的需求:如果我們在Button控件(Button控件的視窗過程為comctl32!Button_CalcRect)"7"(spy++顯示句柄值0x001016A判斷)上按下左鍵(消息值為0x201)時,使windbg中斷。把這段話轉變為windbg能了解的指令:
0:000> bp comctl32!Button_WndProc ".if(dwo(esp+4)==0001016A&dwo(esp+8)==201){.echo Button7Down;}.else{gc;}"
0:000> g
其中dwo(esp+4)用于判斷按鍵7的視窗句柄值,dwo(esp+8)用于判斷消息值是否為左鍵按下。我們看下斷點的效果:
當我按下7,windbg在輸出視窗輸出"Button7Down",看來斷點生效了~再來看下調用堆棧:
0:000> kb
ChildEBP RetAddr Args to Child
00> 0007fd04 77d18734 0001016a 00000201 00000001 comctl32!Button_WndProc
01> 0007fd30 77d18816 771a8eb4 0001016a 00000201 USER32!InternalCallWinProc+0x28
02> 0007fd98 77d189cd 00000000 771a8eb4 0001016a USER32!UserCallWinProcCheckWow+0x150
03> 0007fdf8 77d18a10 0007fee8 00000000 0007ff1c USER32!DispatchMessageWorker+0x306
04> 0007fe08 010021a7 0007fee8 7c80b731 000a2348 USER32!DispatchMessageW+0xf
...
0007ffc0 7c817067 00330039 00360037 7ffd3000 calc+0x125e9
0007fff0 00000000 01012475 00000000 78746341 kernel32!BaseProcessStart+0x23
按調用棧可以看到是USER32!InternalCallWinProc調用了comctl32!Button_WndProc,現在我們回過頭來看下前面我得到的結論對不對----USER32!InternalCallWinProc的參數1存放了處理消息的視窗過程:
0:000> ln 771a8eb4
(771a8eb4) comctl32!Button_WndProc | (771a99c9) comctl32!InitButtonClass
Exact matches:
comctl32!Button_WndProc = <no type information>
果不其然,0x771a8eb4是Button控件的消息處理函數!
既然USER32!InternalCallWinProc對參數1傳入的值不加甄别,我完全可以對USER32!InternalCallWinProc下同樣的條件斷點,觸發斷點後在函數入口處修改參數1的值,把他指向程序空間中其他一段可執行代碼,那就ExitProcess了:
0:000> x *!*ExitProcess*
7c81cafa kernel32!ExitProcess = <no type information>
0:000> bp USER32!InternalCallWinProc ".if(dwo(esp+8)==0001016A&dwo(esp+c)==201){.echo Button7Down;}.else{gc;}"
0:000> g
Button7Down
eax=c0000000 ebx=00000000 ecx=40000000 edx=00000040 esi=771a8eb4 edi=0007fd6c
eip=77d1870c esp=0007fd34 ebp=0007fd98 iopl=0 ov up ei ng nz na pe cy
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000a87
USER32!InternalCallWinProc:
77d1870c 55 push ebp
0:000> kb
ChildEBP RetAddr Args to Child
0007fd30 77d18816 771a8eb4 0001016a 00000201 USER32!InternalCallWinProc
0007fd98 77d189cd 00000000 771a8eb4 0001016a USER32!UserCallWinProcCheckWow+0x150
@注意傳入給InternalCallWinProc的函數指針位于參數2,是以要修改的是esp+8
0:000> ed 0007fd30+8 7c81cafa
0:000> g
eax=000000c0 ebx=00000102 ecx=77d2b401 edx=0007f720 esi=000000d4 edi=00000000
eip=7c92e4f4 esp=00bfff24 ebp=00bfff88 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
ntdll!KiFastSystemCallRet: <--------------程序退出
7c92e4f4 c3 ret
修改參數2後,繼續執行windbg馬上就能看到程序退出,有意思吧?來看下工作列的截圖:
calc.exe已經不複存在,偷換成功~
本篇完~