找回密码
 立即注册
搜索
查看: 856|回复: 0

[翻译] 趣探另一款符合 PatchGuard 规范的钩子技术

[复制链接]

251

主题

0

回帖

2345

积分

管理员

积分
2345
发表于 2025-10-10 11:37:26 | 显示全部楼层 |阅读模式
概述
    在本文中,我们将介绍一种有趣的替代方案,用以替代备受推崇的由尼克·彼得森(Nick Peterson)开发的 InfinityHook。在微软发布并随后修补了 EtwpGetCycleCount 目标函数(且未对原作者进行任何致谢)之后,艾丹·库里(Aidan Khoury)发现了这一替代方法。该方法已在从早期 Windows 10 到最新 Windows 11 23H2 版本的系统中经过测试。借助此钩子(hook),可以实现多种不同的、实用的钩取操作。我们将探讨其中在我看来对抗恶意软件/反作弊用途而言最为有趣的一种。

免责声明
    任何试图复现本文内容的人,都需要在其目标 NTOS 版本中找到相应的钩子/事件 ID。本文不会提供未来任何版本的钩子/事件 ID。本文中使用的值分别为 0x0F33:0x00501802。
    首次测试是在 Windows 10 1903(19H1)版本上进行的;最新测试则是在 Windows 11 23H2 22631.2506 版本上完成的。不过,Windows 10 的早期版本也已通过测试和验证。首次测试仅是在发现该方法和进行概念验证开发时进行的。本文部分内容可能已过时,文中仅包含了从初稿重构代码后的更新内容。


Windows 内核中的常见钩取点
    在 Windows 内核中,最常见的钩取目标是存储在全局表或自由浮动区域中的 .data 指针(即函数指针)。一个典型的例子就是 NtConvertBetweenAuxiliaryCounterAndPerformanceCounter 函数。许多游戏外挂开发者曾利用此函数作为“隐蔽通信”的手段(实际上它并不隐蔽),但它仍然是一个受欢迎的选择,也是绕过 PatchGuard 对系统 API 保护的一种滥用示例。使用该方法时,需要修改来自 HalPrivateDispatchTable 的函数指针,具体是修改指向 xKdEnumerateDebuggingDevices 的条目,然后从用户模式组件中获取 NTDLL 中的对应例程,并调用该函数。其中三个参数会被传递到调用的 HAL 函数中,这意味着用户可以在用户模式下灵活地使用和处理这些信息。
  1. // NtConvertBetweenAuxiliaryCounterAndPerformanceCounter 反编译代码片段
  2. //
  3. HalpTimerConvertConvert = HalTimerConvertAuxiliaryCounterToPerformanceCounter[0];
  4. if ( !ConvertAuxToPerf )
  5.     HalpTimerConvertConvert = HalTimerConvertPerformanceCounterToAuxiliaryCounter[0];
  6. Result = (HalpTimerConvertConvert)(PerfCounterValue, &AuxCounter, v13);
复制代码
    上述代码片段展示了如何根据参数指示(是否从辅助计数器转换为性能计数器)初始化指向 HAL 分发函数 HalTimerConvertXxx 的指针。基于这些信息,人们高度关注哪些机制可能被滥用,从而在不触发内置防篡改机制的情况下破坏系统安全或实现完全控制。因此,人们对以下内容产生了浓厚兴趣……

