Windows端点防御技术全景解析:初始访问攻防实战

本文深度解析Windows端点防御技术体系,涵盖ETW事件追踪、内核回调机制、API钩子、进程树分析、内存页扫描、调用栈追踪、硬件强制堆栈保护及驱动阻止列表等核心防御技术,揭示现代红队作战面临的挑战。

初始访问操作第一部分:Windows端点防御技术全景

当今Windows桌面平台的端点防御领域充斥着具有高度复杂能力的产品。除防病毒产品外,扩展检测与响应(XDR)及互补的行为分析方法为初始访问恶意软件技术和后期利用活动检测提供了广泛覆盖。

参与红队活动的任何人都将指出,通过执行某些二进制工件获取初始访问权限比以往更加困难。即使在Windows桌面平台上获得访问权限,常见命令控制平台的后期利用活动在成熟环境中也经过充分研究并具备完善的防御响应机制。

这种增强的端点防御态势促使红队和威胁行为者将战术转向其他脆弱领域,例如云资源配置错误、团队协作工具、软件开发供应链以及跨信息技术解决方案持续存在的凭证(错误)管理挑战。

以下防御技术/产品列表专注于防御产品技术,并简要提及集成的Windows功能。Windows操作系统还存在许多其他安全功能,例如地址空间布局随机化(ASLR)、数据执行保护(DEP)和控制流防护(CFG),但这些不在本文讨论范围内。

防御技术覆盖示例包括以下项目:

  • 静态工件分析
  • Windows事件追踪(ETW)
  • Windows内核通知回调
  • Windows DLL API钩子
  • 进程树分析
  • 内存页扫描
  • 调用堆栈追踪
  • Windows 10/11硬件强制堆栈保护
  • 内核驱动阻止列表

Windows事件追踪(ETW)

该技术为用户模式应用程序和内核驱动程序活动实现追踪和事件日志记录。Windows事件追踪API通过三个组件实现:

  • 控制器:可启动或停止事件追踪会话
  • 提供程序:提供事件数据本身
  • 消费者:消费事件数据

提供程序有三种类型:托管对象格式(MOF)、Windows软件追踪预处理器(WPP)以及最终提供ETW日志记录的追踪日志记录。毋庸置疑,如果您作为消费者订阅ETW数据,将收到大量追踪日志记录,包括应用程序可以进行的几乎任何Windows API调用,这简直是信息的"消防水管"。

Windows内核通知回调

在过去,许多防御产品会将重定向钩子放入系统服务描述符表(SSDT)以接收应用程序活动的必要遥测数据。微软(理所当然地)决定他们不太喜欢这种技术,因为任何小型第三方开发人员的软件缺陷都可能(并且确实)导致Windows内核不稳定。随着Windows Vista的发布,微软发布了一项名为Patch Guard(内核补丁保护)的伴随更改,强制要求第三方供应商不能再将钩子放入SSDT等。

可以想象,许多防御厂商确实不喜欢失去遥测数据的想法,因此微软确实提供了一项称为内核通知回调的功能。自首次发布以来,该功能一直在稳步增强。

内核通知回调允许签名的内核驱动程序注册回调例程以接收通知。高层级上,不同的通知包括:

  • PsSetCreateProcessNotifyRoutine:注册进程创建事件的回调例程
  • PsSetCreateThreadNotifyRoutine:注册线程创建事件的回调例程
  • PsSetLoadImageNotifyRoutine:注册映像加载事件的回调例程
  • ObRegisterCallbacks:注册对象更改的回调例程,例如当进程、线程或桌面句柄被打开或复制时
  • CmRegisterCallback:注册任何Windows注册表操作的回调例程

鉴于这种通知信息的粒度,防御厂商使用签名驱动程序来利用上述许多通知也就不足为奇了。在内核级别执行此操作的优势是恶意软件篡改回调注册或数据的机会较小。但请注意,存在不在阻止列表上的签名且易受攻击的内核驱动程序,这带来了篡改风险。

Windows DLL API钩子

API钩子长期以来一直是一种在Windows用户模式进程中重定向代码执行的技术。这几乎可以在进程中的任何加载映像/模块上执行,但最常见的是在"ntdll.dll"上执行,该库始终与"kernel32.dll"以及现在的"kernelbase.dll"一起在所有应用程序进程中加载。

Windows本机API是Windows操作系统中使用的一组应用程序编程接口。“ntdll.dll"是一个动态链接库,包含属于Windows本机API的函数集合。“ntdll.dll"系统调用(syscalls)是低级函数,提供用户模式应用程序与Windows内核之间的接口。

