ETW HOOK原理探析

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.mfbz.cn/a/133198.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【字符串】【双指针翻转字符串+快慢指针】Leetcode 151 反转字符串中单词【好】

【字符串】【双指针翻转字符串快慢指针】Leetcode 151 反转字符串中单词 解法1 双指针翻转字符串快慢指针更新数组大小 ---------------&#x1f388;&#x1f388;题目链接&#x1f388;&#x1f388;------------------- ---------------&#x1f388;&#x1f388;解答链接…

UI自动化测试 | Jenkins配置优化

前一段时间帮助团队搭建了UI自动化环境&#xff0c;这里将Jenkins环境的一些配置分享给大家。 背景&#xff1a; 团队下半年的目标之一是实现自动化测试&#xff0c;这里要吐槽一下&#xff0c;之前开发的测试平台了&#xff0c;最初的目的是用来做接口自动化测试和性能测试&…

MySQL 数据库查询与数据操作:使用 ORDER BY 排序和 DELETE 删除记录

使用 ORDER BY 进行排序 使用 ORDER BY 语句按升序或降序对结果进行排序。 ORDER BY 关键字默认按升序排序。要按降序排序结果&#xff0c;使用 DESC 关键字。 示例按名称按字母顺序排序结果&#xff1a; import mysql.connectormydb mysql.connector.connect(host"l…

Linux 内核定时器

一个人总要走陌生的路&#xff0c;看陌生的风景&#xff0c;听陌生的歌&#xff0c;然后在某个不经意的瞬间&#xff0c;你会发现&#xff0c;原本费尽心机想要忘记的事情真的就这么忘记了。 ----小新 一、引言 Linux内核定时器是一种用于在特定时间间隔后触发特定事件的重要组…

数据结构之AVL树

map/multimap/set/multiset这几个容器有个共同点是: 其底层都是按照二叉搜索树来实现的,但是普通的二叉搜索树有其自身的缺陷, 假如往树中插入的元素有序或者接近有序, 二叉搜索树就会退化成单支树, 时间复杂度会退化成O(N),因此map、set等关联式容器的底层结构是对二叉树进行了…

JavaScript基础入门04

目录 1.WebAPI 背景知识 1.1什么是 WebAPI 1.2什么是 API 2.DOM 基本概念 2.1什么是 DOM 2.2DOM 树 3.获取元素 3.1querySelector 3.2querySelectorAll 4.事件初识 4.1基本概念 4.2事件三要素 4.3简单示例 5.操作元素 5.1获取/修改元素内容 5.2获取/修改元素属性…

记事本简单运行java代码,理解程序运行

1.记事本创建一个文件, 把后缀.txt改为.java 如果没有显示尾缀, 勾选出文件扩展名 2.在文件里面编辑java代码并保存 3.在当前目录打开cmd 4.执行 javac Test.java 会生成好编译的.class文件 5.执行下面代码, 就成功得到j编写ava打印的代码 java Test 6.注意上面的中文在cmd中…

Windows下安装Anaconda5.3.1+Python3.8+TensorFlow2.13.0-CPU版本总结

Python3.8安装可以参考博文https://janus.blog.csdn.net/article/details/55274849 进行安装即可。 【1】Anaconda 清华的开源软件镜像站&#xff1a;https://mirrors.tuna.tsinghua.edu.cn/anaconda/archive/下载&#xff0c;这里选择的是5.3.1版本。 然后正常安装就可以&am…

C++17中std::optional的使用

模版类std::optional管理一个可选的(optional)存储值(contained value)&#xff0c;即可能存在也可能不存在的值。std::optional的一个常见用例是存储可能失败的函数的返回值。与其它方法相反(例如std::pair<T, bool>),std::optional可以很好地处理构造成本高昂的对象&am…

帧同步的思想与FIFO复位

02基于FDMA三缓存构架_哔哩哔哩_bilibili 图像从外部传输进来的时候&#xff0c;会产生若干延迟&#xff0c;可能会出现各种各样的问题&#xff08;断帧等&#xff09;&#xff0c;此时可以通过VS信号清空FIFO进行复位。 这个过程中的复位信号可能需要拓展&#xff0c;这是因为…

运筹说 第102期 | 非线性规划—制约函数法

通过上期学习&#xff0c;大家已经了解了非线性规划中约束极值问题的最优性条件。本期小编将为大家介绍约束极值问题的求解方法&#xff1a;制约函数法&#xff0c;包括概念以及最基本的两种制约函数法&#xff1a;罚函数法、障碍函数法等内容。 制约函数法是通过构造某种制约函…

数据分析 - 数据案例流程分析

有这样的一个案例&#xff1a;外卖骑手的未接单率上升 1&#xff1a;分析有哪些因素会造成这种后果 骑手和订单的一个占比情况订单配送距离的情况平台补贴情况和收入情况时间段的订单和骑手的需求比 2&#xff1a;清理错误数据&#xff0c;或者无用的数据&#xff0c;确保数…

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

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

专业128分总分390+上岸中山大学884信号与系统电通院考研经验分享

专业课884 信号系统 过年期间开始收集报考信息&#xff0c;找到了好几个上岸学姐和学长&#xff0c;都非常热情&#xff0c;把考研的准备&#xff0c;复习过程中得与失&#xff0c;都一一和我分享&#xff0c;非常感谢。得知这两年专业课难度提高很多&#xff0c;果断参加了学长…

【Linux】语言层面缓冲区的刷新问题以及简易模拟实现

文章目录 前言一、缓冲区刷新方法分类a.无缓冲--直接刷新b.行缓冲--不刷新&#xff0c;直到碰到\n才刷新c.全缓冲--缓冲区满了才刷新 二、 缓冲区的常见刷新问题1.问题2.刷新本质 三、模拟实现1.Mystdio.h2.Mystdio.c3.main.c 前言 我们接下来要谈论的是我们语言层面的缓冲区&…

数据分析实战 | 逻辑回归——病例自动诊断分析

目录 一、数据及分析对象 二、目的及分析任务 三、方法及工具 四、数据读入 五、数据理解 六、数据准备 七、模型训练 八、模型评价 九、模型调参 十、模型预测 一、数据及分析对象 CSV文件——“bc_data.csv” 数据集链接&#xff1a;https://download.csdn.net/d…

Oracle(18)Auditing

文章目录 一、基础知识1、审计介绍2、Auditing Types 审计类型3、Auditing Guidelines 审计准则4、Auditing Categories 审核类别5、Database Auditing 数据库审计6、Auditing User SYS 审计sys用户7、Getting Auditing Informatio 获取审计信息8、获取审计记录通知 二、基础操…

智能指针,c++11,单例,类型转换

c11 unique_ptr 防拷贝 shared_ptr / weak_ptr: 引用计数,支持拷贝 面试 手写shared_ptr 各种ptr的特性对比, 不会问定制删除器和weak_ptr,但是问shared_ptr时,可以往这边延展. 单例 保证一写数据在一个进程中,只有一份,并且方便访问修改. 饿汉模式 在main函数之前就创…

雷达检测及MATLAB仿真

文章目录 前言一、雷达检测二、Matlab 仿真1、高斯和瑞利概率密度函数①、MATLAB 源码②、仿真 2、归一化门限相对虚警概率的曲线①、MATLAB 源码②、仿真 3、检测概率相对于单个脉冲 SNR 的关系曲线①、MATLAB 源码②、仿真 4、改善因子和积累损失相对于非相干积累脉冲数的关系…

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

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