HalPrivateDispatchTable
    HAL_PRIVATE_DISPATCH,通常被称为HalPrivateDispatchTable,是Windows操作系统(尤其是其硬件抽象层,即HAL)中的一个关键结构。该表在Windows操作系统与其所运行的硬件之间的交互中发挥着至关重要的作用,特别是对于那些未通过标准HAL接口暴露的硬件特定功能而言。简而言之,它只是一个函数指针表,表中的每个条目都对应一个特定的硬件功能,这些功能是根据Windows运行所在的平台需求量身定制的。其中一些条目的存在取决于平台类型(移动设备/工作站/终端)和启动参数,并且可能因Windows版本的不同而有所差异。正如前一段所述,HalPrivateDispatchTable的使用仅限于内核模式,驱动程序或其他内核组件会访问它以执行低级硬件操作。
    这些操作大多涵盖从专门的硬件初始化到高级电源管理功能等各个方面。它是关乎系统稳定性和安全性的关键数据结构;我们接下来会深入探讨其中一些功能的重要性,不过,如果你熟悉InfinityHook的话,我相信你也能猜到个中缘由。
    这个结构体有个令人遗憾之处,那就是相关文档通常非常匮乏,因为它涉及Windows内部更复杂、更低层次的方面。它主要吸引的是经验丰富的Windows内核开发人员,或是那些从事贴近硬件层面编程工作的人员。此处提取的大多数细节均是通过多种资源(见参考文献部分)以及对相关组件进行独立的逆向工程而获得的。以下是HalPrivateDispatchTable的详细布局。
  1. struct HAL_PRIVATE_DISPATCH
  2. {
  3.   unsigned int Version;
  4.   BUS_HANDLER *(*HalHandlerForBus)(INTERFACE_TYPE, unsigned int);
  5.   BUS_HANDLER *(*HalHandlerForConfigSpace)(BUS_DATA_TYPE, unsigned int);
  6.   void (*HalLocateHiberRanges)(void *);
  7.   int (*HalRegisterBusHandler)(INTERFACE_TYPE, BUS_DATA_TYPE, unsigned int, INTERFACE_TYPE, unsigned int, unsigned int, int (*)(BUS_HANDLER *), BUS_HANDLER **);
  8.   void (*HalSetWakeEnable)(unsigned __int8);
  9.   int (*HalSetWakeAlarm)(unsigned __int64, unsigned __int64);
  10.   unsigned __int8 (*HalPciTranslateBusAddress)(INTERFACE_TYPE, unsigned int, LARGE_INTEGER, unsigned int *, LARGE_INTEGER *);
  11.   int (*HalPciAssignSlotResources)(UNICODE_STRING *, UNICODE_STRING *, DRIVER_OBJECT *, DEVICE_OBJECT *, INTERFACE_TYPE, unsigned int, unsigned int, CM_RESOURCE_LIST **);
  12.   void (*HalHaltSystem)();
  13.   unsigned __int8 (*HalFindBusAddressTranslation)(LARGE_INTEGER, unsigned int *, LARGE_INTEGER *, unsigned __int64 *, unsigned __int8);
  14.   unsigned __int8 (*HalResetDisplay)();
  15.   int (*HalAllocateMapRegisters)(_ADAPTER_OBJECT *, unsigned int, unsigned int, MAP_REGISTER_ENTRY *);
  16.   int (*KdSetupPciDeviceForDebugging)(void *, DEBUG_DEVICE_DESCRIPTOR *);
  17.   int (*KdReleasePciDeviceForDebugging)(DEBUG_DEVICE_DESCRIPTOR *);
  18.   void *(*KdGetAcpiTablePhase0)(LOADER_PARAMETER_BLOCK *, unsigned int);
  19.   void (*KdCheckPowerButton)();
  20.   unsigned __int8 (*HalVectorToIDTEntry)(unsigned int);
  21.   void *(*KdMapPhysicalMemory64)(LARGE_INTEGER, unsigned int, unsigned __int8);
  22.   void (*KdUnmapVirtualAddress)(void *, unsigned int, unsigned __int8);
  23.   unsigned int (*KdGetPciDataByOffset)(unsigned int, unsigned int, void *, unsigned int, unsigned int);
  24.   unsigned int (*KdSetPciDataByOffset)(unsigned int, unsigned int, void *, unsigned int, unsigned int);
  25.   unsigned int (*HalGetInterruptVectorOverride)(INTERFACE_TYPE, unsigned int, unsigned int, unsigned int, unsigned __int8 *, unsigned __int64 *);
  26.   int (*HalGetVectorInputOverride)(unsigned int, GROUP_AFFINITY *, unsigned int *, KINTERRUPT_POLARITY *, INTERRUPT_REMAPPING_INFO *);
  27.   int (*HalLoadMicrocode)(void *);
  28.   int (*HalUnloadMicrocode)();
  29.   int (*HalPostMicrocodeUpdate)();
  30.   int (*HalAllocateMessageTargetOverride)(DEVICE_OBJECT *, GROUP_AFFINITY *, unsigned int, KINTERRUPT_MODE, unsigned __int8, unsigned int *, unsigned __int8 *, unsigned int *);
  31.   void (*HalFreeMessageTargetOverride)(DEVICE_OBJECT *, unsigned int, GROUP_AFFINITY *);
  32.   int (*HalDpReplaceBegin)(HAL_DP_REPLACE_PARAMETERS *, void **);
  33.   void (*HalDpReplaceTarget)(void *);
  34.   int (*HalDpReplaceControl)(unsigned int, void *);
  35.   void (*HalDpReplaceEnd)(void *);
  36.   void (*HalPrepareForBugcheck)(unsigned int);
  37.   unsigned __int8 (*HalQueryWakeTime)(unsigned __int64 *, unsigned __int64 *);
  38.   void (*HalReportIdleStateUsage)(unsigned __int8, KAFFINITY_EX *);
  39.   void (*HalTscSynchronization)(unsigned __int8, unsigned int *);
  40.   int (*HalWheaInitProcessorGenericSection)(WHEA_ERROR_RECORD_SECTION_DESCRIPTOR *, WHEA_PROCESSOR_GENERIC_ERROR_SECTION *);
  41.   void (*HalStopLegacyUsbInterrupts)(SYSTEM_POWER_STATE);
  42.   int (*HalReadWheaPhysicalMemory)(LARGE_INTEGER, unsigned int, void *);
  43.   int (*HalWriteWheaPhysicalMemory)(LARGE_INTEGER, unsigned int, void *);
  44.   int (*HalDpMaskLevelTriggeredInterrupts)();
  45.   int (*HalDpUnmaskLevelTriggeredInterrupts)();
  46.   int (*HalDpGetInterruptReplayState)(void *, void **);
  47.   int (*HalDpReplayInterrupts)(void *);
  48.   unsigned __int8 (*HalQueryIoPortAccessSupported)();
  49.   int (*KdSetupIntegratedDeviceForDebugging)(void *, DEBUG_DEVICE_DESCRIPTOR *);
  50.   int (*KdReleaseIntegratedDeviceForDebugging)(DEBUG_DEVICE_DESCRIPTOR *);
  51.   void (*HalGetEnlightenmentInformation)(HAL_INTEL_ENLIGHTENMENT_INFORMATION *);
  52.   void *(*HalAllocateEarlyPages)(LOADER_PARAMETER_BLOCK *, unsigned int, unsigned __int64 *, unsigned int);
  53.   void *(*HalMapEarlyPages)(unsigned __int64, unsigned int, unsigned int);
  54.   void *Dummy1;
  55.   void *Dummy2;
  56.   void (*HalNotifyProcessorFreeze)(unsigned __int8, unsigned __int8);
  57.   int (*HalPrepareProcessorForIdle)(unsigned int);
  58.   void (*HalRegisterLogRoutine)(HAL_LOG_REGISTER_CONTEXT *);
  59.   void (*HalResumeProcessorFromIdle)();
  60.   void *Dummy;
  61.   unsigned int (*HalVectorToIDTEntryEx)(unsigned int);
  62.   int (*HalSecondaryInterruptQueryPrimaryInformation)(INTERRUPT_VECTOR_DATA *, unsigned int *);
  63.   int (*HalMaskInterrupt)(unsigned int, unsigned int);
  64.   int (*HalUnmaskInterrupt)(unsigned int, unsigned int);
  65.   unsigned __int8 (*HalIsInterruptTypeSecondary)(unsigned int, unsigned int);
  66.   int (*HalAllocateGsivForSecondaryInterrupt)(char *, unsigned __int16, unsigned int *);
  67.   int (*HalAddInterruptRemapping)(unsigned int, unsigned int, PCI_BUSMASTER_DESCRIPTOR *, unsigned __int8, INTERRUPT_VECTOR_DATA *, unsigned int);
  68.   void (*HalRemoveInterruptRemapping)(unsigned int, unsigned int, PCI_BUSMASTER_DESCRIPTOR *, unsigned __int8, INTERRUPT_VECTOR_DATA *, unsigned int);
  69.   void (*HalSaveAndDisableHvEnlightenment)();
  70.   void (*HalRestoreHvEnlightenment)();
  71.   void (*HalFlushIoBuffersExternalCache)(MDL *, unsigned __int8);
  72.   void (*HalFlushExternalCache)(unsigned __int8);
  73.   int (*HalPciEarlyRestore)(_SYSTEM_POWER_STATE);
  74.   int (*HalGetProcessorId)(unsigned int, unsigned int *, unsigned int *);
  75.   int (*HalAllocatePmcCounterSet)(unsigned int, _KPROFILE_SOURCE *, unsigned int, struct _HAL_PMC_COUNTERS **);
  76.   void (*HalCollectPmcCounters)(struct HAL_PMC_COUNTERS *, unsigned __int64 *);
  77.   void (*HalFreePmcCounterSet)(struct HAL_PMC_COUNTERS *);
  78.   int (*HalProcessorHalt)(unsigned int, void *, int (*)(void *));
  79.   unsigned __int64 (*HalTimerQueryCycleCounter)(unsigned __int64 *);
  80.   void *Dummy3;
  81.   void (*HalPciMarkHiberPhase)();
  82.   int (*HalQueryProcessorRestartEntryPoint)(LARGE_INTEGER *);
  83.   int (*HalRequestInterrupt)(unsigned int);
  84.   int (*HalEnumerateUnmaskedInterrupts)(unsigned __int8 (*)(void *, HAL_UNMASKED_INTERRUPT_INFORMATION *), void *, HAL_UNMASKED_INTERRUPT_INFORMATION *);
  85.   void (*HalFlushAndInvalidatePageExternalCache)(LARGE_INTEGER);
  86.   int (*KdEnumerateDebuggingDevices)(void *, DEBUG_DEVICE_DESCRIPTOR *, KD_CALLBACK_ACTION (*)(DEBUG_DEVICE_DESCRIPTOR *));
  87.   void (*HalFlushIoRectangleExternalCache)(_MDL *, unsigned int, unsigned int, unsigned int, unsigned int, unsigned __int8);
  88.   void (*HalPowerEarlyRestore)(unsigned int);
  89.   int (*HalQueryCapsuleCapabilities)(void *, unsigned int, unsigned __int64 *, unsigned int *);
  90.   int (*HalUpdateCapsule)(void *, unsigned int, LARGE_INTEGER);
  91.   unsigned __int8 (*HalPciMultiStageResumeCapable)();
  92.   void (*HalDmaFreeCrashDumpRegisters)(unsigned int);
  93.   unsigned __int8 (*HalAcpiAoacCapable)();
  94.   int (*HalInterruptSetDestination)(INTERRUPT_VECTOR_DATA *, GROUP_AFFINITY *, unsigned int *);
  95.   void (*HalGetClockConfiguration)(HAL_CLOCK_TIMER_CONFIGURATION *);
  96.   void (*HalClockTimerActivate)(unsigned __int8);
  97.   void (*HalClockTimerInitialize)();
  98.   void (*HalClockTimerStop)();
  99.   int (*HalClockTimerArm)(_HAL_CLOCK_TIMER_MODE, unsigned __int64, unsigned __int64 *);
  100.   unsigned __int8 (*HalTimerOnlyClockInterruptPending)();
  101.   void *(*HalAcpiGetMultiNode)();
  102.   void (*(*HalPowerSetRebootHandler)(void (*)(unsigned int, volatile int *)))(unsigned int, volatile int *);
  103.   void (*HalIommuRegisterDispatchTable)(HAL_IOMMU_DISPATCH *);
  104.   void (*HalTimerWatchdogStart)();
  105.   void (*HalTimerWatchdogResetCountdown)();
  106.   void (*HalTimerWatchdogStop)();
  107.   unsigned __int8 (*HalTimerWatchdogGeneratedLastReset)();
  108.   int (*HalTimerWatchdogTriggerSystemReset)(unsigned __int8);
  109.   int (*HalInterruptVectorDataToGsiv)(INTERRUPT_VECTOR_DATA *, unsigned int *);
  110.   int (*HalInterruptGetHighestPriorityInterrupt)(unsigned int *, unsigned __int8 *);
  111.   int (*HalProcessorOn)(unsigned int);
  112.   int (*HalProcessorOff)();
  113.   int (*HalProcessorFreeze)();
  114.   int (*HalDmaLinkDeviceObjectByToken)(unsigned __int64, DEVICE_OBJECT *);
  115.   int (*HalDmaCheckAdapterToken)(unsigned __int64);
  116.   void *Dummy4;
  117.   int (*HalTimerConvertPerformanceCounterToAuxiliaryCounter)(unsigned __int64, unsigned __int64 *, unsigned __int64 *);
  118.   int (*HalTimerConvertAuxiliaryCounterToPerformanceCounter)(unsigned __int64, unsigned __int64 *, unsigned __int64 *);
  119.   int (*HalTimerQueryAuxiliaryCounterFrequency)(unsigned __int64 *);
  120.   int (*HalConnectThermalInterrupt)(unsigned __int8 (*)(KINTERRUPT *, void *));
  121.   unsigned __int8 (*HalIsEFIRuntimeActive)();
  122.   unsigned __int8 (*HalTimerQueryAndResetRtcErrors)(unsigned __int8);
  123.   void (*HalAcpiLateRestore)();
  124.   int (*KdWatchdogDelayExpiration)(unsigned __int64 *);
  125.   int (*HalGetProcessorStats)(HAL_PROCESSOR_STAT_TYPE, unsigned int, unsigned int, unsigned __int64 *);
  126.   unsigned __int64 (*HalTimerWatchdogQueryDueTime)(unsigned __int8);
  127.   int (*HalConnectSyntheticInterrupt)(unsigned __int8 (*)(KINTERRUPT *, void *));
  128.   void (*HalPreprocessNmi)(unsigned int);
  129.   int (*HalEnumerateEnvironmentVariablesWithFilter)(unsigned int, unsigned __int8 (*)(const _GUID *, const wchar_t *), void *, unsigned int *);
  130.   int (*HalCaptureLastBranchRecordStack)(unsigned int, HAL_LBR_ENTRY *, unsigned int *);
  131.   unsigned __int8 (*HalClearLastBranchRecordStack)();
  132.   int (*HalConfigureLastBranchRecord)(unsigned int, unsigned int);
  133.   unsigned __int8 (*HalGetLastBranchInformation)(unsigned int *, unsigned int *);
  134.   void (*HalResumeLastBranchRecord)(unsigned __int8);
  135.   int (*HalStartLastBranchRecord)(unsigned int, unsigned int *);
  136.   int (*HalStopLastBranchRecord)(unsigned int);
  137.   int (*HalIommuBlockDevice)(void *);
  138.   int (*HalIommuUnblockDevice)(EXT_IOMMU_DEVICE_ID *, void **);
  139.   int (*HalGetIommuInterface)(unsigned int, DMA_IOMMU_INTERFACE *);
  140.   int (*HalRequestGenericErrorRecovery)(void *, unsigned int *);
  141.   int (*HalTimerQueryHostPerformanceCounter)(unsigned __int64 *);
  142.   int (*HalTopologyQueryProcessorRelationships)(unsigned int, unsigned int, unsigned __int8 *, unsigned __int8 *, unsigned __int8 *, unsigned int *, unsigned int *);
  143.   void (*HalInitPlatformDebugTriggers)();
  144.   void (*HalRunPlatformDebugTriggers)(unsigned __int8);
  145.   void *(*HalTimerGetReferencePage)();
  146.   int (*HalGetHiddenProcessorPowerInterface)(HIDDEN_PROCESSOR_POWER_INTERFACE *);
  147.   unsigned int (*HalGetHiddenProcessorPackageId)(unsigned int);
  148.   unsigned int (*HalGetHiddenPackageProcessorCount)(unsigned int);
  149.   int (*HalGetHiddenProcessorApicIdByIndex)(unsigned int, unsigned int *);
  150.   int (*HalRegisterHiddenProcessorIdleState)(unsigned int, unsigned __int64);
  151.   void (*HalIommuReportIommuFault)(unsigned __int64, FAULT_INFORMATION *);
  152.   unsigned __int8 (*HalIommuDmaRemappingCapable)(EXT_IOMMU_DEVICE_ID *, unsigned int *);
  153. };
