内核补丁保护(一)初始化

😄

1 PatchGuard初始化

分析系统: Windows 10 企业版 21H2 19044.2130

PatchGuard 原理:

1.png

PatchGuard 的初始化主要由 KiFilterFiberContext 执行。此函数在引导开始时调用,在任何用户驱动程序加载之前。KiFilterFiberContext 有两种调用方式,后面会详细介绍。

  1. 在函数 KeInitAmd64SpecificState 中调用 KiFilterFiberContext。
  2. 在函数 ExpLicenseWatchInitWorker 中调用 KiFilterFiberContext。

1.1 KeInitAmd64SpecificState

原理:在函数 KeInitAmd64SpecificState 中触发异常,然后在异常过滤器中调用 KiFilterFiberContext 函数。

静态分析

  1. 系统初始化过程中,在 KeInitAmd64SpecificState 函数中,如果系统没有被内核调试器调试,则会主动触发一个有符号的 idiv 除法异常(div无符号除法,idiv有符号除法)。

    函数 KeInitAmd64SpecificState 位置如下:

    1
    2
    3
    KiSystemStartup --> KiInitializeKernel --> InitBootProcessor --> PsInitSystem --> PspInitPhase0 --> Phase1Initialization
    --> IoInitSystem --> IoInitSystemPreDrivers --> IopInitializeBootDrivers --> PsNotifyCoreDriversInitialized
    --> KeInitAmd64SpecificState

    函数分析如下:

    触发初始化 PatchGuard 的条件:

    1. 如果没有启用安全模式,且当前系统不被调试,则直接触发 idiv 异常,进入到 KeInitAmd64SpecificState 的 __except filter 中,然后初始化 PatchGuard。
    2. 如果启用安全模式,然后调用 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
    72
    INIT: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
  2. 分析 KeInitAmd64SpecificState 的 __except filter。

    调用 KiFilterFiberContext 函数。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    INIT: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. 双机调试,第一次断点断下来后,如下。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    Executable 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
  2. 给 KeInitAmd64SpecificState、KiFilterFiberContext 下断点。

    1
    2
    3
    4
    5
    kd> 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
  3. 命中 KeInitAmd64SpecificState 函数(此时还没有到判断是否处于安全模式、是否被调试)。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    kd> 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
    19
    kd> 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,也就不是在安全模式下。

  4. 通过 Windbg 修改寄存器的值,故意触发异常,然后初始化 PG。

    单步调试到 fffff807‘3ce5a5a0 41f7f8 idiv eax,r8d 时,查看寄存器的状态:

    2.png

    可以看到,rax = 0,rdx = 0,r8d = 0x11。由于当前存在内核调试器,且系统正在被调试,所以结果与静态分析相吻合。

    根据静态分析,此时应该将 rdx = rax = 0x80000000,r8d = 0xFFFFFFFF 进行设置,让其产生异常。

    3.png

  5. 单步运行后,可以看到断点 KiFilterFiberContext 被命中。

    1
    2
    3
    4
    kd> 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
    18
    kd> 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
    21
    kd> 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
    11
    kd> 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
    3
    kd> 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
    10
    kd> 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
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
72
73
74
75
76
77
78
79
80
81
82
83
84
85
INIT:0000000140A44DF0 // =============== S U B R O U T I N E =======================================
INIT:0000000140A44DF0
INIT:0000000140A44DF0
INIT:0000000140A44DF0 ExpLicenseWatchInitWorker proc near // CODE XREF: ExpWatchProductTypeInitialization:loc_140A4485D↑p
INIT:0000000140A44DF0 // DATA XREF: .rdata:00000001400A0684↑o ...
INIT:0000000140A44DF0
INIT:0000000140A44DF0 var_18 = qword ptr -18h
INIT:0000000140A44DF0 arg_0 = qword ptr 8
INIT:0000000140A44DF0 arg_8 = qword ptr 10h
INIT:0000000140A44DF0 arg_10 = qword ptr 18h
INIT:0000000140A44DF0
INIT:0000000140A44DF0 // FUNCTION CHUNK AT INIT:0000000140A7C4B0 SIZE 00000033 BYTES
INIT:0000000140A44DF0
INIT:0000000140A44DF0 mov [rsp+arg_0], rbx
INIT:0000000140A44DF5 mov [rsp+arg_8], rbp
INIT:0000000140A44DFA mov [rsp+arg_10], rsi
INIT:0000000140A44DFF push rdi
INIT:0000000140A44E00 sub rsp, 30h
INIT:0000000140A44E04 lfence
INIT:0000000140A44E07 mov rax, cs:KiProcessorBlock // KPRCB
INIT:0000000140A44E0E mov rsi, [rax+_KPRCB.HalReserved[6]]
INIT:0000000140A44E12 mov rbp, [rax+_KPRCB.HalReserved[5]]
INIT:0000000140A44E16 and [rax+_KPRCB.HalReserved[6]], 0
INIT:0000000140A44E1B and [rax+_KPRCB.HalReserved[5]], 0
INIT:0000000140A44E20 mov al, ds:0FFFFF780000002D4h
INIT:0000000140A44E29 mov bl, al
INIT:0000000140A44E2B shr bl, 1
INIT:0000000140A44E2D mov dil, bl // bl = *(UCHAR *)FFFFF780000002D4 >> 1 // 取最低 1 字节
INIT:0000000140A44E2D //
INIT:0000000140A44E2D // dil = bl// // dil: rdi lower 8 bits
INIT:0000000140A44E30 call KiSwInterruptPresent // 和 Virtual Secure Mode (VSM) 有关
INIT:0000000140A44E35 test eax, eax
INIT:0000000140A44E37 sets cl // if rflags.SF = 1, set CL = 1
INIT:0000000140A44E37 // 符号标志。运算结果是负数时置 1。
INIT:0000000140A44E37 // eax < 0, CL = 1
INIT:0000000140A44E3A cmp cs:InitSafeBootMode, 0
INIT:0000000140A44E41 setnz al // InitSafeBootMode > 0, AL = 1
INIT:0000000140A44E44 and cl, al
INIT:0000000140A44E46 or bl, cl // if(KiSwInterruptPresent() < 0) cl = 1
INIT:0000000140A44E46 // if(InitSafeBootMode != 0) al = 1
INIT:0000000140A44E46 //
INIT:0000000140A44E46 // bl = bl | (cl & al)
INIT:0000000140A44E48 rdtsc // rdx:rax
INIT:0000000140A44E4A shl rdx, 20h
INIT:0000000140A44E4E or rax, rdx
INIT:0000000140A44E51 mov r8, rax
INIT:0000000140A44E54 mov eax, 51EB851Fh
INIT:0000000140A44E59 shr r8, 3
INIT:0000000140A44E5D mul r8d
INIT:0000000140A44E60 shr edx, 5
INIT:0000000140A44E63 imul eax, edx, 64h // r8 = __rdstc()
INIT:0000000140A44E63 //
INIT:0000000140A44E63 // r8d = *(ULONG *)(__rdstc() >> 3)
INIT:0000000140A44E63 // edx:eax = r8d * 0x51eb851f // 取得 edx 的值
INIT:0000000140A44E63 // eax = (edx >> 5) * 0x64
INIT:0000000140A44E66 sub r8d, eax // r8d = r8d - eax
INIT:0000000140A44E69 cmp r8d, 3
INIT:0000000140A44E6D jbe short loc_140A44E75 // 有两个路线来这里:
INIT:0000000140A44E6D // 1、r8d <= 3 时,必须 bl == 0 才会 call rax。(即 bl = bl | (cl & al) == 0)
INIT:0000000140A44E6D // 也就是以下两个条件必须同时满足:
INIT:0000000140A44E6D // -- (*(UCHAR *)FFFFF780000002D4 >> 1) == 0
INIT:0000000140A44E6D // -- cl & al == 0
INIT:0000000140A44E6D // 2、r8d > 3 时,一定不会 call rax。
INIT:0000000140A44E6F mov bl, dil
INIT:0000000140A44E72 or bl, 1 // bl >= 1
INIT:0000000140A44E75
INIT:0000000140A44E75 loc_140A44E75: // CODE XREF: ExpLicenseWatchInitWorker+7D↑j
INIT:0000000140A44E75 test bl, bl // 有两个路线来这里:
INIT:0000000140A44E75 // 1、r8d <= 3 时,必须 bl == 0 才会 call rax。(即 bl = bl | (cl & al) == 0)
INIT:0000000140A44E75 // 也就是以下两个条件必须同时满足:
INIT:0000000140A44E75 // -- (*(UCHAR *)FFFFF780000002D4 >> 1) == 0
INIT:0000000140A44E75 // -- cl & al == 0
INIT:0000000140A44E75 // 2、r8d > 3 时,一定不会 call rax。
INIT:0000000140A44E77 jz loc_140A7C4B0
INIT:0000000140A44E7D
INIT:0000000140A44E7D loc_140A44E7D: // CODE XREF: ExpLicenseWatchInitWorker+376CE↓j
INIT:0000000140A44E7D mov rbx, [rsp+38h+arg_0]
INIT:0000000140A44E82 mov rbp, [rsp+38h+arg_8]
INIT:0000000140A44E87 mov rsi, [rsp+38h+arg_10]
INIT:0000000140A44E8C add rsp, 30h
INIT:0000000140A44E90 pop rdi
INIT:0000000140A44E91 retn
INIT:0000000140A44E91 // ---------------------------------------------------------------------------
INIT:0000000140A44E92 db 0CCh
INIT:0000000140A44E92 ExpLicenseWatchInitWorker endp

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
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
__int64 ExpLicenseWatchInitWorker()
{
__int64 v0; // rax
__int64 v1; // rsi
__int64 (__fastcall *v2)(__int64); // rbp
char v3; // bl
char v4; // di
char v5; // bl
unsigned __int64 v6; // rax
unsigned __int64 v7; // r8
__int64 result; // rax

_mm_lfence();
v0 = KiProcessorBlock[0]; //KPRCB
v1 = *(_QWORD *)(KiProcessorBlock[0] + offsetof(_KPRCB, HalReserved_6_));
// pKiFilterFiberParm = KPRCB[0].HalReserved[6];
v2 = *(__int64 (__fastcall **)(__int64))(KiProcessorBlock[0] + offsetof(_KPRCB, HalReserved_5_));
// PG_KiFilterFiberContext = KPRCB[0].HalReserved[5];
*(_QWORD *)(KiProcessorBlock[0] + offsetof(_KPRCB, HalReserved_6_)) = 0i64;
*(_QWORD *)(v0 + offsetof(_KPRCB, HalReserved_5_)) = 0i64;
v3 = MEMORY[0xFFFFF780000002D4] >> 1;
v4 = MEMORY[0xFFFFF780000002D4] >> 1;

v5 = (InitSafeBootMode != 0) & ((signed int)KiSwInterruptPresent() < 0) | v3;
v6 = __rdtsc();
v7 = v6 >> 3;

// r8 = __rdstc();
// r8d = *(ULONG *)(__rdstc() >> 3);
// edx:eax = r8d * 0x51eb851f; // 取得 edx 的值;
// result = eax = (edx >> 5) * 0x64;
result = 100 * ((unsigned int)(v6 >> 3) / 0x64);

if ( (unsigned int)(v7 - result) > 3 )
v5 = v4 | 1;

if ( !v5 )
{
result = v2(v1);
// PG_KiFilterFiberContext(pKiServiceTablesLocked);
if ( (_DWORD)result != 1 )
KeBugCheckEx(0x9Au, 0x42424242ui64, 0xC000026Aui64, 0i64, 0i64);
}
return result;
}

