😄
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
方法 0 :插入与 DPC 链接的计时器 。初始化 PGContext、DPC 函数。将 DPC 函数出入到定时器中。
方法 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 :系统线程 。初始化 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 ]; CHAR padding[4 ]; PVOID pPsCreatesystemThread; PVOID Pg_Method3stubTocheckRoutine_sub_1405B9FB0; PVOID pKiBalanceSetManagerPeriodicDpc; }KI_FILTER_FIBER_PARAM, *PKI_FILTER_FIBER_PARAM;
在这种情况下,会在 PG_KiInitializePatchGuard 函数里调用 sub_xxx(为了方便,将其命名为 Pg_InitMethod3SystemThread) 来创建一个新的系统线程,该系统线程的回调函数就是 Pg_Method3stubTocheckRoutine_sub_1405B9FB0。
方法 4 :异步过程调用 。初始化 PGContext、KAPC 结构。并将 APC 函数插入到已有的系统线程中。在函数中为 KiInsertQueueApc 函数准备参数:
NormalRoutine = xHalTimerWatchdogStop
KernelRoutine = KiDispatchCallout
RundownRoutine = NULL
1 2 3 4 *((_QWORD *)v212 + 317 ) = KiDispatchCallout; *((_QWORD *)v212 + 318 ) = xHalTimerWatchdogStop; *((_DWORD *)v212 + 597 ) = 2122344719 ;
使用 PsEnumProcessThreads 来枚举 System 系统进程中的系统线程,如果哪一个线程回调函数等于 PopIrpWorkerControl,则将该线程 ETHREAD 保存至 PGContext(将APC插入到该线程),然后将 ETHREAD 存放在该 KAPC 结构中。
挂钩常规 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 KiTimerDispatch (copied to random pool allocation) KiDpcDispatch (copied into patchguard context) 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 区段中。
默认情况下对该函数 F5 反汇编会提示该函数太大无法进行反汇编(0x1AAEF = 106KB > 64KB),只需要修改 IDA 配置文件即可:
在该函数中,多次使用了全局变量 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 v6 = 41 ; if ( a4 ) { if ( a2 > 5 || !_bittest(&v6, a2) ) a2 = 0 ; v7 = a5 | 4 ; } else { if ( (a2 - 3 ) & 0xFFFFFFFD ) { v4369 = a5; goto LABEL_12; } v7 = a5; a2 = 0 ; } v4369 = v7; LABEL_12: v8 = __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 if ( __2a ){ v64 = 24 ; v65 = &__2a; v66 = &v4920_NtoskrnlBase; v67 = 3 i64; 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 = 5368709120 i64; v13_ExAlloc_BugCheckParameter2_+_4 = 0 i64;
三、从 ntoskrl.exe 中获取 FUNCTIONEXTENTLIST 已压缩资源
调用 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, 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))。
根据下面的分析,可以知道获取到的资源是 压缩 的,后面还要对其进行解压。
如果 RtlImageNtHeader(NtoskrlBase) 存在,则会调用 RtlImageDirectoryEntryToData 函数得到 IMAGE_DIRECTORY_ENTRY_EXCEPTION(3) 异常资源表的数据。该函数返回指定镜像文件中获取到的数据目录表的基址 。(参考 XP 源码)
1 2 3 4 5 6 PVOID NTAPI RtlImageDirectoryEntryToData ( IN PVOID Base, IN BOOLEAN MappedAsImage, 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 #define IMAGE_DIRECTORY_ENTRY_IMPORT 1 #define IMAGE_DIRECTORY_ENTRY_RESOURCE 2 #define IMAGE_DIRECTORY_ENTRY_EXCEPTION 3 #define IMAGE_DIRECTORY_ENTRY_SECURITY 4 #define IMAGE_DIRECTORY_ENTRY_BASERELOC 5 #define IMAGE_DIRECTORY_ENTRY_DEBUG 6 #define IMAGE_DIRECTORY_ENTRY_ARCHITECTURE 7 #define IMAGE_DIRECTORY_ENTRY_GLOBALPTR 8 #define IMAGE_DIRECTORY_ENTRY_TLS 9 #define IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG 10 #define IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT 11 #define IMAGE_DIRECTORY_ENTRY_IAT 12 #define IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT 13 #define IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR 14
四、根据压缩数据的 ExceptionDirectory 签名执行不同操作 ——签名 == “EXTC”
压缩数据的前 4 字节表示 ExceptionDirectory 签名。
1 if ( *(_DWORD *)OUT_Compress_ResourceBuffer == 1163416643 )
压缩数据的第二个 DWORD 表示压缩数据的完整大小。
1 v23 = *(unsigned int *)(OUT_Compress_ResourceBuffer + 4 );
使用函数 RtlGetCompressionWorkSpaceSize 获取压缩、解压时应该使用的空间大小。方便后面使用 RtlDecompressBufferEx 对获取的资源进行解压。函数功能参见《压缩技术-windows压缩API 》。
1 2 3 4 5 NT_RTL_COMPRESS_API NTSTATUS RtlGetCompressionWorkSpaceSize ( [in] USHORT CompressionFormatAndEngine, [out] PULONG CompressBufferWorkSpaceSize, [out] PULONG CompressFragmentWorkSpaceSize ) ;
该部分代码如下:
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 if ( (signed int )LdrResFindResource( 5368709120 i64, 10 i64, (__int64)L"FUNCTIONEXTENTLIST" , 0 i64, (__int64)&OUT_Compress_ResourceBuffer, (__int64)&OUT_Compress_ResourceSize, 0 i64, 0 i64, 0 ) >= 0 && (unsigned __int64)(OUT_Compress_ResourceSize - 8 ) <= 0xFFFFFFF7 ) { LODWORD(v4374) = OUT_Compress_ResourceSize; if ( RtlImageNtHeader(5368709120 i64) ) { LOBYTE(v14) = 1 ; v15_pExceptionDirectory = RtlImageDirectoryEntryToData(5368709120 i64, v14, 3 i64, &v4407_ExceptionDirectory_Size); if ( v15 ) { v16 = 0 i64; v17 = -1073741823 ; if ( *(_DWORD *)OUT_Compress_ResourceBuffer == 1163416643 ) { if ( (signed int )RtlGetCompressionWorkSpaceSize(4 i64, &NumberOfBytes, &v4471) < 0 ) goto LABEL_34; v23_Compress_ResourceSize = *(unsigned int *)(OUT_Compress_ResourceBuffer + 4 ); if ( (unsigned int )v23_Compress_ResourceSize < 8 ) { HIDWORD(NumberOfBytes) = -805294751 ; KeBugCheckEx(__ROR4__(-805306349 , 92 ), 0xE ui64, OUT_Compress_ResourceBuffer, 0x140000000 ui64, (unsigned int )v4374); } v24 = __rdtsc(); v25 = (__ROR8__(v24, 3 ) ^ v24) * (unsigned __int128)0x7010008004002001 ui64; v4626 = *((_QWORD *)&v25 + 1 ); v26 = (v25 ^ *((_QWORD *)&v25 + 1 )) - 11 * ((unsigned __int64)(((unsigned __int64)v25 ^ *((_QWORD *)&v25 + 1 )) * (unsigned __int128)0x2E8BA2E8BA2E8BA3 ui64 >> 64 ) >> 1 );
使用算法生成 ExAllocatePoolWithTag 的 Tag 参数申请内存
下面会调用 ExAllocatePoolWithTag 函数 2 次,都是申请的非分页内存不可执行内存 NonPagedPoolNx。
第一次。从后面分析中可以看到,这块内存 v16 并没有使用到,应该算是一个Bug了 。
内存类型:NonPagedPoolNx
内存大小:RtlCompressBuffer 函数的 WorkSpace
内存标签:v29
1 2 v16 = (unsigned int *)ExAllocatePoolWithTag(NonPagedPoolNx, (unsigned int )Compress_NumberOfBytes, v29);
第二次。用来保存 FUNCTIONEXTENTLIST 资源解压后的数据。
内存类型:NonPagedPoolNx
内存大小:FUNCTIONEXTENTLIST 资源的大小
内存标签:v40
1 2 v13_1ExAlloc_Compress_Point_2DecompressBuffer = (unsigned int *)ExAllocatePoolWithTag(NonPagedPoolNx, v23_Compress_ResourceSize, v40);
解压上面获取的 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 ) ;
解压过程如下:
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 ]; }
对 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 LABEL_111: v60 = 0 ; v61 = v15_pExceptionDirectory - 0x40000 if ( v4407_ExceptionDirectory_Size ) { do { v62 = v60; v60 += 4 ; v61 += *(_DWORD *)(v15_pExceptionDirectory + 4 * (v62 >> 2 )); } while ( v60 < v4407_ExceptionDirectory_Size ); } v63 = v13_1ExAllocCompressBuffer_2DecompressBuffer[1 ]; if ( v61 != (_DWORD)v63 ) { v4236 = -805294751 ; KeBugCheckEx(__ROR4__(-805306349 , 92 ), 0x10 ui64, (ULONG_PTR)v13_1ExAllocCompressBuffer_2DecompressBuffer, 0x140000000 ui64, 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”
根据计算得到的参数 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 ) { 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 进行解压。
然后重复上面的步骤 5 ——对 ExceptionDirectory 进行累加后对比 。
六、总结
在函数 PG_KiInitializePatchGuardContext 一开头,也就是开始初始化 PatchGuard 时。如果全局变量 $$2a <= 0,则会对 ntoskrl.exe 模块中名称为 FUNCTIONEXTENTLIST 的资源进行处理后,与该模块的异常资源表进行比较,如果不相等则直接会蓝屏(KeBugCheckEx)。我们知道异常资源表中存放的是 RUNTIME_FUNCTION,所以要在系统启动前修改 ntoskrl.exe 模块中的函数,就要了解本节中细节,了解 FUNCTIONEXTENTLIST 资源对比处理过程,然后进行绕过。
FUNCTIONEXTENTLIST 资源的处理过程如下:
2.2 INITKDBG 节的处理
获取 FsRtlUninitializeSmallMcb 函数所在的模块、区段。该模块为 ntoskrnl.exe。
调用 RtlPcToFileHeader 函数,得到目标函数所在的模块基址。
1 2 3 4 PVOID RtlPcToFileHeader ( IN PVOID PcValue, OUT PVOID *BaseOfImage )
调用 RtlImageNtHeader 函数获取 ntoskrnl.exe 的 pNtHeader。
1 2 3 PIMAGE_NT_HEADERS RtlImageNtHeader ( IN PVOID Base )
这里的返回值是 VA 值。
调用 RtlSectionTableFromVirtualAddress 函数获取目标函数所在的区段 VA 地址。
1 2 3 4 5 PIMAGE_SECTION_HEADER RtlSectionTableFromVirtualAddress ( IN PIMAGE_NT_HEADERS NtHeaders, IN PVOID Base, IN ULONG Address )
返回值是 VA。目标函数 FsRtlUninitializeSmallMcb 位于 INITKDBG 区段。
获取如下几个函数相对于 INITKDBG 区段的 RVA。
1 2 3 sub_140A0DD10 sub_140A0E830 RtlLookupFunctionEntryEx
设置一个全局 BOOL 值 v4202。
如果函数 MmStrongCodeGuaranteesEnforced() > 0、sub_140A360D4() > 0 ,则 v4202 = TRUE。
如果函数 PG_KiInitializePatchGuardContext 的参数二(调度方法)为 7,则 v4202 = TRUE。
疑似检查 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; char v1; unsigned int v2; unsigned __int8 v4; v4 = KeGetCurrentIrql(); __writecr8(0xF ui64); v0 = __readcr0(); __writecr0(v0 & 0xFFFFFFFFFFFEFFFF ui64); v1 = *((_BYTE *)KiErrata671Present + 2 ); *((_BYTE *)KiErrata671Present + 2 ) = 0xC3 ; 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 数组如下:
可以看到 qword_140C00210[0] 是空的,在初始化 PatchGuard 时进行填充。而填充的地址指向 xHalHaltSystem 函数。
1 .data:0000000140 C005D8 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 ) ; 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 模块中。
先获取关键函数 CriticalRoutines[i] 所在模块的异常资源表基地址(使用RtlLookupFunctionTable函数)。
然后使用 CriticalRoutines[i] 函数地址,循环地与异常资源表 ExceptionTable 中每一个 RUNTIME_FUNCTION 函数地址比较。
如果 CriticalRoutines[i] 函数不在异常资源表中,就跳转到 LABEL_184,不会保存函数所在页面的 PTE(下面2.4节内容)。
2.4 保存关键函数的 PTE 页面 步骤 2.3 中的关键函数验证通过后才会执行本节内容,否则直接跳转至 LABEL_184。
在一个大循环里面,首先找到 CriticalRoutines[i] 函数所在页面基地址,有可能一个函数的大小超过 1 个页面,所以要找到该函数所在页面所有的页面基地址,我将页面基地址命名为 CriticalRoutinesPageBaseAddress。
1 2 3 4 5 6 7 8 v112_PageBasedAddress = (v99 + v110) & 0xFFFFFFFFFFFFF000 ui64; v4207 = v99 + v110 + v111;
获取页面的 4 级页表虚拟地址(实际上是函数所在所有页面,外面还有一层循环)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 v115_PteAddress = MmPteBase + ((v112_PageBasedAddress >> 9 ) & 0x7FFFFFFFF8 i64); do { *v113_4levelPagetableAddress = v115_PteAddress ; ++v113_4levelPagetableAddress; v115_PteAddress = MmPteBase + ((v115_PteAddress >> 9 ) & 0x7FFFFFFFF8 i64); --v114; }while ( v114 );
如果 4 级页表的 PAT/PS 启用,则将该级页表置 0。
调用 ExAllocatePoolWithTag 函数分配 NonPagedPoolNx 类型内存,将该函数的 4 级页表保存起来。
2.5 Allocate PgContext 本节主要是申请一块非分页内存,这块非分页内存就是 PGContext。
1 194 _TempPgContext = ExAllocatePoolWithTag(0 , v190 + v177, v183);
内存大小:v190 + v177。
v190:可以看到 v190 最大为 0x7FF。
1 2 3 4 v189 = __rdtsc(); v4495 = (__ROR8__(v189, 3 ) ^ v189) * (unsigned __int128)0x7010008004002001 ui64 >> 64 ; v190 = (v4495 ^ 0x4002001 * (__ROR8__(v189, 3 ) ^ v189)) & 0x7FF ;
v177:最大为 sizeof(INITKDBG) + 0xAA8 + 0x80000 + 0x7FF + Max(0x10*16)【4-levelpagetable】 + 16 个 CriticalRoutines[i] 函数大小。
标签 v183 通过计算获得,此处忽略。
根据代码分析有以下结论:
申请 v190 + v177 大小的 PGContext。
在 PGContext 首尾使用随机值进行填充,填充的字节最大为 v190 = 0x7FF 字节。
PGContext 中间有 v177 大小的填充数据。包含:
INITKDBG 大小
0xAA8
16 个 CriticalRoutines[i] 函数大小、及这 16 个关键函数对应的 4-level 页表(0x100)。
0x80000
0x7FF(v190最大可达)
所以 PGContext 结构大致如下:
紧接着,将 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 v189 = __rdtsc(); v4495 = (__ROR8__(v189, 3 ) ^ v189) * (unsigned __int128)0x7010008004002001 ui64 >> 64 ; v190 = (v4495 ^ 67117057 * (__ROR8__(v189, 3 ) ^ v189)) & 0x7FF ; v191 = __rdtsc(); v192 = (__ROR8__(v191, 3 ) ^ v191) * (unsigned __int128)0x7010008004002001 ui64; 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)0x7010008004002001 ui64; v4497 = *((_QWORD *)&v199 + 1 ); *v196 = v199 ^ *((_QWORD *)&v199 + 1 ); ++v196; --v197; } while ( v197 ); } if ( v195 ){ v200 = __rdtsc(); v201 = (__ROR8__(v200, 3 ) ^ v200) * (unsigned __int128)0x7010008004002001 ui64; 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)0x7010008004002001 ui64; 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)0x7010008004002001 ui64; 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 ; v213 = v175 + 0x80000 ; v4202_BOOL_GlobleFlag = v175 + 0x80000 ; memset (v212_ContextData, 0 , (unsigned int )(v175 + 0x80000 ));
3 PGContext 数据填充 3.1 CmpAppendDllSection 先观察 CmpAppendDllSection 函数特征:
从 IDA 反汇编结果来看函数 CmpAppendDllSection 大小为 0xC8。
0x190 + 0xC8 = 0x258,可以看到函数从 0x0000000140A36246 到 0x0000000140A36258 都是填充数据。
1 2 3 4 5 6 7 8 9 10 INIT:0000000140 A36190 CmpAppendDllSection proc near INIT:0000000140 A36190 db 2 Eh INIT:0000000140 A36190 2 E 48 31 11 xor [rcx ], rdx ... INIT:0000000140 A36241 BA 01 00 00 00 mov edx, 1 INIT:0000000140 A36246 41 FF E0 jmp r8 INIT:0000000140 A36246 ; ------------------------------------------------------------------- INIT:0000000140 A36249 90 90 90 90 90 90 90 90 + db 0 Fh dup(90 h ) INIT:0000000140 A36249 90 90 90 90 90 90 90 CmpAppendDllSection endp INIT:0000000140 A36258 CC CC CC CC CC CC CC CC+ byte_140A36258 db 0 Eh dup(0 CCh)
函数首字节 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(); *((_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 ); *(_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 ); *(_DWORD *)(v323 + 64 ) = *((_DWORD *)CmpAppendDllSection + 48 ); LODWORD(v323) = v4210; *((_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();
注意:在反汇编代码中,这部分代码的赋值使用 movups 指令(SSE 指令),表示对 16 字节(128 Bytes)移动处理。
1 2 3 4 5 INIT:0000000140 A1DA5B lea rax, CmpAppendDllSection INIT:0000000140 A1DA62 movups xmm0, xmmword ptr [rax] INIT:0000000140 A1DA65 movups xmmword ptr [r14], xmm0 INIT:0000000140 A1DA69 movups xmm1, xmmword ptr [rax+10 h] ...
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(); *((_QWORD *)v212_ContextData + 29 ) = ExAcquireResourceSharedLite; *((_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(); if ( !(_BYTE)KdDebuggerNotPresent ) { 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 ) = 0 i64; *((_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 ) = -9345848836096 i64; *((_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 = 2 i64; *((_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 ]; BYTE UnknownData[0x24 ]; 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.sys https://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]