[机翻]Fun With Another PG-Compliant Hook/另一个符合 PG 标准的钩子的乐趣

发布时间 2023-11-26 15:03:48作者: DirWangK

原文链接:https://revers.engineering/fun-with-pg-compliant-hook/

Overview /概述

​ In this article, we’ll be covering a fun alternative to the treasured InfinityHook from Nick Peterson / Nemanja Mulasmajic. This was discovered following the release and subsequent patch of the EtwpGetCycleCount target by Microsoft without any acknowledgements to the original authors. This uses similar mechanics, and was derived from continued research within the ETW functions of Windows Kernel. This method has been tested from early Windows 10 to latest Windows 11 23h2. The hook allows for a number of different, useful hooks. We’ll take a look at the most interesting one, in my opinion, for anti-malware/anti-cheat purposes.

​ 在本文中,我们将介绍一个有趣的替代品,以替代 Nick Peterson / Nemanja Mulasmajic 珍贵的 InfinityHook。这是在 Microsoft 发布 EtwpGetCycleCount 目标并随后修补后发现的,未向原始作者致谢。这使用类似的机制,并且源自 Windows 内核的 ETW 函数中的持续研究。此方法已从早期的 Windows 10 到最新的 Windows 11 23h2 进行了测试。钩子允许许多不同的、有用的钩子。在我看来,我们将看看最有趣的一个,用于反恶意软件/反作弊目的。

**Disclaimer 免責聲明
**

Anyone trying to replicate this will need to locate the appropriate hook/event IDs within their targeted NTOS version(s). I do not supply the hook/event IDs for any versions in the future within this article. The values that are used in this article are 0x0F33:0x00501802, respectively.
任何尝试复制此内容的人都需要在其目标 NTOS 版本中找到适当的挂钩/事件 ID。在本文中,我没有为将来的任何版本提供钩子/事件 ID。本文中使用的值分别为 0x0F33:0x00501802。

The first test took place on Windows 10 1903 (19H1); the latest test took place on Windows 11 23h2 22631.2506. However, earlier versions of Windows 10 have been tested and verified. The first test was just upon discovery and proof-of-concept development. There may parts of this post that are old, I have only put updates of refactored code from the original draft.
第一次测试是在 Windows 10 1903 (19H1) 上进行的;最新的测试是在 Windows 11 23h2 22631.2506 上进行的。但是,早期版本的 Windows 10 已经过测试和验证。第一次测试是在发现和概念验证开发时进行的。这篇文章可能有些部分是旧的,我只对原始草稿中的重构代码进行了更新。

Common Hook Points in Windows Kernel /Windows 内核中的常见钩子点

​ The most common targets for hooks within the Windows kernel are .data pointers, which are just function pointers stored in global tables or free-floating. A prime example of this is the NtConvertBetweenAuxiliaryCounterAndPerformanceCounter function. Many game hacking types have been using this as a means of “covert communication”, it by no means is, but it remains a popular choice and an example of the abuses to circumvent PatchGuard’s protection on system API. The usage requires you modify the function pointer from the HalPrivateDispatchTable, specifically an entry pointing to xKdEnumerateDebuggingDevices, and then from a usermode component acquire the routine from NTDLL, and then call the function. Three of the arguments get passed through to the invoked HAL function, meaning a user has flexibility in their usage and processing of information from usermode.
​ Windows 内核中钩子的最常见目标是 .data 指针,它只是存储在全局表中或自由浮动的函数指针。这方面的一个典型示例是 NtConvertBetweenAuxiliaryCounterAndPerformanceCounter 函数。许多游戏黑客类型一直将其用作“秘密通信”的手段,绝不是,但它仍然是一个流行的选择,也是规避 PatchGuard 对系统 API 保护的滥用示例。该用法要求您修改 HalPrivateDispatchTable 中的函数指针,特别是指向 xKdEnumerateDebuggingDevices 的条目,然后从用户模式组件从 NTDLL 获取例程,然后调用该函数。其中三个参数被传递到调用的 HAL 函数,这意味着用户可以灵活地使用和处理来自用户模式的信息。

// NtConvertBetweenAuxiliaryCounterAndPerformanceCounter Decompilation Excerpt
//
HalpTimerConvertConvert = HalTimerConvertAuxiliaryCounterToPerformanceCounter[0];
if ( !ConvertAuxToPerf )
    HalpTimerConvertConvert = HalTimerConvertPerformanceCounterToAuxiliaryCounter[0];
Result = (HalpTimerConvertConvert)(PerfCounterValue, &AuxCounter, v13);

​ The above snippet shows the initialization of a pointer to the HAL dispatch function HalTimerConvertXxx depending on whether an argument indicates to convert from auxiliary counter to performance counter. Given this information, there is a notable amount of focus on what can be abused to compromise a system or enable complete control without triggering built-in anti-tamper. Hence the interest in…
​ 上面的代码片段显示了指向 HAL 调度函数 HalTimerConvertXxx 的指针的初始化,具体取决于参数是否指示从辅助计数器转换为性能计数器。鉴于这些信息,人们非常关注哪些内容可以被滥用来破坏系统或在不触发内置防篡改的情况下实现完全控制。因此,对...

The HalPrivateDispatchTable