复制代码

目标发现
    掌握了HalPrivateDispatchTable的信息和结构后,如果我们重新审视InfinityHook所使用的机制,就会发现原始代码已被打补丁,但仍存在一些值得关注的功能。所有这些功能都出现在EtwpLogKernelEvent(ETW内核事件日志记录函数)中。我们可以看到其中三个似乎是很不错的目标。
  1. if( /* etw init conditions */ )
  2. {
  3.   v33 = EtwpReserveTraceBuffer(v14, v15 + 0x10, &v59, &v55, a6);
  4.   //
  5.   // ... [etc]
  6.   //
  7.   goto LABEL_19;
  8. }
  9. if ( /* etw init conditions */  )
  10. {
  11.   v34 = EtwpReserveWithPebsIndex(v14, 0x524, v15, &v59, &v55, a6);
  12. }
  13. else
  14. {
  15.   //
  16.   // ... [etc]
  17.   //
  18.   v34 = EtwpReserveWithPmcCounters(v14, a5, v15, &v59, &v55, a6);
  19. }
复制代码
    目前,尚未有任何关于EtwpReserveWithPmcCounters函数的公开信息。如果ETW记录器以正确的方式进行了设置,该函数就会被执行;而当我们查看其内部实现时,会发现另一个非常适合进行补丁修改(即挂钩或篡改)的目标。
  1. signed __int64 __fastcall EtwpReserveWithPmcCounters(
  2.         WMI_LOGGER_CONTEXT *LoggerContext,
  3.         UINT16 HookId,
  4.         UINT64 AuxSize,
  5.         void *BufferHandle,
  6.         LARGE_INTEGER *TimeStamp,
  7.         UINT64 Flags)
  8. {
  9.   volatile unsigned int CountersCount;
  10.   unsigned int CtrIndex;
  11.   unsigned int RequiredSize;
  12.   unsigned __int8 CurrentIrql;
  13.   struct_TraceBuffer *TraceBuffer;
  14.   struct_TraceBuffer *pTracebuf;
  15.   struct _HAL_PMC_COUNTERS *PmcEnabledForProc;
  16.   _ETW_PMC_SUPPORT *PmcData;
  17.   PmcData = LoggerContext->PmcData;
  18.   CountersCount = PmcData->CountersCount;
  19.   CtrIndex = 8 * CountersCount + 0x10;
  20.   RequiredSize = CtrIndex + AuxSize;
  21.   CurrentIrql = KeGetCurrentIrql();
  22.   if ( CurrentIrql < DISPATCH_LEVEL )
  23.   {
  24.     KeGetCurrentIrql();
  25.     __writecr8(DISPATCH_LEVEL);
  26.   }
  27.   TraceBuffer = EtwpReserveTraceBuffer(&LoggerContext->LoggerId, RequiredSize, BufferHandle, TimeStamp, Flags);
  28.   pTracebuf = TraceBuffer;
  29.   if ( TraceBuffer )
  30.   {
  31.     TraceBuffer->TimeStamp = *TimeStamp;
  32.     TraceBuffer->TotalCounters = RequiredSize;
  33.     TraceBuffer->HookId = HookId;
  34.     TraceBuffer->Flags = Flags | (CountersCount << 8) | 0xC0110000;
  35.     PmcEnabledForProc = PmcData->ProcessorCtrs[KeGetPcr()->Prcb.Number];
  36.     if ( PmcEnabledForProc )
  37.       HalPrivateDispatch->HalpCollectPmcCounters(PmcEnabledForProc, &TraceBuffer->Counters);
  38.     else
  39.       memset(&TraceBuffer->Counters, 0, 8 * CountersCount);
  40.     if ( CurrentIrql < DISPATCH_LEVEL )
  41.       __writecr8(CurrentIrql);
  42.     return pTracebuf + CtrIndex;
  43.   }
  44.   else
  45.   {
  46.     if ( CurrentIrql < DISPATCH_LEVEL )
  47.       __writecr8(CurrentIrql);
  48.     return 0;
  49.   }
  50. }