当用户模式应用程序需要执行特权操作或与操作系统内核交互时,它会进行系统调用。应用程序不是直接调用内核函数,而是调用"ntdll.dll"内的函数,这些函数进而向Windows内核进行必要的系统调用。

让我们看一下"ntdll.dll” API调用"NtWriteVirtualMemory()“的示例。对于下图,我启动了Windows调试器(WinDBG)和一个"notepad.exe"进程(因为我们喜欢挑剔记事本)。将调试器附加到进程后,我反汇编了API调用。

首先请注意这是一个64位进程,我们保持在那里,因为如今32位系统数量正在减少。下面的前两个机器代码操作码执行以下操作:

  • “mov r10,rcx”:在R10寄存器中保存RCX的副本
  • “mov eax,":将系统调用号移动到EAX寄存器中(EAX只是RCX 64位寄存器的32位表示法)

接下来,有一个测试,查看我们应该使用"int 0x2e"中断驱动的系统调用约定还是"syscall"指令。无论哪种方式,系统调用都将发生!

现在,因为我们可以打开任何我们具有适当安全令牌权限的进程,我们也可以覆盖进程中的虚拟内存。事实证明,我们可以用JMP指令替换第二个操作码,将代码重定向到新的内存位置。方便的是,32位短JMP指令正好适合我们可用的5个字节。

总体而言,防御产品钩取"ntdll.dll"中API的序列可能如下所示:

  1. 防御产品接收某些它认为"有趣"的内核通知回调
  2. 防御产品打开应用程序进程并注入一些代码以加载其自己的签名DLL模块。或者,防御产品可能配置为其自己的签名DLL模块为所有进程创建而加载,在这种情况下,此步骤将是不必要的
  3. 防御产品找到感兴趣的"ntdll.dll” API地址,并用JMP指令覆盖第二个操作码,跳转到新加载的DLL/模块的代码中
  4. 根据加载的DLL,进行某种防御扫描操作或进一步通知,甚至可能直接阻止API调用。如果未阻止,防御产品DLL将将正确的SYSCALL号移动到EAX寄存器中,然后跳回原始API的SYSCALL指令

“ntdll.dll” API钩子可能使用内核通知回调动态实现,也可能不是。在某些情况下,防御产品设计可能在映像加载通知时为所有进程钩取许多"ntdll.dll” API,并在进程的整个生命周期内保持钩取状态。

进程树分析

这里的想法是创建系统上运行进程及其层次关系的内部表示。父子关系数据可以与一组静态规则进行比较,和/或通过人工智能模型处理以识别异常异常值。一个简单的例子可能是,如果PowerShell进程是Excel电子表格的子进程,则可能认为其可疑。

内存页扫描

恶意软件分配一些虚拟内存页,将一些机器代码(shellcode)复制到该内存,将页权限设置为PAGE_EXECUTE_READ,并创建指向分配内存起始地址的线程,这种情况并不少见。虚拟内存是一种内存管理技术,提供物理内存资源的抽象。在Windows和许多其他操作系统上,虚拟内存的一页由4096个连续分配的字节组成。

创建的执行线程可能在恶意软件启动的同一运行进程中,也可能注入到恶意软件访问的外部进程中,假设恶意软件进程的安全令牌具有足够的权限来访问外部进程。

防御产品可以使用"ntdll.dll” API “NtQueryInformationThread()“来确定任何进程线程的起始地址。此外,可以使用kernel32.dll API “VirtualQuery()“或"VirtualQueryEx()“来获取分配的虚拟内存属性。以"Ex"结尾的"kernel32.dll” API函数通常是那些可以使用打开的进程句柄访问三级进程的函数,而不是本地进程。也可以使用"ntdll.dll” API调用"NtReadVirtualMemory()“直接检查内存内容本身。

内存页扫描产生了一些简单的检测机会:

  • 如果虚拟内存被分配且保护设置为READ、WRITE和EXECUTE,则几乎可以肯定是恶意的
  • 虚拟内存页上的READ、WRITE和EXECUTE权限之所以是入侵指标,与虚拟内存如何在典型Windows进程中使用有关。例如,PE/COFF可执行文件的”.text"节中的可执行机器代码通常将映射到仅设置为READ和EXECUTE的虚拟内存页中
  • 如果虚拟内存设置为READ和EXECUTE,并且在线程创建时内存没有由DLL模块映像加载支持,则可能是恶意的
  • 常用shellcode,如Metasploit项目、Cobalt Strike等项目中的shellcode,具有可以直接匹配的独特已知内存模式。检测方法还包括对Cobalt Strike配置文件数据的堆分配扫描

