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

😄

1 PG_KiInitializePatchGuardContext 前奏

初始化的方法决定了 PG Check 的方式,比如:

  • 初始化时初始化了 PGContext 和 DPC 函数,PG Check 时就是通过 DPC 触发的。
  • 初始化时初始化了 PGContext 和 APC 函数,PG Check 时就是通过 APC 触发的。
  • 初始化时初始化了 PGContext 和 系统线程,PG Check 时就是通过该系统线程的回调函数触发的。

1.1 初始化方法

函数 PG_KiInitializePatchGuard 并不是微软给出的符号名称,而是许多相关 PatchGuard 文献对其进行的命名。

该函数一共有 5 个参数,在不同的情况下,他们决定着对 PGContext 不同的初始化方式。

  • 参数 1:DPC 方法的索引。
  • 参数 2:调度方式,决定着初始化 PGContext 和创建 DPC 的方法。
  • 参数 3:决定要检查的最大尺寸大小,是一个随机值。该随机值可以是 1 或 2。
  • 参数 4:指向 KI_FILTER_FIBER_PARAM 结构的指针,该指针从 ExpLicenseWatchInitWorker 中来自 KPRCB[0].HalReserved[6]。
  • 参数 5:BOOL 类型,决定着是否需要重新计算 NT 内核函数的校验值。

我们主要关注第 2、4 个参数。先介绍方法 0,1,2,3,4,5,7 这些方法由参数 2 决定:

1
2
3
4
5
6
KeSetCoalescableTimer = 0,
Prcb.AcpiReserved = 1,
Prcb.HalReserved = 2,
PsCreateSystemThread = 3,
KeInsertQueueApc = 4,
KiBalanceSetManagerPeriodicDpc = 5
  1. 方法 0:插入与 DPC 链接的计时器。初始化 PGContext、DPC 函数。将 DPC 函数出入到定时器中。

  2. 方法 1,2:隐藏 DPC。初始化 PGContext、KDPC 结构。不使用定时器,而是将 DPC 函数保存至 KPRCB。

    • 方法 1:将 DPC 函数保存至 KPRCB.AcpiReserved。使用 HalpTimerDpcRoutine 函数插入队列(Queue),当某个 ACPI 事件发生时调用 HalpTimerDpcRoutine,例如过渡到空闲状态。
    • 方法 2:将 DPC 函数保存至 KPECB.HalReserved[7]。使用 HalpMcaQueueDpc 函数插入队列,在发生 HAL 定时器时钟中断时完成检查(请参阅 HalpTimerClockInterrupt/HalpTimerAlwaysOnClockInterrupt)
  3. 方法 3:系统线程。初始化 PGContext、并创建一个系统线程。此时需要 ExpLicenseWatchInitWorker 函数传来的 KI_FILTER_FIBER_PARAM 参数。

    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;

    在这种情况下,会在 PG_KiInitializePatchGuard 函数里调用 sub_xxx(为了方便,将其命名为 Pg_InitMethod3SystemThread) 来创建一个新的系统线程,该系统线程的回调函数就是 Pg_Method3stubTocheckRoutine_sub_1405B9FB0。

  4. 方法 4:异步过程调用。初始化 PGContext、KAPC 结构。并将 APC 函数插入到已有的系统线程中。在函数中为 KiInsertQueueApc 函数准备参数:

    NormalRoutine = xHalTimerWatchdogStop

    KernelRoutine = KiDispatchCallout

    RundownRoutine = NULL

    1
    2
    3
    4
    // Line 8592
    *((_QWORD *)v212 + 317) = KiDispatchCallout;
    *((_QWORD *)v212 + 318) = xHalTimerWatchdogStop;
    *((_DWORD *)v212 + 597) = 2122344719;

    使用 PsEnumProcessThreads 来枚举 System 系统进程中的系统线程,如果哪一个线程回调函数等于 PopIrpWorkerControl,则将该线程 ETHREAD 保存至 PGContext(将APC插入到该线程),然后将 ETHREAD 存放在该 KAPC 结构中。

  5. 挂钩常规 DPC。此时需要 ExpLicenseWatchInitWorker 函数传来的 KI_FILTER_FIBER_PARAM 参数最后一个成员提供的指向全局变量 KiBalanceSetManagerPeriodicDpc 的指针,该指针类型为 KDPC,其 DPC 例程在函数 KiInitSystem 中初始化。

1.2 参数说明

参数一:DPC 例程的索引。

我们上面描述了在方法 0,1,2,5 上,我们创建了 DPC 函数(我简称为PG_DpcFuctions),参数一索引指向的函数,就是即将要被 PG_DpcFuctions 调用的函数。这些被参数一索引指向的函数主要作用是用于检查 PGContext。

下面这些函数我们可以在 PG_KiInitializePatchGuard 函数中看到。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// These ones don't use exception handlers to fire checks
KiTimerDispatch (copied to random pool allocation)
KiDpcDispatch (copied into patchguard context)

// These use exception handlers to fire patchguard checks
ExpTimerDpcRoutine
IopTimerDispatch
IopIrpStackProfilerTimer
PopThermalZoneDpc
CmpEnableLazyFlushDpcRoutine
CmpLazyFlushDpcRoutine
KiBalanceSetManagerDeferredRoutine
ExpTimeRefreshDpcRoutine
ExpTimeZoneDpcRoutine
ExpCenturyDpcRoutine

使用异常处理的这 10 个 DPC 是系统 DPC 函数,如果传给这些函数的参数 DeferredContext 地址是 Non-Canonical Address(不合法的地址)时,则会调用相应的 KiCustomAccessRoutine 函数。

下面是对应的 10 个 KiCustomAccessRoutine:

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
ExpTimerDpcRoutine
- KiCustomAccessRoutine0 (+ FsRtlTruncateSmallMcb)

IopTimerDispatch
- KiCustomAccessRoutine1

IopIrpStackProfilerTimer
- KiCustomAccessRoutine2

PopThermalZoneDpc
- KiCustomAccessRoutine3

CmpEnableLazyFlushDpcRoutine
- KiCustomAccessRoutine4

CmpLazyFlushDpcRoutine
- KiCustomAccessRoutine5

KiBalanceSetManagerDeferredRoutine
- KiCustomAccessRoutine6

ExpTimeRefreshDpcRoutine
- KiCustomAccessRoutine7

ExpTimeZoneDpcRoutine
- KiCustomAccessRoutine8

ExpCenturyDpcRoutine
- KiCustomAccessRoutine9

参数二:调度方式,决定着初始化 PGContext 和创建 DPC 的方法。

1
2
3
4
5
6
KeSetCoalescableTimer = 0,
Prcb.AcpiReserved = 1,
Prcb.HalReserved = 2,
PsCreateSystemThread = 3,
KeInsertQueueApc = 4,
KiBalanceSetManagerPeriodicDpc = 5

参数三:决定要检查的最大尺寸大小,是一个随机值。该随机值可以是 1 或 2。

此值用于确定每次 PatchGuard 检查时校验和的最大数据大小(以字节为单位)。主要思想是 PatchGuard 使用一个结构列表来检查完整性,并且在每次校验和之后,计数器会根据数据的大小递增。虽然检查的数据总量小于先前定义的最大值,PatchGuard 继续下一个结构以检查其列表。

参数四:指 KI_FILTER_FIBER_PARAM 结构体。

该结构体是调用 KiFilterFiberContext 的各种方法中,由 ExpLicenseWatchInitWorker 调用时传递的结构体。

参数五:表示是否需要重新计算 NT 内核函数校验的 Boolean 型值。

1.3 通知回调

2 PG_KiInitializePatchGuardContext