复制代码
    我们关注的目标显而易见,就是HalPrivateDispatch->HalpCollectPmcCounters(...)(通过HalPrivateDispatch表调用HalpCollectPmcCounters函数)。如果我们在实时系统中追踪调用链,会发现类似这样的流程:
  1. KernelBase.dll!SleepEx
  2. |- ntdll.dll!NtDelayExecution
  3. |    |- ntoskrnl.exe!KiSystemServiceExitPico
  4. |    |    |- ntoskrnl.exe!PerfInfoLogSysCallEntry
  5. |    |    |    |- ntoskrnl.exe!EtwTraceKernelEvent
  6. |    |    |    |    |- ntoskrnl.exe!EtwpLogKernelEvent
  7. |    |    |    |    |    |- ntoskrnl.exe!EtwpReservePmcCounters
  8. |    |    |    |    |    |    |- ntoskrnl.exe!HalpCollectPmcCounters   ---> | 这些情况仅在ETW记录器被正确配置时才会发生。
复制代码
    ETW的配置将导致NT内核记录器(NT Kernel Logger)输出包含如下信息的内容:
  1. Logger Name           : NT Kernel Logger
  2. Logger Id             : ffff
  3. Logger Thread Id      : 00000000000012A4
  4. Buffer Size           : 8192
  5. Maximum Buffers       : 118
  6. Minimum Buffers       : 96
  7. Number of Buffers     : 118
  8. Free Buffers          : 70
  9. Buffers Written       : 106898
  10. Events Lost           : 0
  11. Log Buffers Lost      : 0
  12. Real Time Buffers Lost: 0
  13. Flush Timer           : 0
  14. Age Limit             : 0
  15. Log File Mode         : Secure PersistOnHybridShutdown SystemLogger
  16. Maximum File Size     : 0
  17. Log Filename          : EdgyNameHere
  18. Trace Flags           : SYSCALL
  19. PoolTagFilter         : *
