内核对象直接篡改攻击技术 (DKOM) 对 ETW 提供程序的攻击

一位深夜工作的男士看着电脑屏幕的侧视图

作者

Ruben Boonen

CNE Capability Lead, Adversary Services

IBM X-Force

在本文中,IBM Security X-Force Red 红队研究人员针对攻击者提权后,如何借助已获取的权限部署 Windows 内核层后期渗透能力这一问题展开分析。过去数年,公开披露的案例表明,即便是技术水平相对有限的攻击者,也开始采用此类技术达成攻击目的。因此,我们亟需重点关注这项攻击能力,并深入探究其潜在危害。具体而言,本文将分析攻击者如何利用内核层后期渗透技术对 ETW 传感器实施致盲攻击,同时结合去年在真实攻击场景中捕获到的恶意软件样本,对该技术的实际应用展开关联性分析。

介绍

近年来,Windows 系统的安全防护措施与检测遥测能力已得到显著完善。当这些能力与配置得当的端点检测与响应 (EDR) 解决方案相结合时,能够对攻击者的后期渗透行动构成一道难以突破的屏障。攻击者需要持续投入成本,对其战术、技术与程序 (TTP) 进行研发与迭代,以此规避安全检测的启发式规则。而在 IBM Security X-Force 的对手模拟团队,我们也正面临着同样的挑战。团队的核心任务,是在部分规模庞大且防护严密的网络环境中,对各类高级威胁攻击能力进行模拟验证。复杂且经过精细调优的安全解决方案,再加上训练有素的安全运营中心 (SOC) 团队,会对攻击者的攻击手法形成极大的压制。在某些场景下,某一项特定的 TTP 往往在三到四个月内就会彻底失效,这通常与特定的技术架构密切相关。

攻击者可能会选择借助 Windows 内核层代码执行权限,篡改部分安全防护机制,或是直接绕过大量用户态传感器的检测。此类攻击能力的首次公开演示,可追溯至 1999 年发布于《Phrack 杂志的相关内容。在此后的数十年间,已有多起公开案例证实,威胁参与者 (TA) 会利用内核级 Rootkit 开展后期渗透活动。其中较为典型的早期案例包括 Derusbi 家族恶意软件与 Lamberts 工具包

传统上,这类攻击能力基本仅限高级威胁参与者掌握。然而近年来,我们发现越来越多的普通攻击者也开始利用自带易受攻击驱动程序 (BYOVD) 这一攻击载体,实现对终端的恶意操作。在某些情况下,此类技术的实现方式相当初级,仅能完成一些简单任务;但与此同时,也出现了不少功能更为完善的技术验证案例

2022 年 9 月底,ESET 的研究人员发布了一份白皮,披露了 Lazarus 威胁参与者在针对比利时与荷兰境内目标机构发起的多起攻击中,所采用的一项内核层攻击技术,其攻击目的为数据窃取。该白皮书详细阐述了此载荷所使用的多项内核对象直接篡改 (DKOM) 原语,这些技术被用于对操作系统、杀毒软件及终端检测与响应系统的遥测功能实施致盲。目前,针对这类技术的公开研究资料十分匮乏,因此深入理解内核层后期渗透攻击手法,对防御方而言至关重要。深入理解内核层后期渗透攻击手法,对于防御工作而言至关重要。一种常见且片面的观点认为:攻击者一旦获取提权,便可肆意妄为,因此我们无需对该场景下的攻击能力进行建模分析。但这种看法站不住脚。防御方必须明确以下几点:攻击者提权后具备哪些具体攻击能力;哪些数据源仍具备可信度(哪些已不可信);存在哪些攻击遏制方案;以及如何检测各类高级攻击技术(即便当前尚不具备实现这类检测的能力)。我将通过本文重点探讨通过篡改 Event Tracing for Windows (ETW) 内核层结构体,使 ETW 提供程序完全失效或无法运行的具体攻击技术。我将首先介绍该技术的相关背景,分析攻击者篡改内核层 ETW 结构体的具体手法,并深入阐述定位这些结构体的技术原理。最后,本文还将剖析 Lazarus 组织如何在其载荷中实现该攻击技术。

辅以专家洞察分析的最新科技新闻

通过 Think 时事通讯,了解有关 AI、自动化、数据等方面最重要且最有趣的行业趋势。请参阅 IBM 隐私声明

