ETW HOOK原理探析

news2025/1/17 2:56:34

ETW HOOK研究

文章目录

前言

关于ETW是什么我就不多说了,可以通过微软的相关文档了解到。据网上得知这项技术最早被披露于2345的驱动中,一位工程师将其代码逆向还原之后大白于天下。随后各大安全厂商相继使用这种技术实现监控系统调用、内存页错误等。它是一个相对于VT来说更稳定,更简单的系统监控方式。

原理探究

【环境】 W i n 10   1903   x 64 ( 19045.3208 之前) \textcolor{green}{【环境】Win10\ 1903\ x64(19045.3208之前)} 【环境】Win10 1903 x6419045.3208之前)

有逆向过系统模块的小伙伴肯定没少发现在很多函数调用中都会写入相关调用信息到事件中,例如在nt内核模块的系统调用函数 K i S y s t e m C a l l 64 \textcolor{cornflowerblue}{KiSystemCall64} KiSystemCall64

在这里插入图片描述

​ 图 2.1 KiSystemCall64

所有系统调用都会走到这个函数,然后再根据调用号派发到最终的目标函数上,而红框中的r10寄存器保存的就是最终的目标函数。我们发现r10的值是保存在栈中的,如果能够通过某种方式修改到栈中的数据,那么就可以实现动态HOOK系统调用了。请记住这一点,这将会是ETW HOOK能够监控系统调用的一个重要前提条件。

我们继续跟进 P e r f I n f o L o g S y s C a l l E n t r y \textcolor{cornflowerblue}{PerfInfoLogSysCallEntry} PerfInfoLogSysCallEntry函数去分析,当然默认情况下系统是不会走loc_14040D253这个分支的,因为第一个红框部分的条件不满足。

在这里插入图片描述

​ 图 2.2 PerfInfoLogSysCallEntry

E t w T r a c e S i l o K e r n e l E v e n t \textcolor{cornflowerblue}{EtwTraceSiloKernelEvent} EtwTraceSiloKernelEvent内部:

在这里插入图片描述

​ 图 2.3 EtwTraceSiloKernelEvent

跟进 E t w p L o g K e r n e l E v e n t \textcolor{cornflowerblue}{EtwpLogKernelEvent} EtwpLogKernelEvent函数,该函数内部会将系统调用相关的数据封装成日志,再调用 E t w p R e s e r v e T r a c e B u f f e r \textcolor{cornflowerblue}{EtwpReserveTraceBuffer} EtwpReserveTraceBuffer函数将数据写到日志缓冲区中。在 E t w p R e s e r v e T r a c e B u f f e r \textcolor{cornflowerblue}{EtwpReserveTraceBuffer} EtwpReserveTraceBuffer中有个关键的地方

在这里插入图片描述

​ 图 2.4 EtwpReserveTraceBuffer

的条件不满足时就会调用日志上下文只保存的函数指针,该结构体类型为_WMI_LOGGER_CONTEXT,在我的环境下是这样的:

1: kd> dt nt!_WMI_LOGGER_CONTEXT
   +0x000 LoggerId         : Uint4B
   +0x004 BufferSize       : Uint4B
   +0x008 MaximumEventSize : Uint4B
   +0x00c LoggerMode       : Uint4B
   +0x010 AcceptNewEvents  : Int4B
   +0x014 EventMarker      : [2] Uint4B
   +0x01c ErrorMarker      : Uint4B
   +0x020 SizeMask         : Uint4B
   +0x028 GetCpuClock      : Uint8B
   +0x030 LoggerThread     : Ptr64 _ETHREAD
 ...

偏移0x28就是 G e t C p u C l o c k \textcolor{cornflowerblue}{GetCpuClock} GetCpuClock函数指针,别的版本的系统可能偏移不一样。

系统启动之后会初始化一些系统的ETW日志,并初始化它们的日志上下文,然后讲这些上下文存放到一个全局变量中,全局变量的结构就是_WMI_LOGGER_CONTEXT列表。它恰好位于 E t w p D e b u g g e r D a t a + 0 x 10 \textcolor{orange}{EtwpDebuggerData+0x10} EtwpDebuggerData+0x10位置处。系统ETW日志初始化工作是在函数 E t w I n i t i a l i z e S i l o S t a t e \textcolor{cornflowerblue}{EtwInitializeSiloState} EtwInitializeSiloState

在这里插入图片描述

​ 图 2.5 EtwInitializeSiloState

从这个上下文列表中就可以看到当前系统上已注册的ETW日志都有

