介绍
自从我写下第一篇关于通过手动修改绕过反恶意软件扫描接口 (AMSI) 以及 Powershell 和 .NET 特定绕过之间的区别的博客文章以来,已经过去了四年多的时间:
-
https://s3cur3th1ssh1t.github.io/Powershell-and-the-.NET-AMSI-Interface/
自 2020 年以来,已经发布了许多新的绕过方法,可以作为以前方法的替代方案。这篇博文将阐明 AMSI 背后的原因(大致如此,但希望容易理解)以及如何在四年多后仍然有效地绕过它。有什么变化吗?剧透:只有部分 1.什么时候需要绕过 AMSI?
尽管 AMSI 已在许多论文和工具中进行了分析和描述,但我仍然惊讶地发现社区中存在如此多的混乱和误解。例如,GitHub 上发布了许多 shellcode 加载器,它们只执行 shellcode。但 README 还指出它包含一个 AMSI 绕过,这就是它从未被检测到的原因。因此存在很多误解,至少在 GitHub 或社交网络上是如此,这可能会让越来越多的人感到困惑。
我们什么时候真的需要使用 AMSI 绕过?至少对于 shellcode 执行来说,我们不需要。正如我在四年多前在博客中写到的,AMSI 主要用于在运行时分析脚本语言和 .NET 托管代码,例如
-
Powershell
-
VBS
-
Javascript
-
VBA macros
-
C# assemblies
因此,如果您使用的是命令和控制框架的有效负载,并且主要从那里运行 BOF 或 COFF,那么您根本不需要绕过 AMSI。如果您在加载器中实现了绕过,那么您只会增加 IoC 以及被该绕过尝试检测到的可能性。除非您真的需要它们,否则最好不要使用绕过!
另一方面,如果您想运行已知的恶意和未混淆的公共工具(例如来自 GitHub 的上述任何一种语言),或者在您自己的工具中重用这些工具的代码,则需要绕过 AMSI 才能运行这些工具。您要通过 Powershell 中的 Invoke-Expression 执行 GitHub 脚本吗?您是否通过 assembly::load() 加载 .NET 程序集?创建恶意办公宏?通过 mshta.exe 、csc ript.e xe? 或 wscript.exe 将脚本加载到内存中?您可能需要绕过 AMSI。
2. AMSI 的工作原理以及如何绕过它
AMSI 主要是基于签名的检测。与传统的基于签名的检测的主要区别在于,这些签名是在运行时查找的,只要从内存中加载了潜在的恶意内容。正如 IBM X-Force Red 最近指出的那样,当某些内容从磁盘加载到内存时,架构上的 AMSI 在某些时候根本不会触发扫描。
AMSI 签名可能是什么样的?它们可以是像 Invoke-Mimikatz 这样的简单字符串,也可以是像经典 AmsiScanBuffer 补丁使用的字节这样的字节数组:
Powershell[Byte[]] (0xB8, 0x57, 0x00, 0x07, 0x80, 0xC3)
对于 C# 程序集,这可以是特定的十六进制字节,但根据我的经验,AMSI 还使用某种 Yara 规则 - 或者至少是正则表达式。但如果 AMSI 与签名有关,这也意味着我们总是可以通过修改代码来绕过它。如果您更改代码以使签名不适用,则可以绕过 AMSI。就这么“简单”,尽管签名有时很难找出来。
绕过 AMSI 检测的替代方法是以某种方式破坏 amsi.dll 或扫描过程中涉及的其他库中的功能,或者完全阻止 DLL 加载。几乎所有公开记录的绕过方法都是关于这一点的。它们主要在破坏功能的技术上有所不同,例如
-
修补内存区域(从我的角度来看也包括钩子)
-
使用向量异常处理程序和硬件断点等来操纵工作流程
-
创建新进程,并通过各种方式阻止相关 DLL 加载
-
防止在启动 CLR 和/或初始化 AMSI 之前加载相关 DLL
这些事情通常在运行时完成。IoC 以及对这些绕过的检测依赖于:
-
绕过代码的签名,或
-
运行时检测,例如用户空间挂钩、ETWti 或内存扫描。
3. 使用公共混淆器的缺点
混淆技术如何对抗已知的恶意 GitHub 工具?我们来看两个例子:
普通 GitHub 版本
1.PowersharpPack 中的 Invoke-Rubeus — 撰写本文时有 29 次检测。
2.WinPwn—— 截至撰写本文时,已检测到 19 项
Invoke-Obfuscation 混淆版本
-
PowersharpPack 中的 Invoke-Rubeus - 10 次检测
-
WinPwn - 首次上传时检测到 2 个
我们可以看到,近五年后,仍然可以使用相同的公共混淆器来逃避基于签名的检测。哇,说实话我没想到这一点,我以为现在会有更多 Invoke-Obfuscation 的特定检测。 注意: 在完成这篇博文后,我摆弄了一些 EDR 和上面的脚本 - 事实证明,有些脚本有专门用于 Invoke-Obfuscation 的 AMSI 签名!即使是非恶意的第一个脚本也被标记为恶意。所以这意味着,VirusTotal 不会向您显示基于 AMSI 的检测,而只会显示基于文件本身签名的检测。这一事实使得 Invoke-Obfuscation 对这些供应商完全无用。
图 2:部分破坏的脚本执行
但菜单仍然显示,并且原生的 Powershell 功能可以正常使用。
由于混淆,混淆的 Invoke-Rubeus 版本首先在这里出现问题,因为类型 [dreIKOpFhund.pROGRam] 被放置在函数之前并作为变量,此时这些命名空间和类名无法正确解析。为了解决这个问题,我在混淆的 [assembly::load] 行之后手动将此部分放入函数本身中:
图 3:Invoke-Rubeus 的混淆修复
图4:脚本加载/执行
乍一看,我们似乎确实这样做了,但只针对 Powershell 脚本本身,而不是针对运行时调用的 Rubeus 程序集。为什么会这样?这已经在我的第二篇私人博客中描述过了。因此,在这种情况下,我们必须先混淆程序集,然后嵌入它,再混淆 Powershell 脚本。我们需要对每个程序集/脚本都采用这种方法。使用现有的 AMSI 绕过方法会不会更容易?
4. 使用哪种旁路?
注意:这种用于入口点修补的检测已经被其他几家 EDR 供应商使用了好几年 ,但由于其广泛的使用,在 Defender 推出它时得到了*更多*的关注。
使用硬件断点
由于上述修补部分的发现,社区中的人们提出了使用硬件断点的想法。它们具有很大的优势,即用户空间钩子不需要被绕过,目标 DLL 的完整性仍然有效,并且内存扫描器无法检测到操纵。
根据我的经验,很少有供应商在运行时检测到使用硬件断点绕过 AMSI 的情况。然而,从理论上讲,可以通过检查调试寄存器值轻松检测到硬件断点 - 例如,如果其中一个设置为 AmsiScanBuffer 地址,则可以发出警报。理论与实践,从未遇到过这样的检测,可能是因为误报率?然而,一些供应商最近通过 SetThreadContext 提出了基于 ETWti 的检测,如下所述。
总体而言,根据我的经验,使用硬件断点仍然被认为是 OpSec 安全,可以抵御大多数 AV/EDR 供应商,因此是推荐的方法。但是,随着新的检测出现,这种情况随时可能改变,Cat & Mouse
防止 DLL 加载
已经发布了一些技术,可以防止 AMSI 相关的 DLL 加载,这样初始化和扫描就根本不会发生。如上所述,这主要可以用于本机语言或新生成的进程,因为在这些情况下,加载和初始化都尚未完成。例如
-
使用 DEBUG_PROCESS 标志创建一个新进程,并使用 SharpBlock 修补 LOAD_DLL_DEBUG_EVENT 上的入口点
https://github.com/CCob/SharpBlock
-
以 NtCreateSection 为例, 在 DLL 加载过程中钩住函数返回失败
https://waawaa.github.io/es/amsi_bypass-hooking-NtCreateSection/
-
DLL 加载过程中的挂钩函数会因使用 Ruy-Lopez 的新生成的进程而返回失败
https://github.com/S3cur3Th1sSh1t/Ruy-Lopez
虽然对于 EDR 来说,挂钩通常很容易被发现,但我不知道有任何供应商会标记新设置的挂钩,这可能也是由于误报率的原因。即使他们确实对新挂钩发出警报,也可以使用硬件断点来实现相同的效果。所以我不知道有任何基于运行时的检测来检测这些技术,而且它们至今仍然有效。
针对特定替代方案
根据 AMSI 绕过目标(例如 Powershell 或 C# 程序集),可以使用其他几种替代方案。在许多情况下,它仍在修补 - 但在不同的偏移量/位置。关于 Powerhell,下图反映了我在撰写这篇博文时的个人经历 - 不要责怪我 :
第一个绕过方法现在是一个特殊情况。它发布时,既适用于 Powershell 脚本,也适用于加载的 .NET 程序集。但在发布后,微软对 Powershell 内部进行了调整,使其不再影响脚本,而只影响 .NET 程序集。因此,这可以与橙色标记的绕过方法之一结合使用,或者如果您的脚本未被标记并且加载了程序集,那么没问题。
提供程序补丁在我的仓库中有两个代码片段,带有 Add-Type 代码片段仅适用于 Powershell 脚本,而使用反射的代码片段适用于脚本和 .NET 程序集。
如您所见,绿色/橙色仍然包含一些基于补丁的旁路。但这些不太为人所知/使用,因此根据我的经验,内存扫描无法检查/发现它们。
在少数情况下,EDR 供应商甚至不再依赖 amsi.dll 进行扫描。任何针对此特定 DLL 的绕过都不会导致绕过。在这些情况下,您需要通过注册表或内存遍历枚举其 AMSI 提供程序 DLL,然后修补该 DLL 或自定义 AMSI DLL。更多信息可以在 2022 年的这个黑帽演讲中找到。
上面链接的 IBM 的 C# 程序集特定 AMSI 绕过现在也不应该在运行时被标记。整个“欺骗” CLR 从磁盘加载程序集的概念并不新鲜,并且已在 2021 年与另一个 .NET 特定绕过一起发布。自该版本发布以来,Windows Defender 的行为没有改变,通过 PoC SharpTransactedLoad 加载程序集仍然有效。但是,一些 EDR 供应商的行为并不相同,并且还将 AMSI 扫描应用于从磁盘加载的程序集,因此至少 2021 年的 PoC 不再完全 OpSec 安全。
5.AmsiScanbuffer 补丁真的失效了吗?
在阅读了一篇试图了解最近的 Defender 补丁检测的博客文章后,我开始好奇地想亲自深入研究这些检测。还有什么可能导致这样的检测?最知名的公共补丁在 AmsiScanBuffer 的入口点某处使用 0xC3 (RET) 退出该函数并返回 INVALID ARG ,让调用者认为没有发现任何恶意内容。让我们在这里玩一玩,只是为了好玩。
它只是入口点吗?
正如我之前所说,我相信 Defender 在采取行动之前会使用内存扫描来检查是否存在恶意操作。那么内存扫描只是检查函数的开头吗?那个入口点是什么样的?
图 6:AmsiScanBuffer 入口点
图 7:偏移量 0x14 处的替代补丁字节
> powershell$Win32 = @"using System;using System.Runtime.InteropServices;public class Win32 { [DllImport("kernel32")] public static extern IntPtr GetProcAddress(IntPtr hModule, string procName); [DllImport("kernel32")] public static extern IntPtr LoadLibrary(string name); [DllImport("kernel32")] public static extern bool VirtualProtect(IntPtr lpAddress, UIntPtr dwSize, uint flNewProtect, out uint lpflOldProtect);}"@Add-Type $Win32 $LoadLibrary = [Win32]::LoadLibrary("am" + "si.dll")$Address = [Win32]::GetProcAddress($LoadLibrary, "Amsi" + "Scan" + "Buffer")$p = 0[Win32]::VirtualProtect($Address, [uint32]5, 0x40, [ref]$p)# POP R14# POP R15# POP RDI$Patch = [Byte[]] (0x41, 0x5F, 0x41, 0x5E, 0x5F, 0xB8, 0x57, 0x00, 0x07, 0x80, 0xC3)$Address = [Int64]$Address + 0x14$new = [System.Runtime.InteropServices.Marshal]$new::Copy($Patch, 0, $Address, 11)
结果是 -> 我们不再被标记并且绕过了 AMSI:
图 8:成功绕过 AMSI
第三个输入参数是要扫描的缓冲区的长度。如果我们将其设置为 0 会怎么样?这实际上应该会导致扫描 0 个字节的大小,对吗?所以我们的脚本或程序集根本不会被看到。AmsiScanBuffer 函数将输入参数从 r8 寄存器移动并将其放置在 edi 寄存器中,如下所示:
图 10:存储在 edi 中的第三个参数的值
我们可以用 sub edi edi 替换 mov edi, r8d 来清除它的值,如下所示:
图 11:补丁替代方案二
再次,结果是一个有效的绕过,而我们的进程没有被终止:
图 12:无内存扫描触发器的工作旁路演示
有趣的事实:您可能还记得 Defender 曾经将字符串 amsiscanbuffer 、 amsi.dll 和补丁字节标记为恶意,对吧?现在情况已不再如此,因为这种新引入的检测现在是查找和阻止已修补的 AmsiScanBuffer 函数的主要检测。因此,这些“旧”签名现在已被内存扫描取代。
理论上,在发布本博客后,很容易添加所示的两个旁路(以及补丁的内存签名),因此不要指望它们能持续太久。但好消息是,还有几十种其他补丁替代方案。您只需要在调整补丁偏移量和字节方面发挥创造力即可。
6. 结论
多年前关于绕过 AMSI 的许多内容在几年后仍然适用。它仍然与签名有关,并通过手动修改或混淆来绕过签名。出于某种原因,多年前的混淆工具仍然未被通用签名覆盖。但一些 EDR 供应商确实为其构建了基于 AMSI 的签名,这实际上使得未经修改的工具变得毫无用处。然而,一般来说,修改或混淆仍然足以完全逃避 AMSI 检测,但由于有许多不同的供应商,因此签名数据库也不同,很难确定它们都被绕过了。
或者,在运行时操纵 AMSI 进程中涉及的 DLL 会导致通用绕过,从而可以加载已知的恶意脚本或程序集。已发布的绕过方法主要使用内存补丁或矢量异常处理程序(例如硬件断点)来在运行时操纵扫描或初始化过程。其他一些方法依赖于操纵 DLL 加载过程 - 无论是在 AMSI 尚未初始化时还是对于新生成的进程。
2025 年什么才是有效的?从我的角度来看,有效性可以通过基于行为的检测来衡量,因为所有绕过方法都可以轻松修改以避免基于签名的检测。根据我的经验,在 amsi.dll 函数的入口点使用补丁不再被认为是安全的,因为几年来,一些供应商一直通过内核事件触发的内存扫描来检测这些补丁。在撰写本文时,使用硬件断点可以被认为是更安全的 OpSec,但供应商也开始为此使用基于行为的检测,猫捉老鼠的游戏仍在继续。在加载之前操纵 DLL 加载过程或 AMSI 初始化尚未被行为检测到,但只能在初始化之前或新生成的进程中使用。
尽管由于内存扫描检测,在入口点处修补不再被认为是安全的,但对于 amsi.dll 来说,在自定义偏移处修补仍然是合适的。修补 clr.dll 或 AMSI 进程中涉及的其他 DLL 的替代方案通常也不会触发基于内存扫描的检测。那么修补就此终结了吗?我想说它还远没有终结。
原文始发于微信公众号(Ots安全):Bypass AMSI in 2025
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论