爲什麼對TerminateProcess斷點不起作用

最初發表在QQ空間,全文見 爲什麼對TerminateProcess斷點不起作用

在內核態下巧設用戶模塊斷點介紹了在內核態下設置用戶模塊的斷點,結尾處留了一個問號,爲了簡化問題,這次直接在用戶態下調試。使用windbg 打開一個notepad程序。設置斷點。

0:000> bl
0 e 77e616b8     0001 (0001)  0:**** kernel32!TerminateProcess
0:000> g

關閉notepad,正如在內核態下巧設用戶模塊斷點描述的,期望的斷點沒有起到作用,windbg顯示如下信息。
eax=00000000 ebx=00000000 ecx=ffffffff edx=00000000 esi=77f7663e edi=00000000
eip=7ffe0304 esp=0006fdf8 ebp=0006fef0 iopl=0         nv up ei pl nz na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000202
SharedUserData!SystemCallStub+0x4:
7ffe0304 c3               ret

正常的解釋會是這樣,TerminateProcess不會使用在進程的正常退出過程中。看看MSDN的解釋。

The TerminateProcess function is used to unconditionally cause a process to exit. The state of global data maintained by dynamic-link libraries (DLLs) may be compromised if TerminateProcess is used rather than ExitProcess.

TerminateProcess initiates termination and returns immediately. This stops execution of all threads within the process and requests cancellation of all pending I/O. The terminated process cannot exit until all pending I/O has been completed or canceled.

A process cannot prevent itself from being terminated.

也就是說進程的正常退出一般使用ExitProcess。如果我沒有做下面的動作的話,這個解釋應該足夠了。可惜人生總是充滿意外,
0:000> kv
ChildEBP RetAddr  Args to Child             
0006fdf4 77f7664a 77e798ec ffffffff 00000000 SharedUserData!SystemCallStub+0x4 (FPO: [0,0,0])
0006fdf8 77e798ec ffffffff 00000000 77e7ad86 ntdll!NtTerminateProcess+0xc (FPO: [2,0,0])
0006fef0 77e7990f 00000000 77e8f3b0 ffffffff kernel32!_ExitProcess+0x57 (FPO: [Non-Fpo])
0006ff04 77c379c8 00000000 77c37ad9 00000000 kernel32!TerminateProcess (FPO: [Non-Fpo])
0006ff0c 77c37ad9 00000000 00000000 77c37aea msvcrt!__crtExitProcess+0x2f (FPO: [1,0,0])
0006ff18 77c37aea 00000000 00000000 00000000 msvcrt!_cinit+0xe4 (FPO: [2,0,1])
0006ff28 01006c65 00000000 70a71a29 80000002 msvcrt!exit+0xe (FPO: [1,0,1])
0006ffc0 77e814c7 70a71a29 80000002 7ffdf000 notepad!WinMainCRTStartup+0x185 (FPO: [Non-Fpo])
0006fff0 00000000 01006ae0 00000000 78746341 kernel32!BaseProcessStart+0x23 (FPO: [Non-Fpo])
看看發生了什麼?

0006ff04 77c379c8 00000000 77c37ad9 00000000 kernel32!TerminateProcess (FPO: [Non-Fpo])
是多麼醒目的出現在call stack裏。這裏一定有什麼地方出現問題,

測試環境是xp sp1的虛擬機,仔細觀察這個stack,可以看出有些問題。

1. 爲什麼是 kernel32!TerminateProcess , 而不是kernel32!TerminateProcess + 0x??,這不合常理。

解決問題之前,做一些嘗試,

0:000> uf kernel32!terminateprocess
kernel32!TerminateProcess:
77e616b8 837c240400       cmp     dword ptr [esp+0x4],0x0
77e616bd 7418             jz      kernel32!TerminateProcess+0x7 (77e616d7)

kernel32!TerminateProcess+0x10:
77e616bf ff742408         push    dword ptr [esp+0x8]
77e616c3 ff742408         push    dword ptr [esp+0x8]
77e616c7 ff15f813e677 call dword ptr [kernel32!_imp__NtTerminateProcess (77e613f8)]
77e616cd 85c0             test    eax,eax
77e616cf 7c0f             jl      kernel32!TerminateProcess+0x27 (77e616e0)

kernel32!TerminateProcess+0x22:
77e616d1 33c0             xor     eax,eax
77e616d3 40               inc     eax

kernel32!TerminateProcess+0x2f:
77e616d4 c20800           ret     0x8

kernel32!TerminateProcess+0x7:
77e616d7 6a06             push    0x6
77e616d9 e8d28c0100       call    kernel32!SetLastError (77e7a3b0)
77e616de eb06             jmp     kernel32!TerminateProcess+0x2d (77e616e6)