谢谢!您已订阅。

您的订阅将以英语提供。每份时事通讯都包含取消订阅链接。您可以在此管理您的订阅或取消订阅。更多相关信息,请参阅我们的 IBM 隐私声明

ETW DKOM

ETW 是 Windows 操作系统内置的一套高速跟踪机制。它支持应用程序、驱动程序及操作系统对各类事件与系统活动进行日志记录,可为调试排障、性能分析及安全诊断工作,提供对系统行为的全面可视性。

在本节中,我将对内核层 ETW 及其对应的攻击面进行概述。掌握这些内容,有助于更好地理解篡改 ETW 提供程序的底层技术原理,以及此类篡改操作所引发的相关影响。

内核层 ETW 攻击面

Binarly 研究人员曾在 2021 年 BHEU 大会上发表演讲,探讨了 Windows 系统中 ETW 机制的通用攻击面。下文将展示该威胁模型的概览图。

用于 ETW 威胁建模的流程图
图 1 – 来而未见,见而未胜:针对 ETW 的攻击可致盲 EDR 传感器(来源:Binarly)

在这篇文章中,我们将重点关注内核层的攻击面。

用于展示并阐述针对内核态 ETW 提供程序攻击手法的图表
图 2 – 来而未见,见而未胜:针对 ETW 的攻击可致盲 EDR 传感器(来源:Binarly)

本文仅探讨图 2 所示第一类攻击手段,即通过各类方式禁用或篡改 ETW 跟踪功能的攻击。

需要特别提醒的是,在研究 Windows 系统中的不透明结构体时,必须时刻谨记:这类结构体的定义会随系统版本迭代发生变更,且事实上在不同 Windows 版本中往往存在频繁改动。这一点在对内核数据执行篡改操作时尤为关键,任何操作失误都极有可能导致系统蓝屏死机 (BSoD),务必谨慎操作!

初始化

内核提供程序通过 nt!EtwRegister 函数完成注册,该函数由 ntoskrnl 导出。下文展示了该函数的反编译代码。

nt!EwRegister 反编译代码的屏幕截图
图 3 - nt!EwRegister 反编译

完整的初始化流程在内部函数 EtwpRegisterKMProvider 中完成,不过我们可以从该流程中提炼出两个核心要点:

  • ProviderId 是一个指向 16 字节 GUID 的指针。此 GUID 在操作系统中是静态的,因此可用于识别正在初始化的提供程序。
  • RegHandle 是一个内存地址,成功调用时用于接收指向 _ETW_REG_ENTRY 结构体的指针。依据 Binarly 的研究,该数据结构体及其部分嵌套属性,为篡改 ETW 提供程序提供了可行路径。

下面,我们简要罗列 Binarly 在图 2 的幻灯片中重点提及的各类结构体。

ETW_REG_ENTRY

下文展示了 _ETW_REG_ENTRY 结构的完整 64 位定义清单。更多详细信息可参见 Geoff Chappell 的博客。也可通过 Vergilius Project 进一步研究该结构。

// 0x70 bytes (sizeof)
// Win11 22H2 10.0.22621.382
struct _ETW_REG_ENTRY
{
    struct _LIST_ENTRY RegList;                           //0x0
    struct _LIST_ENTRY GroupRegList;                      //0x10
    struct _ETW_GUID_ENTRY* GuidEntry;                    //0x20
    struct _ETW_GUID_ENTRY* GroupEntry;                   //0x28
    union
    {
        struct _ETW_REPLY_QUEUE* ReplyQueue;              //0x30
        struct _ETW_QUEUE_ENTRY* ReplySlot[4];            //0x30
        struct
        {
            VOID* Caller;                                 //0x30
            ULONG SessionId;                              //0x38
        };
    };
    union
    {
        struct _EPROCESS* Process;                        //0x50
        VOID* CallbackContext;                            //0x50
    };
    VOID* Callback;                                       //0x58
    USHORT Index;                                         //0x60
    union
    {
        USHORT Flags;                                     //0x62
        struct
        {
            USHORT DbgKernelRegistration:1;               //0x62
            USHORT DbgUserRegistration:1;                 //0x62
            USHORT DbgReplyRegistration:1;                //0x62
            USHORT DbgClassicRegistration:1;              //0x62
            USHORT DbgSessionSpaceRegistration:1;         //0x62
            USHORT DbgModernRegistration:1;               //0x62
            USHORT DbgClosed:1;                           //0x62
            USHORT DbgInserted:1;                         //0x62
            USHORT DbgWow64:1;                            //0x62
            USHORT DbgUseDescriptorType:1;                //0x62
            USHORT DbgDropProviderTraits:1;               //0x62
        };
    };
    UCHAR EnableMask;                                     //0x64
    UCHAR GroupEnableMask;                                //0x65
    UCHAR HostEnableMask;                                 //0x66
    UCHAR HostGroupEnableMask;                            //0x67
    struct _ETW_PROVIDER_TRAITS* Traits;                  //0x68
};