注意:相关的攻击规避技术是在C2客户端shellcode进入/退出睡眠模式时加密任何堆数据。

调用堆栈追踪/分析

每当创建进程线程时,总是为线程的堆栈分配内存区域。堆栈组织成堆栈帧,每个函数调用创建一个新帧。堆栈帧将包含属于特定函数的局部变量,以及函数返回地址。

当线程中的任何代码执行时,其堆栈将随着各种函数的调用而增长和收缩。这意味着在任何时间点,线程的堆栈(有时称为调用堆栈)都有显示函数调用序列和深度的证据轨迹。

防御产品可以使用内核回调来触发"调用堆栈分析”,这将展开调用堆栈,并最常见地检查是否有任何函数调用是从没有由从磁盘加载的DLL模块/映像支持的内存进行的。

与此相关的概念是异常处理信息,其中64位PE/COFF映像的”.pdata"节包含函数表条目,这些条目包含每个函数的异常处理代码。

从攻击角度来看,可以将虚假信息写入调用堆栈,使其看起来所有函数调用都是完全合法的。这种行为将规避/击败调用堆栈追踪防御。

Windows 10/11硬件强制堆栈保护

随着Windows 11的推出和适当的处理器支持,出现了一种称为硬件强制堆栈保护的新防御技术。此功能仅在下层处理器提供支持时工作,例如Intel的控制流增强技术(CET)或AMD的影子堆栈。

简而言之,对于所有运行进程,任何函数调用的返回地址都被推送到进程线程的调用堆栈以及处理器维护的影子堆栈上。每当遇到返回指令(RET)时,比较两个堆栈上的返回地址。如果地址不匹配,则发出控制保护异常。此异常被Windows内核捕获,内核进而将终止违规进程。

使用影子堆栈比较函数返回地址提供了针对面向返回编程(ROP)小工具使用的防御,以及任何伪造调用堆栈的尝试,从而使任何调用堆栈分析更加有效。在同一套保护措施中,Intel还实现了间接分支跟踪(IBT),专注于击败跳转和调用导向编程(COP/JOP)攻击方法。

内核驱动阻止列表

Microsoft Windows内核环境中存在的更有吸引力的攻击者目标之一是具有漏洞的签名驱动程序。通过易受攻击的签名驱动程序直接访问内核内存,可以修改任何内核模式数据结构,包括禁用签名驱动程序强制执行并加载攻击者希望的任何自定义驱动程序的能力。

如果易受攻击的驱动程序已存在于端点上,可以编写一些自定义用户模式应用程序代码来对该驱动程序施加控制并执行进一步利用。如果攻击者在系统上具有权限,则可以使用称为自带易受攻击驱动程序(BYOVD)的方法来安装驱动程序以进行进一步利用。一旦在内核中建立了任何级别的控制,系统安全性就完全受到威胁,攻击者能力仅受想象力限制。

不幸的是,驱动程序开发是一项非平凡的工作,社区中存在大量代码共享。除此之外,还有大量在不同环境中使用的设备和驱动程序可以追溯到过去。这对今天的威胁行为者来说是一个丰富的利用领域。

因此,随着2022年更新中Windows 11的推出,微软启用了易受攻击驱动程序阻止列表。微软运行一个易受攻击驱动程序提交计划,并在每个主要Windows版本发布时(大约每年两次)更新易受攻击驱动程序阻止列表。微软还提供了一种使用Windows Defender应用程序控制策略手动更新列表的方法。

不幸的是,微软的阻止列表远非全面,而且正如您可能想象的那样,维护这样一个包含如此多设备和驱动程序的资源非常困难。因此,今天仍然存在许多易受攻击且签名的驱动程序正在被积极利用。此外,还有在线提供的易受攻击驱动程序列表。其中之一在https://www.loldrivers.io/。

结论

上述技术列表和描述现在应该让您很好地理解为什么在成熟检测的环境中越来越难以在Windows端点上建立立足点并执行后期利用活动。话虽如此,红队的初始访问操作并非不可能,只是依赖于更复杂的成熟工件生成技术。在我看来,实现这需要一种攻击性DevOps方法。这将在下一篇博客文章"初始访问操作第二部分 - 攻击性DevOps"中描述(即将推出!)。

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计