Powershell GzipStream加载技术如何绕过主流杀软?

4 人参与

在红队的日常作业里,PowerShell 已不再是单纯的管理工具,而是被打磨成一把兼具灵活性和隐蔽性的“匕首”。当传统的 Invoke-ExpressionInvoke-Command 已被杀软的行为特征库牢牢捕获时,攻击者往往转向更底层的 .NET 类库——尤其是 System.IO.Compression.GzipStream,用它把脚本压缩、再解压执行,形成一种“压缩后即用”的加载链。

GzipStream 的加载机制

GzipStream 本质上是一个流式压缩器,能够在内存中对任意字节序列进行 CompressDecompress。攻击者先把完整的 PowerShell 脚本保存为 .ps1,随后使用 [System.IO.Compression.GzipStream]::Compress 将其转为二进制,再通过 [System.Convert]::ToBase64String 编码成纯文本。受害机器上,仅需一行 scriptblock::Create 读取 MemoryStreamGzipStreamStreamReader,即可还原出原始脚本并执行。

为何能逃过主流杀软

  • 压缩后代码不再呈现原始关键字,例如 Invoke-ExpressionDownloadFile,特征匹配失效。
  • Base64 只是一层包装,解码行为在内存完成,文件系统上没有可供扫描的可执行体。
  • GzipStream 属于 .NET 标准库,几乎所有 Windows 环境默认加载,安全产品难以将其列入黑名单而不影响正常业务。
  • 使用 -ExecutionPolicy Bypass -NoProfile -WindowStyle Hidden 启动 PowerShell,进一步抑制日志与弹窗。

典型攻击示例

$payload = 'H4sIAAAAAAAAC...(省略)...'
$bytes   = [System.Convert]::FromBase64String($payload)
$ms      = New-Object System.IO.MemoryStream($bytes)
$gs      = New-Object System.IO.Compression.GzipStream($ms,[System.IO.Compression.CompressionMode]::Decompress)
$sr      = New-Object System.IO.StreamReader($gs)
$script  = $sr.ReadToEnd()
Invoke-Command -ScriptBlock ([scriptblock]::Create($script))

上述片段在一次渗透演练中成功加载了经过压缩的 Mimikatz 模块,杀软的实时监控页面只捕获到普通的 powershell.exe 进程,未触发任何告警。唯一的异常是内存中出现了短暂的 gzip 流对象,但多数 AV 并未对其进行深度分析。

防御思路与检测要点

针对 GzipStream 方案,防御侧可以从两条路径入手:其一是对 PowerShell 进程的行为进行监控,尤其是 MemoryStream → GzipStream → StreamReader 的链式调用;其二是对 Base64 解码后立即进入压缩流的内存块进行启发式分析,结合 entropy(熵)阈值与异常 API 调用序列,提升检测准确度。值得注意的是,单纯的签名匹配已难以覆盖此类“无文件”攻击,行为层面的监控才是关键。

在实际运维中,开启 PowerShell 的脚本日志、限制 -ExecutionPolicyRemoteSigned,以及对不可信的 Base64 载荷进行强制审计,能够在早期拦截大部分利用 GzipStream 的免杀尝试。毕竟,安全的本质仍是把“看得见的风险”先行曝光。

参与讨论

4 条评论
  • Zen_禅意

    这个加载方式挺巧妙的,不过对内存监控要求就高了

    回复
  • 夜影织网者

    之前做渗透测试时用过类似的,确实能过不少杀软

    回复
  • 无涯

    那如果换成DeflateStream效果会不会更好?

    回复
  • 黑暗守望

    压缩加载现在算常规操作了吧,老手都用烂了

    回复