ETW_GUID_ENTRY

_ETW_REG_ENTRY 结构的嵌套成员之一为 GuidEntry,其类型是 _ETW_GUID_ENTRY 结构。关于这一未公开结构的更多信息,可查阅 Geoff Chappell 的博客,以及 Vergilius Project

// 0x1a8 bytes (sizeof)
// Win11 22H2 10.0.22621.382
struct _ETW_GUID_ENTRY
{
    struct _LIST_ENTRY GuidList;                          //0x0
    struct _LIST_ENTRY SiloGuidList;                      //0x10
    volatile LONGLONG RefCount;                           //0x20
    struct _GUID Guid;                                    //0x28
    struct _LIST_ENTRY RegListHead;                       //0x38
    VOID* SecurityDescriptor;                             //0x48
    union
    {
        struct _ETW_LAST_ENABLE_INFO LastEnable;          //0x50
        ULONGLONG MatchId;                                //0x50
    };
    struct _TRACE_ENABLE_INFO ProviderEnableInfo;         //0x60
    struct _TRACE_ENABLE_INFO EnableInfo[8];              //0x80
    struct _ETW_FILTER_HEADER* FilterData;                //0x180
    struct _ETW_SILODRIVERSTATE* SiloState;               //0x188
    struct _ETW_GUID_ENTRY* HostEntry;                    //0x190
    struct _EX_PUSH_LOCK Lock;                            //0x198
    struct _ETHREAD* LockOwner;                           //0x1a0
};

TRACE_ENABLE_INFO

最后,_ETW_GUID_ENTRY 结构的嵌套成员之一为 ProviderEnableInfo,其类型是 _TRACE_ENABLE_INFO 结构。如需了解该数据结构的成员细节,可参考 Microsoft 官方文档以及 Vergilius Project。该结构中的配置参数,会直接影响 ETW 提供程序的运行状态与功能能力。

// 0x20 bytes (sizeof)
// Win11 22H2 10.0.22621.382
struct _TRACE_ENABLE_INFO
{
    ULONG IsEnabled;                                       //0x0
    UCHAR Level;                                           //0x4
    UCHAR Reserved1;                                       //0x5
    USHORT LoggerId;                                       //0x6
    ULONG EnableProperty;                                  //0x8
    ULONG Reserved2;                                       //0xc
    ULONGLONG MatchAnyKeyword;                             //0x10
    ULONGLONG MatchAllKeyword;                             //0x18
};

注册句柄的使用方法解析

理论背景知识固然重要,但想要深入理解一个技术主题,最佳方式始终是结合具体的应用案例来分析。接下来我们简要看一个示例。大多数关键的内核态 ETW 提供程序,均是在未导出函数 nt!EtwpInitialize 中完成初始化的。对该函数进行分析可以发现,它初始化了约 15 个提供程序。

nt!EtwpInitialize 部分反编译的代码屏幕截图
图 4 - nt!EtwpInitialize 部分反编译

我们以 Microsoft-Windows-Threat-Intelligence (EtwTi) 注册项为例,可通过查询全局参数 ThreatIntProviderGuid 来还原该提供程序对应的 GUID。

EewTi 提供程序 GUID 的代码屏幕截图
图 5 – EtwTi 提供程序 GUID

在网络上检索该 GUID,即可直接验证我们还原出了正确的数值 (f4e1897c-bb5d-5668-f1d8-040f4d8dd344)。