Circular Kernel Context Logger
Eventlog-Security
AppModel
DefenderApiLogger
DefenderAuditLogger
DiagLog
Diagtrack-Listener
EventLog-Application
EventLog-System
LwtNetLog
Microsoft-Windows-Rdp-Graphics-RdpIdd-Trace
NetCore
NtfsLog
RadioMgr
UBPM
WdiContextLog
WiFiSession
PerfDiag Logger
umstartup
WUDFTrace
UserNotPresentTraceSession
COM
Terminal-Services-LSM
Terminal-Services-RCM
UserMgr
WFP-IPsec Diagnostics
MpWppTracing-20231109-203630-00000003-ffffffff
ScreenOnPowerStudyTraceSession
WindowsUpdate_trace_log
MSDTC_TRACE_SESSION
SHS-11092023-203650-7-7f
ECCB175F-1EB2-43DA-BFB5-A8D58A40A4D7
Terminal-Services-LSM-ApplicationLag-4140

并且看了这些系统日志,发现它们的 F l a g s   &   0 x 8000000 \textcolor{orange}{Flags\ \&\ 0x8000000} Flags & 0x8000000均是为0的,不仅如此,就算是用户自己开启ETW日志,其上下文的Flags字段的值同样也不满足图2.4中红框①的条件。换句话说就是,系统开启的ETW日志和我们开启的ETW日志,都会在记录日志的时候调用位于日志上下文中的 G e t C p u C l o c k \textcolor{cornflowerblue}{GetCpuClock} GetCpuClock函数指针。

回过头来再看图2.1第一个红框部分的全局标志PerfGlobalGroupMask,共16个字节,在函数 E t w p U p d a t e G l o b a l G r o u p M a s k s \textcolor{cornflowerblue}{EtwpUpdateGlobalGroupMasks} EtwpUpdateGlobalGroupMasks中会设置该标志低8个字节为0x40。而该函数会在 E t w p S t a r t L o g g e r \textcolor{cornflowerblue}{EtwpStartLogger} EtwpStartLogger中调用,但前提是满足:

在这里插入图片描述

​ 图 2.6 EtwpStartLogger判断日志类型

应用层和内核层都可以通过导出的函数 N t T r a c e C o n t r o l \textcolor{cornflowerblue}{NtTraceControl} NtTraceControl调用 E t w p S t a r t L o g g e r \textcolor{cornflowerblue}{EtwpStartLogger} EtwpStartLogger

在这里插入图片描述

​ 图 2.7 NtTraceControl

NTSTATUS NtTraceControl(
	_In_ ULONG ControlCode,
	_In_ _WMI_LOGGER_INFORMATION* pLoggerInfo,
	_In_ ULONG LoggerInfoSize,
	_Out_ _WMI_LOGGER_INFORMATION* pOutLoggerInfo,
	_In_ ULONG OutLoggerInfoSize,
	_Out_ PULONG RetSize);
  • ControlCode:控制码,取值和对应功能见下表

    取值功能
    1开启ETW日志追踪
    2停止ETW日志追踪
    3查询ETW日志追踪
    4更新ETW日志追踪
    5刷新ETW日志追踪
    6增加追踪文件
  • pLoggerInfo:日志基本配置信息,_WMI_LOGGER_INFORMATION结构如下:

    typedef struct _WMI_LOGGER_INFORMATION
    {
    	WNODE_HEADER Wnode;						// WNODE_HEADER是公开的,这里就不贴上来了
    	ULONG BufferSize;
    	ULONG MinimumBuffers;
    	ULONG MaximumBuffers;
    	ULONG MaximumFileSize;
    	ULONG LogFileMode;
    	ULONG FlushTimer;
    	ULONG EnableFlags;
    	union
    	{
    		LONG AgeLimit;
    		LONG FlushThreshold;
    	};
    	ULONG Wow;
    	LONG Padding_719;
    	union
    	{
    		PVOID LogFileHandle;
    		ULONGLONG LogFileHandle64;
    	};
    	union
    	{
    		ULONG NumberOfBuffers;
    		ULONG InstanceCount;
    	};
    	union
    	{
    		ULONG FreeBuffers;
    		ULONG InstanceId;
    	};
    	union
    	{
    		ULONG EventsLost;
    		ULONG NumberOfProcessors;
    	};
    	ULONG BuffersWritten;
    	union
    	{
    		ULONG LogBuffersLost;
    		ULONG Flags;
    	};
    	ULONG RealTimeBuffersLost;
    	union
    	{
    		PVOID LoggerThreadId;
    		ULONGLONG LoggerThreadId64;
    	};
    	union
    	{
    		UNICODE_STRING LogFileName;
    		STRING64 LogFileName64;
    	};
    	union
    	{
    		UNICODE_STRING LoggerName;
    		STRING64 LoggerName64;
    	};
    	ULONG RealTimeConsumerCount;
    	ULONG SpareUlong;
    	union
    	{
    		union
    		{
    			PVOID LoggerExtension;
    			ULONGLONG LoggerExtension64;
    		};
    	}  DUMMYUNIONNAME10;
    } WMI_LOGGER_INFORMATION, * PWMI_LOGGER_INFORMATION;
    