复制代码
    这只是一个快速验证,旨在直观展示我们能够以符合保护引导(PG,Protected Guard)要求的方式挂钩系统调用(SYSCALL)(即不会导致机器出现错误检查并蓝屏崩溃)。

自己动手丰衣足食……
    那么,我们该如何利用这些信息呢?方法相对简单,让我们来一步步说明。

  • 定位HalPrivateDispatchTable。
  • 定位系统调用处理程序(System Call Handler)。
  • 获取用于记录系统调用(SYSCALL)的ETW(事件跟踪)事件ID。
  • 以编程方式配置ETW会话。
  • 配置适当的事件跟踪类数据。
  • 替换HalPrivateDispatchTable中的指针。
  • 尽情享受又一个符合保护引导(PG)要求的挂钩机制。

δ 定位HalPrivateDispatchTable
    获取指向HalPrivateDispatchTable的指针,可按如下方式实现:
  1. UNICODE_STRING target = RTL_CONSTANT_STRING( L"HalPrivateDispatchTable" );
  2. HalPrivateDispatchTable = reinterpret_cast< PHAL_PRIVATE_DISPATCH_TABLE >( MmGetSystemRoutineAddress( &target ) );
  3. if ( !HalPrivateDispatchTable )
  4.     return STATUS_RESOURCE_UNAVAILABLE;