接下来我们分析注册句柄参数 EtwThreatIntProvRegHandle 的实际应用场景与使用逻辑。该句柄被引用的其中一处是函数 nt!EtwTiLogDriverObjectUnLoad。从函数名称即可直观判断,其作用是在内核卸载驱动对象时生成对应的 ETW 事件。

nt!EtwTiLogDriverUnload 反编译代码屏幕截图
图 6 – nt!EtwTiLogDriverUnload 反编译

此处调用了 nt!EtwEventEnablednt!EtwProviderEnabled 两个函数,且均将该注册句柄作为入参传入。接下来我们分析其中一个子函数,以深入理解其底层执行逻辑。

nt!EtwProviderEnable 反编译代码屏幕截图
图 7 - nt!EtwProviderEnable 反编译

诚然,这有点难以理解。不过,指针运算并不是特别重要。我们将重点分析该函数对注册句柄的处理逻辑。可以看到,此函数会对 _ETW_REG_ENTRY 结构体及其子结构体(例如 GuidEntry 属性)的多项属性执行有效性校验。

struct _ETW_REG_ENTRY
{
    …
    struct _ETW_GUID_ENTRY* GuidEntry;                    //0x20
    …
}

还有 GuidEntry->ProviderEnableInfo 属性。

struct _ETW_GUID_ENTRY
{
    …
    struct _TRACE_ENABLE_INFO ProviderEnableInfo;         //0x60
    …
}

随后该函数会执行一系列类似的基于级别的校验。最终,函数返回布尔值,用以标识该提供程序是否针对指定级别和关键字用了事件日志记录功能。更多细节可参考 Microsoft 官方文档

由此可见,当通过注册句柄访问某个提供程序时,其关联结构体的完整性会直接决定该提供程序能否正常运行。反之,一旦攻击者能够篡改这些结构体,就可以篡改调用方的执行流程,进而实现事件日志的丢弃或彻底屏蔽。

攻击注册句柄

结合前文提及的 Binarly 所定义的攻击面,再辅以我们上述的浅层分析,即可推导得出若干能够破坏事件采集功能的攻击策略。

  • 攻击者可将 _ETW_REG_ENTRY 结构体对应的指针置空。如此一来,所有引用该注册句柄的函数都会判定,此提供程序尚未完成初始化。
  • 攻击者可将 _ETW_REG_ENTRY->GuidEntry->ProviderEnableInfo 指针置空。这一操作能够直接禁用该提供程序的事件采集功能,因为 ProviderEnableInfo 是指向 _TRACE_ENABLE_INFO 结构体的指针,而后者定义了该提供程序的核心运行规则。
  • 攻击者可覆写 _ETW_REG_ENTRY->GuidEntry->ProviderEnableInfo 这个数据结构体的属性,以此篡改提供程序的配置。
    • IsEnabled:设为 1 时,启用该提供程序的事件接收功能(或调整接收事件时采用的配置参数);设为 0 时,禁用该提供程序的事件接收功能。
    • Level:该参数用于指定希望提供程序记录的最高事件级别。只有当事件自身的级别小于或等于此参数值,且同时满足MatchAnyKeywordMatchAllKeyword 条件时,提供程序才会记录该事件。
    • MatchAnyKeyword:这是一个 64 位的关键字位掩码,用于定义希望提供程序记录的事件类别。在满足 Level 与 MatchAllKeyword 条件的前提下,只要事件自身的关键字位与该参数中任意一个置位的比特位相匹配,或该事件未设置任何关键字位,提供程序通常就会记录该事件。
    • MatchAllKeyword:这是一个 64 位的关键字位掩码,用于限制提供程序需要记录的事件范围。在满足 Level 与 MatchAnyKeyword 条件的前提下,只有当事件自身的关键字位与该参数中所有置位的比特位完全匹配,或该事件未设置任何关键字位时,提供程序才会记录该事件。

内核搜索技巧

我们现在已经清晰掌握了针对 ETW 的 DKOM 攻击的基本原理。假设攻击者已通过某种漏洞获得了内核读写原语(正如本次案例中的 Lazarus 恶意软件,就是通过加载存在漏洞的驱动实现这一目的)。目前攻击者尚缺的关键环节,是找到这些注册句柄的方法。

我将介绍两种定位这些句柄的核心技术方法,并阐述其中一种方法的变体,该变体已被 Lazarus 组织应用于其内核载荷中。