​ The HAL_PRIVATE_DISPATCH, commonly referred to as HalPrivateDispatchTable, is a critical structure within the Windows operating system, specifically within its Hardware Abstraction Layer (HAL). This table plays a pivotal role in the interaction between the Windows OS and the hardware it runs on, particularly for hardware-specific functions that are not exposed through standard HAL interfaces. Put plainly, it’s just a table of function pointers, with each entry corresponding to a specific hardware function, tailored to the needs of the platform Windows is operating on. Some of these entries existence depends on the type of platform (mobile/workstation/terminal) and startup parameters, and may vary depending on Windows version. As has been noted in the paragraph prior, the use of the HalPrivateDispatchTable is limited to kernel mode, where it is accessed by drivers or other kernel components to perform low-level hardware operations.
​ 通常 HAL_PRIVATE_DISPATCH 称为 HalPrivateDispatchTable ,是 Windows 操作系统中的一个关键结构,特别是在其硬件抽象层 (HAL) 中。此表在 Windows 操作系统与其运行的硬件之间的交互中起着关键作用,特别是对于未通过标准 HAL 接口公开的特定于硬件的功能。简单来说,它只是一个函数指针表,每个条目对应一个特定的硬件功能,根据 Windows 运行平台的需求进行定制。其中一些条目的存在取决于平台类型(移动/工作站/终端)和启动参数,并且可能因 Windows 版本而异。如上一段所述,该模式的使用仅限于内核模式,在该 HalPrivateDispatchTable 模式下,驱动程序或其他内核组件可以访问它以执行低级硬件操作。

​ Most of the operations range from specialized hardware initialization to advanced power management functions. It’s a crucial data structure for system stability and security; we’ll get into the significance of some of these functions, but if you’re familiar with InfinityHook, I’m sure you can guess.
​ 大多数操作范围从专门的硬件初始化到高级电源管理功能。它是系统稳定性和安全性的关键数据结构;我们将讨论其中一些函数的意义,但如果你熟悉 InfinityHook,我相信你能猜到。

An unfortunate thing about this construct is that documentation is typically sparse, as it delves into the more intricate and lower-level aspects of Windows internals. It’s primarily of interest to experienced Windows kernel developers or those working in close proximity to hardware-level programming. Most of the details extracted here were done so through a variety of resources (see references section), and independent reverse-engineering of associated components. The layout of the HalPrivateDispatchTable is given below in long form.
这种结构的一个不幸之处在于,文档通常是稀疏的,因为它深入研究了 Windows 内部更复杂和更低级的方面。它主要对有经验的 Windows 内核开发人员或那些在硬件级编程附近工作的人感兴趣。这里提取的大多数细节都是通过各种资源(参见参考文献部分)和相关组件的独立逆向工程完成的。下面以长格式给出了布局 HalPrivateDispatchTable