剩下的参数顾名思义,不再解释。

只需要关注_WMI_LOGGER_INFORMATION结构中的几个字段:

  • EnableFlags - 启用追踪标志。如果想启用syscall的追踪,需要将该标志设置为EVENT_TRACE_FLAG_SYSTEMCALL(0x80)。更多标志参见:https://learn.microsoft.com/zh-cn/windows/win32/api/evntrace/ns-evntrace-event_trace_properties

  • Wnode.Guid - 日志GUID。可使用系统定义,亦可以自己定义。系统定义的GUID和对应的LoggerName有很多,这里只列举两个:

    日志名GUID
    Circular Kernel Context Logger{54DEA73A-ED1F-42A4-AF71-3E63D056F174}
    NT Kernel Logger{9E814AAD-3204-11D2-9A82-006008A86939}
  • LoggerName - 日志名。不可为空。

  • Wnode.Flags - 标志。必须包含 WNODE_FLAG_TRACED_GUID 0x20000),以指示结构包含事件跟踪信息。

  • Wnode.ClientContext - 记录每个事件的时间戳时要使用的时钟解析。这里是一个关键地方,系统会根据这个字段的取值来设置日志上下文中的 G e t C p u C l o c k \textcolor{cornflowerblue}{GetCpuClock} GetCpuClock函数指针。 默认值为 QPC查询性能计数器。详情参考:https://learn.microsoft.com/zh-cn/windows/win32/etw/wnode-header

  • LogFileMode - 日志文件模式。详情参考:https://learn.microsoft.com/zh-cn/windows/win32/etw/logging-mode-constants

为了了解如何正确设置上述结构以便在内核中开启ETW日志追踪,需要着重关注 E t w p S t a r t L o g g e r \textcolor{cornflowerblue}{EtwpStartLogger} EtwpStartLogger N t T r a c e C o n t r o l \textcolor{cornflowerblue}{NtTraceControl} NtTraceControl函数,这两个多数都是在检查_WMI_LOGGER_INFORMATION中的数据设置是否合法。而决定我们开启ETW系统日志成败的检查有这几个地方

在这里插入图片描述

​ 图 2.8 NtTraceControl检查LoggerInfo

在这里插入图片描述

​ 图2.9 EtwpValidateLoggerInfo

由此看出,需要设置 W n o d e . B u f f e r S i z e   > =   0 x B 0 \textcolor{orange}{Wnode.BufferSize\ >=\ 0xB0} Wnode.BufferSize >= 0xB0,而0xB0正好是_WMI_LOGGER_INFORMATION结构的大小;需要设置Wnode.Flags包含WNODE_FLAG_TRACED_GUID0x20000)。

在这里插入图片描述

​ 图 2.10 检查LogFileMode

这里涉及到LogFileMode和MaxmunFile*相关的合法性检查,但我们不关心MaxmunFile*相关的字段,所以满足条件的LogFileMode有很多,例如:0x400,0x700,0x900…而在后面的函数 E t w p I n i t L o g g e r C o n t e x t \textcolor{cornflowerblue}{EtwpInitLoggerContext} EtwpInitLoggerContext中会将LogFileMode传递到日志上下文中的LoggerMode。这又触碰到了图2.6中的检查。所以LogFileMode应该设置为 E V E N T _ T R A C E _ B U F F E R I N G _ M O D E   ∣   E V E N T _ T R A C E _ S Y S T E M _ L O G G E R _ M O D E \textcolor{orange}{EVENT\_TRACE\_BUFFERING\_MODE\ |\ EVENT\_TRACE\_SYSTEM\_LOGGER\_MODE} EVENT_TRACE_BUFFERING_MODE  EVENT_TRACE_SYSTEM_LOGGER_MODE

对于Wnode.ClientContext的选值建议从简出发,选择3。因为3对应的 G e t C p u C l o c k \textcolor{cornflowerblue}{GetCpuClock} GetCpuClock指向的函数最简单,只有一句 r d t s c \textcolor{cornflowerblue}{rdtsc} rdtsc

到这里,已经把重要字段的填值都弄明白了,可以开始进入到内核开启ETW日志的程序了。

内核开启ETW日志

Z w T r a c e C o n t r o l \textcolor{cornflowerblue}{ZwTraceControl} ZwTraceControl是内核导出的,只需在使用前定义一下原型即可。

通过前面的分析,可以将开启、更新和停止ETW日志都封装成一个函数:

NTSTATUS ChangeETWStatus(ETW_STATUS_ENUM Status, ETW_TRACE_FLAG TraceFlag)
{
	ULONG ulRetSize;
	// 填充etw logger注册信息
	WMI_LOGGER_INFORMATION LoggerInfo = { 0 };
	// 开启 Syscall的追踪
	LoggerInfo.EnableFlags = TraceFlag;
	// 某个日志对应的GUID
	LoggerInfo.Wnode.Guid = GUID_CKCL;
	// 使用CPU周期
	LoggerInfo.Wnode.ClientContext = 3;
	// 初始化日志名
	RtlInitUnicodeString(&LoggerInfo.LoggerName, CKCL_LOGGER_NAME);
	// 为了通过EtwpValidateLoggerInfo的校验
	LoggerInfo.Wnode.BufferSize = sizeof(LoggerInfo);
	LoggerInfo.Wnode.Flags = WNODE_FLAG_TRACED_GUID;
	// 需要不满足 win10 1903 x64 nt!EtwpStartLogger+161 的条件,并且满足 win10 1903 x64 nt!EtwpStartLogger+80A 的条件
	LoggerInfo.LogFileMode = EVENT_TRACE_BUFFERING_MODE | EVENT_TRACE_SYSTEM_LOGGER_MODE;
	
	return ZwTraceControl(Status, &LoggerInfo, sizeof(LoggerInfo), &LoggerInfo, sizeof(LoggerInfo), &ulRetSize);
}

相关枚举值

typedef enum _ETW_STATUS_ENUM
{
	E_ETW_STATUS_START = 1,
	E_ETW_STATUS_STOP = 2,
	E_ETW_STATUS_UPDATE = 4
}ETW_STATUS_ENUM;

typedef enum _ETW_TRACE_FLAG
{
	E_ETW_TRACE_SYSCALL = 0x80,
	E_ETW_TRACE_ALPC = 0x00100000
}ETW_TRACE_FLAG;

注意:不建议使用 N t T r a c e C o n t r o l ,因为换成别的系统会有蓝屏的风险。内核下建议使用 Z w ∗ 系函数代替 N t ∗ 系函数。 \textcolor{BrickRed}{注意:不建议使用NtTraceControl,因为换成别的系统会有蓝屏的风险。内核下建议使用Zw*系函数代替Nt*系函数。} 注意:不建议使用NtTraceControl,因为换成别的系统会有蓝屏的风险。内核下建议使用Zw系函数代替Nt系函数。

HOOK ETW

修改ETW日志上下文

在第2小节中,我们成功控制了ETW日志状态,接下来就是HOOK我们控制的ETW日志上下文,将 G e t C p u C l o c k \textcolor{cornflowerblue}{GetCpuClock} GetCpuClock修改成我们自己的函数,然后在我们自己的函数中进行后续处理。观察ETW日志上下文列表( E t w p D e b u g g e r D a t a + 0 x 10 \textcolor{orange}{EtwpDebuggerData+0x10} EtwpDebuggerData+0x10)可以发现,对于NT Kernel Logger日志,其上下文在列表中的下标为0;对于Circular Kernel Context Logger日志,则是2,并且这个规律是固定不变的。

还有一个问题就是如何找到ETW日志上下文列表,它所在的全局变量并不导出。但幸运的是它位于 E t w p D e b u g g e r D a t a + 0 x 10 \textcolor{orange}{EtwpDebuggerData+0x10} EtwpDebuggerData+0x10,在多数64位系统上也是固定的。而EtwpDebuggerData

在多数系统上特征码也是固定的,我们可以通过特征码定位找到ETW日志上下文列表。

EtwpDebuggerData特征码:

0x2c, 0x08, 0x04, 0x38, 0x0c 

代理GetCpuClock函数