中等完整性水平 (MediIL) KASLR 绕过技术

首先,我们有必要先明确一点:尽管系统部署了内核 KASLR 机制,但对于能够以 MedIL 或更高权限执行代码的本地攻击者而言,该机制并不能构成一道有效的安全边界。存在多种可泄露内核指针的方法,而这些方法仅会在沙盒环境或 LowIL 场景下受到限制。若需了解相关技术背景,可参考 Alex Ionescu 发表的文章《我遇到的麻烦千千万,内核指针泄露却从不算,文中提及的诸多技术手段至今仍具备实用性。

此处选用的工具是调用 ntdll!NtQuerySystemInformation 函数,并指定 SystemModuleInformation 类别:

internal static UInt32 SystemModuleInformation = 0xB;

[DllImport(“ntdll.dll”)]
internal static extern UInt32 NtQuerySystemInformation(
    UInt32 SystemInformationClass,
    IntPtr SystemInformation,
    UInt32 SystemInformationLength,
    ref UInt32 ReturnLength);

该函数会返回内核空间中所有已加载模块的实时基地址。基于这些基地址,攻击者可解析磁盘上对应的模块文件,实现原始文件偏移量与相对虚拟地址之间的双向转换。

public static UInt64 RvaToFileOffset(UInt64 rva, List<SearchTypeData.IMAGE_SECTION_HEADER> sections)
{
    foreach (SearchTypeData.IMAGE_SECTION_HEADER section in sections)
    {
        if (rva >= section.VirtualAddress && rva < section.VirtualAddress + section.VirtualSize)
        {
            return (rva – section.VirtualAddress + section.PtrToRawData);
        }
    }
    return 0;
}

public static UInt64 FileOffsetToRVA(UInt64 fileOffset, List<SearchTypeData.IMAGE_SECTION_HEADER> sections)
{
    foreach (SearchTypeData.IMAGE_SECTION_HEADER section in sections)
    {
        if (fileOffset >= section.PtrToRawData && fileOffset < (section.PtrToRawData + section.SizeOfRawData))
        {
            return (fileOffset – section.PtrToRawData) + section.VirtualAddress;
        }
    }
    return 0;
}

攻击者也可通过标准的加载库 API(例如 ntdll!LdrLoadDll),将这些内核模块加载至自身的用户态进程中。这种方式能够省去文件偏移量与相对虚拟地址 (RVA) 之间来回转换的繁琐操作。但从操作安全的角度来看,这种方法并非最优选择,因为它会产生更多可供检测的遥测数据。

方法 1:小工具链

在条件允许的情况下,这是我更倾向采用的技术方案,因为该方法能提升内核指针泄露功能在不同模块版本间的可移植性,其受补丁更新带来的变动影响更小。但该方案也存在一个弊端,即它的实现依赖于目标泄露对象所对应的小工具链的存在。

以 ETW 注册句柄为例,我们选取 Microsoft-Windows-Threat-Intelligence 作为分析对象。下文将展示调用 nt!EtwRegister 函数的完整代码逻辑。

nt!EtwRegister 完整调用反汇编代码屏幕截图
图 8 – nt!EtwRegister 完整调用反汇编代码

此处我们的目标是泄露指向该注册句柄的指针 EtwThreatIntProvRegHandle。如图 8 第一行所示,该指针被加载到 param_4 中。此指针最终指向内核模块 .data 节区中的一个全局变量。由于该调用发生在未导出函数内,我们无法直接泄露其地址。取而代之的思路是,查找这个全局变量被哪些函数引用,并确认这些引用函数中是否存在地址可被泄露的目标。

nt!EtwThreatIntProvRegHandle 引用代码屏幕截图
图 9 - nt!EwThreatIntProvRegHandle 引用

对这些引用项展开排查后,很快就能定位到一个候选目标,即 nt!KeInsertQueueApc 函数。

屏幕截图 nt!KeInsertQueueApc 部分反编译代码
图 10 – nt!KeInsertQueueApc 部分反编译

该函数之所以是理想的候选目标,主要基于以下几点原因:

  • nt!KeInsertQueueApc 是一个导出函数。这意味着我们可通过 KASLR 绕过技术泄露其实时内存地址。随后,便能利用已获取的内核漏洞读取该地址处的数据。
  • 该全局变量在函数起始位置即被调用。这一点至关重要,因为它意味着我们大概率无需构建复杂的指令解析逻辑,即可定位到这个全局变量。