struct HAL_PRIVATE_DISPATCH
{
  unsigned int Version;
  BUS_HANDLER *(*HalHandlerForBus)(INTERFACE_TYPE, unsigned int);
  BUS_HANDLER *(*HalHandlerForConfigSpace)(BUS_DATA_TYPE, unsigned int);
  void (*HalLocateHiberRanges)(void *);
  int (*HalRegisterBusHandler)(INTERFACE_TYPE, BUS_DATA_TYPE, unsigned int, INTERFACE_TYPE, unsigned int, unsigned int, int (*)(BUS_HANDLER *), BUS_HANDLER **);
  void (*HalSetWakeEnable)(unsigned __int8);
  int (*HalSetWakeAlarm)(unsigned __int64, unsigned __int64);
  unsigned __int8 (*HalPciTranslateBusAddress)(INTERFACE_TYPE, unsigned int, LARGE_INTEGER, unsigned int *, LARGE_INTEGER *);
  int (*HalPciAssignSlotResources)(UNICODE_STRING *, UNICODE_STRING *, DRIVER_OBJECT *, DEVICE_OBJECT *, INTERFACE_TYPE, unsigned int, unsigned int, CM_RESOURCE_LIST **);
  void (*HalHaltSystem)();
  unsigned __int8 (*HalFindBusAddressTranslation)(LARGE_INTEGER, unsigned int *, LARGE_INTEGER *, unsigned __int64 *, unsigned __int8);
  unsigned __int8 (*HalResetDisplay)();
  int (*HalAllocateMapRegisters)(_ADAPTER_OBJECT *, unsigned int, unsigned int, MAP_REGISTER_ENTRY *);
  int (*KdSetupPciDeviceForDebugging)(void *, DEBUG_DEVICE_DESCRIPTOR *);
  int (*KdReleasePciDeviceForDebugging)(DEBUG_DEVICE_DESCRIPTOR *);
  void *(*KdGetAcpiTablePhase0)(LOADER_PARAMETER_BLOCK *, unsigned int);
  void (*KdCheckPowerButton)();
  unsigned __int8 (*HalVectorToIDTEntry)(unsigned int);
  void *(*KdMapPhysicalMemory64)(LARGE_INTEGER, unsigned int, unsigned __int8);
  void (*KdUnmapVirtualAddress)(void *, unsigned int, unsigned __int8);
  unsigned int (*KdGetPciDataByOffset)(unsigned int, unsigned int, void *, unsigned int, unsigned int);
  unsigned int (*KdSetPciDataByOffset)(unsigned int, unsigned int, void *, unsigned int, unsigned int);
  unsigned int (*HalGetInterruptVectorOverride)(INTERFACE_TYPE, unsigned int, unsigned int, unsigned int, unsigned __int8 *, unsigned __int64 *);
  int (*HalGetVectorInputOverride)(unsigned int, GROUP_AFFINITY *, unsigned int *, KINTERRUPT_POLARITY *, INTERRUPT_REMAPPING_INFO *);
  int (*HalLoadMicrocode)(void *);
  int (*HalUnloadMicrocode)();
  int (*HalPostMicrocodeUpdate)();
  int (*HalAllocateMessageTargetOverride)(DEVICE_OBJECT *, GROUP_AFFINITY *, unsigned int, KINTERRUPT_MODE, unsigned __int8, unsigned int *, unsigned __int8 *, unsigned int *);
  void (*HalFreeMessageTargetOverride)(DEVICE_OBJECT *, unsigned int, GROUP_AFFINITY *);
  int (*HalDpReplaceBegin)(HAL_DP_REPLACE_PARAMETERS *, void **);
  void (*HalDpReplaceTarget)(void *);
  int (*HalDpReplaceControl)(unsigned int, void *);
  void (*HalDpReplaceEnd)(void *);
  void (*HalPrepareForBugcheck)(unsigned int);
  unsigned __int8 (*HalQueryWakeTime)(unsigned __int64 *, unsigned __int64 *);
  void (*HalReportIdleStateUsage)(unsigned __int8, KAFFINITY_EX *);
  void (*HalTscSynchronization)(unsigned __int8, unsigned int *);
  int (*HalWheaInitProcessorGenericSection)(WHEA_ERROR_RECORD_SECTION_DESCRIPTOR *, WHEA_PROCESSOR_GENERIC_ERROR_SECTION *);
  void (*HalStopLegacyUsbInterrupts)(SYSTEM_POWER_STATE);
  int (*HalReadWheaPhysicalMemory)(LARGE_INTEGER, unsigned int, void *);
  int (*HalWriteWheaPhysicalMemory)(LARGE_INTEGER, unsigned int, void *);
  int (*HalDpMaskLevelTriggeredInterrupts)();
  int (*HalDpUnmaskLevelTriggeredInterrupts)();
  int (*HalDpGetInterruptReplayState)(void *, void **);
  int (*HalDpReplayInterrupts)(void *);
  unsigned __int8 (*HalQueryIoPortAccessSupported)();
  int (*KdSetupIntegratedDeviceForDebugging)(void *, DEBUG_DEVICE_DESCRIPTOR *);
  int (*KdReleaseIntegratedDeviceForDebugging)(DEBUG_DEVICE_DESCRIPTOR *);
  void (*HalGetEnlightenmentInformation)(HAL_INTEL_ENLIGHTENMENT_INFORMATION *);
  void *(*HalAllocateEarlyPages)(LOADER_PARAMETER_BLOCK *, unsigned int, unsigned __int64 *, unsigned int);
  void *(*HalMapEarlyPages)(unsigned __int64, unsigned int, unsigned int);
  void *Dummy1;
  void *Dummy2;
  void (*HalNotifyProcessorFreeze)(unsigned __int8, unsigned __int8);
  int (*HalPrepareProcessorForIdle)(unsigned int);
  void (*HalRegisterLogRoutine)(HAL_LOG_REGISTER_CONTEXT *);
  void (*HalResumeProcessorFromIdle)();
  void *Dummy;
  unsigned int (*HalVectorToIDTEntryEx)(unsigned int);
  int (*HalSecondaryInterruptQueryPrimaryInformation)(INTERRUPT_VECTOR_DATA *, unsigned int *);
  int (*HalMaskInterrupt)(unsigned int, unsigned int);
  int (*HalUnmaskInterrupt)(unsigned int, unsigned int);
  unsigned __int8 (*HalIsInterruptTypeSecondary)(unsigned int, unsigned int);
  int (*HalAllocateGsivForSecondaryInterrupt)(char *, unsigned __int16, unsigned int *);
  int (*HalAddInterruptRemapping)(unsigned int, unsigned int, PCI_BUSMASTER_DESCRIPTOR *, unsigned __int8, INTERRUPT_VECTOR_DATA *, unsigned int);
  void (*HalRemoveInterruptRemapping)(unsigned int, unsigned int, PCI_BUSMASTER_DESCRIPTOR *, unsigned __int8, INTERRUPT_VECTOR_DATA *, unsigned int);
  void (*HalSaveAndDisableHvEnlightenment)();
  void (*HalRestoreHvEnlightenment)();
  void (*HalFlushIoBuffersExternalCache)(MDL *, unsigned __int8);
  void (*HalFlushExternalCache)(unsigned __int8);
  int (*HalPciEarlyRestore)(_SYSTEM_POWER_STATE);
  int (*HalGetProcessorId)(unsigned int, unsigned int *, unsigned int *);
  int (*HalAllocatePmcCounterSet)(unsigned int, _KPROFILE_SOURCE *, unsigned int, struct _HAL_PMC_COUNTERS **);
  void (*HalCollectPmcCounters)(struct HAL_PMC_COUNTERS *, unsigned __int64 *);
  void (*HalFreePmcCounterSet)(struct HAL_PMC_COUNTERS *);
  int (*HalProcessorHalt)(unsigned int, void *, int (*)(void *));
  unsigned __int64 (*HalTimerQueryCycleCounter)(unsigned __int64 *);
  void *Dummy3;
  void (*HalPciMarkHiberPhase)();
  int (*HalQueryProcessorRestartEntryPoint)(LARGE_INTEGER *);
  int (*HalRequestInterrupt)(unsigned int);
  int (*HalEnumerateUnmaskedInterrupts)(unsigned __int8 (*)(void *, HAL_UNMASKED_INTERRUPT_INFORMATION *), void *, HAL_UNMASKED_INTERRUPT_INFORMATION *);
  void (*HalFlushAndInvalidatePageExternalCache)(LARGE_INTEGER);
  int (*KdEnumerateDebuggingDevices)(void *, DEBUG_DEVICE_DESCRIPTOR *, KD_CALLBACK_ACTION (*)(DEBUG_DEVICE_DESCRIPTOR *));
  void (*HalFlushIoRectangleExternalCache)(_MDL *, unsigned int, unsigned int, unsigned int, unsigned int, unsigned __int8);
  void (*HalPowerEarlyRestore)(unsigned int);
  int (*HalQueryCapsuleCapabilities)(void *, unsigned int, unsigned __int64 *, unsigned int *);
  int (*HalUpdateCapsule)(void *, unsigned int, LARGE_INTEGER);
  unsigned __int8 (*HalPciMultiStageResumeCapable)();
  void (*HalDmaFreeCrashDumpRegisters)(unsigned int);
  unsigned __int8 (*HalAcpiAoacCapable)();
  int (*HalInterruptSetDestination)(INTERRUPT_VECTOR_DATA *, GROUP_AFFINITY *, unsigned int *);
  void (*HalGetClockConfiguration)(HAL_CLOCK_TIMER_CONFIGURATION *);
  void (*HalClockTimerActivate)(unsigned __int8);
  void (*HalClockTimerInitialize)();
  void (*HalClockTimerStop)();
  int (*HalClockTimerArm)(_HAL_CLOCK_TIMER_MODE, unsigned __int64, unsigned __int64 *);
  unsigned __int8 (*HalTimerOnlyClockInterruptPending)();
  void *(*HalAcpiGetMultiNode)();
  void (*(*HalPowerSetRebootHandler)(void (*)(unsigned int, volatile int *)))(unsigned int, volatile int *);
  void (*HalIommuRegisterDispatchTable)(HAL_IOMMU_DISPATCH *);
  void (*HalTimerWatchdogStart)();
  void (*HalTimerWatchdogResetCountdown)();
  void (*HalTimerWatchdogStop)();
  unsigned __int8 (*HalTimerWatchdogGeneratedLastReset)();
  int (*HalTimerWatchdogTriggerSystemReset)(unsigned __int8);
  int (*HalInterruptVectorDataToGsiv)(INTERRUPT_VECTOR_DATA *, unsigned int *);
  int (*HalInterruptGetHighestPriorityInterrupt)(unsigned int *, unsigned __int8 *);
  int (*HalProcessorOn)(unsigned int);
  int (*HalProcessorOff)();
  int (*HalProcessorFreeze)();
  int (*HalDmaLinkDeviceObjectByToken)(unsigned __int64, DEVICE_OBJECT *);
  int (*HalDmaCheckAdapterToken)(unsigned __int64);
  void *Dummy4;
  int (*HalTimerConvertPerformanceCounterToAuxiliaryCounter)(unsigned __int64, unsigned __int64 *, unsigned __int64 *);
  int (*HalTimerConvertAuxiliaryCounterToPerformanceCounter)(unsigned __int64, unsigned __int64 *, unsigned __int64 *);
  int (*HalTimerQueryAuxiliaryCounterFrequency)(unsigned __int64 *);
  int (*HalConnectThermalInterrupt)(unsigned __int8 (*)(KINTERRUPT *, void *));
  unsigned __int8 (*HalIsEFIRuntimeActive)();
  unsigned __int8 (*HalTimerQueryAndResetRtcErrors)(unsigned __int8);
  void (*HalAcpiLateRestore)();
  int (*KdWatchdogDelayExpiration)(unsigned __int64 *);
  int (*HalGetProcessorStats)(HAL_PROCESSOR_STAT_TYPE, unsigned int, unsigned int, unsigned __int64 *);
  unsigned __int64 (*HalTimerWatchdogQueryDueTime)(unsigned __int8);
  int (*HalConnectSyntheticInterrupt)(unsigned __int8 (*)(KINTERRUPT *, void *));
  void (*HalPreprocessNmi)(unsigned int);
  int (*HalEnumerateEnvironmentVariablesWithFilter)(unsigned int, unsigned __int8 (*)(const _GUID *, const wchar_t *), void *, unsigned int *);
  int (*HalCaptureLastBranchRecordStack)(unsigned int, HAL_LBR_ENTRY *, unsigned int *);
  unsigned __int8 (*HalClearLastBranchRecordStack)();
  int (*HalConfigureLastBranchRecord)(unsigned int, unsigned int);
  unsigned __int8 (*HalGetLastBranchInformation)(unsigned int *, unsigned int *);
  void (*HalResumeLastBranchRecord)(unsigned __int8);
  int (*HalStartLastBranchRecord)(unsigned int, unsigned int *);
  int (*HalStopLastBranchRecord)(unsigned int);
  int (*HalIommuBlockDevice)(void *);
  int (*HalIommuUnblockDevice)(EXT_IOMMU_DEVICE_ID *, void **);
  int (*HalGetIommuInterface)(unsigned int, DMA_IOMMU_INTERFACE *);
  int (*HalRequestGenericErrorRecovery)(void *, unsigned int *);
  int (*HalTimerQueryHostPerformanceCounter)(unsigned __int64 *);
  int (*HalTopologyQueryProcessorRelationships)(unsigned int, unsigned int, unsigned __int8 *, unsigned __int8 *, unsigned __int8 *, unsigned int *, unsigned int *);
  void (*HalInitPlatformDebugTriggers)();
  void (*HalRunPlatformDebugTriggers)(unsigned __int8);
  void *(*HalTimerGetReferencePage)();
  int (*HalGetHiddenProcessorPowerInterface)(HIDDEN_PROCESSOR_POWER_INTERFACE *);
  unsigned int (*HalGetHiddenProcessorPackageId)(unsigned int);
  unsigned int (*HalGetHiddenPackageProcessorCount)(unsigned int);
  int (*HalGetHiddenProcessorApicIdByIndex)(unsigned int, unsigned int *);
  int (*HalRegisterHiddenProcessorIdleState)(unsigned int, unsigned __int64);
  void (*HalIommuReportIommuFault)(unsigned __int64, FAULT_INFORMATION *);
  unsigned __int8 (*HalIommuDmaRemappingCapable)(EXT_IOMMU_DEVICE_ID *, unsigned int *);
};