通过对函数的分析,要想调用 PG_KiFilterFiberContext 函数,必须满足满足 v5 != 0。而 v5 的值在以下两种情况进行修改:

  1. 如果 (v7 - result) > 3,则 v5 >= 1。这个时候将不会调用 PG_KiFilterFiberContext 函数。

  2. 如果 (v7 - result) <= 3,则 v5 的值由如下决定:

    1
    2
    v3 = MEMORY[0xFFFFF780000002D4] >> 1;
    v5 = (InitSafeBootMode != 0) & ((signed int)KiSwInterruptPresent() < 0) | v3;

    这个时候,如果要调用 PG_KiFilterFiberContext 函数,必须 v5 == 0。

    r8d <= 3 时,要想调用 PG_KiFilterFiberContext 函数,必须使 bl = 0。

4.png

一、优化除法

在函数中,有个乘数为 0x51eb851f,在 Tetrane 材料中说这是用来优化除法的,IDA 也将这个乘法转换为除法,具体转换过程没有探究。

二、KPRCB[0].HalReserved[6]、KPRCB[0].HalReserved[5] 的初始化

这两给字段是在 KiLockServiceTable 函数中再次使用异常处理程序(主要是用来混淆控制流,加大逆向分析难度),但是并不是通过触发异常,而是通过调用 RtlLookupExceptionHandler 函数来执行 KiLockServiceTable$filt$0(过滤器函数):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
.text:0000000140414AB1 // ---------------------------------------------------------------------------
.text:0000000140414AB1 // START OF FUNCTION CHUNK FOR KiLockServiceTable
.text:0000000140414AB1
.text:0000000140414AB1 KiLockServiceTable$filt$0: // DATA XREF: .rdata:000000014009D8A4↑o
.text:0000000140414AB1 // .pdata:00000001400E3FDC↑o
.text:0000000140414AB1 // __except filter // owned by 14039CD93
.text:0000000140414AB1 push rbp
.text:0000000140414AB3 sub rsp, 30h
.text:0000000140414AB7 mov rbp, rdx
.text:0000000140414ABA call KiFatalExceptionFilter
.text:0000000140414ABF nop
.text:0000000140414AC0 add rsp, 30h
.text:0000000140414AC4 pop rbp
.text:0000000140414AC5 retn
.text:0000000140414AC5 // END OF FUNCTION CHUNK FOR KiLockServiceTable
.text:0000000140414AC5 // ---------------------------------------------------------------------------

