最初发表在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!ExitProcess 和kernel32!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一下这个位置,想想会发生什么?