复制代码
    有关HAL_PRIVATE_DISPATCH_TABLE的定义,请参考上文给出的详细结构。

δ 额外的ETW配置
    要使该功能正常运行,需注意:除了构建跟踪属性结构并将其修改为启用系统调用(SYSCALL)跟踪外,还必须通过ZwTraceControl函数配置各种跟踪控制参数。为此,您至少需要处理ZwTraceControl函数的以下操作码:EtwStartLoggerCode(启动记录器)、EtwStopLoggerCode(停止记录器)和EtwUpdateLoggerCode(更新记录器)。以下是概念验证代码中的部分示例:

  1. switch (operation) {
  2.     case kl_trace_operation::start: {
  3.         status = ZwTraceControl(EtwStartLoggerCode, pproperty, sizeof(KL_TRACE_PROPERTIES), pproperty, sizeof(KL_TRACE_PROPERTIES), &return_length);
  4.         break;
  5.     }
  6.     case kl_trace_operation::end: {
  7.         status = ZwTraceControl(EtwStopLoggerCode, pproperty, sizeof(KL_TRACE_PROPERTIES), pproperty, sizeof(KL_TRACE_PROPERTIES), &return_length);
  8.         break;
  9.     }
  10.     case kl_trace_operation::syscall: {
  11.         pproperty->EnableFlags |= EVENT_TRACE_FLAG_SYSTEMCALL;
  12.         status = ZwTraceControl(EtwUpdateLoggerCode, pproperty, sizeof(KL_TRACE_PROPERTIES), pproperty, sizeof(KL_TRACE_PROPERTIES), &return_length);
  13.         break;
  14.     }
  15. }