在 KiFatalExceptionFilter 函数中进行的初始化:

1
2
3
4
5
6
7
8
INIT:0000000140A6FF5C KiFatalExceptionFilter proc near        
...
INIT:0000000140A6FF7C lea rbx, KiServiceTablesLocked
...
INIT:0000000140A6FF90 mov [rsi+_KPRCB.HalReserved[6]], rbx
...
INIT:0000000140A70027 mov [rsi+_KPRCB.HalReserved[5]], rax
...

注意在 KiFatalExceptionFilter 函数中的 KiServiceTablesLocked 指针,实际上它就是函数 KiFilterFiberContext 的参数,将其保存在 KPRCB.HalReserved[6] 中。KI_FILTER_FIBER_PARAM 是全局变量 KiServiceTablesLocked 指向的结构:

1
2
3
4
5
6
7
8
9
10
INITDATA:0000000140D58010                               KiServiceTablesLocked proc near 
INITDATA:0000000140D58010 ; __unwind { // KiFilterFiberContext
INITDATA:0000000140D58010 0F 0D 09 prefetchw byte ptr [rcx]
INITDATA:0000000140D58013 C3 retn
INITDATA:0000000140D58018 D0 ED 6C 40 01 00 00 00 dq offset PsCreateSystemThread
INITDATA:0000000140D58020 B0 9F 5B 40 01 00 00 00 dq offset sub_1405B9FB0
INITDATA:0000000140D58028 40 1D C3 40 01 00 00 00 dq offset KiBalanceSetManagerPeriodicDpc
INITDATA:0000000140D58030 00 00 00 00 00 00 00 00 00 00+ dq 6 dup(0)
INITDATA:0000000140D58030 00 00 00 00 00 00 00 00 00 00+; } // starts at 140D58010
INITDATA:0000000140D58030 00 00 00 00 00 00 00 00 00 00+KiServiceTablesLocked endp