kernel32!TerminateProcess+0x27:
77e616e0 50               push    eax
77e616e1 e8808d0100       call    kernel32!BaseSetLastNTError (77e7a466)

kernel32!TerminateProcess+0x2d:
77e616e6 33c0             xor     eax,eax
77e616e8 ebea             jmp     kernel32!TerminateProcess+0x2f (77e616d4)

先看看這個函數,幸運的是,不長,而且很簡單。有個疑惑是爲什麼+0x7的地方竟然在+0x10,+0x22之後,這順序很奇怪。uf是怎麼整理函數內容的?

這裏引出第2個問題,

2. 上層函數kernel32!_ExitProcess+0x57 的返回地址77e7990f ,是哪裏?並不在前面看到的函數範圍之內。

下面需要怎麼做?當然還是斷點。這次從下層的函數着手,重新啓動,設置斷點。

0:000> bl
0 e 77c379c2     0001 (0001)  0:**** msvcrt!__crtExitProcess+0x29

關閉notepad,這次斷點命中了,

eax=00000000 ebx=00000000 ecx=77e7b5e1 edx=00000000 esi=00000001 edi=77e7ad86
eip=77c379c2 esp=0006ff0c ebp=0006ffc0 iopl=0         nv up ei pl zr na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000246
msvcrt!__crtExitProcess+0x29:
77c379c2 ff151412c177 call dword ptr [msvcrt!_imp__ExitProcess (77c11214)]{kernel32!ExitProcess (77e798fd)} ds:0023:77c11214=77e798fd

看stack,一切正常。

0:000> kv
ChildEBP RetAddr  Args to Child             
0006ff0c 77c37ad9 00000000 00000000 77c37aea msvcrt!__crtExitProcess+0x29 (FPO: [1,0,0])
0006ff18 77c37aea 00000000 00000000 00000000 msvcrt!_cinit+0xe4 (FPO: [2,0,1])
0006ff28 01006c65 00000000 70a71a29 80000002 msvcrt!exit+0xe (FPO: [1,0,1])
0006ffc0 77e814c7 70a71a29 80000002 7ffdf000 notepad!WinMainCRTStartup+0x185 (FPO: [Non-Fpo])
0006fff0 00000000 01006ae0 00000000 78746341 kernel32!BaseProcessStart+0x23 (FPO: [Non-Fpo])
0:000> t
eax=00000000 ebx=00000000 ecx=77e7b5e1 edx=00000000 esi=00000001 edi=77e7ad86
eip=77e798fd esp=0006ff08 ebp=0006ffc0 iopl=0         nv up ei pl zr na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000246
kernel32!ExitProcess:
77e798fd 55               push    ebp
0:000> kv
ChildEBP RetAddr  Args to Child             
0006ff04 77c379c8 00000000 77c37ad9 00000000 kernel32!ExitProcess (FPO: [Non-Fpo])
0006ff0c 77c37ad9 00000000 00000000 77c37aea msvcrt!__crtExitProcess+0x2f (FPO: [1,0,0])
0006ff18 77c37aea 00000000 00000000 00000000 msvcrt!_cinit+0xe4 (FPO: [2,0,1])
0006ff28 01006c65 00000000 70a71a29 80000002 msvcrt!exit+0xe (FPO: [1,0,1])
0006ffc0 77e814c7 70a71a29 80000002 7ffdf000 notepad!WinMainCRTStartup+0x185 (FPO: [Non-Fpo])
0006fff0 00000000 01006ae0 00000000 78746341 kernel32!BaseProcessStart+0x23 (FPO: [Non-Fpo])

看出問題了沒有?此時的stack中顯示的是kernel32!ExitProcess 而不是kernel32!TerminateProcess, 那爲什麼最後的結果顯示會變了?

0:000> uf kernel32!exitprocess
kernel32!TerminateProcess:
77e616b8 837c240400       cmp     dword ptr [esp+0x4],0x0
77e616bd 7418             jz      kernel32!TerminateProcess+0x7 (77e616d7)

kernel32!TerminateProcess+0x10:
77e616bf ff742408         push    dword ptr [esp+0x8]
77e616c3 ff742408         push    dword ptr [esp+0x8]
77e616c7 ff15f813e677 call dword ptr [kernel32!_imp__NtTerminateProcess (77e613f8)]
77e616cd 85c0             test    eax,eax
77e616cf 7c0f             jl      kernel32!TerminateProcess+0x27 (77e616e0)

kernel32!TerminateProcess+0x22:
77e616d1 33c0             xor     eax,eax
77e616d3 40               inc     eax

kernel32!TerminateProcess+0x2f:
77e616d4 c20800           ret     0x8