根据 1.3 的分析,sub_140A1AEE4 就是函数 PG_KiInitializePatchGuardContext。如下图在函数窗口将函数大小升序排序后,可以看到该函数是最大的一个,位于 INIT 区段中。

10.png

默认情况下对该函数 F5 反汇编会提示该函数太大无法进行反汇编(0x1AAEF = 106KB > 64KB),只需要修改 IDA 配置文件即可:

1
2
3
// 修改文件 \cfg\hexrays.cfg

MAX_FUNCSIZE = 1024 // Functions over 64K are not decompiled, Defult:64

在该函数中,多次使用了全局变量 KdDebuggerNotPresent 在关中断下进行反调试。

  • KdDebuggerNotPresent != 0,内核调试器不存在。
  • KdDebuggerNotPresent == 0,存在内核调试器。
1
2
3
4
5
6
7
_disable();
v5 = 0;
if ( !(_BYTE)KdDebuggerNotPresent )
{
while ( 1 );
}
_enable();

2.1 FUNCTIONEXTENTLIST 资源处理

一、参数二,初始化方法的判断与设置。

  • KI_FILTER_FIBER_PARAM != NULL,有参数,只能使用方法 0,3,5。
  • KI_FILTER_FIBER_PARAM == NULL && (a2 != 3 | a2 != 5),无参数,只能使用方法 0,1,2,4,7。
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
  // KI_FILTER_FIBER_PARAM != NULL
// 只能使用方法0,3,5
v6 = 41;
if ( a4 )
{
if ( a2 > 5 || !_bittest(&v6, a2) ) // 有参数,a2 = 1,2,4,7将转化为0
a2 = 0;
v7 = a5 | 4;
}
else
{
if ( (a2 - 3) & 0xFFFFFFFD ) //0xD: 1101, a2 - 3 = 2
{
v4369 = a5;
goto LABEL_12; // KI_FILTER_FIBER_PARAM == NULL && (a2 != 3 | a2 != 5)
} // 无参数,a2 = 0,1,2,4,7有效
v7 = a5;
a2 = 0;
}
v4369 = v7;
LABEL_12:
v8 = __2b; // 全局变量 cs:$$2b
v9 = 0;
v10 = 0;
if ( !_bittest(&v8, a2) )
v10 = a2;

二、疑似将 ntoskrnl.exe 模块前 24 字节保存至全局变量

该部分需要动态调试确认拷贝的内容。

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
// 将 __2a 前 24 字节拷贝至全局变量 v66、v4920_NtoskrnlBase
// __2a 疑似 Ntoskrnl 模块基址
if ( __2a )
{
v64 = 24;
v65 = &__2a;
v66 = &v4920_NtoskrnlBase;
v67 = 3i64;
do
{
v64 -= 8;
*v66 = *v65;
++v65;
++v66;
--v67;
}
while ( v67 );
for ( ; v64; --v64 ) // 不会被执行
{
v68 = *(_BYTE *)v65;
v65 = (__int64 *)((char *)v65 + 1);
*(_BYTE *)v66 = v68;
v66 = (__int64 *)((char *)v66 + 1);
}
goto LABEL_126;
}
v4920_NtoskrnlBase = 5368709120i64; // 0x00000001`40000000, v4920_NtoskrnlBase = g_Ntoskrnl
v13_ExAlloc_BugCheckParameter2_+_4 = 0i64;

三、从 ntoskrl.exe 中获取 FUNCTIONEXTENTLIST 已压缩资源

  1. 调用 LdrResFindResource 函数从 ntoskrl.exe 获取名称为 FUNCTIONEXTENTLIST 的资源。根据名字应该是函数导出列表列表资源,在后面看了一看到,该资源与 IMAGE_DIRECTORY_ENTRY_EXCEPTION 相关。参考 FindResourceExW 该函数的定义如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    NTSTATUS LdrResFindResource(
    IN PVOID ImageBase, // 目标资源所属模块基址,NULL表示当前模块
    IN ULONG ResourceType, // 资源类型
    IN wchar_t* ResourceName, // 资源名称
    IN ULONG Unknown,
    OUT PVOID ResourceBuffer, // 保存获取到的资源缓冲区
    OUT PULONG ResourceSize, // 获取到的资源大小(字节)
    OPTIONAL ULONG64 Unknown,
    OPTIONAL ULONG64 Unknown,
    OPTIONAL ULONG Unknown,
    );

    ResourceType = 10,对应的 类型 为 RT_RCDATA(Application-defined resource (raw data))。

    根据下面的分析,可以知道获取到的资源是 压缩 的,后面还要对其进行解压。

  2. 如果 RtlImageNtHeader(NtoskrlBase) 存在,则会调用 RtlImageDirectoryEntryToData 函数得到 IMAGE_DIRECTORY_ENTRY_EXCEPTION(3) 异常资源表的数据。该函数返回指定镜像文件中获取到的数据目录表的基址。(参考 XP 源码)

    1
    2
    3
    4
    5
    6
    PVOID NTAPI RtlImageDirectoryEntryToData (
    IN PVOID Base, // 文件的基地址
    IN BOOLEAN MappedAsImage, // TRUE-Image 文件,FALSE-Maping的数据文件
    IN USHORT DirectoryEntry, // 数据目录表的索引
    OUT PULONG Size // 返回的目标数据目录表的大小(字节)
    )

    数据目录表索引如下,参考 A dive into the PE file format - PE file structure - Part 4: Data Directories, Section Headers and Sections :

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    #define IMAGE_DIRECTORY_ENTRY_EXPORT          0   // Export Directory
    #define IMAGE_DIRECTORY_ENTRY_IMPORT 1 // Import Directory
    #define IMAGE_DIRECTORY_ENTRY_RESOURCE 2 // Resource Directory
    #define IMAGE_DIRECTORY_ENTRY_EXCEPTION 3 // Exception Directory
    #define IMAGE_DIRECTORY_ENTRY_SECURITY 4 // Security Directory
    #define IMAGE_DIRECTORY_ENTRY_BASERELOC 5 // Base Relocation Table
    #define IMAGE_DIRECTORY_ENTRY_DEBUG 6 // Debug Directory
    // IMAGE_DIRECTORY_ENTRY_COPYRIGHT 7 // (X86 usage)
    #define IMAGE_DIRECTORY_ENTRY_ARCHITECTURE 7 // Architecture Specific Data
    #define IMAGE_DIRECTORY_ENTRY_GLOBALPTR 8 // RVA of GP
    #define IMAGE_DIRECTORY_ENTRY_TLS 9 // TLS Directory
    #define IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG 10 // Load Configuration Directory
    #define IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT 11 // Bound Import Directory in headers
    #define IMAGE_DIRECTORY_ENTRY_IAT 12 // Import Address Table
    #define IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT 13 // Delay Load Import Descriptors
    #define IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR 14 // COM Runtime descriptor

四、根据压缩数据的 ExceptionDirectory 签名执行不同操作 ——签名 == “EXTC”

  1. 压缩数据的前 4 字节表示 ExceptionDirectory 签名。

    1
    if ( *(_DWORD *)OUT_Compress_ResourceBuffer == 1163416643 ) // Signature —— “EXTC”

    压缩数据的第二个 DWORD 表示压缩数据的完整大小。

    1
    v23 = *(unsigned int *)(OUT_Compress_ResourceBuffer + 4); // 压缩异常数据目录资源表+4 = 大小
  2. 使用函数 RtlGetCompressionWorkSpaceSize 获取压缩、解压时应该使用的空间大小。方便后面使用 RtlDecompressBufferEx 对获取的资源进行解压。函数功能参见《压缩技术-windows压缩API 》。

    1
    2
    3
    4
    5
    NT_RTL_COMPRESS_API NTSTATUS RtlGetCompressionWorkSpaceSize(
    [in] USHORT CompressionFormatAndEngine, // Bitmask指定压缩格式和引擎类型。
    [out] PULONG CompressBufferWorkSpaceSize, // 此值用于确定RtlCompressBuffer的WorkSpace缓冲区的正确大小。
    [out] PULONG CompressFragmentWorkSpaceSize // 此值用于确定RtlDecompressFragment的WorkSpace缓冲区的正确大小
    );

