客户机: VMWare16 + Win10.17763 x64, 4核,8G
宿主机: Win10
启动应用,EAC驱动加载,首先会主动触发一个单步调试异常。
反调试很常见的一种调试器检测方法,调试器忽略此次异常即可(gn)。
gn以后,系统卡死无反应并且无法中断到调试器。挂起系统,使用vmss2core转储dump(VMWare官网下载vmss2core)。
使用WinDBG加载dump,查看系统挂起原因。
CPU0-2未见异常,CPU3看起来进入了死循环,继续查看:
当有调试事件发生需要中断到调试器时需要调用nt!KdEnterDebugger函数,函数内部如果触发了异常,则会产生无限递归,资源耗尽后会触发KeBugCheckEx调用。为了正常中断到调试器,可以先把 fffff804`447b3e71 处指令NOP掉。
重新尝试,重启虚拟机,内核附加调试,NOP fffff804`447b3e71(nt!KdEnterDebugger+0x131) 处指令:
启动应用,调试器可以正常中断了:
需要注意此时EAC已经设置nt!KdEnteredDebugger(0xfffff80170a4a900)和nt!KeBugCheckEx(0xfffff801707cbb40)两个硬件断点。
检测机制已经很清晰了:
对nt!KdEnteredDebugger下硬件写断点,对nt!KeBugCheckEx下硬件执行断点;
主动触发CC断点异常;
如果内核调试开启且调试器已附加,则内核调试引擎会执行nt!KdEnterDebugger中断到调试器以报告异常;
nt!KdEnterDebugger写入nt!KdEnteredDebugger触发硬件写断点;
资源允许则回到3形成递归调用,资源不足则调用nt!KeBugCheckEx;
nt!KeBugCheckEx触发硬件执行断点,回到3形成递归调用。(系统卡死,不会形成崩溃转储)
gn忽略EAC主动触发的CC异常,继续执行:
可以看到nt!KdExitDebugger同样会写nt!KdEnteredDebugger字段,按同样方式NOP掉即可。
对CC进行gn之后,我们发现虽然系统正常运行,但是调试机再次'失联'了,并且应用提示检测到’调试模式‘,查看dump,可以发现nt!KdDebuggerEnabled被清零了,这是一个对内核调试很重要的字段,清零就意味着客户机不会再响应调试器的任何请求。
机制很简单,我不打算继续深究其具体实现,打补丁,过校验等,也许可以用一种相对通用的思路来解决这种检测(考虑到TP等也有类似的检测机制):
将nt!KdDebuggerEnabled及nt!KdDebuggerNotPresent换个位置存储
找到系统对这两个变量的引用,重定向到我们指定的位置
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)