然后在我们自己的 G e t C p u C l o c k \textcolor{cornflowerblue}{GetCpuClock} GetCpuClock函数中需要做一些事情:

  • 判断ETW事件是否为syscall类型事件:从 P e r f I n f o L o g S y s C a l l E n t r y \textcolor{cornflowerblue}{PerfInfoLogSysCallEntry} PerfInfoLogSysCallEntry来看,有两个特别的标志

    在这里插入图片描述

    ​ 图4.2.1 PerfInfoLogSysCallEntry记录系统调用到ETW日志

    这两个标志会从栈中写道日志里去,所以在我们函数里可以从栈中找到这两个值就可以确定这是一个syscall的事件,接着就可以尝试在栈中寻找syscall的目标函数了。

    • 如果我们只是检测syscall调用,到这里就可以结束了。如果要对syscall进行干预的话,我们还需要将栈中的syscall目标函数进行替换。如何从栈中找到syscall的调用目标,这需要一定的技巧。首先我们知道了栈中存放有这两个标志值,然后还有syscall的目标函数。分布如下:

      fffff888`978ae888  fffff800`36e7cdcc nt!EtwpGetPerfCounter+0xc -----------------> 当前函数返回地址
      fffff888`978ae890  00000000`00000010
      fffff888`978ae898  00000000`00000344
      fffff888`978ae8a0  fffff888`978ae8b8
      fffff888`978ae8a8  00000000`00000018
      fffff888`978ae8b0  fffff888`978ae8c0
      fffff888`978ae8b8  fffff800`36eb11ca nt!EtwpLogKernelEvent+0x28a
      fffff888`978ae8c0  00000000`00000000
      fffff888`978ae8c8  00000000`00000000
      fffff888`978ae8d0  00000000`00000000
      fffff888`978ae8d8  00000000`00000000
      fffff888`978ae8e0  00000000`00000000
      fffff888`978ae8e8  00000000`00000000
      fffff888`978ae8f0  00000000`00000000
      fffff888`978ae8f8  00000000`00000000
      fffff888`978ae900  00000008`00100000
      fffff888`978ae908  00000000`00000000
      fffff888`978ae910  00000002`0009e388
      fffff888`978ae918  fffff888`00000018
      fffff888`978ae920  00000000`00000000
      fffff888`978ae928  fffff800`36eb0f59 nt!EtwpLogKernelEvent+0x19
      fffff888`978ae930  00000000`00000010
      fffff888`978ae938  ffff8904`d8253880
      fffff888`978ae940  00000000`00000010
      fffff888`978ae948  00000000`00000018
      fffff888`978ae950  00000000`00501802
      fffff888`978ae958  fffff888`978aea18
      fffff888`978ae960  00000000`00000000
      fffff888`978ae968  00000000`00000000
      fffff888`978ae970  00000000`40000040
      fffff888`978ae978  00000000`00000000
      fffff888`978ae980  00000000`00000001
      fffff888`978ae988  fffff800`36f22afe nt!EtwTraceSiloKernelEvent+0xbe
      fffff888`978ae990  00000000`00000f33 --------------------------------------------> 标志1:0xF33
      fffff888`978ae998  ffff8904`d78ad000
      fffff888`978ae9a0  00000000`00000010
      fffff888`978ae9a8  00000000`00000001
      fffff888`978ae9b0  fffff888`978a0f33 --------------------------------------------> 标志1:0xF33
      fffff888`978ae9b8  00000000`00501802 --------------------------------------------> 标志2:0x501802
      fffff888`978ae9c0  00000000`00000000
      fffff888`978ae9c8  00000000`00000000
      fffff888`978ae9d0  00000000`00000000
      fffff888`978ae9d8  fffff800`370cda00 nt!PerfInfoLogSysCallEntry+0x70
      fffff888`978ae9e0  fffff800`373aed88 nt!NtUpdateWnfStateData --------------------> syscall调用的目标函数
      fffff888`978ae9e8  fffff888`978aeb80
      fffff888`978ae9f0  0000005d`fd37e958
      fffff888`978ae9f8  00000000`00000f33 --------------------------------------------> 标志1:0xF33
      fffff888`978aea00  00000000`00000f33 --------------------------------------------> 标志1:0xF33
      fffff888`978aea08  fffff800`00501802 --------------------------------------------> 标志2:0x501802
      fffff888`978aea10  fffff800`373aed88 nt!NtUpdateWnfStateData
      fffff888`978aea18  fffff888`978aea10
      fffff888`978aea20  00000000`00000008
      fffff888`978aea28  ffffc7c4`9fea7b16
      fffff888`978aea30  fffff888`978aeaa8
      fffff888`978aea38  fffff800`36fd85a1 nt!KiSystemServiceExitPico+0x206 -----------> KisystemCall64 + 0x8e1
      ...
      

      由上可以知道他们的大致位置。顺序是逆着栈的生长方向,先是两个标志值,随后才是syscall的目标函数,并且他们都在当前函数的返回地址之后,并且位于。于是,我们可以确定搜索的起始位置就是当前函数的返回地址在栈中的位置。同时他们又位于 n t ! K i S y s t e m S e r v i c e E x i t P i c o + 0 x 206 \textcolor{orange}{nt!KiSystemServiceExitPico+0x206} nt!KiSystemServiceExitPico+0x206以上,而这个地址处于 K i s y s t e m C a l l 64 \textcolor{cornflowerblue}{KisystemCall64} KisystemCall64函数内,所以我们就选择这个值来作为搜索的终点。如果栈中都没有两个标志位和 n t ! K i S y s t e m S e r v i c e E x i t P i c o + 0 x 206 \textcolor{orange}{nt!KiSystemServiceExitPico+0x206} nt!KiSystemServiceExitPico+0x206,则搜索的终点就定在 _ K T H R E A D . I n i t i a l S t a c k \textcolor{orange}{\_KTHREAD.InitialStack} _KTHREAD.InitialStack

      _KTHREAD结构体中有个SystemCallNumber字段,保存了当前线程最近一次syscall的调用号。可以根据这个调用号去SSDT表中取目标函数地址,然后与栈中的数据进行对比,就能找到syscall目标在栈中的位置了。

示例代码:

ULONG GetCpuClock_Proxy()
{
	// 忽略内核模式的调用,防重入
	if (ExGetPreviousMode() == KernelMode)
	{
		return __rdtsc();
	}
	
	ULONG_PTR pfnCall = 0;
	PCHAR pThread = (PCHAR)PsGetCurrentThread();
	BOOLEAN bFoundMagicNum1 = FALSE;
	BOOLEAN bFoundMagicNum2 = FALSE;

	__try
	{
		ULONG ulSyscallNumber = *(PULONG)(pThread + SYSTEMCALL_NUMBER_OFFSET_IN_KTHREDA);
		ULONG ulThreadFlags = *(PULONG)(pThread + THREAD_FLAGS_OFFSET_IN_KTHREAD);
		if (FlagOn(ulThreadFlags, GUI_THREAD))
		{
			pfnCall = gKeServiceDescriptorTableShadow[ulSyscallNumber];
		}
		else
		{
			pfnCall = gKeServiceDescriptorTable[ulSyscallNumber];
		}
		
		ULONG_PTR ulRetAddressInStack = (ULONG_PTR)_AddressOfReturnAddress();

		ULONG_PTR ulInitialStack = *(PULONG_PTR)(pThread + INITIAL_STACK_OFFSET_IN_KPRCB);

		for (ULONG_PTR ulCurrentStack = ulRetAddressInStack; ulCurrentStack < ulInitialStack; ulCurrentStack += sizeof(PVOID))
		{
			ULONG_PTR ulValue = *(PULONG_PTR)ulCurrentStack;
			if ( (ulValue >= gKiSystemCall64Entry &&
					ulValue < (gKiSystemCall64Entry + 2 * PAGE_SIZE)))
			{
				break;
			}

			if ((ulValue & 0xFFFF) == MAGIC_NUMBER1)
			{
				bFoundMagicNum1 = TRUE;
			}
			else if ((ulValue & 0xFFFFFFFF) == MAGIC_NUMBER2)
			{
				bFoundMagicNum2 = TRUE;
			}
			else if (ulValue == pfnCall && bFoundMagicNum1 && bFoundMagicNum2)
			{
				// Hook
				// *(PULONG_PTR)ulCurrentStack = pfnYours;
				break;
			}
		}
	}
	__except (EXCEPTION_EXECUTE_HANDLER)
	{

	}
	
	return __rdtsc();
}

寻找SSDT和SSDT Shadow

纵观Win7 x64到Win10 x64,SSDT和SSDT Shadow在 K i S y s t e m C a l l 64 \textcolor{cornflowerblue}{KiSystemCall64} KiSystemCall64中按以下方式进行引用:

.text:0000000140073FF2 4C 8D 15 47 A9 23 00                                            lea     r10, KeServiceDescriptorTable
.text:0000000140073FF9 4C 8D 1D 80 A9 23 00                                            lea     r11, KeServiceDescriptorTableShadow

K i S y s t e m C a l l 64 \textcolor{cornflowerblue}{KiSystemCall64} KiSystemCall64地址可以通过 _ _ r e a d m s r ( I A 32 _ L S T A R _ M S R ) \textcolor{orange}{\_\_readmsr(IA32\_LSTAR\_MSR)} __readmsr(IA32_LSTAR_MSR)取得。不过在打了页表隔离补丁之后,这种方式返回的是位于 .KVASCODE 段内的 K i S y s t e m C a l l 64 S h a d o w \textcolor{cornflowerblue}{KiSystemCall64Shadow} KiSystemCall64Shadow,在其尾部会跳转到 K i S y s t e m C a l l 64 \textcolor{cornflowerblue}{KiSystemCall64} KiSystemCall64内部:

KiSystemCall64+23A                                KiSystemServiceUser:                    ; CODE XREF: KiSystemService+231↑j
KiSystemCall64+23A                                                                        ; KiSystemCall64Shadow+252↓j
KiSystemCall64+23A  C6 45 AB 02                                   mov     byte ptr [rbp-55h], 2
KiSystemCall64+23E  65 48 8B 1C 25 88 01 00 00                    mov     rbx, gs:188h
----------------------------------------------------------------------------------------------------------------------------------------------
KiSystemCall64Shadow+246                                loc_140A173C6:                          ; CODE XREF: KiSystemCall64Shadow+11B↑j
KiSystemCall64Shadow+246  0F AE E8                                      lfence
KiSystemCall64Shadow+249  65 C6 04 25 53 08 00 00 00                    mov     byte ptr gs:853h, 0
KiSystemCall64Shadow+252  E9 63 54 9F FF                                jmp     KiSystemServiceUser