IDA 将其识别为一个函数,但是提取硬编码后该结构的数据类型如下(具体见下一篇PG_KiInitializePatchGuardContext分析):

1
2
3
4
5
6
7
8
typedef struct _KI_FILTER_FIBER_PARAM
{
CHAR code_prefetch_rcx_retn[4]; // prefetchw byte ptr [rcx]; retn;
CHAR padding[4]; // 用于对齐的4字节(Align)
PVOID pPsCreatesystemThread; // PsCreatesystemThread 函数的存根
PVOID Pg_Method3stubTocheckRoutine_sub_1405B9FB0; // 被创建的系统线程的回调函数
PVOID pKiBalanceSetManagerPeriodicDpc; // 指向一个全局的 KDPC 结构
}KI_FILTER_FIBER_PARAM, *PKI_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
2
3
4
5
6
7
8
9
10
11
12
// 查看 KPRCB.HalReserved[5]、KPRCB.HalReserved[6]
// 是否是 KiFilterFiberContext 函数,并观察参数的值
// 观察 KiServiceTablesLocked 结构的成员
KiLockServiceTable;
RtlLookupExceptionHandler;
KiFatalExceptionFilter;

// 观察 ExpLicenseWatchInitWorker、KelnitAmd64SpecificState 谁先被命中
// 分析并验证静态分析中 v2(v1) 是否是 PG_KiFilterFiberContext(pKiFilterFiberParm)
ExpLicenseWatchInitWorker;
KeInitAmd64SpecificState;
KiFilterFiberContext;
  1. 重启系统并触发首次中断,查看此时的调用堆栈。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    Executable 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
  2. 给上述函数下断点,并查看其他信息。

    1
    2
    3
    4
    5
    6
    7
    kd> 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
    29
    kd> 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
    31
    kd> 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
  3. 继续运行,第一个断点断在 KiLockServiceTable 函数。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    kd> 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 函数。

  4. 查看此时 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
    13
    kd> 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

  5. 继续运行,断点断在 ExpLicenseWatchInitWorker 函数。说明该函数在 KeInitAmd64SpecificState 函数前执行。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    kd> 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
    3
    fffff805`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。

  6. 修改寄存器,使得 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。

  7. 继续运行,则 nt!KiFilterFiberContext 函数会被命中,此时是带有参数 rcx 的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    kd> 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
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
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
_BOOL8 __fastcall KiFilterFiberContext(__int64 a1)
{
v1 = a1;
v2 = KdDisableDebugger();
KeKeepData(KiFilterFiberContext);
_disable();
if ( !(_BYTE)KdDebuggerNotPresent )
{
while ( 1 )
;
}
_enable();
v3 = __rdtsc();
v4 = (__ROR8__(v3, 3) ^ v3) * (unsigned __int128)0x7010008004002001ui64;
v52 = *((_QWORD *)&v4 + 1);
v5 = (v4 ^ *((_QWORD *)&v4 + 1))
- 10
* ((unsigned __int64)(((unsigned __int64)v4 ^ *((_QWORD *)&v4 + 1)) * (unsigned __int128)0xCCCCCCCCCCCCCCCDui64 >> 64) >> 3);
if ( !Src && !v1 && !__32 ) // cs:Src, cs:$$32, v1 = pKiFilterFiberParm
{
if ( PsIntegrityCheckEnabled )
{
ObjectAttributes.Length = 48;
ObjectAttributes.ObjectName = (__int64)L"TV";
ObjectAttributes.RootDirectory = 0i64;
ObjectAttributes.Attributes = 64;
_mm_storeu_si128((__m128i *)&ObjectAttributes.SecurityDescriptor, (__m128i)0i64);
if ( ExCreateCallback(&CallbackObject, &ObjectAttributes, 0, 0) >= 0 )
{
ExNotifyCallback((PVOID)CallbackObject, sub_1403DB780, &__29);
HalPutDmaAdapter(CallbackObject);
if ( __29 )
__32 = 1;
ExInitializeNPagedLookasideList(
(PNPAGED_LOOKASIDE_LIST)&Lookaside,
0i64,
0i64,
0x200u,
0xAF0ui64,
0x746E494Bu,
0);
}
}
}
v6 = __rdtsc();
v7 = (__ROR8__(v6, 3) ^ v6) * (unsigned __int128)0x7010008004002001ui64;
v53 = *((_QWORD *)&v7 + 1);
v8 = v7;
*(_QWORD *)&v7 = __rdtsc();
v9 = v8 ^ *((_QWORD *)&v7 + 1);
v32 = (v5 < 6) + 1;
v33 = v1;
v34 = 1;
v35 = 0;
v10 = (__ROR8__(v7, 3) ^ (unsigned __int64)v7) * (unsigned __int128)0x7010008004002001ui64;
v54 = *((_QWORD *)&v10 + 1);
v11 = (v10 ^ *((_QWORD *)&v10 + 1))
- 6
* ((unsigned __int64)(((unsigned __int64)v10 ^ *((_QWORD *)&v10 + 1)) * (unsigned __int128)0xAAAAAAAAAAAAAAABui64 >> 64) >> 2);
v31 = (v10 ^ DWORD2(v10))
- 6
* ((unsigned __int64)(((unsigned __int64)v10 ^ *((_QWORD *)&v10 + 1)) * (unsigned __int128)0xAAAAAAAAAAAAAAABui64 >> 64) >> 2);
v30 = v9 - 13 * ((unsigned __int64)((unsigned __int64)v9 * (unsigned __int128)0x4EC4EC4EC4EC4EC5ui64 >> 64) >> 2);
v12 = KeExpandKernelStackAndCallout((ULONG_PTR)sub_140A359E0, (ULONG_PTR)&v30, 0xC000);
v13 = v35;
if ( v12 < 0 )
v13 = 0;
v35 = v13;
if ( v13 )
{
if ( v5 < 6 )
{
v14 = __rdtsc();
v15 = (__ROR8__(v14, 3) ^ v14) * (unsigned __int128)0x7010008004002001ui64;
v48 = *((_QWORD *)&v15 + 1);
v16 = (v15 ^ DWORD2(v15))
- 13
* ((unsigned __int64)(((unsigned __int64)v15 ^ *((_QWORD *)&v15 + 1))
* (unsigned __int128)0x4EC4EC4EC4EC4EC5ui64 >> 64) >> 2);
do
{
v17 = __rdtsc();
v18 = (__ROR8__(v17, 3) ^ v17) * (unsigned __int128)0x7010008004002001ui64;
v49 = *((_QWORD *)&v18 + 1);
v19 = (v18 ^ *((_QWORD *)&v18 + 1))
- 6
* ((unsigned __int64)(((unsigned __int64)v18 ^ *((_QWORD *)&v18 + 1))
* (unsigned __int128)0xAAAAAAAAAAAAAAABui64 >> 64) >> 2);
}
while ( (_DWORD)v11 && (_DWORD)v19 == (_DWORD)v11 );
v36 = v16;
v37 = (v18 ^ DWORD2(v18))
- 6
* ((unsigned __int64)(((unsigned __int64)v18 ^ *((_QWORD *)&v18 + 1))
* (unsigned __int128)0xAAAAAAAAAAAAAAABui64 >> 64) >> 2);
v38 = (v5 < 6) + 1;
v39 = v1;
v40 = 0;
v41 = 0;
v20 = KeExpandKernelStackAndCallout((ULONG_PTR)sub_140A359E0, (ULONG_PTR)&v36);
v21 = v41;
if ( v20 < 0 )
v21 = 0;
v41 = v21;
v13 = v21;
}
if ( v13 )
{
if ( !Src && !v1 && ((signed int)KiSwInterruptPresent() >= 0 || __32) )
{
v42 = 0;
v43 = 7;
v44 = 1;
v45 = 0i64;
v22 = KiSwInterruptPresent();
v47 = 0;
v23 = 8;
if ( v22 >= 0 )
v23 = 0;
v46 = v23;
v24 = KeExpandKernelStackAndCallout((ULONG_PTR)sub_140A359E0, (ULONG_PTR)&v42);
v25 = v47;
if ( v24 < 0 )
v25 = 0;
v47 = v25;
v13 = v25;
}
if ( v13 && !v1 )
{
v26 = 24;
v27 = &__2a;
v28 = 3i64;
do
{
*v27 = 0i64;
v26 -= 8;
++v27;
--v28;
}
while ( v28 );
for ( ; v26; --v26 )
{
*(_BYTE *)v27 = 0;
v27 = (__int64 *)((char *)v27 + 1);
}
__20 = 0;
__2b = 0;
__2c = 0i64;
dword_140C12E80 = 0;
qword_140D58080 = 0i64;
}
}
}
_disable();
if ( !(_BYTE)KdDebuggerNotPresent )
{
while ( 1 )
;
}
_enable();
_disable();
if ( !(_BYTE)KdDebuggerNotPresent )
{
while ( 1 )
;
}
_enable();
if ( v2 >= 0 )
KdEnableDebugger();
return v13 != 0;
}

上面代码看着非常长,但是从代码逻辑来看,两个比较大的 if ( v13 ){} 里面的代码是得不到执行的。在代码中 v13 是用 r13d 寄存器保存的,该值一直为 0,即使期间可能有修改,但都是将其修改为 0。所以该函数的返回值是 FALSE。解析 KiFilterFiberContext 函数几个关键点:

  1. 打开回调对象。条件 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
      6
      NTSTATUS ExCreateCallback(
      [out] PCALLBACK_OBJECT *CallbackObject,
      [in] POBJECT_ATTRIBUTES ObjectAttributes,
      [in] BOOLEAN Create,
      [in] BOOLEAN AllowMultipleCallbacks
      );
    • 回调对象名称。注意此处在 IDA 中 F5 后解析错了。原代码如下:

      1
      2
      3
      INIT: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
      2
      INIT: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
      5
      void ExNotifyCallback(
      [in] PVOID CallbackObject,
      [in, optional] PVOID Argument1,
      [in, optional] PVOID Argument2
      );
  2. KeExpandKernelStackAndCallout 函数。在 KiFilterFiberContext 函数中一共调用该函数 3 此,但是后两次不会被调用到。

    KeExpandKernelStackAndCallout 的原型如下:

    1
    2
    3
    4
    5
    NTSTATUS 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
    23
    KiFilterFiberContext
    {
    ...

    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
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
KiFilterFiberContext
{
...

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((ULONG_PTR)PG_Initialize_Caller, (ULONG_PTR)&v30, 2)
{
// sub_140A359E0(&v30)
PG_Initialize_Caller(&v30);
}

在 PG_Initialize_Caller 函数中,sub_140A1AEE4 即为初始化 PatchGuard Context 的函数。我将其命名为 PG_KiInitializePatchGuard。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
__int64 __fastcall sub_140A359E0(__int64 a1)
{
__int64 v1; // rbx
__int64 v2; // r9
__int64 v3; // r8
__int64 v4; // rdx
int v5; // ST20_4
__int64 result; // rax

v1 = *(_DWORD *)a1;
v2 = *(unsigned int *)(a1 + 4);
v3 = *(unsigned int *)(a1 + 8);
v4 = *(_QWORD *)(a1 + 16);
v5 = *(_DWORD *)(a1 + 24);

// PG_KiInitializePatchGuard(v1, v2, v3, v4, v5)
result = sub_140A1AEE4(v1, v2, v3, v4, v5);
*(_BYTE *)(a1 + 28) = result;
return result;
}

这里非常重要的就是 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

http://www.longmagu.com/thread-50085-1-1.html

https://shhoya.github.io/windows_pgintro.html