前言
安全设计软件的一个关键特性是在执行特权操作时生成审计日志。 这些本机审计日志可以包含内部软件状态的详细信息,而第三方安全供应商无法在事后添加这些信息。
大多数 Windows 组件使用Windows 事件跟踪(ETW) 生成日志。 这些事件暴露了 Windows 的一些内部工作原理,在某些情况下,端点安全产品可以从订阅这些事件中受益。 但出于安全目的,并非所有 ETW 提供程序都是平等的。
首先要考虑的通常是事件提供者本身的可靠性——特别是日志记录发生的地方。 它是否在客户端进程内并且容易受到 ETW 篡改? 或者在 RPC 服务器进程中可能稍微更安全一些? 但理想情况下,遥测数据将来自内核。 考虑到用户到内核的安全边界,这为进程内遥测提供了更强的防篡改保证。 这是微软推荐的方法。 与 Elastic Endpoint 一样,Microsoft Defender for Endpoint 也使用内核 ETW,而不是脆弱的用户模式ntdll
挂钩。
例如,攻击者可能能够轻松避开ntdll!NtProtectVirtualMemory
上的进程内用户模式挂钩,但绕过内核PROTECTVM ETW 事件则要困难得多。 或者,至少,应该是。
安全事件日志实际上只是来自 Microsoft-Windows-Security-Auditing ETW 提供程序的事件的持久存储。 令人惊讶的是,进程创建的安全事件 4688不是内核事件。 内核将数据发送到本地安全机构( lsass.exe
)服务,并发出 ETW 事件供事件日志使用。 因此,数据可能会从该服务器进程内部被篡改。 将此与来自 Microsoft-Windows-Kernel-Process 提供程序的ProcessStart
事件进行对比,该事件由内核直接记录,并且需要内核级权限才能干预。
第二个考虑因素是所记录信息的可靠性。 您可能信任事件源,但如果它只是盲目地记录与正在记录的事件无关的客户端提供的数据,该怎么办?
在本文中,我们将重点关注内核 ETW 事件。 这些通常是与安全最相关的,因为它们很难绕过并且通常涉及代表客户端线程执行的特权操作。
当微软推出内核补丁保护时,安全供应商监控内核的能力受到了很大限制。 鉴于微软提供的内核扩展点数量有限,他们越来越被迫依赖异步 ETW 事件来事后了解恶意软件执行的内核操作。
鉴于这种依赖性,遗憾的是 Windows 内核遥测源的公共文档有些稀少。
内核 ETW 事件
目前有四种类型的 ETW 提供商我们需要考虑。
首先,“事件提供者”有传统和现代的变体:
然后还有“跟踪提供程序”的传统和现代变体:
- 旧版 Windows 软件跟踪预处理器 ( WPP ) 跟踪提供程序
- 现代TraceLogging跟踪提供程序
“事件”与“痕迹”的区别主要是语义上的。 事件提供程序通常会提前在操作系统中注册,您可以检查可用的遥测元数据。 这些通常由系统管理员用于故障排除目的,并且通常是半文档化的。 但是当出现非常非常严重的问题时,就会有(隐藏的)跟踪提供程序。 这些通常仅由原始软件作者用于高级故障排除并且没有记录。
实际上,每个文件都使用略有不同的格式来描述和注册其事件,这会在事件记录方式上引入细微的差别 - 更重要的是,如何枚举潜在事件。
现代内核事件提供程序
现代内核 ETW 提供程序没有严格记录。 但是,可以通过跟踪数据助手 API从操作系统查询已注册的事件详细信息。 Microsoft 的PerfView工具使用这些 API 来重建提供商的注册清单,然后 Pavel Yosifovich 的EtwExplorer将这些清单包装在一个简单的 GUI 中。 您可以使用来自连续 Windows 版本的注册清单的这些制表符分隔值文件。 尽管其他人已经发布了原始 XML 清单,但每个事件单行对于 grepping 来说非常有用。
然而,这些并不是所有可能的 Windows ETW 事件。 它们只是默认在操作系统中注册的。 例如,许多服务器角色的 ETW 事件在启用该功能之前不会注册。
旧版内核事件提供程序
遗留的内核事件由 Microsoft 记录。 大多。
旧式提供程序也作为 WMI EventTrace类存在于操作系统中。 提供者是根类,组是子类,事件是孙类。
为了以与现代事件相同的方式搜索遗留事件,需要解析这些类,并重建原始 MOF(大部分)。 此MOF 支持已添加到 EtwExplorer,并且旧式事件的制表符分隔值摘要是对这些类进行解析并重建原始 MOF(大部分)。 此MOF 支持已添加到 EtwExplorer 中,并发布了旧事件的制表符分隔值摘要。
完全重建的 Windows 内核跟踪 MOF 在这里(或以表格格式在这里)。
在 340 已注册的遗留事件中,仅记录了 116 。 通常,每个遗留事件都需要通过特定标志来启用,但这些也没有记录。 内核对象管理器跟踪事件的文档中有一个线索。 它提到了PERF_OB_HANDLE
,这是最新 SDK 的标头中未定义的常量。 幸运的是, Geoff Chappell和 Windows 10 1511 WDK 来拯救我们了。 此信息用于向 Microsoft 的KrabsETW库添加对PERFINFO_GROUPMASK
内核跟踪标志的支持。 事实证明 Object Trace 文档是错误的。 该非公共常量只能与未记录的 API 扩展一起使用。 幸运的是,微软的公共项目(例如PerfView
经常提供如何使用未记录的 API 的示例。
随着清单和 MOF 都发布在 GitHub 上,现在可以使用此查询找到大多数内核事件。
有趣的是,微软经常混淆与安全相关的事件的名称,因此搜索具有通用名称前缀(例如task_
的事件会产生一些有趣的结果。
有时关键词会暗示事件的目的。 例如, Microsoft-Windows-Kernel-General
中的task_014
使用关键字KERNEL_GENERAL_SECURITY_ACCESSCHECK.
启用
值得庆幸的是,这些参数几乎总是有着很好的命名。 我们可能会猜测Microsoft-Windows-Kernel-Audit-API-Calls
中的task_05
与OpenProcess相关,因为它记录了名为TargetProcessId
和DesiredAccess
的字段。
另一个有用的查询是搜索具有明确ProcessStartKey
字段的事件。 ETW 事件可以配置为在日志记录过程中包含此字段,并且任何包含其他过程的此信息的事件通常与安全相关。
如果您有特定的 API,您可能会查询其名称或参数。 例如,如果您想要命名管道事件,您可能会使用此查询。
但在本例中, Microsoft-Windows-SEC
属于 Microsoft Defender for Endpoint (MDE) 使用的内置 Microsoft 安全驱动程序。 尽管Sebastian Feldmann 和 Philipp Schmied已演示如何使用AutoLogger启动会话并订阅该会话的事件,但该提供程序仅正式提供给 MDE。 这目前仅对 MDE 用户有用,否则,驱动程序未配置为发出事件。
但是跟踪提供者又如何呢?
现代内核跟踪提供程序
TraceLogging 元数据作为不透明的 blob 存储在日志二进制中。 值得庆幸的是,这种格式已被Matt Graeber颠覆。 我们可以使用 Matt 的脚本转储ntoskrnl.exe
的所有 TraceLogging 元数据。 Windows 11 TraceLogging 元数据的示例转储在此处。
不幸的是,单独的元数据结构无法保留提供商和事件之间的关联。 有一些有趣的提供商名称,例如Microsoft.Windows.Kernel.Security
和AttackSurfaceMonitor
,但从我们的元数据转储中尚不清楚哪些事件属于这些提供商。
旧版内核跟踪提供程序
WPP 元数据存储在符号文件 (PDB) 中。 微软将此信息包含在某些(但不是全部)驱动程序的公共符号中。 但是内核本身不会产生任何 WPP 事件。 相反,可以向旧的 Windows 内核跟踪事件提供程序传递未记录的标志,以启用通常仅对 Microsoft 内核开发人员可用的旧式“跟踪”事件。
提供商 | Documentation | 事件元数据 |
---|---|---|
现代活动提供商 | 无 | 已注册的 XML 清单 |
旧版事件提供商 | 部分的 | EventTrace WMI 对象 |
现代跟踪提供程序 | 无 | 二进制文件中未记录的 blob |
旧版跟踪提供程序 | 无 | 符号中未记录的 blob |
后续步骤
我们现在拥有四种 ETW 提供程序的内核事件元数据,但 ETW 事件列表只是我们的起点。 了解提供者和事件关键字可能不足以生成我们期望的事件。 有时,需要额外的配置注册表项或 API 调用。 不过,更多时候,我们只需要了解记录事件的具体条件。
准确了解记录位置和记录内容对于真正理解遥测及其局限性至关重要。 而且,由于反编译器变得越来越普及,我们可以选择进行一些适当的逆向操作。 在 IDA 中我们称之为“按 F5”。 Ghidra 是开源替代品,它支持使用 Java 编写脚本。
对于内核 ETW,我们特别感兴趣的是可通过系统调用访问的EtwWrite
调用。 我们希望获得尽可能多的调用站点参数信息,包括任何相关的公共符号信息。 这意味着我们需要遍历调用图,同时也尝试解析特定参数的可能值。
必要参数是RegHandle
和EventDescriptor
。 前者对于提供者来说是一个不透明的句柄,后者提供事件特定的信息,例如事件 ID 及其关联的关键字。 ETW 关键字是用于启用一组事件的标识符。
更好的是,这些事件描述符通常存储在具有公共符号的全局常量中。
我们有足够的事件元数据,但仍然需要将运行时分配的不透明提供程序句柄解析回有关提供程序的元数据。 为此,我们还需要EtwRegister
调用。
内核现代事件提供程序的典型模式是将常量提供程序 GUID 和运行时句柄存储在具有公共符号的全局变量中。
遇到的另一种模式是调用EtwRegister
、 EtwEwrite
和EtwUnregister
,都在同一个函数中。 在这种情况下,我们利用本地性来查找事件的提供者 GUID。
然而,现代 TraceLogging 提供程序没有与每个提供程序相关的公共符号来提供每个提供程序用途的提示。 然而,Matt Graeber 已经反转了 TraceLogging 元数据格式并记录了提供商名称存储在距提供商 GUID 的固定偏移量处。 拥有准确的提供商名称甚至比我们为现代事件恢复的公共符号更好。
这只剩下了遗留提供商。 它们似乎没有公共符号或元数据 blob。 一些常量被传递给名为EtwTraceKernelEvent
的未记录函数,该函数包装了最终的 ETW 写入调用。
这些常量存在于 Windows 10 1511 WDK 标头(和System Informer标头)中,因此我们可以用常量名称标记这些事件。
该脚本最近已针对 Ghidra 11 进行了更新,同时改进了对 TraceLogging 和 Legacy 事件的支持。 您现在可以在 GitHub 上找到它 - https://github.com/jdu2600/API-To-ETW
Windows 11 内核的示例输出在这里。
我们的先前匿名的Microsoft-Windows-Kernel-Audit-API-Calls
事件很快就被该脚本揭露。
ID | EVENT_DESCRIPTOR 符号 | 函数 |
---|---|---|
1 | KERNEL_AUDIT_API_PSSETLOADIMAGENOTIFYROUTINE | PsSetLoadImageNotifyRoutineEx |
2 | KERNEL_AUDIT_API_TERMINATEPROCESS | 终止进程 |
3 | KERNEL_AUDIT_API_CREATESYMBOLICLINKOBJECT | 创建符号链接 |
4 | KERNEL_AUDIT_API_SETCONTEXTTHREAD | 设置上下文线程 |
5 | KERNEL_AUDIT_API_OPENPROCESS | 进程管理工具 |
6 | KERNEL_AUDIT_API_OPENTHREAD | 线程管理工具 |
7 | KERNEL_AUDIT_API_IOREGISTERLASTCHANCESHUTDOWNNOTIFICATION | IoRegisterLastChanceShutdownNotification |
8 | KERNEL_AUDIT_API_IOREGISTERSHUTDOWNNOTIFICATION | IoRegisterShutdownNotification |
Microsoft-Windows-Kernel-Audit-API-Calls 事件的符号和包含函数
通过脚本恢复的调用路径和参数信息,我们还可以看到之前的SECURITY_ACCESSCHECK
事件与SeAccessCheck内核 API 相关联,但仅记录在名为SeLogAccessFailure
的函数中。 仅记录失败情况是 ETW 事件中很常见的情况。 对于故障排除目的,原始 ETW 用例通常是最有用的,并且大多数组件中的实现都反映了这一点。 不幸的是,出于安全目的,情况往往相反。 成功的操作日志通常对于发现恶意活动更有用。 因此,一些遗留事件的价值通常较低。
现代安全设计实践是审核记录安全相关活动的成功和失败,并且 Microsoft 继续添加执行此操作的新的安全相关 ETW 事件。 例如,Windows 11 24H2 的预览版本在Microsoft-Windows-Threat-Intelligence
提供程序中包含一些有趣的新 ETW 事件。 希望这些能够在发布之前为安全供应商提供记录。
在有趣的 Windows 驱动程序和服务 DLL 上运行这个反编译器脚本留给读者作为练习。