内核补丁保护(一)初始化
😄
1 PatchGuard初始化
分析系统: Windows 10 企业版 21H2 19044.2130
PatchGuard 原理:

PatchGuard 的初始化主要由 KiFilterFiberContext 执行。此函数在引导开始时调用,在任何用户驱动程序加载之前。KiFilterFiberContext 有两种调用方式,后面会详细介绍。
- 在函数
KeInitAmd64SpecificState中调用KiFilterFiberContext。 - 在函数
ExpLicenseWatchInitWorker中调用KiFilterFiberContext。
1.1 KeInitAmd64SpecificState
原理:在函数 KeInitAmd64SpecificState 中触发异常,然后在异常过滤器中调用 KiFilterFiberContext 函数。
静态分析
系统初始化过程中,在
KeInitAmd64SpecificState函数中,如果系统没有被内核调试器调试,则会主动触发一个有符号的idiv除法异常(div无符号除法,idiv有符号除法)。函数
KeInitAmd64SpecificState位置如下:1
2
3KiSystemStartup --> KiInitializeKernel --> InitBootProcessor --> PsInitSystem --> PspInitPhase0 --> Phase1Initialization
--> IoInitSystem --> IoInitSystemPreDrivers --> IopInitializeBootDrivers --> PsNotifyCoreDriversInitialized
--> KeInitAmd64SpecificState函数分析如下:
触发初始化 PatchGuard 的条件:
- 如果没有启用安全模式,且当前系统不被调试,则直接触发
idiv异常,进入到KeInitAmd64SpecificState的__except filter中,然后初始化 PatchGuard。 - 如果启用安全模式,然后调用
KiSwInterruptPresent函数,如果该函数返回值大于等于 0,且且当前系统不被调试,则直接触发idiv异常,进入到KeInitAmd64SpecificState的__except filter中,然后初始化 PatchGuard。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72INIT:0000000140A59018 // =============== S U B R O U T I N E =======================================
INIT:0000000140A59018
INIT:0000000140A59018
INIT:0000000140A59018 KeInitAmd64SpecificState proc near // CODE XREF: PsNotifyCoreDriversInitialized+A2↑p
INIT:0000000140A59018 // DATA XREF: .rdata:00000001400A3B84↑o ...
INIT:0000000140A59018
INIT:0000000140A59018 arg_0 = dword ptr 8
INIT:0000000140A59018
INIT:0000000140A59018 // FUNCTION CHUNK AT INIT:0000000140A7774C SIZE 00000017 BYTES
INIT:0000000140A59018 // FUNCTION CHUNK AT INIT:0000000140A82AAC SIZE 00000012 BYTES
INIT:0000000140A59018
INIT:0000000140A59018 // __unwind { // __C_specific_handler
INIT:0000000140A59018 sub rsp, 28h
INIT:0000000140A5901C lfence
INIT:0000000140A5901F cmp cs:InitSafeBootMode, 0
INIT:0000000140A59026 jnz loc_140A82AAC
INIT:0000000140A5902C
INIT:0000000140A5902C loc_140A5902C: // CODE XREF: KeInitAmd64SpecificState+29AA1↓j
INIT:0000000140A5902C movzx edx, cs:KdDebuggerNotPresent // 是否存在内核调试器
INIT:0000000140A5902C // 1:不存在内核调试器
INIT:0000000140A5902C // 0:存在内核调试器
INIT:0000000140A5902C // https://bbs.pediy.com/thread-275226.htm
INIT:0000000140A59033 movzx eax, cs:KdPitchDebugger // 标志是否已被内核调试器附加
INIT:0000000140A59033 // 1:没有被内核调试器 attach
INIT:0000000140A59033 // 0:已被内核调试器 attach
INIT:0000000140A5903A or edx, eax
INIT:0000000140A5903C mov ecx, edx
INIT:0000000140A5903E neg ecx
INIT:0000000140A59040 sbb r8d, r8d // r8d = -1,调试关系没建立好
INIT:0000000140A59040 // r8d = 0,调试关系已建立
INIT:0000000140A59043 and r8d, 0FFFFFFEEh // r8d = 0xFFFFFFEE,调试关系已建立好
INIT:0000000140A59043 // r8d = 0,调试关系没建立好
INIT:0000000140A59047 add r8d, 11h // r8d = 0x11,调试关系已建立
INIT:0000000140A59047 // r8d = -1,调试关系没建立好
INIT:0000000140A5904B ror edx, 1 // 调试关系已建立:rdx = 0
INIT:0000000140A5904B // 调试关系没建立好:rdx = 0x80000000
INIT:0000000140A5904D
INIT:0000000140A5904D loc_140A5904D: // DATA XREF: .rdata:00000001400A3B70↑o
INIT:0000000140A5904D // __try { // __except at loc_140A59059
INIT:0000000140A5904D mov eax, edx
INIT:0000000140A5904F cdq
INIT:0000000140A59050 idiv r8d // rdx:rax/r8d
INIT:0000000140A59050 // r8d = 0时,必须 edx、eax 同时为 0
INIT:0000000140A59050 // 也就是必须存在调试器、当前被调试时就不会引发异常
INIT:0000000140A59050 // --------
INIT:0000000140A59050 // 如果调试关系建立好:0/11
INIT:0000000140A59050 // 如果调试关系没有建立好:0x8000000080000000/0xFFFFFFFF = 0x80000001
INIT:0000000140A59050 //
INIT:0000000140A59050 // 如 AMD64 文档中所定义,*如果正结果大于 7FFFFFFFH 或负结果小于 80000000H*,
INIT:0000000140A59050 // 则会触发除法错误。在这种情况下,两个
INIT:0000000140A59050 // 操作数都是负数,应该给出正结果,但是这个除法的结果是 0x80000001,因此出现除法错误。
INIT:0000000140A59050 // --------
INIT:0000000140A59050 //
INIT:0000000140A59050 // ============
INIT:0000000140A59050 // 如果建立好调试关系,并不会初始化 PG
INIT:0000000140A59050 // 如果没有建立好调试关系,则会进入异常,进行初始化 PG
INIT:0000000140A59050 // ============
INIT:0000000140A59053 mov [rsp+28h+arg_0], eax
INIT:0000000140A59057 jmp short $+2
INIT:0000000140A59057 // } // starts at 140A5904D
INIT:0000000140A59059 // ---------------------------------------------------------------------------
INIT:0000000140A59059
INIT:0000000140A59059 loc_140A59059: // CODE XREF: KeInitAmd64SpecificState+3F↑j
INIT:0000000140A59059 // KeInitAmd64SpecificState+29A9B↓j
INIT:0000000140A59059 // DATA XREF: ...
INIT:0000000140A59059 // __except(KeInitAmd64SpecificState$filt$0) // owned by 140A5904D
INIT:0000000140A59059 add rsp, 28h
INIT:0000000140A5905D retn
INIT:0000000140A5905D // ---------------------------------------------------------------------------
INIT:0000000140A5905E db 0CCh
INIT:0000000140A5905E // } // starts at 140A59018
INIT:0000000140A5905E KeInitAmd64SpecificState endp- 如果没有启用安全模式,且当前系统不被调试,则直接触发
分析
KeInitAmd64SpecificState的__except filter。调用
KiFilterFiberContext函数。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20INIT:0000000140A7774C // ---------------------------------------------------------------------------
INIT:0000000140A7774C // START OF FUNCTION CHUNK FOR KeInitAmd64SpecificState
INIT:0000000140A7774C
INIT:0000000140A7774C KeInitAmd64SpecificState$filt$0:
INIT:0000000140A7774C
INIT:0000000140A7774C // __except filter // owned by 140A5904D
INIT:0000000140A7774C push rbp
INIT:0000000140A7774E sub rsp, 20h
INIT:0000000140A77752 mov rbp, rdx // 调试关系已建立:rdx = 0
INIT:0000000140A77752 // 调试关系没建立好:rdx = 0x80000000
INIT:0000000140A77755 xor ecx, ecx
INIT:0000000140A77757 call KiFilterFiberContext
INIT:0000000140A7775C nop
INIT:0000000140A7775D add rsp, 20h
INIT:0000000140A77761 pop rbp
INIT:0000000140A77762 retn
INIT:0000000140A77762 // END OF FUNCTION CHUNK FOR KeInitAmd64SpecificState
INIT:0000000140A77762 // ---------------------------------------------------------------------------
INIT:0000000140A77763 db 0CCh
INIT:0000000140A77764 byte_140A77764 db 6 dup(0CCh)
动态分析
双机调试,第一次断点断下来后,如下。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15Executable search path is:
Windows 10 Kernel Version 19041 MP (1 procs) Free x64
Edition build lab: 19041.1.amd64fre.vb_release.191206-1406
Machine Name:
Kernel base = 0xfffff807`3c402000 PsLoadedModuleList = 0xfffff807`3d02c270
System Uptime: 0 days 0:00:00.000
nt!DebugService2+0x5:
fffff807`3c803015 cc int 3
kd> kv
# Child-SP RetAddr : Args to Child : Call Site
00 fffff807`3f2a3de8 fffff807`3c772445 : fffff807`3af204e1 fffff807`3c402118 fffff807`3af20a80 fffff807`3c0917f5 : nt!DebugService2+0x5
01 fffff807`3f2a3df0 fffff807`3cdb5c2d : fffff807`3d042610 fffff807`3cd9cab9 fffff807`3af20a80 fffff807`3c7b0d11 : nt!DbgLoadImageSymbols+0x45
02 fffff807`3f2a3e40 fffff807`3cd8f17c : 00000000`0000008e 00000000`00000006 00000000`00000000 00000000`00000000 : nt!KdInitSystem+0xacd
03 fffff807`3f2a3fc0 00000000`00000000 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : nt!KiSystemStartup+0x16c给
KeInitAmd64SpecificState、KiFilterFiberContext下断点。1
2
3
4
5kd> bp KeInitAmd64SpecificState
kd> bp KiFilterFiberContext
kd> bl
0 e Disable Clear fffff807`3ce5a568 0001 (0001) nt!KeInitAmd64SpecificState
1 e Disable Clear fffff807`3ce1bba0 0001 (0001) nt!KiFilterFiberContext命中
KeInitAmd64SpecificState函数(此时还没有到判断是否处于安全模式、是否被调试)。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15kd> g
Breakpoint 0 hit
nt!KeInitAmd64SpecificState:
fffff807`3ce5a568 4883ec28 sub rsp,28h
kd> kv
# Child-SP RetAddr : Args to Child : Call Site
00 fffffe0b`79a06888 fffff807`3ce5a49b : fffff807`3af20a80 00000000`00000000 00000000`00000000 fffff807`3af20a80 : nt!KeInitAmd64SpecificState
01 fffffe0b`79a06890 fffff807`3ce59167 : 00000000`00000008 00000080`00000008 ffffffff`800000bc ffffda0f`3032ca80 : nt!PsNotifyCoreDriversInitialized+0xa7
02 fffffe0b`79a068c0 fffff807`3ce3e390 : fffff807`00000000 fffff807`3d047600 00000000`00000006 fffff807`3af20a80 : nt!IopInitializeBootDrivers+0x14b
03 fffffe0b`79a06a70 fffff807`3ce45511 : fffff807`3f2a3fc0 fffff807`3af20a80 fffff807`3cb8f6c0 fffff807`3af20a00 : nt!IoInitSystemPreDrivers+0xb24
04 fffffe0b`79a06bb0 fffff807`3cb8f6fb : fffff807`3af20a80 fffff807`3d048f68 fffff807`3cb8f6c0 fffff807`3af20a80 : nt!IoInitSystem+0x15
05 fffffe0b`79a06be0 fffff807`3c673d25 : ffffda0f`30290040 fffff807`3cb8f6c0 fffff807`3af20a80 ffff0700`00000000 : nt!Phase1Initialization+0x3b
06 fffffe0b`79a06c10 fffff807`3c802778 : fffff807`3b2e5180 ffffda0f`30290040 fffff807`3c673cd0 00000000`00000000 : nt!PspSystemThreadStartup+0x55
07 fffffe0b`79a06c60 00000000`00000000 : fffffe0b`79a07000 fffffe0b`79a01000 00000000`00000000 00000000`00000000 : nt!KiStartSystemThread+0x28根据静态分析内容,查看此时的
nt!InitSafeBootMode、nt!KdDebuggerNotPresent、nt!KdPitchDebugger。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19kd> u fffff807`3ce5a568
nt!KeInitAmd64SpecificState:
fffff807`3ce5a568 4883ec28 sub rsp,28h
fffff807`3ce5a56c 0faee8 lfence
fffff807`3ce5a56f 833d463a1f0000 cmp dword ptr [nt!InitSafeBootMode (fffff807`3d04dfbc)],0
fffff807`3ce5a576 0f85649d0200 jne nt!KeInitAmd64SpecificState+0x29d78 (fffff807`3ce842e0)
fffff807`3ce5a57c 0fb615dfa81e00 movzx edx,byte ptr [nt!KdDebuggerNotPresent (fffff807`3d044e62)]
fffff807`3ce5a583 0fb605f26d1b00 movzx eax,byte ptr [nt!KdPitchDebugger (fffff807`3d01137c)]
fffff807`3ce5a58a 0bd0 or edx,eax
fffff807`3ce5a58c 8bca mov ecx,edx
kd> dd nt!InitSafeBootMode
fffff807`3d04dfbc 00000000 00080006 00000000 ada29fc0
kd> dq nt!KdDebuggerNotPresent
fffff807`3d044e62 00000000`00000000 00000000`00000000
kd> dq nt!KdPitchDebugger
fffff807`3d01137c 00000000`00000000 4a61000f`00000000注意:
nt!InitSafeBootMode前面是dword,说明是 4 字节大小,切该值为 0,也就不是在安全模式下。通过 Windbg 修改寄存器的值,故意触发异常,然后初始化 PG。
单步调试到
fffff807‘3ce5a5a0 41f7f8 idiv eax,r8d时,查看寄存器的状态:![2.png]()
可以看到,rax = 0,rdx = 0,r8d = 0x11。由于当前存在内核调试器,且系统正在被调试,所以结果与静态分析相吻合。
根据静态分析,此时应该将 rdx = rax = 0x80000000,r8d = 0xFFFFFFFF 进行设置,让其产生异常。
![3.png]()
单步运行后,可以看到断点
KiFilterFiberContext被命中。1
2
3
4kd> p
Breakpoint 1 hit
nt!KiFilterFiberContext:
fffff807`3ce1bba0 4055 push rbp查看此时调用堆栈:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18kd> kv
# Child-SP RetAddr : Args to Child : Call Site
00 fffffe0b`79a05638 fffff807`3ce797ec : fffffe0b`79a06860 fffff807`3ce5a5a0 fffffe0b`79a05c50 fffffe0b`79a05750 : nt!KiFilterFiberContext
01 fffffe0b`79a05640 fffff807`3c7d033f : fffff807`00000003 fffff807`3c52faa0 fffffe0b`79a01000 fffffe0b`79a07000 : nt!KeInitAmd64SpecificState$filt$0+0x10
02 fffffe0b`79a05670 fffff807`3c803fdf : fffff807`3c52faa0 fffffe0b`79a05c50 fffff807`3c7d02a0 00000000`00000000 : nt!_C_specific_handler+0x9f
03 fffffe0b`79a056e0 fffff807`3c679a77 : fffffe0b`79a05c50 00000000`00000000 fffffe0b`79a05e60 fffff807`3ce5a5a0 : nt!RtlpExecuteHandlerForException+0xf
04 fffffe0b`79a05710 fffff807`3c678676 : fffffe0b`79a06628 fffffe0b`79a06360 fffffe0b`79a06628 00000000`00020019 : nt!RtlDispatchException+0x297
05 fffffe0b`79a05e30 fffff807`3c80d1ac : fffff807`3af20a80 00000000`c0000034 fffffe0b`79a06770 fffff807`3c80d1ac : nt!KiDispatchException+0x186
06 fffffe0b`79a064f0 fffff807`3c805e0a : 00000000`00000000 fffff807`3d7f15db 00000000`00000002 fffff807`3c806b16 : nt!KiExceptionDispatch+0x12c
07 fffffe0b`79a066d0 fffff807`3ce5a5a0 : 00000000`00000010 00000000`00040384 fffffe0b`79a06888 00000000`00000018 : nt!KiDivideErrorFault+0x30a (TrapFrame @ fffffe0b`79a066d0)
08 fffffe0b`79a06860 fffff807`3ce5a49b : fffff807`3af20a80 00000000`00000000 00000000`00000000 fffff807`3af20a80 : nt!KeInitAmd64SpecificState+0x38
09 fffffe0b`79a06890 fffff807`3ce59167 : 00000000`00000008 00000080`00000008 ffffffff`800000bc ffffda0f`3032ca80 : nt!PsNotifyCoreDriversInitialized+0xa7
0a fffffe0b`79a068c0 fffff807`3ce3e390 : fffff807`00000000 fffff807`3d047600 00000000`00000006 fffff807`3af20a80 : nt!IopInitializeBootDrivers+0x14b
0b fffffe0b`79a06a70 fffff807`3ce45511 : fffff807`3f2a3fc0 fffff807`3af20a80 fffff807`3cb8f6c0 fffff807`3af20a00 : nt!IoInitSystemPreDrivers+0xb24
0c fffffe0b`79a06bb0 fffff807`3cb8f6fb : fffff807`3af20a80 fffff807`3d048f68 fffff807`3cb8f6c0 fffff807`3af20a80 : nt!IoInitSystem+0x15
0d fffffe0b`79a06be0 fffff807`3c673d25 : ffffda0f`30290040 fffff807`3cb8f6c0 fffff807`3af20a80 ffff0700`00000000 : nt!Phase1Initialization+0x3b
0e fffffe0b`79a06c10 fffff807`3c802778 : fffff807`3b2e5180 ffffda0f`30290040 fffff807`3c673cd0 00000000`00000000 : nt!PspSystemThreadStartup+0x55
0f fffffe0b`79a06c60 00000000`00000000 : fffffe0b`79a07000 fffffe0b`79a01000 00000000`00000000 00000000`00000000 : nt!KiStartSystemThread+0x28根据堆栈中的异常触发点查看:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21kd> u nt!KeInitAmd64SpecificState+0x38
nt!KeInitAmd64SpecificState+0x38:
fffff807`3ce5a5a0 41f7f8 idiv eax,r8d
fffff807`3ce5a5a3 89442430 mov dword ptr [rsp+30h],eax
fffff807`3ce5a5a7 eb00 jmp nt!KeInitAmd64SpecificState+0x41 (fffff807`3ce5a5a9)
fffff807`3ce5a5a9 4883c428 add rsp,28h
fffff807`3ce5a5ad c3 ret
fffff807`3ce5a5ae cc int 3
kd> u nt!KeInitAmd64SpecificState$filt$0 la
nt!KeInitAmd64SpecificState$filt$0:
fffff807`3ce797dc 4055 push rbp
fffff807`3ce797de 4883ec20 sub rsp,20h
fffff807`3ce797e2 488bea mov rbp,rdx
fffff807`3ce797e5 33c9 xor ecx,ecx
fffff807`3ce797e7 e8b423faff call nt!KiFilterFiberContext (fffff807`3ce1bba0)
fffff807`3ce797ec 90 nop
fffff807`3ce797ed 4883c420 add rsp,20h
fffff807`3ce797f1 5d pop rbp
fffff807`3ce797f2 c3 ret
fffff807`3ce797f3 cc int 3注意:在这里的异常处理过滤器中,调用
KiFilterFiberContext函数的第一个参数为0,暗示着其他地方有可能调用该函数,且应该有非0的参数值。然后查看一下此时寄存器的值:1
2
3
4
5
6
7
8
9
10
11kd> r
rax=fffff8073ce797dc rbx=fffff8073c4a5e84 rcx=0000000000000000
rdx=fffffe0b79a06860 rsi=0000000000000000 rdi=0000000000000000
rip=fffff8073ce1bba0 rsp=fffffe0b79a05638 rbp=fffffe0b79a06860
r8=fffffe0b79a07000 r9=fffffe0b79a05688 r10=fffffe0b79a06628
r11=0000000000000000 r12=fffffe0b79a06860 r13=fffffe0b79a06628
r14=fffffe0b79a05ca0 r15=fffff8073c402000
iopl=0 nv up ei pl zr na po nc
cs=0010 ss=0018 ds=002b es=002b fs=0053 gs=002b efl=00040246
nt!KiFilterFiberContext:
fffff807`3ce1bba0 4055 push rbp可以看到,此时
KiFilterFiberContext第一个参数 rcx = 0。第二个参数 rdx = rbp = fffffe0b79a06860,内容如下:1
2
3kd> dq fffffe0b79a06860
fffffe0b`79a06860 00000000`00000010 00000000`00040384
fffffe0b`79a06870 fffffe0b`79a06888 00000000`00000018很有趣的是此时 r9 寄存器的内容指向 r8:
1
2
3
4
5
6
7
8
9
10kd> dq fffffe0b79a05688
fffffe0b`79a05688 fffffe0b`79a07000 fffff807`3c52faa0
fffffe0b`79a05698 fffff807`00000000 fffffe0b`79a06628
fffffe0b`79a056a8 fffffe0b`79a05e60 fffffe0b`79a06628
fffffe0b`79a056b8 fffffe0b`79a05750 fffff807`3c402000
kd> dq fffffe0b79a07000
fffffe0b`79a07000 ????????`???????? ????????`????????
fffffe0b`79a07010 ????????`???????? ????????`????????
fffffe0b`79a07020 ????????`???????? ????????`????????
1.2 ExpLicenseWatchInitWorker
在函数 ExpLicenseWatchInitWorker 也会调用 KiFilterFiberContext,不过存在一定概率,约为 4%。
在系统启动过程中,该函数在 KelnitAmd64SpecificState 之前被调用。
静态分析
1 | INIT:0000000140A44DF0 // =============== S U B R O U T I N E ======================================= |
F5 如下内容已通过逆向进行验证没问题。通过参考 PatchGuard Initialize -1-、Updated Analysis of PatchGuard on Microsoft Windows 10 RS4 等资料:
KPRCB[0].HalReserved[5] 应该就是 KiFilterFiberContext 函数,此处没有引用内核模块的导出符号,我将其命名为 PG_KiFilterFiberContext,KPRCB[0].HalReserved[6] 应该是 KiFilterFiberContext 函数的参数,他是一个数据结构(KI_FILTER_FIBER_PARAM),此处将其命名为 pKiServiceTablesLocked。
1 | __int64 ExpLicenseWatchInitWorker() |
通过对函数的分析,要想调用 PG_KiFilterFiberContext 函数,必须满足满足 v5 != 0。而 v5 的值在以下两种情况进行修改:
如果 (v7 - result) > 3,则
v5 >= 1。这个时候将不会调用PG_KiFilterFiberContext函数。如果 (v7 - result) <= 3,则
v5的值由如下决定:1
2v3 = MEMORY[0xFFFFF780000002D4] >> 1;
v5 = (InitSafeBootMode != 0) & ((signed int)KiSwInterruptPresent() < 0) | v3;这个时候,如果要调用
PG_KiFilterFiberContext函数,必须v5 == 0。r8d <= 3 时,要想调用 PG_KiFilterFiberContext 函数,必须使
bl = 0。

一、优化除法
在函数中,有个乘数为 0x51eb851f,在 Tetrane 材料中说这是用来优化除法的,IDA 也将这个乘法转换为除法,具体转换过程没有探究。
二、KPRCB[0].HalReserved[6]、KPRCB[0].HalReserved[5] 的初始化
这两给字段是在 KiLockServiceTable 函数中再次使用异常处理程序(主要是用来混淆控制流,加大逆向分析难度),但是并不是通过触发异常,而是通过调用 RtlLookupExceptionHandler 函数来执行 KiLockServiceTable$filt$0(过滤器函数):
1 | .text:0000000140414AB1 // --------------------------------------------------------------------------- |
在 KiFatalExceptionFilter 函数中进行的初始化:
1 | INIT:0000000140A6FF5C KiFatalExceptionFilter proc near |
注意在 KiFatalExceptionFilter 函数中的 KiServiceTablesLocked 指针,实际上它就是函数 KiFilterFiberContext 的参数,将其保存在 KPRCB.HalReserved[6] 中。KI_FILTER_FIBER_PARAM 是全局变量 KiServiceTablesLocked 指向的结构:
1 | INITDATA:0000000140D58010 KiServiceTablesLocked proc near |
IDA 将其识别为一个函数,但是提取硬编码后该结构的数据类型如下(具体见下一篇PG_KiInitializePatchGuardContext分析):
1 | typedef struct _KI_FILTER_FIBER_PARAM |
总结一下他们的关系:
KiLockServiceTable --call--> RtlLookupExceptionHandler --call--> KiLockServiceTable$filt$0 --call--> KiFatalExceptionFilter。
然后在 KiFatalExceptionFilter 中填充字段 KPRCB.HalReserved[6]、KPRCB.HalReserved[5]。
三、校验和初始化
在函数 KiLockServiceTable 中会调用 KiLockExtendedServiceTable 函数,该函数也与 PatchGuard 相关。它是用来多对多个 Section、函数表中的各个函数条目进行校验,并将校验结果存储在全局变量中,以便在 PatchGuard 的 Context 初始化过程中使用,后面会进行研究。
动态分析
前面提到过,在系统启动过程中,ExpLicenseWatchInitWorker 函数在 KeInitAmd64SpecificState 之前被调用。
在动态调试中,需要给以下几个函数下断点:
1 | // 查看 KPRCB.HalReserved[5]、KPRCB.HalReserved[6] |
重启系统并触发首次中断,查看此时的调用堆栈。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15Executable search path is:
Windows 10 Kernel Version 19041 MP (1 procs) Free x64
Edition build lab: 19041.1.amd64fre.vb_release.191206-1406
Machine Name:
Kernel base = 0xfffff805`75405000 PsLoadedModuleList = 0xfffff805`7602f270
System Uptime: 0 days 0:00:00.000
nt!DebugService2+0x5:
fffff805`75806015 cc int 3
kd> kv
# Child-SP RetAddr : Args to Child : Call Site
00 fffff805`782a3de8 fffff805`75775445 : fffff805`740004e1 fffff805`75405118 fffff805`74000a80 fffff805`751717f5 : nt!DebugService2+0x5
01 fffff805`782a3df0 fffff805`75db8c2d : fffff805`76045610 fffff805`75d9fab9 fffff805`74000a80 fffff805`757b3d11 : nt!DbgLoadImageSymbols+0x45
02 fffff805`782a3e40 fffff805`75d9217c : 00000000`0000008e 00000000`00000006 00000000`00000000 00000000`00000000 : nt!KdInitSystem+0xacd
03 fffff805`782a3fc0 00000000`00000000 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : nt!KiSystemStartup+0x16c给上述函数下断点,并查看其他信息。
1
2
3
4
5
6
7kd> bl
0 e Disable Clear fffff805`757a2234 0001 (0001) nt!KiLockServiceTable
1 e Disable Clear fffff805`757661ec 0001 (0001) nt!RtlLookupExceptionHandler
2 e Disable Clear fffff805`75e752a4 0001 (0001) nt!KiFatalExceptionFilter
3 e Disable Clear fffff805`75e49e9c 0001 (0001) nt!ExpLicenseWatchInitWorker
4 e Disable Clear fffff805`75e5d568 0001 (0001) nt!KeInitAmd64SpecificState
5 e Disable Clear fffff805`75e1eba0 0001 (0001) nt!KiFilterFiberContext查看此时 KPRCB.HalReserved[5]、KPRCB.HalReserved[6] 为空:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29kd> rdmsr 0xC0000101 // IA32_GS_BASE
msr[c0000101] = fffff805`743c5000
kd> dt _KPCR fffff805`743c5000
nt!_KPCR
+0x000 NtTib : _NT_TIB
+0x000 GdtBase : 0xfffff805`78299fb0 _KGDTENTRY64
+0x008 TssBase : 0xfffff805`78298000 _KTSS64
+0x010 UserRsp : 0
+0x018 Self : 0xfffff805`743c5000 _KPCR
+0x020 CurrentPrcb : 0xfffff805`743c5180 _KPRCB
+0x028 LockArray : 0xfffff805`743c5870 _KSPIN_LOCK_QUEUE
+0x030 Used_Self : (null)
+0x038 IdtBase : 0xfffff805`78297000 _KIDTENTRY64
+0x040 Unused : [2] 0
kd> dt _KPRCB 0xfffff805`743c5180 HalReserved
nt!_KPRCB
+0x048 HalReserved : [8] 0
kd> dx -id 0,0,fffff80576129a00 -r1 (*((ntkrnlmp!unsigned __int64 (*)[8])0xfffff805743c51c8))
(*((ntkrnlmp!unsigned __int64 (*)[8])0xfffff805743c51c8)) [Type: unsigned __int64 [8]]
[0] : 0x0 [Type: unsigned __int64]
[1] : 0x0 [Type: unsigned __int64]
[2] : 0x0 [Type: unsigned __int64]
[3] : 0x0 [Type: unsigned __int64]
[4] : 0x0 [Type: unsigned __int64]
[5] : 0x0 [Type: unsigned __int64]
[6] : 0x0 [Type: unsigned __int64]
[7] : 0x0 [Type: unsigned __int64]查看即将要传递给
KiFilterFiberContext的参数KiServiceTablesLocked此时已经被初始化了:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31kd> dq nt!KiServiceTablesLocked
fffff805`7615d010 00000000`c3090d0f fffff805`75a5a5d0
fffff805`7615d020 fffff805`759bf340 fffff805`76036d40
fffff805`7615d030 00000000`00000000 00000000`00000000
fffff805`7615d040 00000000`00000000 00000000`00000000
fffff805`7615d050 00000000`00000000 00000000`00000000
fffff805`7615d060 cccccccc`cccccccc cccccccc`cccccccc
fffff805`7615d070 cccccccc`cccccccc cccccccc`cccccccc
fffff805`7615d080 00000000`00000000 00000000`00000000
kd> u fffff805`75a5a5d0
nt!PsCreateSystemThread:
fffff805`75a5a5d0 4c8bdc mov r11,rsp
kd> u fffff805`759bf340
nt!HviIsIommuInUse+0x50:
fffff805`759bf340 4055 push rbp
fffff805`759bf342 53 push rbx
fffff805`759bf343 56 push rsi
fffff805`759bf344 57 push rdi
kd> u fffff805`76036d40
nt!KiBalanceSetManagerPeriodicDpc:
fffff805`76036d40 0000 add byte ptr [rax],al
fffff805`76036d42 0000 add byte ptr [rax],al
fffff805`76036d44 0000 add byte ptr [rax],al
kd> dq fffff805`76036d40
fffff805`76036d40 00000000`00000000 00000000`00000000
fffff805`76036d50 00000000`00000000 00000000`00000000
fffff805`76036d60 00000000`00000000 00000000`00000000
fffff805`76036d70 00000000`00000000 00000000`00000000继续运行,第一个断点断在
KiLockServiceTable函数。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21kd> g
Breakpoint 0 hit
nt!KiLockServiceTable:
fffff805`757a2234 48895c2408 mov qword ptr [rsp+8],rbx
kd> g
Breakpoint 1 hit
nt!RtlLookupExceptionHandler:
fffff805`757661ec 4883ec28 sub rsp,28h
kd> g
Breakpoint 2 hit
nt!KiFatalExceptionFilter:
fffff805`75e752a4 48895c2420 mov qword ptr [rsp+20h],rbx
kd> kv
# Child-SP RetAddr : Args to Child : Call Site
00 fffff805`782a36e8 fffff805`7581a2e9 : fffff805`782a3700 00000000`00000018 00000000`00000010 00000000`00040144 : nt!KiFatalExceptionFilter
01 fffff805`782a36f0 fffff805`757a23e9 : 00000000`00000000 fffff805`754cd1f0 00000000`00000000 00000000`00000000 : nt!KiLockServiceTable$filt$0+0xe
02 fffff805`782a3730 fffff805`757a221f : fffff805`754cd3c7 fffff805`743c5180 00000000`00000000 00000000`00067950 : nt!KiLockServiceTable+0x1b5
03 fffff805`782a3790 fffff805`75da0309 : fffff805`743cd510 00000000`00000001 00000000`00000000 00000000`00000000 : nt!KeCompactServiceTable+0x4b
04 fffff805`782a37c0 fffff805`75d92219 : 00000000`00000000 00000000`00000000 00000000`00000000 fffff805`782a3ba8 : nt!KiInitializeKernel+0x7f9
05 fffff805`782a3ad0 00000000`00000000 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : nt!KiSystemStartup+0x209继续运行,下一个断点应该是
RtlLookupExceptionHandler函数。该函数在KiLockServiceTable中被调用,目的就是来获取一个异常。继续运行,下一个断点应该就是
KiLockServiceTable函数的异常过滤器,在过滤器中调用KiFatalExceptionFilter函数。查看此时 KPRCB.HalReserved[5]、KPRCB.HalReserved[6] 字段。
查看 KPRCB.HalReserved[6] 的填充。
当运行到如下代码时,可以看到
KPRCB.HalReserved[6] = 0。
1
fffff805`75e752d8 48895e78 mov qword ptr [rsi+78h],rbx ds:002b:fffff805`743c51f8=0000000000000000
当该指令运行后:
1
2
3
4
5
6
7
8
9
10
11
12
13kd> dt _KPRCB 0xfffff805`743c5180 HalReserved -r1
nt!_KPRCB
+0x048 HalReserved : [8] 0
kd> dx -id 0,0,fffff80576129a00 -r1 (*((ntkrnlmp!unsigned __int64 (*)[8])0xfffff805743c51c8))
(*((ntkrnlmp!unsigned __int64 (*)[8])0xfffff805743c51c8)) [Type: unsigned __int64 [8]]
[0] : 0x0 [Type: unsigned __int64]
[1] : 0x0 [Type: unsigned __int64]
[2] : 0x0 [Type: unsigned __int64]
[3] : 0x0 [Type: unsigned __int64]
[4] : 0x0 [Type: unsigned __int64]
[5] : 0x0 [Type: unsigned __int64]
[6] : 0xfffff8057615d010 [Type: unsigned __int64]
[7] : 0x0 [Type: unsigned __int64]- 查看 KPRCB.HalReserved[5] 的填充。
运行前:
![5.png]()
运行后,可以看到
KPRCB.HalReserved[5]就是函数KiFilterFiberContext。![6.png]()
继续运行,断点断在
ExpLicenseWatchInitWorker函数。说明该函数在KeInitAmd64SpecificState函数前执行。1
2
3
4
5
6
7
8
9
10
11
12
13
14kd> g
Breakpoint 3 hit
nt!ExpLicenseWatchInitWorker:
fffff805`75e49e9c 48895c2408 mov qword ptr [rsp+8],rbx
kd> kv
# Child-SP RetAddr : Args to Child : Call Site
00 ffffa70f`98a06820 fffff805`75e4990e : 00000000`00000002 ffffa70f`98a06960 00000000`00000000 00000000`00000002 : nt!ExpLicenseWatchInitWorker+0x30
01 ffffa70f`98a06860 fffff805`75e487e1 : fffff805`75405000 00000000`00000002 fffff805`740004b0 fffff805`7540f728 : nt!ExpWatchProductTypeInitialization+0x1b2
02 ffffa70f`98a06a00 fffff805`75e3e222 : 00000000`00000001 00000000`fffffe20 00000000`00000002 fffff805`740004b0 : nt!ExInitSystemPhase2+0x9
03 ffffa70f`98a06a30 fffff805`75b926e3 : fffff805`74000a80 fffff805`74000a80 fffff805`75b926c0 fffff805`74000a80 : nt!Phase1InitializationDiscard+0xe8a
04 ffffa70f`98a06be0 fffff805`75676d25 : ffffcf82`e7e7f040 fffff805`75b926c0 fffff805`74000a80 ffff0700`00000000 : nt!Phase1Initialization+0x23
05 ffffa70f`98a06c10 fffff805`75805778 : fffff805`743c5180 ffffcf82`e7e7f040 fffff805`75676cd0 00000000`00000000 : nt!PspSystemThreadStartup+0x55
06 ffffa70f`98a06c60 00000000`00000000 : ffffa70f`98a07000 ffffa70f`98a01000 00000000`00000000 00000000`00000000 : nt!KiStartSystemThread+0x28下面开始分析该函数,根据静态分析情况来看。最主要的就是指令
cmp r8d, 3。在此之前需要判断硬编码写死的地址0xFFFFF780000002D4中存放的值,此时为 3。![7.png]()
根据静态分析,如果
r8d <= 3时将不会调用KiFilterFiberContext函数,因为此时bl = 1(bl = bl | (cl & al))。接着调用
KiSwInterruptPresent函数,KiSwInterruptPresent函数里面又调用nt!HvlQueryVsmConnection,此时得到的nt!HvlpVsmVtlCallVa == 0。KiSwInterruptPresent得到的返回值为0xc0000001 < 0。此时的
nt!InitSafeBootMode == 0。如下代码运算完后,
bl == 1:1
2
3fffff805`75e49eed 0f95c0 setne al
fffff805`75e49ef0 22c8 and cl,al
fffff805`75e49ef2 0ad9 or bl,cl当指令
sub r8d,eax运行后,r8d = 0x34大于 3。则此时一定不会调用KiFilterFiberContext函数(PG_KiFilterFiberContext),r8d <= 3 时,要想调用 PG_KiFilterFiberContext 函数,必须使bl = 0。修改寄存器,使得
PG_KiFilterFiberContext(HalReserved[6])函数被调用。修改 r8d,使其 <= 3。
修改 bl、c l、al,使得
bl = bl | (cl & al) == 0。![8.png]()
如下图可以看到,此时的
rax = 0xfffff80575e1eba0(PG_KiFilterFiberContext),参数 rcx 也都对得上。![9.png]()
继续运行后可以看到 0 环的 CFGBitmap 保存在
nt!guard_icall_bitmap。继续运行,则
nt!KiFilterFiberContext函数会被命中,此时是带有参数 rcx 的。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15kd> g
Breakpoint 5 hit
nt!KiFilterFiberContext:
fffff805`75e1eba0 4055 push rbp
kd> r
rax=fffff80575e1eba0 rbx=0000000000000000 rcx=fffff8057615d010
rdx=000000000076377f rsi=fffff8057615d010 rdi=ffff9705ef10ee01
rip=fffff80575e1eba0 rsp=ffffa70f98a06818 rbp=fffff80575e1eba0
r8=0000000000000002 r9=ffffcf82e7e7f2a8 r10=fffff80575e1eba0
r11=0000000000000000 r12=0000000000000030 r13=0000000000000000
r14=0000000000000048 r15=0000000000000001
iopl=0 nv up ei pl zr na po nc
cs=0010 ss=0018 ds=002b es=002b fs=0053 gs=002b efl=00040246
nt!KiFilterFiberContext:
fffff805`75e1eba0 4055 push rbp
下面一节将会分析 nt!KiFilterFiberContext 初始化 PG_Context。
1.3 KiFilterFiberContext
静态分析
1 | _BOOL8 __fastcall KiFilterFiberContext(__int64 a1) |
上面代码看着非常长,但是从代码逻辑来看,两个比较大的 if ( v13 ){} 里面的代码是得不到执行的。在代码中 v13 是用 r13d 寄存器保存的,该值一直为 0,即使期间可能有修改,但都是将其修改为 0。所以该函数的返回值是 FALSE。解析 KiFilterFiberContext 函数几个关键点:
打开回调对象。条件
if ( !Src && !v1 && !__32 )中除v1外另外两个都是全局变量。v1是KiFilterFiberContext函数的参数,为KI_FILTER_FIBER_PARAM结构。只有在KI_FILTER_FIBER_PARAM == 0的情况下,条件才有可能满足。如果条件满足,则会接着判断
PsIntegrityCheckEnabled == 1 ?,如果条件满足则会调用ExCreateCallback打开一个回调对象,打开成功后,将会调用该回调对象链表上的所有回调该函数。关于此处的回调对象,后面会单独解释,但是这里需要弄懂以下几点。此处是打开回调对象不是创建回调对象,因为
ExCreateCallback参数 3 为 0。(说明该对象是在其他地方创建和注册ExRegisterCallback的)1
2
3
4
5
6NTSTATUS ExCreateCallback(
[out] PCALLBACK_OBJECT *CallbackObject,
[in] POBJECT_ATTRIBUTES ObjectAttributes,
[in] BOOLEAN Create,
[in] BOOLEAN AllowMultipleCallbacks
);回调对象名称。注意此处在 IDA 中 F5 后解析错了。原代码如下:
1
2
3INIT:0000000140A19C68 lea rax, aTv_2;
...
INIT:0000000140A19C79 mov [rbp+57h+ObjectAttributes.ObjectName], rax是将变量
aTv_2的地址赋值给ObjectAttributes.ObjectName(PUNICODE_STRING)。而
aTv_2变量的内容如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20.rdata:0000000140005C48 aTv_2: // DATA XREF: KiFilterFiberContext+C8↓o
.rdata:0000000140005C48 54 00 56 00 00 00 text "UTF-16LE", 'TV',0
.rdata:0000000140005C4E 00 00 align 10h
.rdata:0000000140005C50 80 22 A7 40 01 00 00 00 dq offset aCallback542875 // "\\Callback\\542875F90F9B47F497B64BA219C"...
typedef struct _OBJECT_ATTRIBUTES {
ULONG Length;
HANDLE RootDirectory;
PUNICODE_STRING ObjectName;
ULONG Attributes;
PVOID SecurityDescriptor;
PVOID SecurityQualityOfService;
} OBJECT_ATTRIBUTES;
typedef struct _UNICODE_STRING
{
USHORT Length;
USHORT MaxmumLength;
PWSTR Buffer;
} UNICODE_STRING;”TV“ 处应该解析为
ObjectName.Length = 0x0054、ObjectName.MaxmumLength = 0x0056、ObjectName.Buffer = &aCallback542875。aCallback542875的内容如下:1
2INIT:0000000140A72280 aCallback542875:
INIT:0000000140A72280 5C 00 43 00 61 00 6C 00+ text "UTF-16LE", '\Callback\542875F90F9B47F497B64BA219CACF69',0即回调对象名称为
\\Callback\\542875F90F9B47F497B64BA219CACF69。调用回调对象链表上所有的回调函数,可参考ReactOS-ExNotifyCallback。
1
2
3
4
5void ExNotifyCallback(
[in] PVOID CallbackObject,
[in, optional] PVOID Argument1,
[in, optional] PVOID Argument2
);
KeExpandKernelStackAndCallout函数。在KiFilterFiberContext函数中一共调用该函数 3 此,但是后两次不会被调用到。KeExpandKernelStackAndCallout的原型如下:1
2
3
4
5NTSTATUS KeExpandKernelStackAndCallout (
_In_ PEXPAND_STACK_CALLOUT Callout, // 即将要调用的函数,函数类型为 EXPAND_STACK_CALLOUT
_In_opt_ PVOID Parameter, // Callout 函数的参数
_In_ SIZE_T Size // 指定 Callout 函数堆栈的大小(必须量足够大)
);该函数的作用:
- 以参数三指定的堆栈大小调用
Callout函数,这个堆栈大小应该足够大,足以让Callout的子函数也是用。 - 此处的
Callout实际上是马上用来调用初始化 PG_Context 的函数,我将其命名为PG_Initialize_Caller。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23KiFilterFiberContext
{
...
ExInitializeNPagedLookasideList(
(PNPAGED_LOOKASIDE_LIST)&Lookaside,
0i64,
0i64,
0x200u,
0xAF0ui64,
0x746E494Bu,
0);
// PG_Initialize_Caller = sub_140A359E0
KeExpandKernelStackAndCallout((ULONG_PTR)PG_Initialize_Caller, (ULONG_PTR)&v30, 0xC000);
return FALSE;
}
KeExpandKernelStackAndCallout((ULONG_PTR)PG_Initialize_Caller, (ULONG_PTR)&v30)
{
return KeExpandKernelStackAndCalloutInternal((ULONG_PTR)PG_Initialize_Caller, (ULONG_PTR)&v30, 2);
}KeExpandKernelStackAndCalloutInternal函数如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43__int64 __usercall KeExpandKernelStackAndCalloutInternal@<rax>(
ULONG_PTR BugCheckParameter2@<rcx>, // PG_Initialize_Caller
ULONG_PTR BugCheckParameter3@<rdx>, // &v30
unsigned __int64 a3@<r8>, // 0xC000
char a4@<r9b> // 2
)
{
ULONG_PTR v4; // rsi
void (__fastcall *v5)(ULONG_PTR); // r14
__int64 result; // rax
unsigned __int8 v7; // r12
struct _KTHREAD *v8; // rbx
__2022111614201F00388DAC811AF7D3CE9 v9; // eax
char v10; // di
v4 = BugCheckParameter3;
v5 = (void (__fastcall *)(ULONG_PTR))BugCheckParameter2;
result = KiExpandKernelStackAndCalloutSwitchStack(BugCheckParameter2, BugCheckParameter3, a3, a4);
if ( (_DWORD)result == 0xC000048B ) // STATUS_CALLBACK_INVOKE_INLINE,The callback function must be invoked inline.
{
v7 = KeGetCurrentIrql();
v8 = KeGetCurrentThread();
v9.MiscFlags = (int)v8->_union_17;
if ( _bittest((const signed int *)&v9, 0xCu) )
{
v10 = 1;
}
else
{
v8->_union_17.MiscFlags = v9.MiscFlags | 0x1000;
v10 = 0;
}
// PG_Initialize_Caller(&v30),即 sub_140A359E0(&v30)
v5(v4);
if ( !v10 )
v8->_union_17.MiscFlags &= 0xFFFFEFFF;
if ( KeGetCurrentIrql() != v7 )
KeBugCheckEx(0xC8u, ((v7 | ((unsigned __int64)KeGetCurrentIrql() << 8)) << 8) | 2, (ULONG_PTR)v5, v4, 0i64);
result = 0i64;
}
return result;
}- 以参数三指定的堆栈大小调用
总结调用关系:
1 | KiFilterFiberContext |
在 PG_Initialize_Caller 函数中,sub_140A1AEE4 即为初始化 PatchGuard Context 的函数。我将其命名为 PG_KiInitializePatchGuard。
1 | __int64 __fastcall sub_140A359E0(__int64 a1) |
这里非常重要的就是 PG_KiInitializePatchGuard 函数的参数来源。可以看到他是从 a1 中来的,而 a1 这个指针地址是在 KiFilterFiberContext 函数中计算得到,并传递到 PG_Initialize_Caller 函数中的。
https://bbs.pediy.com/thread-248011.htm
https://www.cnblogs.com/kuangke/p/9397515.html
https://zhuanlan.zhihu.com/p/44157183
https://bbs.pediy.com/thread-268938.htm






