为什么对TerminateProcess断点不起作用

本文通过windbg调试notepad程序,探讨了为何在用户态设置的TerminateProcess断点未生效。文章揭示了进程正常退出通常使用ExitProcess,而TerminateProcess用于强制终止,且在某些情况下可能导致DLL状态破坏。作者发现,虽然ExitProcess的调用栈中出现TerminateProcess,但实际上是两个函数的代码共享导致的误解。通过调试,确认了程序退出并未调用TerminateProcess,而是遵循了MSDN的描述,进一步证实了ExitProcess的正确使用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值