查看对应的汇编代码,其指令布局如下。

nt!KeInsertQueueApc 部分反汇编代码屏幕截图
图 11 – nt!KeInsertQueueApc 部分反汇编

泄露该注册句柄的操作至此变得十分简单。我们利用已获取的内核漏洞读取一段字节数组,接着查找第一条 mov R10 指令,以此计算出该全局变量的相对虚拟偏移量。计算逻辑如下:

Int32 pOffset = Marshal.ReadInt32((IntPtr)(pBuff.ToInt64() + i + 3));
hEtwTi = (IntPtr)(pOffset + i + 7 + oKeInsertQueueApc.pAddress.ToInt64());

一旦获取该注册句柄,攻击者便能进一步访问 _ETW_REG_ENTRY 数据结构。

总体而言,这类小工具链可被用于泄露多种内核数据结构。但值得指出的是,并非总能找到此类小工具链,且部分小工具链可能包含多个复杂的执行阶段。例如,一条用于泄露页目录项 (PDE) 常量的小工具链,其结构可能如下所示。

MmUnloadSystemImage -> MiUnloadSystemImage -> MiGetPdeAddress

事实上,对 ETW 注册句柄展开的粗略分析显示,大多数注册句柄并不存在前文所述的可用小工具链。

方法 2:内存扫描

泄露这类 ETW 注册句柄的另一种核心方案,是采用内存扫描,既可直接扫描实时内核内存,也可对磁盘中的内核模块文件进行扫描。需要注意的是,若对磁盘模块文件执行扫描,可借助前文提及的方法实现文件偏移量与相对虚拟地址之间的转换。

该方法的核心流程为:识别唯一字节特征码 → 扫描内存中匹配该特征码的位置 → 在特征码匹配位置的偏移处执行后续操作。为更清晰地理解这一思路,我们再回看 nt!EtwpInitialize 函数:

nt!EtwpInitialize 部分反编译的代码屏幕截图
图 12 - nt!EtwpInitialize 部分反编译

该函数中对 nt!EtwRegister 的全部十五次调用基本集中在一起。此处的核心策略是:找到一个出现在第一次 nt!EtwRegister 调用之前的唯一特征码,以及另一个出现在最后一次 nt!EtwRegister 调用之后的特征码。这一操作并不复杂。提升该方法可移植性的一个技巧是,开发一款能够处理通配符字节串的特征码扫描器。具体实现可由读者自行完成。一

旦确定了起始和终止位置的索引,便可解析两者之间的所有指令。

  • 可基于 CALL 指令的操作码 0xe8 识别出所有潜在的 CALL 指令。
  • 随后,读取一个 DWORD 长度的数据,计算出该潜在 CALL 指令的相对偏移量。
  • 再将此偏移量与 CALL 指令自身的相对地址相加,并额外加上 5(即该汇编指令的字节长度)。
  • 最后,可将这一新的数值与 nt!EtwRegister 函数的地址进行比对,从而定位出所有有效的 CALL 指令调用位置。

一旦定位到所有 CALL 指令,便可以向前回溯指令流,提取出该函数的两个关键参数:一是用于标识提供程序的 GUID,二是注册句柄的内存地址。掌握这些信息后,攻击者即可对注册句柄实施有针对性的 DKOM 攻击,从而篡改目标提供程序的运行逻辑。

Lazarus ETW 修补

我获取了 ESET 白皮书中提及的 FudModle DLL 样本并展开了分析。该 DLL 会加载一个经过数字签名但存在漏洞的戴尔驱动程序(该驱动从经内联 XOR 编码的资源段中提取),随后借助该驱动篡改内核中的大量数据结构,以此限制主机上的遥测数据采集。

Lazarus FudModule 哈希代码屏幕截图
图 13 – Lazarus FudModule 哈希

作为本文的最后一部分,我将梳理 Lazarus 组织所采用的内核 ETW 注册句柄查找策略。该策略是前文所述扫描方法的一种变体。

在搜索函数的起始阶段,Lazarus 组织会先解析出 nt!EtwRegister 函数的地址,并以此作为扫描的起始点。