所以这种情况还需要通过解析jmp到的地址,从而取得 K i S y s t e m C a l l 64 \textcolor{cornflowerblue}{KiSystemCall64} KiSystemCall64附近的地址。方法也很简单,就是从 K i S y s t e m C a l l 64 S h a d o w \textcolor{cornflowerblue}{KiSystemCall64Shadow} KiSystemCall64Shadow起始位置,搜索所有jmp指令,跳转的目标地址不在 .KVASCODE 段内即是我们想要的 K i S y s t e m C a l l 64 \textcolor{cornflowerblue}{KiSystemCall64} KiSystemCall64附近的地址。进而找到SSDT和SSDT Shadow。

我的代码就不发了,参考文献中有比较完整的项目。其他版本的系统需要自己去调整里面涉及到的偏移值。

总结

实现ETW HOOK的步骤:

  • 调用 Z w T r a c e C o n t r o l \textcolor{cornflowerblue}{ZwTraceControl} ZwTraceControl启用ETW日志(Win8只有 N t T r a c e C o n t r o l \textcolor{cornflowerblue}{NtTraceControl} NtTraceControl,可以在调用前修改一下先前模式为KernelMode),可以是任意的日志,只是要保证能找到对应的日志上下文即可。
  • 修改对应日志上下文中的GetCpuClock指针为自己的代理函数。
  • 在代理函数中,从栈上寻找syscall调用的目标函数,并修改之。

ETW日志可以在用户层关闭,所以需要对我们自己的ETW日志做一些保护,防止失效。

参考

[1] https://github.com/everdox/InfinityHook

[2] https://bbs.kanxue.com/thread-253450.htm

[3] https://bbs.kanxue.com/thread-258352.htm

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1197370.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Netty--ByteBuffer

2. ByteBuffer 有一普通文本文件 data.txt&#xff0c;内容为 1234567890abcd 使用 FileChannel 来读取文件内容 Slf4j public class ChannelDemo1 {public static void main(String[] args) {// FileChannel// 1. 输入输出流&#xff0c; 2. RandomAccessFile// try (F…

反转链表 --- 递归回溯算法练习三

目录 1. 分析题意 2. 分析算法原理 2.1. 递归思路&#xff1a; 1. 挖掘子问题&#xff1a; 3. 编写代码 3.1. step 1&#xff1a; 3.2. step 2&#xff1a; 3.3. step 3&#xff1a; 3.4. 递归代码&#xff1a; 1. 分析题意 力扣原题链接如下&#xff1a; 206. 反转链…

组件的设计原则

目录 插槽的基本概念 基础用法 具名插槽 使用场景 布局控制 嵌套组件 组件的灵活性 高级用法 作用域插槽 总结 前言 Vue 的 slot 是一项强大的特性&#xff0c;用于组件化开发中。它允许父组件向子组件传递内容&#xff0c;使得组件更加灵活和可复用。通过 slot&…

抢量双11!抖音商城「官方立减」 缘何成为“爆单神器”?

10月20日抖音商城双11好物节正式开跑&#xff0c;仅仅三天&#xff0c;抖音商城整体GMV对比去年同期提升了200%&#xff0c;而在开跑一周后&#xff0c;一些品牌的销售额已经超过了今年整个618&#xff0c;可谓增势迅猛。其中&#xff0c;平台官方特别推出的「官方立减」玩法&a…

抢占全球30%碳化硅市场份额!英飞凌押注低碳化和数字化“新时代”

“未来十年将是低碳化和数字化‘双轮驱动’发展的时代。” 英飞凌科技全球高级副总裁及大中华区总裁、英飞凌科技大中华区电源与传感系统事业部负责人潘大伟在英飞凌2023年大中华区生态创新峰会上表示。 当前&#xff0c;“数字化低碳化”新趋势正在席卷和重塑着未来世界的千行…

vue.cli 中怎样使用自定义的组件

目录 创建自定义组件 注册并使用自定义组件 全局注册自定义组件 使用 Props 传递数据 总结 前言 在Vue CLI中使用自定义组件是构建交互式和模块化Web应用的重要一环。Vue CLI为开发者提供了使用自定义组件的灵活性和简便性。通过Vue CLI&#xff0c;你可以创建、注册和使…

UI自动化测试最佳设计模式POM

&#x1f4e2;专注于分享软件测试干货内容&#xff0c;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff01;&#x1f4e2;交流讨论&#xff1a;加入1000人软件测试技术学习交流群&#x1f4e2;资源分享&#xff1a;进了字节跳动之后&#xff0c;才…

LabVIEW中NIGPIB设备与驱动程序不相关的MAX报错

LabVIEW中NIGPIB设备与驱动程序不相关的MAX报错 当插入GPIB-USB设备时&#xff0c;看到了NI MAX中列出该设备&#xff0c;但却显示了黄色警告指示&#xff0c;并且指出Windows没有与您的设备相关的驱动程序。 解决方案 需要安装能兼容的NI-488.2驱动程序。 通过交叉参考以下有…

WebSphere Liberty 8.5.5.9 (一)