Target Discovery/目标发现

​ Armed with the information and structure of HalPrivateDispatchTable, if we revisit the mechanisms that InfinityHook used we will see the original is patched, but there exists some functions of interest. All of these occur within EtwpLogKernelEvent. We can see three of which appear to be good targets.
​ 有了 HalPrivateDispatchTable 的信息和结构,如果我们重新审视 InfinityHook 使用的机制,我们会看到原始机制被修补了,但存在一些感兴趣的功能。所有这些都发生在 EtwpLogKernelEvent 中。我们可以看到其中三个似乎是很好的目标。

if( /* etw init conditions */ )
{
  v33 = EtwpReserveTraceBuffer(v14, v15 + 0x10, &v59, &v55, a6);
  //
  // ... [etc]
  //
  goto LABEL_19;
}
if ( /* etw init conditions */  )
{
  v34 = EtwpReserveWithPebsIndex(v14, 0x524, v15, &v59, &v55, a6);
}
else
{
  //
  // ... [etc]
  //
  v34 = EtwpReserveWithPmcCounters(v14, a5, v15, &v59, &v55, a6);
}

​ The one that doesn’t have any information published on it currently is EtwpReserveWithPmcCounters. If the ETW Logger is setup in the correct manner, this function will execute, and if we look at the internals we’ll see another target ripe for patching.
​ 目前没有发布任何信息的是 EtwpReserveWithPmcCounters。如果 ETW 记录器的设置方式正确,则此函数将执行,如果我们查看内部,我们将看到另一个适合修补的目标。