Lazarus FudModule 部分 ETW 搜索反编译的屏幕截图
图 14 – Lazarus FudModule 部分 ETW 搜索反编译

这一做法其实有些反常,因为它的有效性取决于函数的存储位置与其被调用位置之间的关系。函数在模块中的相对位置可能会随版本迭代发生变化,毕竟代码会有新增、移除或修改的情况。不过,基于模块的编译机制,函数之间通常会保持相对稳定的排列顺序。据此推测,采用这种策略应该是为了优化扫描速度。

ntoskrnl 中查找 nt!EtwRegister 的引用关系时可以发现,采用这种策略并不会遗漏太多引用项。Lazarus 或许还开展了额外分析,从而判定这些被遗漏的引用项无关紧要,无需对其进行补丁篡改。下文将对这些被遗漏的引用项加以标注。通过该策略,Lazarus 在扫描过程中可直接跳过 0x7b1de0 字节的内存空间;若扫描器的运行速度较慢,这一优化节省的开销将十分可观。

nt!EwRegister 调用实例的代码屏幕截图
图 15 – nt!EtwRegister 调用实例

此外,在启动扫描时,会先跳过前五个匹配项,之后才开始记录注册句柄。该搜索函数的部分实现逻辑如下所示。

Lazarus FudModule 部分 ETW 搜索反编译的屏幕截图
图 16 – Lazarus FudModule 部分 ETW 搜索反编译

这段代码虽略显晦涩,但核心逻辑清晰可辨。代码会定位所有调用 nt!EtwRegister 的指令位置,提取对应的注册句柄,通过 KASLR 绕过技术将该句柄转换为内核实时内存地址,最终将这个句柄指针存储至恶意软件配置结构体中专门预留的数组内(该结构体在初始化阶段完成分配)。

最后,让我们来看看 Lazarus 如何禁用这些提供程序。

Lazarus FudModule 模块对 ETW 注册句柄进行空值化操作的代码屏幕截图
图 17 – Lazarus FudModule 模块对 ETW 注册句柄进行空值化操作

这一操作的逻辑基本清晰:Lazarus 会先泄露我们前文提及的全局变量,随后将该地址上的指针覆写为空值。如此一来,若 _ETW_REG_ENTRY 数据结构原本存在引用关系,便会被彻底清除。

不过,我对这种攻击手法的技术实现并不完全认可,原因如下:

  • 该载荷并未采集提供程序的 GUID,因此无法基于 GUID 做出“是否应该覆写该提供程序注册句柄”的智能决策。
  • ntoskrnl 内某一偏移地址启动扫描的做法也存在争议,因为扫描起始偏移可能会随 ntoskrnl 的版本不同而变化。
  • 而随意跳过前 5 个匹配项的操作同样值得商榷。尽管这么做可能存在某些战略考量,但更优的方案应当是:先采集所有提供程序的信息,再通过程序化逻辑对结果进行筛选。
  • 将指向 _ETW_REG_ENTRY 的指针覆写为空值的做法虽能生效,但该手法的痕迹过于明显。更优的方案应当是直接篡改 _ETW_REG_ENTRY_ETW_GUID_ENTRY_TRACE_ENABLE_INFO 的关键属性字段,而非简单覆写指针。

出于技术研究目的,我重新实现了这一攻击手法;但同时我对其中的技术细节做了若干优化调整。

  • 我采用了一款经过速度优化的搜索算法,来定位 ntoskrnl 中所有值为 0xe8 的字节。
  • 随后我执行了一系列后处理操作,以判定这些字节对应的指令是否为有效的 CALL 指令,并确定它们各自的调用目标。
  • 并非所有对 nt!EtwRegister 的调用都有利用价值,因为该函数有时会以动态参数传入注册句柄,这类调用无法稳定提取有效句柄。正因如此,我额外添加了逻辑来筛选剩余的调用指令。
  • 最终,我将所有提取到的 GUID 解析为人类可读的格式,并完成了对所有注册句柄的枚举。

总体而言,经优化调整后,上述技术无疑是实现此类枚举操作的最优方案。得益于优化算法的加持,搜索耗时已可忽略不计,因此直接扫描磁盘上的完整内核模块文件,再通过额外的扫描后处理逻辑筛选结果,会是更合理的选择。

ETW DKOM 的影响

