客户机: VMWare16 + Win10.17763 x64, 4核,8G
宿主机: Win10
启动应用,EAC驱动加载,首先会主动触发一个单步调试异常。
Single step exception - code 80000004 (first chance) First chance exceptions are reported before any exception handling. This exception may be expected and handled. EasyAntiCheat+0x4b0f82: fffff800`0e750f82 489d popfq
1: kd> uf fffff800`0e750f6c EasyAntiCheat+0x4b0f6c: fffff800`0e750f6c 4055 push rbp fffff800`0e750f6e 488bec mov rbp,rsp fffff800`0e750f71 32d2 xor dl,dl fffff800`0e750f73 489c pushfq fffff800`0e750f75 59 pop rcx fffff800`0e750f76 488bc1 mov rax,rcx fffff800`0e750f79 480fbae808 bts rax,8 ;置EFLAGS:TF标志位 fffff800`0e750f7e 50 push rax fffff800`0e750f7f 489d popfq fffff800`0e750f81 51 push rcx fffff800`0e750f82 489d popfq fffff800`0e750f84 eb02 jmp EasyAntiCheat+0x4b0f88 (fffff800`0e750f88) Branch EasyAntiCheat+0x4b0f88: fffff800`0e750f88 8ac2 mov al,dl fffff800`0e750f8a 488be5 mov rsp,rbp fffff800`0e750f8d 5d pop rbp fffff800`0e750f8e c3 ret
反调试很常见的一种调试器检测方法,调试器忽略此次异常即可(gn)。
gn以后,系统卡死无反应并且无法中断到调试器。挂起系统,使用vmss2core转储dump(VMWare官网下载vmss2core)。
使用WinDBG加载dump,查看系统挂起原因。
3: kd> ~0s 0: kd> k # Child-SP RetAddr Call Site 00 fffff804`46686b80 fffff804`44134084 nt!KiCheckStall+0x7b 01 fffff804`46686bb0 fffff804`4412a3e2 nt!KiFreezeTargetExecution+0x1b8 02 fffff804`46686cb0 fffff804`4412a647 nt!KiCheckForFreezeExecution+0x2a 03 fffff804`46686ce0 fffff804`4405be02 nt!KiProcessNMI+0x57 04 fffff804`46686d30 fffff804`4405bbc3 nt!KxNmiInterrupt+0x82 05 fffff804`46686e70 fffff804`4418397c nt!KiNmiInterrupt+0x203 06 fffff804`466747a0 fffff804`43fcb7cb nt!PpmIdleGuestExecute+0x1c 07 fffff804`466747e0 fffff804`43fcaf7f nt!PpmIdleExecuteTransition+0x6bb 08 fffff804`46674b00 fffff804`4405452c nt!PoIdle+0x33f 09 fffff804`46674c60 00000000`00000000 nt!KiIdleLoop+0x2c 0: kd> ~1s 1: kd> k # Child-SP RetAddr Call Site 00 ffff9780`ba6f3c80 fffff804`44134084 nt!KiCheckStall+0x7b 01 ffff9780`ba6f3cb0 fffff804`4412a3e2 nt!KiFreezeTargetExecution+0x1b8 02 ffff9780`ba6f3db0 fffff804`4412a647 nt!KiCheckForFreezeExecution+0x2a 03 ffff9780`ba6f3de0 fffff804`4405be02 nt!KiProcessNMI+0x57 04 ffff9780`ba6f3e30 fffff804`4405bbc3 nt!KxNmiInterrupt+0x82 05 ffff9780`ba6f3f70 fffff804`4418397c nt!KiNmiInterrupt+0x203 06 fffff88c`110297a0 fffff804`43fcb7cb nt!PpmIdleGuestExecute+0x1c 07 fffff88c`110297e0 fffff804`43fcaf7f nt!PpmIdleExecuteTransition+0x6bb 08 fffff88c`11029b00 fffff804`4405452c nt!PoIdle+0x33f 09 fffff88c`11029c60 00000000`00000000 nt!KiIdleLoop+0x2c 1: kd> ~2 2: kd> k # Child-SP RetAddr Call Site 00 ffff9780`ba798c80 fffff804`44134084 nt!KiCheckStall+0x7b 01 ffff9780`ba798cb0 fffff804`4412a3e2 nt!KiFreezeTargetExecution+0x1b8 02 ffff9780`ba798db0 fffff804`4412a647 nt!KiCheckForFreezeExecution+0x2a 03 ffff9780`ba798de0 fffff804`4405be02 nt!KiProcessNMI+0x57 04 ffff9780`ba798e30 fffff804`4405bbc3 nt!KxNmiInterrupt+0x82 05 ffff9780`ba798f70 fffff804`4418397c nt!KiNmiInterrupt+0x203 06 fffff88c`110377a0 fffff804`43fcb7cb nt!PpmIdleGuestExecute+0x1c 07 fffff88c`110377e0 fffff804`43fcaf7f nt!PpmIdleExecuteTransition+0x6bb 08 fffff88c`11037b00 fffff804`4405452c nt!PoIdle+0x33f 09 fffff88c`11037c60 00000000`00000000 nt!KiIdleLoop+0x2c 2: kd> ~3 3: kd> k # Child-SP RetAddr Call Site 00 ffff9780`ba1ead80 fffff804`4413386b nt!KxTryToAcquireSpinLock+0x6a 01 ffff9780`ba1eadb0 fffff804`447b3da4 nt!KeFreezeExecution+0xb3 02 ffff9780`ba1eaee0 fffff804`441295f1 nt!KdEnterDebugger+0x64 03 ffff9780`ba1eaf10 fffff804`447b7665 nt!KdpReport+0x71 04 ffff9780`ba1eaf50 fffff804`43f108f8 nt!KdpTrap+0x14d 05 ffff9780`ba1eafa0 fffff804`43f10565 nt!KdTrap+0x2c 06 ffff9780`ba1eafe0 fffff804`44062442 nt!KiDispatchException+0x135 07 ffff9780`ba1eb690 fffff804`4405b988 nt!KiExceptionDispatch+0xc2 08 ffff9780`ba1eb870 fffff804`447b3e7b nt!KxDebugTrapOrFault+0x3c8 09 ffff9780`ba1eba00 fffff804`441295f1 nt!KdEnterDebugger+0x13b 0a ffff9780`ba1eba30 fffff804`447b7665 nt!KdpReport+0x71 0b ffff9780`ba1eba70 fffff804`43f108f8 nt!KdpTrap+0x14d 0c ffff9780`ba1ebac0 fffff804`43f10565 nt!KdTrap+0x2c 0d ffff9780`ba1ebb00 fffff804`44062442 nt!KiDispatchException+0x135 0e ffff9780`ba1ec1b0 fffff804`4405b988 nt!KiExceptionDispatch+0xc2 0f ffff9780`ba1ec390 fffff804`447b3e7b nt!KxDebugTrapOrFault+0x3c8 10 ffff9780`ba1ec520 fffff804`441295f1 nt!KdEnterDebugger+0x13b 11 ffff9780`ba1ec550 fffff804`447b7665 nt!KdpReport+0x71 12 ffff9780`ba1ec590 fffff804`43f108f8 nt!KdpTrap+0x14d 13 ffff9780`ba1ec5e0 fffff804`43f10565 nt!KdTrap+0x2c ...
CPU0-2未见异常,CPU3看起来进入了死循环,继续查看:
3: kd> kv # Child-SP RetAddr : Args to Child : Call Site 00 ffff9780`ba1ead80 fffff804`4413386b : 00000000`0007a120 ffff9780`ba1eb000 00000000`00000000 00000000`00000000 : nt!KxTryToAcquireSpinLock+0x6a 01 ffff9780`ba1eadb0 fffff804`447b3da4 : 00000000`00000000 00000000`0000000f ffff9780`ba1eb010 ffff9780`ba1eb870 : nt!KeFreezeExecution+0xb3 02 ffff9780`ba1eaee0 fffff804`441295f1 : ffff9780`ba1eb010 ffff9780`ba1eb510 ffff9780`ba1eb010 00000000`00000000 : nt!KdEnterDebugger+0x64 03 ffff9780`ba1eaf10 fffff804`447b7665 : ffff9780`ba1eb010 ffff9780`ba1eb510 ffff9780`ba1eb010 00000000`00000000 : nt!KdpReport+0x71 04 ffff9780`ba1eaf50 fffff804`43f108f8 : ffff9780`ba1eb7c8 ffff9780`ba1eb000 ffff9780`ba1eb010 ffff9780`ba1eb510 : nt!KdpTrap+0x14d 05 ffff9780`ba1eafa0 fffff804`43f10565 : ffff9780`ba1eb7c8 ffff9780`ba1eb510 ffff9780`ba1eb010 00000000`00000000 : nt!KdTrap+0x2c 06 ffff9780`ba1eafe0 fffff804`44062442 : ffffab8c`cbaa2900 00000000`00000000 00000000`00000000 00000000`00000000 : nt!KiDispatchException+0x135 07 ffff9780`ba1eb690 fffff804`4405b988 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : nt!KiExceptionDispatch+0xc2 08 ffff9780`ba1eb870 fffff804`447b3e7b : 00000000`00000000 00000000`0000000f ffff9780`ba1ebb30 ffff9780`ba1ec390 : nt!KxDebugTrapOrFault+0x3c8 (TrapFrame @ ffff9780`ba1eb870) 09 ffff9780`ba1eba00 fffff804`441295f1 : ffff9780`ba1ebb30 ffff9780`ba1ec030 ffff9780`ba1ebb30 00000000`00000000 : nt!KdEnterDebugger+0x13b 0a ffff9780`ba1eba30 fffff804`447b7665 : ffff9780`ba1ebb30 ffff9780`ba1ec030 ffff9780`ba1ebb30 00000000`00000000 : nt!KdpReport+0x71 0b ffff9780`ba1eba70 fffff804`43f108f8 : ffff9780`ba1ec2e8 ffff9780`ba1ebb00 ffff9780`ba1ebb30 ffff9780`ba1ec030 : nt!KdpTrap+0x14d 0c ffff9780`ba1ebac0 fffff804`43f10565 : ffff9780`ba1ec2e8 ffff9780`ba1ec030 ffff9780`ba1ebb30 00000000`00000000 : nt!KdTrap+0x2c 0d ffff9780`ba1ebb00 fffff804`44062442 : ffff9780`ba1ec1a8 ffffffff`00000000 00000000`00000000 ffff9780`ba1ec1c8 : nt!KiDispatchException+0x135 0e ffff9780`ba1ec1b0 fffff804`4405b988 : ffff9780`ba1ec388 ffffffff`00000000 00000000`00000000 ffff9780`ba1ec3a8 : nt!KiExceptionDispatch+0xc2 0f ffff9780`ba1ec390 fffff804`447b3e7b : 00000000`00000000 00000000`0000000f ffff9780`ba1ec650 ffff9780`ba1eceb0 : nt!KxDebugTrapOrFault+0x3c8 (TrapFrame @ ffff9780`ba1ec390) ... 3: kd> .trap ffff9780`ba1eb870 3: kd> ub rip l10 nt!KdEnterDebugger+0xee: fffff804`447b3e2e 488901 mov qword ptr [rcx],rax fffff804`447b3e31 0fb6054ae3b1ff movzx eax,byte ptr [nt!KdDebuggerNotPresent (fffff804`442d2182)] fffff804`447b3e38 83e001 and eax,1 fffff804`447b3e3b c1e002 shl eax,2 fffff804`447b3e3e 83c801 or eax,1 fffff804`447b3e41 48894108 mov qword ptr [rcx+8],rax fffff804`447b3e45 ff054dd4b0ff inc dword ptr [nt!KdDebuggerEnteredCount (fffff804`442c1298)] fffff804`447b3e4b 418ac6 mov al,r14b fffff804`447b3e4e 381d2c7dbcff cmp byte ptr [nt!KdPortLocked (fffff804`4437bb80)],bl fffff804`447b3e54 488b6c2438 mov rbp,qword ptr [rsp+38h] fffff804`447b3e59 488b742440 mov rsi,qword ptr [rsp+40h] fffff804`447b3e5e 0f94c3 sete bl fffff804`447b3e61 011d35d4b0ff add dword ptr [nt!KdDebuggerEnteredWithoutLock (fffff804`442c129c)],ebx fffff804`447b3e67 488b5c2430 mov rbx,qword ptr [rsp+30h] fffff804`447b3e6c 488b7c2448 mov rdi,qword ptr [rsp+48h] fffff804`447b3e71 c70585bab1ff01000000 mov dword ptr [nt!KdEnteredDebugger (fffff804`442cf900)],1 ;触发调试异常(KdDebugTrapOrFault)
当有调试事件发生需要中断到调试器时需要调用nt!KdEnterDebugger函数,函数内部如果触发了异常,则会产生无限递归,资源耗尽后会触发KeBugCheckEx调用。为了正常中断到调试器,可以先把 fffff804`447b3e71 处指令NOP掉。
重新尝试,重启虚拟机,内核附加调试,NOP fffff804`447b3e71(nt!KdEnterDebugger+0x131) 处指令:
0: kd> u nt!KdEnterDebugger+0x131 nt!KdEnterDebugger+0x131: fffff801`70f2ee71 c70585bab1ff01000000 mov dword ptr [nt!KdEnteredDebugger (fffff801`70a4a900)],1 fffff801`70f2ee7b 4883c420 add rsp,20h fffff801`70f2ee7f 415e pop r14 fffff801`70f2ee81 c3 ret fffff801`70f2ee82 cc int 3 fffff801`70f2ee83 cc int 3 fffff801`70f2ee84 cc int 3 fffff801`70f2ee85 cc int 3 0: kd> eb nt!KdEnterDebugger+0x131 90 90 90 90 90 90 90 90 90 90 0: kd> u nt!KdEnterDebugger+0x131 l10 nt!KdEnterDebugger+0x131: fffff801`70f2ee71 90 nop fffff801`70f2ee72 90 nop fffff801`70f2ee73 90 nop fffff801`70f2ee74 90 nop fffff801`70f2ee75 90 nop fffff801`70f2ee76 90 nop fffff801`70f2ee77 90 nop fffff801`70f2ee78 90 nop fffff801`70f2ee79 90 nop fffff801`70f2ee7a 90 nop fffff801`70f2ee7b 4883c420 add rsp,20h fffff801`70f2ee7f 415e pop r14 fffff801`70f2ee81 c3 ret 0: kd> g
启动应用,调试器可以正常中断了:
0: kd> g Single step exception - code 80000004 (first chance) First chance exceptions are reported before any exception handling. This exception may be expected and handled. EasyAntiCheat+0x70f82: fffff801`756a0f82 489d popfq 2: kd> gn Break instruction exception - code 80000003 (first chance) EasyAntiCheat+0x70f92: fffff801`756a0f92 cc int 3 ;主动触发断点异常 2: kd> r dr0,dr1,dr2,dr3 dr0=fffff80170a4a900 dr1=fffff801707cbb40 dr2=0000000000000000 dr3=0000000000000000
需要注意此时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异常,继续执行:
2: kd> gn Single step exception - code 80000004 (first chance) First chance exceptions are reported before any exception handling. This exception may be expected and handled. nt!KdExitDebugger+0xd: fffff801`70f2ee95 4c8d0de4bab1ff lea r9,[nt!KdLogBuffer (fffff801`70a4a980)] 2: kd> ub rip l3 nt!KdExitDebugger: fffff801`70f2ee88 4053 push rbx fffff801`70f2ee8a 4883ec20 sub rsp,20h fffff801`70f2ee8e 83256bbab1ff00 and dword ptr [nt!KdEnteredDebugger (fffff801`70a4a900)],0 2: kd> eb nt!KdExitDebugger+0x6 90 90 90 90 90 90 90
可以看到nt!KdExitDebugger同样会写nt!KdEnteredDebugger字段,按同样方式NOP掉即可。
对CC进行gn之后,我们发现虽然系统正常运行,但是调试机再次'失联'了,并且应用提示检测到’调试模式‘,查看dump,可以发现nt!KdDebuggerEnabled被清零了,这是一个对内核调试很重要的字段,清零就意味着客户机不会再响应调试器的任何请求。
0: kd> db nt!KdDebuggerEnabled fffff801`16047181 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................ fffff801`16047191 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 14 ................ fffff801`160471a1 00 00 00 01 00 00 00 00-00 00 40 98 f7 ff ff 00 [email protected] fffff801`160471b1 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................ fffff801`160471c1 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
机制很简单,我不打算继续深究其具体实现,打补丁,过校验等,也许可以用一种相对通用的思路来解决这种检测(考虑到TP等也有类似的检测机制):
将nt!KdDebuggerEnabled及nt!KdDebuggerNotPresent换个位置存储
找到系统对这两个变量的引用,重定向到我们指定的位置
将nt!KdDebuggerEnabled置0,nt!KdDebuggerNotPresent置1,原地址展示伪造后的信息以过检测
经过上述操作后,程序对nt!KdDebuggerEnabled和nt!KdDebuggerNotPresent的破坏性修改将不会再影响到调试机制,其中难点在于第2步,可以使用IDA交叉引用查找,也可以WinDBG下硬件断点动态查找(推荐),不多赘述,实现如下:
# Windows 10 Kernel Version 17763 MP (4 procs) Free x64 # Product: WinNt, suite: TerminalServer SingleUserTS # Built by: 17763.1.amd64fre.rs5_release.180914-1434 # 将nt!KdDebuggerEnabled及nt!KdDebuggerNotPresent重定向到nt!KdDebuggerEnabled+2处 # 需要注意:1. 所选择的重定向HOOK是没有通用性的,需灵活修改应用,2. 根据自己的调试环境自行调整偏移和补丁 eb nt!KdDebuggerEnabled 01 00 01 00 eb nt!KdCheckForDebugBreak+0xc80ee+2 56 eb nt!KdPollBreakIn+0x34+2 89 eb nt!KdPollBreakIn+0xf3+2 ca eb nt!KdPollBreakIn+0x8b+3 32 eb nt!KdPollBreakIn+0x114+3 a9 eb nt!KdpCreateRemoteFile+0x54+3 c9 eb nt!KdpCreateRemoteFile+0x141+3 dc eb nt!KdpSendWaitContinue+0xa0+3 dd eb nt!KdpSendWaitContinue+0x780+3 fd eb nt!KdExitDebugger+0x33+3 c2 eb nt!KdEnterDebugger+0xf1+3 4c eb nt!KdDebuggerEnabled 00 01 01 00
PS: 不要处理 nt!ExpQuerySystemInformation,否则NtQuerySystemInformation依然可以检测到调试器。
经过上面的处理后,调试器功能已经正常了,不过应用还不能正常运行,提示检测到‘测试签名’,‘内核调试’等。