signed __int64 __fastcall EtwpReserveWithPmcCounters(
        WMI_LOGGER_CONTEXT *LoggerContext,
        UINT16 HookId,
        UINT64 AuxSize,
        void *BufferHandle,
        LARGE_INTEGER *TimeStamp,
        UINT64 Flags)
{
  volatile unsigned int CountersCount;
  unsigned int CtrIndex;
  unsigned int RequiredSize;
  unsigned __int8 CurrentIrql;
  struct_TraceBuffer *TraceBuffer;
  struct_TraceBuffer *pTracebuf;
  struct _HAL_PMC_COUNTERS *PmcEnabledForProc; 
  _ETW_PMC_SUPPORT *PmcData;

  PmcData = LoggerContext->PmcData;
  CountersCount = PmcData->CountersCount;
  CtrIndex = 8 * CountersCount + 0x10;
  RequiredSize = CtrIndex + AuxSize;
  CurrentIrql = KeGetCurrentIrql();
  if ( CurrentIrql < DISPATCH_LEVEL )
  {
    KeGetCurrentIrql();
    __writecr8(DISPATCH_LEVEL);
  }
  TraceBuffer = EtwpReserveTraceBuffer(&LoggerContext->LoggerId, RequiredSize, BufferHandle, TimeStamp, Flags);
  pTracebuf = TraceBuffer;
  if ( TraceBuffer )
  {
    TraceBuffer->TimeStamp = *TimeStamp;
    TraceBuffer->TotalCounters = RequiredSize;
    TraceBuffer->HookId = HookId;
    TraceBuffer->Flags = Flags | (CountersCount << 8) | 0xC0110000;
    PmcEnabledForProc = PmcData->ProcessorCtrs[KeGetPcr()->Prcb.Number];
    if ( PmcEnabledForProc )
      HalPrivateDispatch->HalpCollectPmcCounters(PmcEnabledForProc, &TraceBuffer->Counters);
    else
      memset(&TraceBuffer->Counters, 0, 8 * CountersCount);
    if ( CurrentIrql < DISPATCH_LEVEL )
      __writecr8(CurrentIrql);
    return pTracebuf + CtrIndex;
  }
  else
  {
    if ( CurrentIrql < DISPATCH_LEVEL )
      __writecr8(CurrentIrql);
    return 0;
  }
}

​ The target of interest is immediately obvious, HalPrivateDispatch->HalpCollectPmcCounters(...). If we follow the call chain on a live system you’ll notice something akin to this:
​ 感兴趣的目标是显而易见的。 HalPrivateDispatch->HalpCollectPmcCounters(...) 如果我们遵循实时系统上的调用链,您会注意到类似于以下内容的内容:

KernelBase.dll!SleepEx
|- ntdll.dll!NtDelayExecution
 |    |- ntoskrnl.exe!KiSystemServiceExitPico
 |    |    |- ntoskrnl.exe!PerfInfoLogSysCallEntry
 |    |    |    |- ntoskrnl.exe!EtwTraceKernelEvent
 |    |    |    |    |- ntoskrnl.exe!EtwpLogKernelEvent
 |    |    |    |    |    |- ntoskrnl.exe!EtwpReservePmcCounters
 |    |    |    |    |    |    |- ntoskrnl.exe!HalpCollectPmcCounters   ---> | These only occur when the ETW logger is configured appropriately.

​ The ETW configuration will result in the NT Kernel Logger having an output with information like this:
​ ETW 配置将导致 NT Kernel Logger 输出包含如下信息:

Logger Name           : NT Kernel Logger
Logger Id             : ffff
Logger Thread Id      : 00000000000012A4
Buffer Size           : 8192
Maximum Buffers       : 118
Minimum Buffers       : 96
Number of Buffers     : 118
Free Buffers          : 70
Buffers Written       : 106898
Events Lost           : 0
Log Buffers Lost      : 0
Real Time Buffers Lost: 0
Flush Timer           : 0
Age Limit             : 0
Log File Mode         : Secure PersistOnHybridShutdown SystemLogger
Maximum File Size     : 0
Log Filename          : EdgyNameHere
Trace Flags           : SYSCALL
PoolTagFilter         : *

​ This is just a quick validation to visualize that we can hook SYSCALL in a PG-compliant manner (i.e. it doesn’t bugcheck the machine).
​ 这只是一个快速验证,以可视化我们可以 SYSCALL 以符合 PG 的方式挂钩(即它不会对机器进行错误检查)。

DIY… MOSTLY DIY.../主要

​ So, how do we do utilize this information? It’s relatively straightforward, let’s lay out the steps.
那么,我们如何利用这些信息呢?这相对简单,让我们列出步骤。

  1. Locate HalPrivateDispatchTable.

    找到 HalPrivateDispatchTable

  2. Locate System Call Handler.
    找到系统调用处理程序。

  3. Acquire the ETW Event Id for SYSCALL logging.
    获取用于 SYSCALL 日志记录的 ETW 事件 ID。

  4. Configure ETW session programmatically.
    以编程方式配置 ETW 会话。

  5. Configure the appropriate event trace class data.
    配置相应的事件跟踪类数据。

  6. Swap pointer in the HalPrivateDispatchTable.
    交换指针 HalPrivateDispatchTable

  7. Enjoy yet another PG-compliant hook.
    享受另一个符合 PG 标准的钩子。

δ Locating HalPrivateDispatchTable/查找 HalPrivateDispatchTable

​ The acquisition of a pointer to the HalPrivateDispatchTable can be done like so:
​ 可以按如下方式获取指向 HalPrivateDispatchTable 的指针:

UNICODE_STRING target = RTL_CONSTANT_STRING( L"HalPrivateDispatchTable" );
HalPrivateDispatchTable = reinterpret_cast< PHAL_PRIVATE_DISPATCH_TABLE >( MmGetSystemRoutineAddress( &target ) );

if ( !HalPrivateDispatchTable )
    return STATUS_RESOURCE_UNAVAILABLE;

δ Additional ETW Configuration/其他 ETW 配置

​ To get this functional, it was noted that in addition to building the trace property structure and modifying it to enable tracing of SYSCALL we would have to configure various trace controls via ZwTraceControl. For this, you will need to handle at a minimum the ZwTraceControl function codes EtwStartLoggerCode, EtwStopLoggerCode, and EtwUpdateLoggerCode. An excerpt is provided below from the proof-of-concept.
​ 为了实现此功能,需要注意的是,除了构建跟踪属性结构并修改它以启用跟踪 SYSCALL 之外,我们还必须通过 ZwTraceControl 配置各种跟踪控件。为此,至少需要处理 ZwTraceControl 函数代码 EtwStartLoggerCode、EtwStopLoggerCode 和 EtwUpdateLoggerCode。下面提供了概念验证的摘录。

switch (operation) {
    case kl_trace_operation::start: {
        status = ZwTraceControl(EtwStartLoggerCode, pproperty, sizeof(KL_TRACE_PROPERTIES), pproperty, sizeof(KL_TRACE_PROPERTIES), &return_length);
        break;
    }
    case kl_trace_operation::end: {
        status = ZwTraceControl(EtwStopLoggerCode, pproperty, sizeof(KL_TRACE_PROPERTIES), pproperty, sizeof(KL_TRACE_PROPERTIES), &return_length);
        break;
    }
    case kl_trace_operation::syscall: {
        pproperty->EnableFlags |= EVENT_TRACE_FLAG_SYSTEMCALL;
        status = ZwTraceControl(EtwUpdateLoggerCode, pproperty, sizeof(KL_TRACE_PROPERTIES), pproperty, sizeof(KL_TRACE_PROPERTIES), &return_length);
        break;
    }
}
const GUID session_guid = { 0x9E814AAD, 0x3204, 0x11D2, { 0x9A, 0x82, 0x0, 0x60, 0x8, 0xA8, 0x69, 0x39 } };
pproperty = nt::trace::build_property( L"NT Kernel Logger", &session_guid, EVENT_TRACE_BUFFERING_MODE );