kernel32!TerminateProcess+0x7:
77e616d7 6a06             push    0x6
77e616d9 e8d28c0100       call    kernel32!SetLastError (77e7a3b0)
77e616de eb06             jmp     kernel32!TerminateProcess+0x2d (77e616e6)

kernel32!TerminateProcess+0x27:
77e616e0 50               push    eax
77e616e1 e8808d0100       call    kernel32!BaseSetLastNTError (77e7a466)

kernel32!TerminateProcess+0x2d:
77e616e6 33c0             xor     eax,eax
77e616e8 ebea             jmp     kernel32!TerminateProcess+0x2f (77e616d4)

kernel32!ExitProcess:
77e798fd 55               push    ebp
77e798fe 8bec             mov     ebp,esp
77e79900 6aff             push    0xff
77e79902 68b0f3e877       push    0x77e8f3b0
77e79907 ff7508           push    dword ptr [ebp+0x8]
77e7990a e886ffffff       call    kernel32!_ExitProcess (77e79895)
77e7990f e9a47dfeff       jmp     kernel32!TerminateProcess (77e616b8)

觀看這段代碼,竟然開頭是kernel32!TerminateProcess的代碼,注意到這應該是種特殊的代碼實現,使得kernel32!ExitProcesskernel32!TerminateProcess合2爲1,結尾處是一條jmp指令,很有意思的地方。

單步執行到下面

0:000> p
eax=00000000 ebx=00000000 ecx=77e7b5e1 edx=00000000 esi=00000001 edi=77e7ad86
eip=77e7990a esp=0006fef8 ebp=0006ff04 iopl=0         nv up ei pl zr na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000246
kernel32!ExitProcess+0xd:
77e7990a e886ffffff       call    kernel32!_ExitProcess (77e79895)

此時的stack是正確的。

0:000> kv
ChildEBP RetAddr  Args to Child             
0006ff04 77c379c8 00000000 77c37ad9 00000000 kernel32!ExitProcess+0xd (FPO: [Non-Fpo])
0006ff0c 77c37ad9 00000000 00000000 77c37aea msvcrt!__crtExitProcess+0x2f (FPO: [1,0,0])
0006ff18 77c37aea 00000000 00000000 00000000 msvcrt!_cinit+0xe4 (FPO: [2,0,1])
0006ff28 01006c65 00000000 70a71a29 80000002 msvcrt!exit+0xe (FPO: [1,0,1])
0006ffc0 77e814c7 70a71a29 80000002 7ffdf000 notepad!WinMainCRTStartup+0x185 (FPO: [Non-Fpo])
0006fff0 00000000 01006ae0 00000000 78746341 kernel32!BaseProcessStart+0x23 (FPO: [Non-Fpo])

繼續跟進,
0:000> t
eax=00000000 ebx=00000000 ecx=77e7b5e1 edx=00000000 esi=00000001 edi=77e7ad86
eip=77e79895 esp=0006fef4 ebp=0006ff04 iopl=0         nv up ei pl zr na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000246
kernel32!_ExitProcess:
77e79895 68cc000000       push    0xcc

發現問題。
0:000> kv
ChildEBP RetAddr  Args to Child             
0006fef0 77e7990f 00000000 77e8f3b0 ffffffff kernel32!_ExitProcess (FPO: [Non-Fpo])
0006ff04 77c379c8 00000000 77c37ad9 00000000 kernel32!TerminateProcess (FPO: [Non-Fpo])
0006ff0c 77c37ad9 00000000 00000000 77c37aea msvcrt!__crtExitProcess+0x2f (FPO: [1,0,0])
0006ff18 77c37aea 00000000 00000000 00000000 msvcrt!_cinit+0xe4 (FPO: [2,0,1])
0006ff28 01006c65 00000000 70a71a29 80000002 msvcrt!exit+0xe (FPO: [1,0,1])
0006ffc0 77e814c7 70a71a29 80000002 7ffdf000 notepad!WinMainCRTStartup+0x185 (FPO: [Non-Fpo])
0006fff0 00000000 01006ae0 00000000 78746341 kernel32!BaseProcessStart+0x23 (FPO: [Non-Fpo])

看上層的返回地址77e7990f ,這實際上應該是kernel32!ExitProcess+0x12由於這邊有條特殊的指令JMP,windbg把它認爲了是某個函數的位置,相當於是個函數調用,因此有了這個美麗的誤會。實際上,這個實驗再次證明了MSDN上的理論,程序的正常退出確實沒有調用kernel32!TerminateProcess驚訝

0:000> u kernel32!exitprocess + 12
kernel32!TerminateProcess:
77e7990f e9a47dfeff       jmp     kernel32!TerminateProcess (77e616b8)

嘗試uf一下這個位置,想想會發生什麼?

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章