标题: 花式作死"bp nt!KiDispatchException;g" 创建: 2018-02-06 17:49 更新: 2018-02-08 17:51 链接: https://scz.617.cn/windows/201802061749.txt 以x64/Win10 16299.15.amd64fre.rs3_release.170928-1534为例。 简要介绍为什么不能在kd里做这个动作,"bp nt!KiDispatchException"。 你可以试试,很快目标OS就失去响应,在kd里Ctrl-C无法陷入。如果提前配置启用过 VMware硬件调试,倒是可以切进去,但RIP已经不在nt模块范围。单步跟几圈,发现 在一个循环里,可能是VMware自己的代码块。面对这种情况,至少我是抢救不回来了。 总之,在kd里"bp nt!KiDispatchException"属于"no zuo no die"。 假设当前因"bp nt!NtGetNlsSectionPtr"命中而停留在"kd>"提示符,然后 "bp nt!KiDispatchException;g"。 g会导致(隐式)单步执行"nt!NtGetNlsSectionPtr"处的指令,这是调试器的基本原理 之一,否则无法正常执行断点处的指令并回写0xCC。单步执行结束时触发int1中断。 nt!NtGetNlsSectionPtr (TF置位,单步执行本条指令,之后触发int1中断) nt!KiDebugTrapOrFault+0x1af (int1的中断处理程序) nt!KiExceptionDispatch+0xc9 nt!KiDispatchException (int3) 流程直奔"nt!KiDispatchException"而去,位于此处的int3命中,进入int3中断处理 程序。 nt!KiDispatchException (int3) nt!KiBreakpointTrap+0xe5 (int3的中断处理程序) nt!KiExceptionDispatch+0xc9 nt!KiDispatchException (int3) 流程再次直奔"nt!KiDispatchException"而去,此时"nt!KiDispatchException"处的 原始指令尚未恢复,内存中仍是int3。 位于"nt!KiDispatchException"的断点再次命中,开始死循环: nt!KiDispatchException (int3) nt!KiBreakpointTrap+0xe5 (int3的中断处理程序) nt!KiExceptionDispatch+0xc9 nt!KiDispatchException (int3) nt!KiBreakpointTrap+0xe5 (int3的中断处理程序) nt!KiExceptionDispatch+0xc9 nt!KiDispatchException (int3) ... nt!KiBreakpointTrap使用的栈是int3所在函数使用的栈。死循环会导致内核栈耗尽。 假设通过VMware硬件调试功能在此设断: nt!KiBreakpointTrap+0xe5 在"kd>"提示符下执行下列命令触发前述VMware断点: bp nt!KiDispatchException;g IDA中依次看到: RSP=0xFFFFF50F7FAAD710 RBP=0xFFFFF50F7FAAD790 RSP=0xFFFFF50F7FAAD390 RBP=0xFFFFF50F7FAAD410 RSP=0xFFFFF50F7FAAD010 RBP=0xFFFFF50F7FAAD090 RSP=0xFFFFF50F7FAACC90 RBP=0xFFFFF50F7FAACD10 RSP持续向低址方向移动,这将耗尽内核栈。即使用VMware硬件调试功能切进去,也 无从抢救起。在此过程中,并未观察到死锁等待。 如果不存在内核栈耗尽问题,用VMware硬件调试功能切进去时,应该看到流程在前述 死循环中,在KiBreakpointTrap、KiExceptionDispatch、KiDispatchException之间 振荡。但实际上,由于内核栈会耗尽,前述死循环不会真地死循环下去,流程会转到 别处去,猜测是VMware自己的代码块。 此时在kd里Ctrl-C,并不会陷入kd。无论是前述死循环不得脱出、内核栈耗尽触发其 他流程,还是系统没有空闲期所致,总之,OS流程永远不会到达: nt!KiDispatchException+0x130 nt!KdTrap+0x27 即使能陷入kd,也无从抢救起,因为你不知道如何恢复栈帧、其他寄存器,可能还有 一些全局变量被改变。或许理论上存在抢救可能,但我不打算深究这个伪需求。 最初因什么原因停留在"kd>"提示符,无关紧要,要点是那个死循环。即使g之后不去 隐式单步执行触发int1中断(不需要回写0xCC时),OS也会有无数流程经过被int3过的 nt!KiDispatchException,从而进入死循环、耗尽内核栈。 一句话,不要用kd调试nt!KiDispatchException。 本回书说到此间,已经盖棺论定。但是,为什么有个但是? 呃,不要用kd调试nt!KiDispatchException,只是一个笼而统之的普遍结论。如果对 内核态异常分发过程相当了解,事实上,可以用kd正常调试nt!KiDispatchException 的部分分支流程,同时不会导致问题。让我们想像一下: -------------------------------------------------------------------------- KiDispatchException () { if () { /* * 与kd调试相关 */ func_a(); } else { /* * 与kd调试无关 */ func_b() } } -------------------------------------------------------------------------- 如果在kd中对func_b设断并单步调试,没有问题。别用kd断KiDispatchException、 func_a就是。 当然,这些都是满足好奇心的。真有调试KiDispatchException的需求,Windows 2000及以前,可以用SoftICE。2004年hume和我还写过几个跟这事有关的SoftICE插件, 测试时拿Y博士做小白鼠,他说,等我先存个盘,然后,果然BSOD了,毕竟曾经是水 木清华驱动开发版的版主,对BSOD有先知一般的觉悟,十多年后我给他补个赞。现在 是Win10的天下,SoftICE连明日黄花都不是了,早特么地转世投胎去了,如今为了调 试KiDispatchException,建议用VMware硬件调试功能,所谓上帝模式,或者说, big brother is watching you。 2018-02-08 17:51 scz yuange提到,能否通过设置条件断点避开前述死循环。这个想法行不通。 断点的CommandString何时得到执行: dbgeng!ConnLiveKernelTargetInfo::WaitForEvent+0x53 dbgeng!ConnLiveKernelTargetInfo::WaitStateChange (kd在此等待来自Guest的事件) nt!KdpReportExceptionStateChange (Guest即将通知Host) dbgeng!ConnLiveKernelTargetInfo::WaitForEvent+0x58 (Host得到来自Guest的通知) dbgeng!ConnLiveKernelTargetInfo::WaitForEvent+0x10d dbgeng!ConnLiveKernelTargetInfo::ProcessStateChange+0x420 dbgeng!ProcessBreakpointOrStepException+0x95 dbgeng!CheckBreakpointOrStepTrace+0x2fa dbgeng!NotifyHitBreakpoints+0xd3 (执行断点的CommandString) dbgeng!ExecuteEventCommand 流程先到达nt!KdpReport,后到达断点的条件判断部分。对于 "bp nt!KiDispatchException"造成的死循环而言,断点的条件判断部分永远没有机 会得到执行,因为流程永远不会到达nt!KdpReport了。其实可以更简单地理解这点, 把断点的CommandString分解成两个动作,一是看到"kd>"提示符,二是手工执行 CommandString。当然这样类比不严谨,实际情况是断点的CommandString先得到执行, 再看到"kd>"提示符,不过这两步都是在Host中完成,而死循环是在Guest中出现。