​ In addition to this, we need to configure a few trace information blocks via ZwSetSystemInformation with the SystemPerformanceTraceInformation class to setup a list of counters and profiling information that allow the PMC collection routines to function. Notably, the EventTraceProfileCounterListInformation and EventTraceProfileEventListInformation. In the interest of space, the code has been omitted from this article.
​ 除此之外,我们还需要通过 ZwSetSystemInformation 和 SystemPerformanceTraceInformation 类配置一些跟踪信息块,以设置允许 PMC 收集例程运行的计数器和分析信息列表。值得注意的是,EventTraceProfileCounterListInformation 和 EventTraceProfileEventListInformation。由于篇幅原因,本文省略了该代码。

δ The HalCollectPmcCounters Hook/HalCollectPmcCounters 挂钩

​ The excerpt given below is all that is required within the hook routine to apply it system wide during the SYSCALL tracing. Walk the stack, verify that the hook IDs / event IDs match the targeted event, if it matches then replace the element on the stack that contains the address to the target system routine.
​ 下面给出的摘录是在 SYSCALL 跟踪期间在系统范围内应用它所需的全部内容。遍历堆栈,验证钩子 ID/事件 ID 是否与目标事件匹配,如果匹配,则替换堆栈上包含目标系统例程地址的元素。

bool is_correct_event_target(void** stack_current) {
    const auto event_id_1 = *reinterpret_cast(stack_current);
    const auto event_id_0 = *reinterpret_cast(stack_current + 1);
    return event_id_1 == target_value_1 && event_id_0 == target_value_1;
}

void process_syscall(stack<64>& sp) {
    const auto target_fn = reinterpret_cast<void**>(sp.at(9));

    // Example that replaces the return address on stack for NtQuerySystemInformation
    // with our hk_NtQuerySystemInformation, allowing us to hook the call system wide
    // in a PG-compliant manner.
    //
    if (*target_fn == o__nt_query_system_information)
        *target_fn = &hkd__nt_query_system_information;
}

void hkd__hal_collect_pmc_counters(HAL_PMC_COUNTERS* pmc_ctrs, unsigned long long* trace_buffer_end) 
{
    // Call original to populate appropriate data structures; avoid unnecessary overhead.
    //
    o__hal_collect_pmc_counters(pmc_ctrs, trace_buffer_end);

    if (!pmc_ctrs || !trace_buffer_end)
        return;

    const auto hook_id = *reinterpret_cast(reinterpret_cast(trace_buffer_end) - 10);
    if (hook_id != target_value_1)
        return;
    
    stack<64> stk(get_pcr()->prcb->rsp_base, _AddressOfReturnAddress());

    auto is_correct_event_target = [target_value_1, target_value_0](stack<64>& sp) {
        const auto event_id_1 = *sp.as<uint16_t*>();
        const auto event_id_0 = *sp.next().as<uint32_t*>();
        return event_id_1 == target_value_1 && event_id_0 == target_value_0;
    };

    auto curr_stack = stk.find_frame( is_correct_event_target );
    if (!curr_stack.valid())
        return;

    curr_stack += 2;

    auto target_sp = curr_stack.find_first_within({ syscall_handler_begin, syscall_handler_end });
    
    if(target_sp.valid())
        process_syscall(target_sp);
}

**SYSCALL Compatibility SYSCALL 兼容性
**

​ It’s worth noting that the SYSCALL hook information provided here is relatively terse, and if you test on your target Windows version you will notice that only ntoskrnl system calls are captured. To capture win32k system calls using this same hook several modifications will need to be made. This is not covered in this article, but it can be achieved relatively straightforward by processing the exports of win32k.sys, searching for __win32kstub, and then acquiring the SYSCALL index and adding it to a table that can be searched during runtime by address/index.
​ 值得注意的是,此处提供的 SYSCALL 钩子信息相对简洁,如果您在目标 Windows 版本上进行测试,您会注意到仅捕获 ntoskrnl 系统调用。若要使用相同的挂钩捕获 win32k 系统调用,需要进行一些修改。本文不对此进行介绍,但可以通过处理 win32k.sys 的导出来实现,搜索 __win32kstub ,然后获取索引并将其添加到可在运行时按地址/ SYSCALL 索引搜索的表中。

δ The NtQuerySystemInformation Hook /NtQuerySystemInformation 挂钩

​ Since we needed a test case for this hook, I’ve provided the hook and associated helper functions for those interested in replicating this to some degree.
​ 由于我们需要这个钩子的测试用例,所以我为那些有兴趣在某种程度上复制它的人提供了钩子和相关的帮助程序函数。

static NTSTATUS hkd__nt_query_system_information(
    SYSTEM_INFORMATION_CLASS system_information_class,
    PVOID system_information,
    ULONG system_information_length,
    PULONG return_length
) {
    NTSTATUS status = o_NtQuerySystemInformation(system_information_class, system_information, system_information_length, return_length);

    if (is_target_process()) {
        log_system_information(system_information_class, system_information, system_information_length, status);
        if (NT_SUCCESS(status)) {
            modify_system_information(system_information_class, system_information, system_information_length);
        }
    }

    return status;
}

static bool is_target_process() {
    return !strcmp(PsGetProcessImageFileName(PsGetCurrentProcess()), "<target>");
}