复制代码
  1. const GUID session_guid = { 0x9E814AAD, 0x3204, 0x11D2, { 0x9A, 0x82, 0x0, 0x60, 0x8, 0xA8, 0x69, 0x39 } };
  2. pproperty = nt::trace::build_property( L"NT Kernel Logger", &session_guid, EVENT_TRACE_BUFFERING_MODE );
复制代码
    除此之外,我们还需要通过ZwSetSystemInformation函数并使用SystemPerformanceTraceInformation类来配置一些跟踪信息块,以设置计数器列表和性能分析信息,从而让性能监控计数器(PMC)收集例程能够正常运行。具体而言,需要配置的信息包括EventTraceProfileCounterListInformation(事件跟踪性能计数器列表信息)和EventTraceProfileEventListInformation(事件跟踪性能事件列表信息)。为节省篇幅,本文未包含相关代码。

δ HalCollectPmcCounters 挂钩机制
    以下代码片段是挂钩例程中所需的全部内容,用于在系统调用(SYSCALL)跟踪期间全局应用该挂钩。具体操作包括:遍历调用栈,验证挂钩ID/事件ID是否与目标事件匹配;若匹配,则替换调用栈中包含目标系统例程地址的元素。
  1. void process_syscall(stack<64>& sp) {
  2.     const auto target_fn = reinterpret_cast<void**>(sp.at(9));
  3.     // Example that replaces the return address on stack for NtQuerySystemInformation
  4.     // with our hk_NtQuerySystemInformation, allowing us to hook the call system wide
  5.     // in a PG-compliant manner.
  6.     //
  7.     if (*target_fn == o__nt_query_system_information)
  8.         *target_fn = &hkd__nt_query_system_information;
  9. }
  10. void hkd__hal_collect_pmc_counters(HAL_PMC_COUNTERS* pmc_ctrs, unsigned long long* trace_buffer_end)
  11. {
  12.     // Call original to populate appropriate data structures; avoid unnecessary overhead.
  13.     //
  14.     o__hal_collect_pmc_counters(pmc_ctrs, trace_buffer_end);
  15.     if (!pmc_ctrs || !trace_buffer_end)
  16.         return;
  17.     const auto hook_id = *reinterpret_cast<uint32_t>(reinterpret_cast<uintptr_t>(trace_buffer_end) - 10);
  18.     if (hook_id != target_value_1)
  19.         return;
  20.    
  21.     stack<64> stk(get_pcr()->prcb->rsp_base, _AddressOfReturnAddress());
  22.     auto is_correct_event_target = [target_value_1, target_value_0](stack<64>& sp) {
  23.         const auto event_id_1 = *sp.as<uint16_t*>();
  24.         const auto event_id_0 = *sp.next().as<uint32_t*>();
  25.         return event_id_1 == target_value_1 && event_id_0 == target_value_0;
  26.     };
  27.     auto curr_stack = stk.find_frame( is_correct_event_target );
  28.     if (!curr_stack.valid())
  29.         return;
  30.     curr_stack += 2;
  31.     auto target_sp = curr_stack.find_first_within({ syscall_handler_begin, syscall_handler_end });
  32.    
  33.     if(target_sp.valid())
  34.         process_syscall(target_sp);
  35. }
复制代码