该部分代码如下:

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
// 从 ntoskrl.exe 中获取如下名称的资源
// 然后将获取的资源保存至参数五中。资源大小保存至参数六(字节)
if ( (signed int)LdrResFindResource(
5368709120i64, // 0x00000001`40000000
10i64,
(__int64)L"FUNCTIONEXTENTLIST", // IN_ResourceName,是压缩数据名称
0i64,
(__int64)&OUT_Compress_ResourceBuffer, // OUT_Compress_ResourceBuffer
(__int64)&OUT_Compress_ResourceSize, // OUT_Compress_ResourceSize
0i64,
0i64,
0) >= 0
&& (unsigned __int64)(OUT_Compress_ResourceSize - 8) <= 0xFFFFFFF7 ) // 注意这里的 0xFFFFFFF7 是无符号数(正数)
{
LODWORD(v4374) = OUT_Compress_ResourceSize;

// 返回指向 Ntoskrl.exe 文件的 NtHeader
if ( RtlImageNtHeader(5368709120i64) )
{
LOBYTE(v14) = 1;

// Get ExceptionDirectory, V15 指向 VA
// https://bbs.pediy.com/thread-36436.htm
v15_pExceptionDirectory = RtlImageDirectoryEntryToData(5368709120i64, v14, 3i64,
&v4407_ExceptionDirectory_Size);
if ( v15 )
{
v16 = 0i64;
v17 = -1073741823; // 0xC0000001
if ( *(_DWORD *)OUT_Compress_ResourceBuffer == 1163416643 ) // Signature —— “EXTC”
{
// NumberOfBytes = RtlCompressBuffer 工作缓冲区的大小
// v4471 = RtlDecompressFragment 工作缓冲区的大小
if ( (signed int)RtlGetCompressionWorkSpaceSize(4i64, &NumberOfBytes, &v4471) < 0 )
goto LABEL_34;
v23_Compress_ResourceSize = *(unsigned int *)(OUT_Compress_ResourceBuffer + 4); // 压缩异常数据目录资源表+4 = 大小
if ( (unsigned int)v23_Compress_ResourceSize < 8 )
{
HIDWORD(NumberOfBytes) = -805294751;
KeBugCheckEx(__ROR4__(-805306349, 92), 0xEui64, OUT_Compress_ResourceBuffer, 0x140000000ui64, (unsigned int)v4374);
}
v24 = __rdtsc();
v25 = (__ROR8__(v24, 3) ^ v24) * (unsigned __int128)0x7010008004002001ui64;
v4626 = *((_QWORD *)&v25 + 1);
v26 = (v25 ^ *((_QWORD *)&v25 + 1))
- 11
* ((unsigned __int64)(((unsigned __int64)v25 ^ *((_QWORD *)&v25 + 1))
* (unsigned __int128)0x2E8BA2E8BA2E8BA3ui64 >> 64) >> 1);
  1. 使用算法生成 ExAllocatePoolWithTag 的 Tag 参数申请内存

    下面会调用 ExAllocatePoolWithTag 函数 2 次,都是申请的非分页内存不可执行内存 NonPagedPoolNx。

    1. 第一次。从后面分析中可以看到,这块内存 v16 并没有使用到,应该算是一个Bug了。

      • 内存类型:NonPagedPoolNx
      • 内存大小:RtlCompressBuffer 函数的 WorkSpace
      • 内存标签:v29
      1
      2
      v16 = (unsigned int *)ExAllocatePoolWithTag(NonPagedPoolNx, 
      (unsigned int)Compress_NumberOfBytes, v29);
    2. 第二次。用来保存 FUNCTIONEXTENTLIST 资源解压后的数据。

      • 内存类型:NonPagedPoolNx
      • 内存大小:FUNCTIONEXTENTLIST 资源的大小
      • 内存标签:v40
      1
      2
      v13_1ExAlloc_Compress_Point_2DecompressBuffer = (unsigned int *)ExAllocatePoolWithTag(NonPagedPoolNx, 
      v23_Compress_ResourceSize, v40);
  2. 解压上面获取的 FUNCTIONEXTENTLIST 资源

    注意:是从 FUNCTIONEXTENTLIST 资源的第 8 字节开始解压。

    调用函数 RtlDecompressBufferEx,从 FUNCTIONEXTENTLIST 资源的第 8 字节开始解压。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    NT_RTL_COMPRESS_API NTSTATUS RtlDecompressBufferEx(
    [in] USHORT CompressionFormat, // 指定压缩格式的位掩码
    [out] PUCHAR UncompressedBuffer, // 接收解压后数据的缓冲区
    [in] ULONG UncompressedBufferSize, // 参数二缓冲区的大小
    [in] PUCHAR CompressedBuffer, // 指向压缩(要解压)数据的指针
    [in] ULONG CompressedBufferSize, // 压缩(要解压)数据的大小
    [out] PULONG FinalUncompressedSize, // 实际解压数据缓的大小
    [in] PVOID WorkSpace // RtlDecompressBufferEx 工作区大小,从 RtlGetCompressionWorkSpaceSize 获取
    );

    解压过程如下:

    1
    2
    v47 = RtlDecompressBufferEx(4, (_DWORD)v13_1ExAllocCompressBuffer_2DecompressBuffer,
    (unsigned int)v23_Compress_ResourceSize, OUT_Compress_ResourceBuffer + 8);

所以在签名 == “EXTC”,可以得知 压缩的 FUNCTIONEXTENTLIST 结构为:

1
2
3
4
5
struct PG_COMPRESSED_DATA{
ULONG Header;
ULONG DecompressSize;
BYTE CompressedData[1];
}
  1. 对 ExceptionDirectory 进行累加后对比。

    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
    // 开始对 ExceptionDirectory 进行累加,每次操作 4 字节
    // 将异常数据目录表内容循环地 4 字节相加,最后的和为 v61

    // v4407_ExceptionDirectory_Size 为 ExceptionDirectory 大小
    // v15_pExceptionDirectory 为指向 ExceptionDirectory 的虚拟地址

    LABEL_111:
    v60 = 0;
    v61 = v15_pExceptionDirectory - 0x40000
    if ( v4407_ExceptionDirectory_Size )
    {
    do
    {
    v62 = v60; // 0
    v60 += 4;
    v61 += *(_DWORD *)(v15_pExceptionDirectory + 4 * (v62 >> 2));
    }
    while ( v60 < v4407_ExceptionDirectory_Size );
    }

    // v13_1ExAllocCompressBuffer_2DecompressBuffer 保存资源 L"FUNCTIONEXTENTLIST" 第 8 字节开始进行解压的数据
    // 保存的值就是未解压的L"FUNCTIONEXTENTLIST"大小
    v63 = v13_1ExAllocCompressBuffer_2DecompressBuffer[1];

    // 确保该值与解压的资源数据相同
    if ( v61 != (_DWORD)v63 )
    {
    v4236 = -805294751;
    KeBugCheckEx(__ROR4__(-805306349, 92), 0x10ui64, (ULONG_PTR)v13_1ExAllocCompressBuffer_2DecompressBuffer, 0x140000000ui64, v63 ^ v61);
    }
    goto LABEL_117;

    可所以在签名 == “EXTC”,,FUNCTIONEXTENTLIST 资源解压后并不是 ExceptionDirectory 的明文数据。

所以在签名 == “EXTC”,得到 解压的 FUNCTIONEXTENTLIST 结构为:

1
2
3
4
5
struct PG_DECOMPRESS_DATA{
ULONG IntegrityCheckLength;
ULONG InitIntegrityCheckData;
BYTE UnknownData[sizeof(UncompressedData-8)];
}

五、根据压缩数据的 ExceptionDirectory 签名执行不同操作 ——签名 == “EXTL”

  1. 根据计算得到的参数 v53 = Tag 调用 ExAllocatePoolWithTag 申请非分页内存,申请的大小是 sizeof(FUNCTIONEXTENTLIST) - 4。然后从 FUNCTIONEXTENTLIST 资源的第 4 四字节开始的地方拷贝至申请的非分页内存中。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    v59 = (unsigned int *)ExAllocatePoolWithTag(NonPagedPoolNx, 
    (unsigned int)v4374_OUT_Compress_ResourceSize - 4, v53);

    v13_1ExAllocCompressBuffer_2DecompressBuffer = v59;
    if ( v59 )
    {
    // 将解压前的资源 L"FUNCTIONEXTENTLIST" 第4字节开始拷贝至非分页内存
    memmove(v59, (const void *)(OUT_Compress_ResourceBuffer + 4),
    (unsigned int)v4374_OUT_Compress_ResourceSize - 4);
    goto LABEL_111;
    }

    这与签名 == “EXTC”是明显不同的:

    • 签名 == “EXTC”,从 FUNCTIONEXTENTLIST + 8 开始处理。 需要调用 RtlDecompressBufferEx 进行解压。
    • 签名 == “EXTL”,从 FUNCTIONEXTENTLIST + 4 开始处理。 无需调用 RtlDecompressBufferEx 进行解压。
  2. 然后重复上面的步骤 5 ——对 ExceptionDirectory 进行累加后对比。

六、总结

在函数 PG_KiInitializePatchGuardContext 一开头,也就是开始初始化 PatchGuard 时。如果全局变量 $$2a <= 0,则会对 ntoskrl.exe 模块中名称为 FUNCTIONEXTENTLIST 的资源进行处理后,与该模块的异常资源表进行比较,如果不相等则直接会蓝屏(KeBugCheckEx)。我们知道异常资源表中存放的是 RUNTIME_FUNCTION,所以要在系统启动前修改 ntoskrl.exe 模块中的函数,就要了解本节中细节,了解 FUNCTIONEXTENTLIST 资源对比处理过程,然后进行绕过。

FUNCTIONEXTENTLIST 资源的处理过程如下:

11

2.2 INITKDBG 节的处理

  1. 获取 FsRtlUninitializeSmallMcb 函数所在的模块、区段。该模块为 ntoskrnl.exe。

    调用 RtlPcToFileHeader 函数,得到目标函数所在的模块基址。

    1
    2
    3
    4
    PVOID RtlPcToFileHeader(
    IN PVOID PcValue, // 目标函数的虚拟地址(VA)
    OUT PVOID *BaseOfImage // 目标函数所在模块的基址(VA)
    )
  2. 调用 RtlImageNtHeader 函数获取 ntoskrnl.exe 的 pNtHeader。

    1
    2
    3
    PIMAGE_NT_HEADERS RtlImageNtHeader (
    IN PVOID Base // 模块基址(VA)
    )

    这里的返回值是 VA 值。

  3. 调用 RtlSectionTableFromVirtualAddress 函数获取目标函数所在的区段 VA 地址。

    1
    2
    3
    4
    5
    PIMAGE_SECTION_HEADER RtlSectionTableFromVirtualAddress (
    IN PIMAGE_NT_HEADERS NtHeaders, // VA
    IN PVOID Base, // VA
    IN ULONG Address // 目标函数地址相对于基址的 RVA
    )

    返回值是 VA。目标函数 FsRtlUninitializeSmallMcb 位于 INITKDBG 区段。

  4. 获取如下几个函数相对于 INITKDBG 区段的 RVA。

    1
    2
    3
    sub_140A0DD10
    sub_140A0E830
    RtlLookupFunctionEntryEx
  5. 设置一个全局 BOOL 值 v4202。

    如果函数 MmStrongCodeGuaranteesEnforced() > 0、sub_140A360D4() > 0 ,则 v4202 = TRUE。

    如果函数 PG_KiInitializePatchGuardContext 的参数二(调度方法)为 7,则 v4202 = TRUE。

  6. 疑似检查 EPT Hook。

    1
    if ( (unsigned int)MmStrongCodeGuaranteesEnforced() && (unsigned int)sub_140A360D4() )

    MmStrongCodeGuaranteesEnforced 函数返回 MiFlags[2] & 1(Bit 15)。

    1
    2
    3
    4
    5
    6
    BYTE MiFlags[4];

    __int64 MmStrongCodeGuaranteesEnforced()
    {
    return BYTE2(MiFlags) & 1;
    }

    sub_140A360D4 函数疑似检查 EPT Hook。参考《Windows PatchGuard KiErrata671Present》。

    • 如果 KiErrata671Presen 函数被成功修改则返回 FALSE。
    • 如果修改失败则返回 TRUE。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    __int64 sub_140A360D4()
    {
    unsigned __int64 v0; // rbx
    char v1; // si
    unsigned int v2; // ecx
    unsigned __int8 v4; // [rsp+50h] [rbp+8h]

    v4 = KeGetCurrentIrql();
    __writecr8(0xFui64);
    v0 = __readcr0();
    __writecr0(v0 & 0xFFFFFFFFFFFEFFFFui64);
    v1 = *((_BYTE *)KiErrata671Present + 2);
    *((_BYTE *)KiErrata671Present + 2) = 0xC3; // 0xC3 == ret
    v2 = KiErrata671Present();
    if ( *((_BYTE *)KiErrata671Present + 2) != v1 )
    *((_BYTE *)KiErrata671Present + 2) = v1;
    __writecr0(v0);
    __writecr8(v4);
    return v2;
    }

2.3 关键函数的检索

如下代码,会将 off_140C005D8 地址保存到数组 qword_140C00210[0] 第一个元素中。

1
qword_140C00210[0] = (__int64)off_140C005D8; 

qword_140C00210 数组如下:

12.png

可以看到 qword_140C00210[0] 是空的,在初始化 PatchGuard 时进行填充。而填充的地址指向 xHalHaltSystem 函数。

1
.data:0000000140C005D8 off_140C005D8   dq offset xHalHaltSystem

可以看到这 15 个函数都是非常关键重要的函数,我将这个数组命名为 CriticalRoutines:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
PVOID CriticalRoutines[15] = {
xHaliHaltSystem,
KeBugCheckEx,
KeBugCheck2,
KiBugCheckDebugBreak,
KiDebugTrapOrFault,
DbgBreakPointWithStatus,
RtlCaptureContext,
KeQueryCurrentStackInformation,
KiSaveProcessorControlState,
memmove,
IoSaveBugCheckProgress,
KeIsEmptyAffinityEx,
VfNotifyVerifierOfEvent,
_guard_check_icall,
KeGuardDispatchICall
};

调用 RtlLookupFunctionTable 函数循环查找 CriticalRoutines 数组 15 个函数所在模块的 KLDR_DATA_TABLE_ENTRY.ExceptionTable 的地址,实际上获得的结果是同一个,因为他们都在 ntoskrnl.exe 模块中。该函数功能:查找参数一函数所在模块的 ExceptionTable 基址,也是异常资源表中第一个 RUNTIME_FUNCTION 结构地址。

1
2
3
4
5
6
7
8
9
10
11
PRUNTIME_FUNCTION RtlLookupFunctionTable(
IN PVOID ControlPC, // 目标函数地址
OUT PVOID* ImageBase, // 接收目标函数所在模块基址
OUT PULONG SizeOfTable // sizeof(RUNTIME_FUNCTION)
);

NTSYSAPI PRUNTIME_FUNCTION RtlLookupFunctionEntry(
[in] DWORD64 ControlPc, // 目标函数地址
[out] PDWORD64 ImageBase, // 接收目标函数所在模块基址
[out] PUNWIND_HISTORY_TABLE HistoryTable
);

然后调用 RtlLookupFunctionEntry 获取目标函数所在的 RUNTIME_FUNCTION 的地址。

  • RtlLookupFunctionTable:获取目标函数所在模块第一个 RUNTIME_FUNCTION 的地址,即 ExceptionTable 表地址。
  • RtlLookupFunctionEntry:获取目标函数所在 RUNTIME_FUNCTION 的地址(并不一定是第一个)。

Windows 10 前,PEB 上的模块链表和 pDriver->DriverSection 都是 _LDR_DATA_TABLE_ENTRY 结构。
Windows 10 开始,PEB 上的模块链表是 _LDR_DATA_TABLE_ENTRY,内核 DriverSection 的模块链是 _KLDR_DATA_TABLE_ENTRY 结构。

下面就是进行关键函数检索,前提是已知这些函数都位于 ntoskrnl.exe 模块中。

  1. 先获取关键函数 CriticalRoutines[i] 所在模块的异常资源表基地址(使用RtlLookupFunctionTable函数)。
  2. 然后使用 CriticalRoutines[i] 函数地址,循环地与异常资源表 ExceptionTable 中每一个 RUNTIME_FUNCTION 函数地址比较。
  3. 如果 CriticalRoutines[i] 函数不在异常资源表中,就跳转到 LABEL_184,不会保存函数所在页面的 PTE(下面2.4节内容)。

2.4 保存关键函数的 PTE 页面

步骤 2.3 中的关键函数验证通过后才会执行本节内容,否则直接跳转至 LABEL_184。

  1. 在一个大循环里面,首先找到 CriticalRoutines[i] 函数所在页面基地址,有可能一个函数的大小超过 1 个页面,所以要找到该函数所在页面所有的页面基地址,我将页面基地址命名为 CriticalRoutinesPageBaseAddress。

    1
    2
    3
    4
    5
    6
    7
    8
    //////////////////////////////////////////////////////////
    // 获取目标函数所在的页面基址
    // 目标函数可能很大,超过一个页面,所以需要计算目标函数的终止地址 v4207
    // 然后在这个函数包含的页面中,进行下面的 do-while 运算
    //////////////////////////////////////////////////////////
    // v99:Ntos_ImageBase
    v112_PageBasedAddress = (v99 + v110) & 0xFFFFFFFFFFFFF000ui64; // 对齐一个页面
    v4207 = v99 + v110 + v111;
  2. 获取页面的 4 级页表虚拟地址(实际上是函数所在所有页面,外面还有一层循环)。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    // 获取 PTE Base
    v115_PteAddress = MmPteBase + ((v112_PageBasedAddress >> 9) & 0x7FFFFFFFF8i64);
    do
    {
    // X 的 PTE 为 PteAddress,PteAddress 的 PTE = X 的 PdeAddress。
    // v113_4levelPagetableAddress[0] = PteAddress
    // v113_4levelPagetableAddress[1] = PdeAddress
    // v113_4levelPagetableAddress[2] = PdpteAddress
    // v113_4levelPagetableAddress[3] = Plm4eAddress
    *v113_4levelPagetableAddress = v115_PteAddress ;
    ++v113_4levelPagetableAddress;
    v115_PteAddress = MmPteBase + ((v115_PteAddress >> 9) & 0x7FFFFFFFF8i64);
    --v114;
    }while ( v114 );
  3. 如果 4 级页表的 PAT/PS 启用,则将该级页表置 0。

  4. 调用 ExAllocatePoolWithTag 函数分配 NonPagedPoolNx 类型内存,将该函数的 4 级页表保存起来。

2.5 Allocate PgContext

本节主要是申请一块非分页内存,这块非分页内存就是 PGContext。

1
194_TempPgContext = ExAllocatePoolWithTag(0, v190 + v177, v183);
  1. 内存大小:v190 + v177。

    v190:可以看到 v190 最大为 0x7FF。

    1
    2
    3
    4
    // v190
    v189 = __rdtsc();
    v4495 = (__ROR8__(v189, 3) ^ v189) * (unsigned __int128)0x7010008004002001ui64 >> 64;
    v190 = (v4495 ^ 0x4002001 * (__ROR8__(v189, 3) ^ v189)) & 0x7FF;

    v177:最大为 sizeof(INITKDBG) + 0xAA8 + 0x80000 + 0x7FF + Max(0x10*16)【4-levelpagetable】 + 16 个 CriticalRoutines[i] 函数大小。

  2. 标签 v183 通过计算获得,此处忽略。

根据代码分析有以下结论:

  1. 申请 v190 + v177 大小的 PGContext。
  2. 在 PGContext 首尾使用随机值进行填充,填充的字节最大为 v190 = 0x7FF 字节。
  3. PGContext 中间有 v177 大小的填充数据。包含:
    • INITKDBG 大小
    • 0xAA8
    • 16 个 CriticalRoutines[i] 函数大小、及这 16 个关键函数对应的 4-level 页表(0x100)。
    • 0x80000
    • 0x7FF(v190最大可达)

13.png

所以 PGContext 结构大致如下:

14.png

紧接着,将 v175 + 0x80000 大小的空间清 0,并开始对 PGContext 进行填充。

1
2
3
v212_ContextData = (char *)194_TempPgContext + (unsigned int)v193_sizeofRandomValue_1;
...
memset(v212_ContextData, 0, (unsigned int)(v175 + 0x80000));

该节描述代码如下:

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
/*************************************************************************************/
// 填充 PGContext 首尾
v189 = __rdtsc();
v4495 = (__ROR8__(v189, 3) ^ v189) * (unsigned __int128)0x7010008004002001ui64 >> 64;
v190 = (v4495 ^ 67117057 * (__ROR8__(v189, 3) ^ v189)) & 0x7FF;
v191 = __rdtsc();
v192 = (__ROR8__(v191, 3) ^ v191) * (unsigned __int128)0x7010008004002001ui64;
v4496 = *((_QWORD *)&v192 + 1);
v193_sizeofRandomValue_1 = (*((_QWORD *)&v192 + 1) ^ (unsigned __int64)v192) % (unsigned int)(v190 + 1);
194_TempPgContext = ExAllocatePoolWithTag(0, v190 + v177, v183);
if ( !194_TempPgContext )
return 0;
v195 = v193_sizeofRandomValue_1;
v196 = 194_TempPgContext;
if ( (unsigned int)v193_sizeofRandomValue_1 >= 8 )
{
v197 = (unsigned __int64)(unsigned int)v193_sizeofRandomValue_1 >> 3;
do
{
v198 = __rdtsc();
v195 -= 8;
v199 = (__ROR8__(v198, 3) ^ v198) * (unsigned __int128)0x7010008004002001ui64;
v4497 = *((_QWORD *)&v199 + 1);
*v196 = v199 ^ *((_QWORD *)&v199 + 1);
++v196;
--v197;
}
while ( v197 );
}
if ( v195 )
{
v200 = __rdtsc();
v201 = (__ROR8__(v200, 3) ^ v200) * (unsigned __int128)0x7010008004002001ui64;
v4498 = *((_QWORD *)&v201 + 1);
v202 = v201 ^ *((_QWORD *)&v201 + 1);
do
{
*(_BYTE *)v196 = v202;
v196 = (_QWORD *)((char *)v196 + 1);
v202 >>= 8;
--v195;
}
while ( v195 );
}
v203 = (unsigned int)v193_sizeofRandomValue_1;
v204_sizeofRandomValue_2 = v190 - v193_sizeofRandomValue_1;
v205_RandomValue2_Start = (_QWORD *)((char *)194_TempPgContext +
(unsigned int)v193_sizeofRandomValue_1 + (unsigned __int64)v177);
if ( v204_sizeofRandomValue_2 >= 8 )
{
v206 = (unsigned __int64)v204_sizeofRandomValue_2 >> 3;
do
{
v207 = __rdtsc();
v204_sizeofRandomValue_2 -= 8;
v208 = (__ROR8__(v207, 3) ^ v207) * (unsigned __int128)0x7010008004002001ui64;
v4499 = *((_QWORD *)&v208 + 1);
*v205_RandomValue2_Start = v208 ^ *((_QWORD *)&v208 + 1);
++v205_RandomValue2_Start;
--v206;
}
while ( v206 );
}
if ( v204_sizeofRandomValue_2 )
{
v209 = __rdtsc();
v210 = (__ROR8__(v209, 3) ^ v209) * (unsigned __int128)0x7010008004002001ui64;
v4500 = *((_QWORD *)&v210 + 1);
v211 = v210 ^ *((_QWORD *)&v210 + 1);
do
{
*(_BYTE *)v205_RandomValue2_Start = v211;
v205_RandomValue2_Start = (_QWORD *)((char *)v205_RandomValue2_Start + 1);
v211 >>= 8;
--v204_sizeofRandomValue_2;
}
while ( v204_sizeofRandomValue_2 );
}
v212_ContextData = (char *)194_TempPgContext + (unsigned int)v193_sizeofRandomValue_1;
v4501 = 194_TempPgContext;
v4199 = (unsigned __int64)194_TempPgContext + v203;
if ( !(_QWORD *)((char *)194_TempPgContext + v203) )
return 0;

// v173 = v4201_INITKDBG_VirtualSize, v174 = 0xAA8
// v175 = v173 + v174 == v4201_INITKDBG_VirtualSize + 0xAA8
v213 = v175 + 0x80000;
v4202_BOOL_GlobleFlag = v175 + 0x80000;
memset(v212_ContextData, 0, (unsigned int)(v175 + 0x80000));
/*************************************************************************************/

3 PGContext 数据填充

3.1 CmpAppendDllSection

先观察 CmpAppendDllSection 函数特征:

  1. 从 IDA 反汇编结果来看函数 CmpAppendDllSection 大小为 0xC8。

    15.png

    0x190 + 0xC8 = 0x258,可以看到函数从 0x0000000140A36246 到 0x0000000140A36258 都是填充数据。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    INIT:0000000140A36190 CmpAppendDllSection proc near          
    INIT:0000000140A36190 db 2Eh
    INIT:0000000140A36190 2E 48 31 11 xor [rcx], rdx
    ...
    INIT:0000000140A36241 BA 01 00 00 00 mov edx, 1
    INIT:0000000140A36246 41 FF E0 jmp r8
    INIT:0000000140A36246 ; -------------------------------------------------------------------
    INIT:0000000140A36249 90 90 90 90 90 90 90 90+ db 0Fh dup(90h)
    INIT:0000000140A36249 90 90 90 90 90 90 90 CmpAppendDllSection endp
    INIT:0000000140A36258 CC CC CC CC CC CC CC CC+ byte_140A36258 db 0Eh dup(0CCh)
  2. 函数首字节 0x2E 像是一个 Header Flag,并不参与函数的实际功能。彷佛是表明此处是 CmpAppendDllSection 函数。

紧接着将该函数拷贝至 PGContext v212 开始的位置,共复制了 0xC4 字节。

注意:_OWORD 表示 16 Bytes。

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
_enable();	// cli
*((_DWORD *)v212_ContextData + 505) = v175;
*((_DWORD *)v212_ContextData + 647) = v213;
*((_QWORD *)v212_ContextData + 249) = v4501;
v323 = (signed __int64)(v212_ContextData + 128);
*((_DWORD *)v212_ContextData + 585) = v4206;
*(_OWORD *)v212_ContextData = *(_OWORD *)CmpAppendDllSection;
*((_OWORD *)v212_ContextData + 1) = *((_OWORD *)CmpAppendDllSection + 1);
*((_OWORD *)v212_ContextData + 2) = *((_OWORD *)CmpAppendDllSection + 2);
*((_OWORD *)v212_ContextData + 3) = *((_OWORD *)CmpAppendDllSection + 3);
*((_OWORD *)v212_ContextData + 4) = *((_OWORD *)CmpAppendDllSection + 4);
*((_OWORD *)v212_ContextData + 5) = *((_OWORD *)CmpAppendDllSection + 5);
*((_OWORD *)v212_ContextData + 6) = *((_OWORD *)CmpAppendDllSection + 6); // 96~192, Total 112 bytes
*(_OWORD *)(v323 - 16) = *((_OWORD *)CmpAppendDllSection + 7);
*(_OWORD *)v323 = *((_OWORD *)CmpAppendDllSection + 8);
*(_OWORD *)(v323 + 16) = *((_OWORD *)CmpAppendDllSection + 9);
*(_OWORD *)(v323 + 32) = *((_OWORD *)CmpAppendDllSection + 10);
*(_OWORD *)(v323 + 48) = *((_OWORD *)CmpAppendDllSection + 11); // 176~192, Total 0xC0 bytes
*(_DWORD *)(v323 + 64) = *((_DWORD *)CmpAppendDllSection + 48); // 192~196, Total 0xC4 bytes
LODWORD(v323) = v4210; // v4210 = v174
*((_DWORD *)v212_ContextData + 508) = v4210 + v4191_ToINITKDBGSectionBase_RVA;
*((_DWORD *)v212_ContextData + 506) = v323 + v4190_sub_140A0DD10ToINITKDBGSectionBase_RVA;
*((_DWORD *)v212_ContextData + 507) = v323 + v4193_RtlLookupFunctionEntryEx_ToINITKDBGSectionBase_RVA;
*((_DWORD *)v212_ContextData + 509) = v323 + v4194_sub_140A0E830_ToINITKDBGSectionBase_RVA;
_disable(); // sti

注意:在反汇编代码中,这部分代码的赋值使用 movups 指令(SSE 指令),表示对 16 字节(128 Bytes)移动处理。

1
2
3
4
5
INIT:0000000140A1DA5B                 lea     rax, CmpAppendDllSection
INIT:0000000140A1DA62 movups xmm0, xmmword ptr [rax]
INIT:0000000140A1DA65 movups xmmword ptr [r14], xmm0
INIT:0000000140A1DA69 movups xmm1, xmmword ptr [rax+10h]
...

3.2 重要函数(API)地址备份

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
_enable();	// cli
*((_QWORD *)v212_ContextData + 29) = ExAcquireResourceSharedLite; // 232~240, Total 0xF0 bytes
*((_QWORD *)v212_ContextData + 30) = ExAcquireResourceExclusiveLite;
*((_QWORD *)v212_ContextData + 31) = ExAllocatePoolWithTag;
*((_QWORD *)v212_ContextData + 32) = ExFreePoolWithTag;
*((_QWORD *)v212_ContextData + 33) = ExMapHandleToPointer;
*((_QWORD *)v212_ContextData + 34) = ExQueueWorkItem;
*((_QWORD *)v212_ContextData + 35) = ExReleaseResourceLite;
*((_QWORD *)v212_ContextData + 36) = ExUnlockHandleTableEntry;
*((_QWORD *)v212_ContextData + 37) = ExAcquirePushLockExclusiveEx;
*((_QWORD *)v212_ContextData + 38) = ExReleasePushLockExclusiveEx;
*((_QWORD *)v212_ContextData + 39) = ExAcquirePushLockSharedEx;
*((_QWORD *)v212_ContextData + 40) = ExReleasePushLockSharedEx;
*((_QWORD *)v212_ContextData + 41) = KeAcquireInStackQueuedSpinLockAtDpcLevel;
*((_QWORD *)v212_ContextData + 42) = ExAcquireSpinLockSharedAtDpcLevel;
*((_QWORD *)v212_ContextData + 43) = KeBugCheckEx;
*((_QWORD *)v212_ContextData + 44) = KeDelayExecutionThread;
*((_QWORD *)v212_ContextData + 45) = KeEnterCriticalRegionThread;
*((_QWORD *)v212_ContextData + 46) = KeLeaveCriticalRegion;
*((_QWORD *)v212_ContextData + 47) = KeEnterGuardedRegion;
*((_QWORD *)v212_ContextData + 48) = KeLeaveGuardedRegion;
*((_QWORD *)v212_ContextData + 49) = KeReleaseInStackQueuedSpinLockFromDpcLevel;
*((_QWORD *)v212_ContextData + 50) = ExReleaseSpinLockSharedFromDpcLevel;
*((_QWORD *)v212_ContextData + 51) = KeRevertToUserGroupAffinityThread;
*((_QWORD *)v212_ContextData + 52) = KeProcessorGroupAffinity;
*((_QWORD *)v212_ContextData + 53) = KeInitializeEnumerationContext;
*((_QWORD *)v212_ContextData + 54) = KeEnumerateNextProcessor;
*((_QWORD *)v212_ContextData + 55) = KeCountSetBitsAffinityEx;
*((_QWORD *)v212_ContextData + 56) = KeQueryAffinityProcess;
*((_QWORD *)v212_ContextData + 57) = KeQueryAffinityThread;
*((_QWORD *)v212_ContextData + 58) = KeSetSystemGroupAffinityThread;
*((_QWORD *)v212_ContextData + 59) = KeSetCoalescableTimer;
*((_QWORD *)v212_ContextData + 63) = RtlImageNtHeader;
*((_QWORD *)v212_ContextData + 66) = RtlSectionTableFromVirtualAddress;
*((_QWORD *)v212_ContextData + 64) = RtlLookupFunctionTableEx;
*((_QWORD *)v212_ContextData + 65) = RtlPcToFileHeader;
*((_QWORD *)v212_ContextData + 60) = HalPutDmaAdapter;
*((_QWORD *)v212_ContextData + 61) = &ObReferenceObjectByName;
*((_QWORD *)v212_ContextData + 62) = RtlImageDirectoryEntryToData;
*((_QWORD *)v212_ContextData + 67) = DbgPrint;
*((_QWORD *)v212_ContextData + 68) = MmAllocateIndependentPages;
*((_QWORD *)v212_ContextData + 69) = MmFreeIndependentPages;
*((_QWORD *)v212_ContextData + 70) = MmSetPageProtection;
*((_QWORD *)v212_ContextData + 76) = RtlLookupFunctionEntry;
*((_QWORD *)v212_ContextData + 77) = KeAcquireSpinLockRaiseToDpc;
*((_QWORD *)v212_ContextData + 78) = KeReleaseSpinLock;
*((_QWORD *)v212_ContextData + 79) = MmGetSessionById;
*((_QWORD *)v212_ContextData + 80) = MmGetNextSession;
*((_QWORD *)v212_ContextData + 81) = MmQuitNextSession;
*((_QWORD *)v212_ContextData + 82) = MmAttachSession;
*((_QWORD *)v212_ContextData + 83) = MmDetachSession;
*((_QWORD *)v212_ContextData + 84) = MmGetSessionIdEx;
*((_QWORD *)v212_ContextData + 85) = MmIsSessionAddress;
*((_QWORD *)v212_ContextData + 86) = MmIsAddressValid;
*((_QWORD *)v212_ContextData + 87) = MmSessionGetWin32Callouts;
*((_QWORD *)v212_ContextData + 88) = KeInsertQueueApc;
*((_QWORD *)v212_ContextData + 89) = KeWaitForSingleObject;
*((_QWORD *)v212_ContextData + 91) = ExReferenceCallBackBlock;
*((_QWORD *)v212_ContextData + 92) = ExGetCallBackBlockRoutine;
*((_QWORD *)v212_ContextData + 93) = ExDereferenceCallBackBlock;
*((_QWORD *)v212_ContextData + 94) = sub_1403DA4F0;
*((_QWORD *)v212_ContextData + 95) = PspEnumerateCallback;
*((_QWORD *)v212_ContextData + 96) = CmpEnumerateCallback;
*((_QWORD *)v212_ContextData + 97) = DbgEnumerateCallback;
*((_QWORD *)v212_ContextData + 98) = ExpEnumerateCallback;
*((_QWORD *)v212_ContextData + 99) = ExpGetNextCallback;
*((_QWORD *)v212_ContextData + 100) = xHalTimerWatchdogStop;
*((_QWORD *)v212_ContextData + 101) = KiSchedulerApcTerminate;
*((_QWORD *)v212_ContextData + 102) = KiSchedulerApc;
*((_QWORD *)v212_ContextData + 103) = xHalTimerWatchdogStop;
*((_QWORD *)v212_ContextData + 104) = sub_1403DB560;
*((_QWORD *)v212_ContextData + 105) = MmAllocatePagesForMdlEx;
*((_QWORD *)v212_ContextData + 106) = MmAllocateMappingAddress;
*((_QWORD *)v212_ContextData + 107) = MmMapLockedPagesWithReservedMapping;
*((_QWORD *)v212_ContextData + 108) = MmUnmapReservedMapping;
*((_QWORD *)v212_ContextData + 109) = sub_1403E7C30;
*((_QWORD *)v212_ContextData + 110) = sub_1403E7CA0;
*((_QWORD *)v212_ContextData + 111) = MmAcquireLoadLock;
*((_QWORD *)v212_ContextData + 112) = MmReleaseLoadLock;
*((_QWORD *)v212_ContextData + 113) = KeEnumerateQueueApc;
*((_QWORD *)v212_ContextData + 114) = KeIsApcRunningThread;
*((_QWORD *)v212_ContextData + 115) = sub_1403DB430;
v324 = (__int64)v5121;
*((_QWORD *)v212_ContextData + 116) = PsAcquireProcessExitSynchronization;
*((_QWORD *)v212_ContextData + 117) = ObDereferenceProcessHandleTable;
*((_QWORD *)v212_ContextData + 118) = PsGetNextProcess;
*((_QWORD *)v212_ContextData + 119) = PsQuitNextProcessThread;
*((_QWORD *)v212_ContextData + 120) = PsGetNextProcessEx;
*((_QWORD *)v212_ContextData + 121) = MmIsSessionLeaderProcess;
*((_QWORD *)v212_ContextData + 122) = PsInvokeWin32Callout;
*((_QWORD *)v212_ContextData + 123) = MmEnumerateAddressSpaceAndReferenceImages;
*((_QWORD *)v212_ContextData + 124) = PsGetProcessProtection;
*((_QWORD *)v212_ContextData + 125) = PsGetProcessSignatureLevel;
*((_QWORD *)v212_ContextData + 126) = PsGetProcessSectionBaseAddress;
*((_QWORD *)v212_ContextData + 127) = SeCompareSigningLevels;
*((_QWORD *)v212_ContextData + 133) = RtlIsMultiSessionSku;
*((_QWORD *)v212_ContextData + 134) = KiEnumerateCallback;
*((_QWORD *)v212_ContextData + 135) = KeStackAttachProcess;
*((_QWORD *)v212_ContextData + 136) = KeUnstackDetachProcess;
*((_QWORD *)v212_ContextData + 137) = KeIpiGenericCall;
*((_QWORD *)v212_ContextData + 138) = sub_1403E7A80;
*((_QWORD *)v212_ContextData + 139) = MmGetPhysicalAddress;
*((_QWORD *)v212_ContextData + 140) = MmUnlockPages;
*((_QWORD *)v212_ContextData + 128) = KeComputeSha256;
*((_QWORD *)v212_ContextData + 129) = KeComputeParallelSha256;
*((_QWORD *)v212_ContextData + 130) = KeSetEvent;
*((_QWORD *)v212_ContextData + 141) = VslVerifyPage;
*((_QWORD *)v212_ContextData + 144) = PsLookupProcessByProcessId;
*((_QWORD *)v212_ContextData + 145) = PsGetProcessId;
*((_QWORD *)v212_ContextData + 146) = MmCheckProcessShadow;
*((_QWORD *)v212_ContextData + 147) = MmGetImageRetpolineCodePage;
*((_QWORD *)v212_ContextData + 300) = &qword_140C12EC0;
if ( v324 )
*((_QWORD *)v212_ContextData + 90) = *(_QWORD *)(v324 + 8);
*((_QWORD *)v212_ContextData + 131) = RtlpConvertFunctionEntry;
*((_QWORD *)v212_ContextData + 132) = RtlpLookupPrimaryFunctionEntry;
*((_QWORD *)v212_ContextData + 142) = KiGetInterruptObjectAddress;
_disable(); // sti
if ( !(_BYTE)KdDebuggerNotPresent ) // Anti Debug
{
while ( 1 )
;
}
_enable();
*((_DWORD *)v212_ContextData + 599) = v175;
_disable();
...
*((_DWORD *)v212_ContextData + 612) ^= (*((_DWORD *)v212_ContextData + 612) ^ 0x1C00) & 0x7C00;
*((_DWORD *)v212_ContextData + 596) = dword_140C12E80++;
*((_QWORD *)v212_ContextData + 256) = 0i64;
*((_QWORD *)v212_ContextData + 294) = VfExcludeSections;
*((_QWORD *)v212_ContextData + 295) = *(&VfExcludeSections + 1);
*((_QWORD *)v212_ContextData + 296) = off_140C0EFE0;
*((_QWORD *)v212_ContextData + 297) = *(&off_140C0EFE0 + 1);

3.3 全局变量及指针

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
_enable();
*((_DWORD *)v212_ContextData + 523) = 0x140000 / v5120;
*((_QWORD *)v212_ContextData + 152) = &qword_140C0F140;
*((_QWORD *)v212_ContextData + 153) = &qword_140C12EA8;
*((_QWORD *)v212_ContextData + 154) = &qword_140C12EB0;
*((_QWORD *)v212_ContextData + 155) = &qword_140C12EB8;
*((_QWORD *)v212_ContextData + 156) = PsInitialSystemProcess;
*((_QWORD *)v212_ContextData + 157) = KiWaitAlways;
*((_QWORD *)v212_ContextData + 158) = &KiEntropyTimingRoutine;
*((_QWORD *)v212_ContextData + 159) = &KiProcessListHead;
*((_QWORD *)v212_ContextData + 160) = &KiProcessListLock;
*((_QWORD *)v212_ContextData + 161) = ObpTypeObjectType;
*((_QWORD *)v212_ContextData + 162) = IoDriverObjectType;
*((_QWORD *)v212_ContextData + 163) = PsProcessType;
*((_QWORD *)v212_ContextData + 164) = &PsActiveProcessHead;
*((_QWORD *)v212_ContextData + 165) = &PsInvertedFunctionTable;
*((_QWORD *)v212_ContextData + 166) = &PsLoadedModuleList;
*((_QWORD *)v212_ContextData + 167) = &PsLoadedModuleResource;
*((_QWORD *)v212_ContextData + 168) = &PsLoadedModuleSpinLock;
*((_QWORD *)v212_ContextData + 169) = &PspActiveProcessLock;
*((_QWORD *)v212_ContextData + 170) = &PspCidTable;
*((_QWORD *)v212_ContextData + 171) = &ExpUuidLock;
*((_QWORD *)v212_ContextData + 172) = &AlpcpPortListLock;
*((_QWORD *)v212_ContextData + 173) = &KeServiceDescriptorTable;
*((_QWORD *)v212_ContextData + 174) = &KeServiceDescriptorTableShadow;
*((_QWORD *)v212_ContextData + 175) = &KeServiceDescriptorTableFilter;
*((_QWORD *)v212_ContextData + 176) = &VfThunksExtended;
*((_QWORD *)v212_ContextData + 177) = &PsWin32CallBack;
*((_QWORD *)v212_ContextData + 178) = &qword_140C12E88;
*((_QWORD *)v212_ContextData + 179) = &KiTableInformation;
*((_QWORD *)v212_ContextData + 180) = &HandleTableListHead;
*((_QWORD *)v212_ContextData + 181) = &HandleTableListLock;
*((_QWORD *)v212_ContextData + 182) = ObpKernelHandleTable;
*((_QWORD *)v212_ContextData + 183) = -9345848836096i64; // 0xFFFFF780000000C4
*((_QWORD *)v212_ContextData + 184) = KiWaitNever;
*((_QWORD *)v212_ContextData + 185) = &SeProtectedMapping;
*((_QWORD *)v212_ContextData + 187) = &KiStackProtectNotifyEvent;
*((_QWORD *)v212_ContextData + 188) = MmPteBase;
*((_QWORD *)v212_ContextData + 189) = PsNtosImageBase;
*((_QWORD *)v212_ContextData + 190) = PsHalImageBase;
*((_QWORD *)v212_ContextData + 191) = &KeNumberProcessors_0;
v333 = &_ti_a;
v334 = 2i64;
*((_QWORD *)v212_ContextData + 192) = &::Src_INITKDBG_VirtualAddress;
*((_QWORD *)v212_ContextData + 193) = &qword_140CFC3D0;
*((_QWORD *)v212_ContextData + 194) = RtlpInvertedFunctionTable;
*((_QWORD *)v212_ContextData + 186) = KiInterruptThunk;

上面这些数据大概表述如下:

1
2
3
4
5
6
7
8
9
10
typedef struct _PG_CONTEXT{
BYTE CmpAppendDllSection[0xC4]; // 0x0000 - 0x00C4
BYTE UnknownData[0x24]; // 0x00C4 - 0x00E8
PVOID ExAcquireResourceSharedLite;
PVOID ExAcquireResourceExclusiveLite;
PVOID ExAllocatePoolWithTag;
PVOID ExFreePoolWithTag;
PVOID ExMapHandleToPointer;
....
}PG_CONTEXT, *PPG_CONTEXT;

3.4 其他数据备份

0xB6 = 182

国科量子

终于出现了与PatchGuardContext有直接关联的逻辑。这里被命名为FixPgContextSize的原因是,实际上池没有分配到相应的大小。因为包括PgContext大小的其他数据也包括在内。

在这里以现在为基准,PgContext 大小为 0xAA0,可以通过很多文件确认。

例如,确认 KiMarkBugCheckRegions 程序的最下端如下。

后续工作:

接下来分析回调b - « TV » 回调,第一次将 PatchGuard 链接到 mssecflt.syshttps://github.com/0xcpu/ExecutiveCallbackObjects/tree/master/542875F90F9B47F497B64BA219CACF69

然后参考PatchGuard Initialize -2-、PatchGuard Initialization Analysis -1-分析PatchGuard的初始化。

然后分析TETRANE-触发检查

INIT:0000000140A1DA5B lea rax, CmpAppendDllSection
INIT:0000000140A1DA62 movups xmm0, xmmword ptr [rax]