static void log_system_information(SYSTEM_INFORMATION_CLASS system_information_class, PVOID system_information, ULONG system_information_length, NTSTATUS status) {
    const char* class_name = xnt::to_string(system_information_class);
    const char* process_name = PsGetProcessImageFileName(PsGetCurrentProcess());
    ULONG_PTR process_id = (ULONG_PTR)PsGetProcessId(PsGetCurrentProcess());

    if (class_name == nullptr) {
        LOG_INFO("%s (%I64d) :: NtQuerySystemInformation( %#x, 0x%p, %#x )", process_name, process_id, system_information_class, system_information, system_information_length);
    } else {
        LOG_INFO("%s (%I64d) :: NtQuerySystemInformation( %s, 0x%p, %#x )", process_name, process_id, class_name, system_information, system_information_length);
    }
}

static void modify_system_information(SYSTEM_INFORMATION_CLASS system_information_class, PVOID system_information, ULONG system_information_length) {
    PMDL mdl = IoAllocateMdl(system_information, system_information_length, FALSE, FALSE, NULL);
    
    if (!mdl) {
        LOG_INFO("Failed to allocate MDL in %s\n", __FUNCTION__);
        return;
    }

    __try {
        MmProbeAndLockPages(mdl, UserMode, IoWriteAccess);
        PVOID buffer = MmGetSystemAddressForMdlSafe(mdl, NormalPagePriority | MdlMappingNoExecute);
        
        if (!buffer) {
            goto __exit;
        }

        if (system_information_class == SystemKernelDebuggerInformation) {
            auto kdbg_info = reinterpret_cast<PSYSTEM_KERNEL_DEBUGGER_INFORMATION>(buffer);
            kdbg_info->KernelDebuggerEnabled = FALSE;
            kdbg_info->KernelDebuggerNotPresent = TRUE;
        } else if (system_information_class == SystemCodeIntegrityInformation) {
            auto code_integrity_info = reinterpret_cast<PSYSTEM_CODEINTEGRITY_INFORMATION>(buffer);
            code_integrity_info->CodeIntegrityOptions &= ~CODEINTEGRITY_OPTION_TESTSIGN;
            code_integrity_info->CodeIntegrityOptions |= CODEINTEGRITY_OPTION_ENABLED;
        }
    }
    __except (EXCEPTION_EXECUTE_HANDLER) {
        status = GetExceptionCode();
        LOG_INFO("Exception in %s :: %#x\n", __FUNCTION__, status);
        IoFreeMdl(mdl);
    }
    //
    // ...additional handling and resource release...
    //
}

​ You can find the list of NtQuerySystemInformation classes and list of IOCTLs at the linked references.
​ 可以在链接的引用中找到 NtQuerySystemInformation 类的列表和 IOCTL 的列表。

**Discoverability 可发现性
**

​ It’s worth noting that this hook, like all other ETW-based hooks, is straightforward to detect through the call-stack. Validation of the HalPrivateDispatchTable is another vector that comes to mind. Call-stack spoofing is sufficient to circumvent the checks on the stack. An entire post could cover the vectors to discover the use of this hook, many of them are quite interesting, but we will refrain for now to keep things brief.
​ 值得注意的是,与所有其他基于 ETW 的挂钩一样,此挂钩很容易通过调用堆栈进行检测。验证 HalPrivateDispatchTable 是我想到的另一个向量。调用堆栈欺骗足以规避堆栈上的检查。整篇文章可以涵盖发现这个钩子使用的矢量,其中许多都非常有趣,但我们现在不会保持简短。

Demonstration Image 演示图像

​ Once all the steps have been implemented, the logs of this example are given below:
​ 实施完所有步骤后,此示例的日志如下:

image

Conclusion 结论

​ This method of hooking SYSCALL in a PG-compliant manner is interesting, but it’s much more versatile than meets the eye. I wrote this blog post to introduce it, and in the future I’ll cover more applications that extend beyond just SYSCALLs. There are numerous ways to instrument operations in the kernel and stay PG-compliant, the majority of which require a lot more technical background and lack additional resources that allow for brevity in writing. One of these is planned for a future post, but it will be quite a blob. Some future research ideas involve continued investigation into the TracePoint and ETW APIs for additional indirect mechanisms that can be leveraged to control system operations.
​ 这种以符合 PG 的方式钩住 SYSCALL 的方法很有趣,但它比看起来要通用得多。我写了这篇博文来介绍它,将来我将介绍更多超出 SYSCALLs .有许多方法可以检测内核中的操作并保持 PG 合规性,其中大多数方法需要更多的技术背景,并且缺乏额外的资源来简化编写。其中一个计划在未来的帖子中发布,但它将是一个相当大的斑点。未来的一些研究思路涉及继续研究 TracePoint 和 ETW API,以寻找可用于控制系统操作的其他间接机制。

It’s important that I credit the following people:
重要的是,我要感谢以下人员:

  • Everdox (N.Peterson) for sharing information of his prior discoveries and applications in ESEA and Riot Vanguard following private disclosure.
    Everdox (N.Peterson) 在私人披露后分享了他之前在 ESEA 和 Riot Vanguard 中的发现和应用信息。
  • iPower for coordinated research efforts and future research ideas; also proof-reading this post.
    iPower用于协调研究工作和未来研究思路;也校对这篇文章。

If you’re interested in this sort of research, feel free to reach out to me @daax_rynd. As always, hope you’ve enjoyed, all the best to the readers.
如果您对此类研究感兴趣,请随时@daax_rynd与我联系。一如既往,希望您喜欢,祝读者一切顺利。