δ NtQuerySystemInformation 挂钩机制
    由于我们需要一个测试用例来验证该挂钩机制,我提供了相应的挂钩代码及辅助函数,供有意进行一定程度复现的读者参考。
  1. static NTSTATUS hkd__nt_query_system_information(
  2.     SYSTEM_INFORMATION_CLASS system_information_class,
  3.     PVOID system_information,
  4.     ULONG system_information_length,
  5.     PULONG return_length
  6. ) {
  7.     NTSTATUS status = o_NtQuerySystemInformation(system_information_class, system_information, system_information_length, return_length);
  8.     if (is_target_process()) {
  9.         log_system_information(system_information_class, system_information, system_information_length, status);
  10.         if (NT_SUCCESS(status)) {
  11.             modify_system_information(system_information_class, system_information, system_information_length);
  12.         }
  13.     }
  14.     return status;
  15. }
  16. static bool is_target_process() {
  17.     return !strcmp(PsGetProcessImageFileName(PsGetCurrentProcess()), "<target>");
  18. }
  19. static void log_system_information(SYSTEM_INFORMATION_CLASS system_information_class, PVOID system_information, ULONG system_information_length, NTSTATUS status) {
  20.     const char* class_name = xnt::to_string(system_information_class);
  21.     const char* process_name = PsGetProcessImageFileName(PsGetCurrentProcess());
  22.     ULONG_PTR process_id = (ULONG_PTR)PsGetProcessId(PsGetCurrentProcess());
  23.     if (class_name == nullptr) {
  24.         LOG_INFO("%s (%I64d) :: NtQuerySystemInformation( %#x, 0x%p, %#x )", process_name, process_id, system_information_class, system_information, system_information_length);
  25.     } else {
  26.         LOG_INFO("%s (%I64d) :: NtQuerySystemInformation( %s, 0x%p, %#x )", process_name, process_id, class_name, system_information, system_information_length);
  27.     }
  28. }
  29. static void modify_system_information(SYSTEM_INFORMATION_CLASS system_information_class, PVOID system_information, ULONG system_information_length) {
  30.     PMDL mdl = IoAllocateMdl(system_information, system_information_length, FALSE, FALSE, NULL);
  31.    
  32.     if (!mdl) {
  33.         LOG_INFO("Failed to allocate MDL in %s\n", __FUNCTION__);
  34.         return;
  35.     }
  36.     __try {
  37.         MmProbeAndLockPages(mdl, UserMode, IoWriteAccess);
  38.         PVOID buffer = MmGetSystemAddressForMdlSafe(mdl, NormalPagePriority | MdlMappingNoExecute);
  39.         
  40.         if (!buffer) {
  41.             goto __exit;
  42.         }
  43.         if (system_information_class == SystemKernelDebuggerInformation) {
  44.             auto kdbg_info = reinterpret_cast<PSYSTEM_KERNEL_DEBUGGER_INFORMATION>(buffer);
  45.             kdbg_info->KernelDebuggerEnabled = FALSE;
  46.             kdbg_info->KernelDebuggerNotPresent = TRUE;
  47.         } else if (system_information_class == SystemCodeIntegrityInformation) {
  48.             auto code_integrity_info = reinterpret_cast<PSYSTEM_CODEINTEGRITY_INFORMATION>(buffer);
  49.             code_integrity_info->CodeIntegrityOptions &= ~CODEINTEGRITY_OPTION_TESTSIGN;
  50.             code_integrity_info->CodeIntegrityOptions |= CODEINTEGRITY_OPTION_ENABLED;
  51.         }
  52.     }
  53.     __except (EXCEPTION_EXECUTE_HANDLER) {
  54.         status = GetExceptionCode();
  55.         LOG_INFO("Exception in %s :: %#x\n", __FUNCTION__, status);
  56.         IoFreeMdl(mdl);
  57.     }
  58.     //
  59.     // ...additional handling and resource release...
  60.     //
  61. }
复制代码

可探测性分析
    值得注意的是,与其他所有基于事件跟踪(ETW)的挂钩机制类似,该挂钩可通过调用栈(call-stack)分析被轻易检测。例如,验证 HalPrivateDispatchTable 的完整性即为一个潜在的检测路径。不过,通过调用栈伪造(call-stack spoofing)技术可绕过此类基于调用栈的检测。尽管存在多种探测该挂钩使用痕迹的方法(其中不乏颇具技术深度的案例,甚至可单独撰写专题文章),但为保持内容简洁,本文暂不展开讨论。

演示效果图
    完成所有操作步骤后,本示例的日志输出如下:
ntqueryexampleimg0.webp

结论
    这种以符合PG原则的方式挂钩系统调用(SYSCALL)的方法颇具趣味,但其实际用途远比表面所见更为广泛。我撰写此博文旨在介绍这一技术,未来还将探讨其在系统调用之外更广泛的应用场景。内核操作监控与合规性实现的方式多种多样,但其中多数需要深厚的技术背景,且缺乏能简化阐述的配套资源。其中一项技术计划在后续文章中详细展开,但内容可能较为复杂。未来研究方向包括:持续探索TracePoint和事件跟踪(ETW)API,挖掘更多可用于间接控制系统操作的间接机制。

致谢
    在此,我必须向以下人士致以诚挚感谢:
  • Aidan Khoury:在私下披露后,于Riot Vanguard中率先发现该方法及其应用场景。
  • Everdox(N. Peterson):首次发现并公开了 InfinityHook 技术。
  • iPower:与我协作开展研究,贡献了独立发现与未来研究方向,并协助审阅了本文内容。

    若您对相关研究感兴趣,欢迎通过 Twitter 联系 @daaximus@aidankhoury。一如既往,感谢各位读者的关注,祝一切顺利!


原文链接

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

快速回复 返回顶部 返回列表