WebSphere Liberty 8.5.5.9 (一) 安装 1. 从官网下载 WebSphere Liberty 8.5.5.9 2. 解压 解压到 D:\wlp-webProfile7-java8-8.5.5.93. 启动 D:\wlp-webProfile7-java8-8.5.5.9\wlp\bin>server start 正在启动服务器 defaultServer。 服务器 defaultServer 已启动。4. …

UWB人员定位系统的原理与应用

uwb定位技术源码 uwb高精度定位系统源码 uwb人员定位系统基于什么原理&#xff1f; UWB人员定位系统基于超宽带(Ultra WideBand)技术进行位置定位。它利用超短脉冲信号&#xff0c;通过测量信号的到达时间差和信号强度等信息&#xff0c;实现对目标位置的定位。UWB技术具有高…

Docker安装详细步骤及相关环境安装配置

目录 一、从空白系统中克隆Centos7系统 二、使用xshell连接docker_tigerhhzz虚拟机 ​编辑 三、在CentOS7基础上安装Docker容器 最近自己在虚拟机上搭建一个docker,将项目运行在虚拟机中。 需要提前准备的工具&#xff0c;XShell(远程链接工具)&#xff0c;VM&#xff08;…

Qt——连接mysql增删查改(仓库管理极简版)

目录 UI布局设计 .pro文件 mainwindow.h main.cpp UI布局设计 .pro文件 QT core gui QT core gui sql QT sqlgreaterThan(QT_MAJOR_VERSION, 4): QT widgetsCONFIG c11# The following define makes your compiler emit warnings if you use # any …

C语言数据结构-----链表类型详解及链表练习题

0.前言 之前我讲解了循序表以及单链表&#xff0c;接下来我会在介绍几个不同的链表&#xff0c;并举例相关习题使大家能够更加深入的理解。 前期内容如下&#xff1a; 链接: 顺序表(动态顺序表增删查改的代码实现) 链接: 单链表(无头单向不循环)增删查改的代码实现 链接: [双向…

带你详细了解git的【分支和标签】

&#x1f3c5;我是默&#xff0c;一个在CSDN分享笔记的博主。&#x1f4da;&#x1f4da; ​​​ &#x1f31f;在这里&#xff0c;我要推荐给大家我的专栏《git》。&#x1f3af;&#x1f3af; &#x1f680;无论你是编程小白&#xff0c;还是有一定基础的程序员&#xff0c;…

使用xlwings实现对excel表中指定列隔行求和

需要对上表中的营业额隔行求和&#xff0c;即橙色背景颜色的求和&#xff0c;无背景颜色的求和。 看了大佬的视频&#xff0c;有两种方法&#xff1a; 1.加辅助列 2.使用判断行的奇偶函数&#xff0c;然后在用sumproduct函数 在此&#xff0c;我使用xlwings对excel表中数据…

来世再不选Java!

危机感 距离上一次找工作面试已经过去快2年了&#xff0c;那时候正值疫情肆虐&#xff0c;虽然还未感受到“寒潮来临”的苗头&#xff0c;但最终还是成功通过了几轮面试&#xff0c;顺利签约。在目前公司待了2年了&#xff0c;在大环境的影响下&#xff0c;没有加薪、没有年终…

经典OJ题:奇偶链表

目录 题目&#xff1a; 示例&#xff1a; 解题思路&#xff1a; 方法一&#xff1a;双链表链接法 图例&#xff1a; 代码演示&#xff1a; 解题效果&#xff1a; 方法二&#xff1a;奇偶指针 图例&#xff1a; 代码演示&#xff1a; 题目&#xff1a; 给定单链表…

vmware开启ipv6

说明 在 ipv4 基础上配置ipv6网络。 分享 大数据博客列表开发记录汇总个人java工具库 项目https://gitee.com/wangzonghui/object-tool 包含json、string、集合、excel、zip压缩、pdf、bytes、http等多种工具&#xff0c;欢迎使用。 vm开启ipv6 设置vmware 打开vmware点击编…

第二章 03Java基础-IDEA相关叙述

文章目录 前言一、IDEA概述二、IDEA下载和安装三、IDEA项目结构介绍四、IDEA的项目和模块操作总结前言 今天我们学习Java基础,IDEA下载以及相关配置和基础使用方法 一、IDEA概述 1.IDEA全称IntelliJ IDEA,是用于Java语言开发的集成工具,是业界公认的目前用于Java程序开发最…

“茶叶销售策略:以‘3人回本大放送’模式引领快速流量裂变“

你是否曾经为茶叶的高品质而烦恼&#xff1f;是否曾经为挑选一款适合自己的茶叶而纠结&#xff1f;现在&#xff0c;有一个销售茶叶的团队&#xff0c;他们家599块钱一盒的茶叶&#xff0c;让你轻松品味高端的滋味。他们在小程序这一个渠道&#xff0c;只用了23天的时间&#x…