有必要简要评估此类攻击的实际影响程度。当提供程序的数据被削减甚至完全阻断时,系统的信息采集能力会出现缺失;但与此同时,并非所有这些提供程序都会上报与安全相关的敏感事件。

但在这些提供程序中,有一部分属于安全敏感类。最典型的例子就是 Microsoft-Windows-Threat-Intelligence (EtwTi),它曾是 Microsoft Defender Advanced Threat Protection (MDATP)(现更名为 Defender for Endpoint,命名变动确实容易造成混淆)的核心数据来源。需要特别指出的是,该提供程序的访问权限受到严格管控:仅早期启动反恶意软件 (ELAM) 驱动能够向其完成注册;与此同时,接收该提供程序事件的用户态进程,必须具备受保护状态 (ProtectedLight / Antimalware),且需使用与 ELAM 驱动相同的数字证书完成签名。

借助 EtwExplorer,我们能够更清晰地了解该提供程序可上报的各类信息类型。

ETW Explorer 文件页面的屏幕截图
图 18 – ETW Explorer

这份 XML 事件清单的内容体量过大,无法在此完整附上;不过,下文展示了其中一个事件示例,可借此了解通过 DKOM 攻击能够抑制的具体数据类型。

EtwTi 部分 XML 事件清单代码屏幕截图
图 19 – EtwTi 部分 XML 事件清单

总结

内核一直是,且未来仍会是关键的攻防对抗领域,Microsoft 及第三方安全供应商均需投入精力保障操作系统的完整性。内核数据篡改不仅是漏洞利用后的攻击手段,更是内核漏洞开发过程中的核心环节。Microsoft 在该领域已取得诸多进展,例如推出了基于虚拟化的安全技术 (VBS),以及其组件之一的内核数据保护 (KDP)。

相应地,Windows 操作系统的使用者需确保充分利用这些技术成果,以此最大限度地增加潜在攻击者的攻击成本。Windows Defender Application Control (WDAC) 可被用于两大核心场景:一是确保 VBS 相关防护机制处于生效状态;二是配置并强制执行相关策略,禁止加载存在潜在风险的驱动程序。

随着越来越多的通用型威胁参与者开始利用 BYOVD 攻击,在内核空间实施 DKOM,上述防护措施的重要性愈发凸显。

 

Mixture of Experts | 12 月 12 日,第 85 集

解码 AI:每周新闻摘要

加入我们世界级的专家小组——工程师、研究人员、产品负责人等将为您甄别 AI 领域的真知灼见,带来最新的 AI 资讯与深度解析。

其他参考资料

  • 《来而未见,见而未胜:针对 ETW 的攻击可致盲 EDR 传感器》(BHEU 2021 幻灯片)- 点击查看
  • 《来而未见,见而未胜:针对 ETW 的攻击可致盲 EDR 传感器》(BHEU 2021 视频)- 点击查看
  • 《Windows 安全技术进阶》(2019 年上海蓝帽技术峰会)- 点击查看
  • 《利用一个“简单”漏洞 - 仅需 35 步或更少!》- 点击查看
  • 《利用一个“简单”漏洞 - 第 1.5 部分 - 信息泄漏》- 点击查看
  • Threat Intelligence ETW 简介 - 点击查看
  • TelemetrySourcerer - 点击查看
  • WDAC WDAC 策略向导 - 点击查看

了解更多关于 X-Force Red 的信息,点击此处查看。点击此处,预约 X-Force 的免费咨询服务。

相关解决方案
企业安全解决方案

部署源自最大企业安全供应商的解决方案,实现企业安全计划的转型。

深入了解网络安全解决方案
网络安全服务

通过网络安全咨询、云端和托管安全服务实现业务转型并有效管理风险。

    深入了解网络安全服务
    人工智能 (AI) 网络安全

    使用人工智能驱动的网络安全解决方案提高安全团队的速度、准确性和工作效率。

    深入了解 AI 网络安全
    采取后续步骤

    无论您需要的是数据安全、端点管理,还是身份和访问管理 (IAM) 解决方案,我们的专家都随时准备为您提供支持,助力企业建立强大的安全环境。 在网络安全咨询、云端和安全托管服务方面的全球行业领导者的帮助下,推动业务转型并有效管控风险。

    深入了解网络安全解决方案 发现网络安全服务