0x01 引子:地表最强的感知系统
几个月前,安全行业爆出一起轰动事件:一支红队成功在某大型企业的内部网络中持续潜伏数月,直到他们的后门被杀死。企业的安全团队随即展开调查,发现这次入侵的核心技术之一是绕过了 Windows 操作系统中强大的 AMSI(Antimalware Scan Interface)机制。作为攻击者,我们有必要深入了解这次事件中的技术细节,掌握如何在实战中规避 AMSI 的检测,继续完成我们的任务。
深入探讨AMSI
AMSI 是 Windows 的一项安全技术,旨在为应用程序提供统一的接口以检测恶意软件行为。无论是 PowerShell 脚本还是其他动态内容,AMSI 都能在执行前对其进行扫描,极大地提高了系统抵御恶意攻击的能力。对红队来说,破解 AMSI 的限制相当于把一把钥匙掌握在手中。
AMSI是怎么工作的?
AMSI 的工作原理简单概述就是:拦截和分析脚本内容,然后与已知的恶意签名进行比对。无论是 PowerShell 的调用还是 JavaScript 的加载,AMSI 都会插手其中,对其内容进行深度检查。这种机制在阻断传统恶意行为上效果显著,但我们总能找到漏洞和绕过点。
流量捕获实战
首先,我们需要搭建一个测试环境。在这里,我们将使用 Windows 10 操作系统,并确保其更新到最新版本,同时安装 PowerShell 5.0 以上,因为 AMSI 与 PowerShell 版本密切相关。
环境准备
- Windows 环境准备:确保已安装 PowerShell 5.0 或更高版本。可以通过以下命令检查版本:
<pre><code class="language-shell"> $PSVersionTable.PSVersion `
- 启用 AMSI:默认情况下,AMSI 是启用的。但为了确保一切运行正常,可以通过检查注册表或组策略来确认。
- 测试工具:准备一个可以用来触发 AMSI 的简单恶意脚本。这将帮助我们验证 AMSI 的检测能力。
攻击工具
我们将使用 Go 语言编写一个简单的工具来模拟恶意行为,并展示如何绕过 AMSI 的检测。 </code></pre>go package main
import ( "syscall" "unsafe" )
func main() { amsiDll, _ := syscall.LoadLibrary("amsi.dll") amsiInit, _ := syscall.GetProcAddress(syscall.Handle(amsiDll), "AmsiInitialize") amsiCtx := uintptr(0) _, _, _ = syscall.Syscall(uintptr(amsiInit), 2, uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr("Example"))), uintptr(unsafe.Pointer(&amsiCtx)), 0) // 在这里插入你的恶意代码 } <pre><code>
验证 AMI 检测
使用上面的工具,我们尝试加载各种脚本和内容,观察其是否触发 AMSI 的警报。在失败的实例中,我们将迭代改进代码,确保能有效绕过检测。
Payload构造的艺术
绕过 AMSI 的关键在于构造有效的 Payload。构建一个高效的 Payload 不仅能规避 AMSI 的检测,还能在目标系统上顺利执行。
常见绕过技巧
- 分段执行:将恶意代码分成多个小段执行,以减小每段代码的检测概率。

- 动态加载:使用动态方法加载和执行代码,避免静态分析。
- 编码和混淆:对代码进行多层编码和混淆,使其难以识别。
- 内存技巧:利用内存操作函数直接在堆中创建和执行代码。

实现代码
我们提供一个绕过 AMSI 的 Go 示例代码,其中结合了动态加载和分段执行的技巧。请注意,这是一个简单的概念验证,在实际应用中还需加入更多的伪装和混淆。 </code></pre>go package main
import ( "syscall" "unsafe" "encoding/hex" )
// 将恶意代码分块存储 var hexCode = "e9bda4fc"
func main() { amsiBypass() }
func amsiBypass() { // 定义一些基础的内存操作 kernel32, _ := syscall.LoadLibrary("kernel32.dll") procVirtualProtect, _ := syscall.GetProcAddress(syscall.Handle(kernel32), "VirtualProtect")
// 将 AMSI 拦截的内存页标记为可写 var oldProtect uint32 addr := unsafe.Pointer(getAmsiScanBuffer()) syscall.Syscall6(procVirtualProtect, 4, uintptr(addr), uintptr(len(hexCode)/2), syscall.PAGE_EXECUTE_READWRITE, uintptr(unsafe.Pointer(&oldProtect)), 0, 0)
// 将恶意代码注入到目标内存位置 code, _ := hex.DecodeString(hexCode) copy((*[len(hexCode)/2]byte)(unsafe.Pointer(addr))[:], code) }

func getAmsiScanBuffer() uintptr { // 获取 AMSI 扫描缓冲区的地址 return uintptr(0xDEADBEEF) // 这里用一个虚拟地址代替,实际场景中需动态获取 } `
细节决定成败
在实战中,攻击者总是需要考虑各种细节。下面是一些实际操作中的注意事项:
动态获取内存地址
如上代码所示,我们使用了一个虚拟地址 0xDEADBEEF。在实际攻击中,攻击者需要动态获取 AMSI 的实际地址,这可以通过枚举系统模块和内存地址来实现。
灵活应对检测
安全厂商不断更新检测机制,攻击者需要反应迅速,实时调整策略,以求与防御者的博弈中占得先机。
实战中的经验教训
经过多次实战,我们总结了一些经验:
- 快与慢之道:快速生成和测试 Payload,同时保持耐心,逐步绕过每一个检测点。
- 混淆百变:永远不要用一成不变的代码,频繁更换加密和混淆策略,最大限度地规避签名检测。
- 情报共享:保持与其他红队和安全研究人员的信息交流,时刻关注最新的攻击与防御技术。
结语
AMSI 为 Windows 系统提供了强大的安全防护,但攻击者也有策略逐个击破。理解其工作原理,并不断探索新的绕过方法,是每个红队成员的必修课。希望通过这篇文章,你能有所启发,拓展思维。切记,所有技术只应用于授权的安全测试中。