[原创]驱动保护-EAC内核调试检测分析
2022-10-27 18:45:50 Author: bbs.pediy.com(查看原文) 阅读量:16 收藏

客户机:     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)两个硬件断点

检测机制已经很清晰了:

  1. nt!KdEnteredDebugger下硬件写断点,对nt!KeBugCheckEx下硬件执行断点;

  2. 主动触发CC断点异常;

  3. 如果内核调试开启且调试器已附加,则内核调试引擎会执行nt!KdEnterDebugger中断到调试器以报告异常;

  4. nt!KdEnterDebugger写入nt!KdEnteredDebugger触发硬件写断点;

  5. 资源允许则回到3形成递归调用,资源不足则调用nt!KeBugCheckEx;

  6. 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等也有类似的检测机制):

  1. nt!KdDebuggerEnabled及nt!KdDebuggerNotPresent换个位置存储

  2. 找到系统对这两个变量的引用,重定向到我们指定的位置

  3. 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依然可以检测到调试器。

经过上面的处理后,调试器功能已经正常了,不过应用还不能正常运行,提示检测到‘测试签名’,‘内核调试’等。

[2022冬季班]《安卓高级研修班(网课)》月薪三万班招生中~


文章来源: https://bbs.pediy.com/thread-274896.htm
如有侵权请联系:admin#unsafe.sh