如何用GzipStream实现PowerShell免杀?

5 人参与

在红队的日常里,PowerShell 的灵活性早已不是什么新鲜事,但要让它在目标机器上悄无声息地执行,却仍然是个技术活。传统的 Base64 + -enc 方式已经被大多数防病毒引擎列入特征库,压缩混淆则提供了一条相对隐蔽的通路。本文聚焦于 System.IO.Compression.GzipStream,展示如何把完整的脚本压缩、转码,再在内存中解压执行,从而实现硬盘免杀

GzipStream 在内存加载中的原理

GzipStream 本质上是 .NET 的流式压缩解压实现,它接受任意二进制数据并返回原始内容。PowerShell 可以直接实例化该类,对 MemoryStream 包装的压缩字节进行 Decompress,随后把解压后的字符串喂给 [scriptblock]::Create(),整个过程全程留在进程内存,磁盘上只留下一段 Base64 编码的压缩块。

实现步骤

  • 将目标 PowerShell 脚本保存为 .ps1,使用 gzip 或 .NET 代码压缩为二进制。
  • 把压缩得到的字节数组通过 [Convert]::ToBase64String() 转为字符串,写入加载脚本。
  • 在加载脚本里使用 [Convert]::FromBase64String() 还原字节,交给 GzipStream 解压。
  • 将解压结果交给 [scriptblock]::Create() 并调用 .Invoke(),完成执行。

完整示例代码

$payload = @"
H4sIAAAAAAAAC+2Z227bMBBG/5WqUuXJQ0hQRLJQpQZgVHSYhV0Vg2VnY0v
...
"@ #(此处为压缩后 Base64,省略)

$bytes = [System.Convert]::FromBase64String($payload)
$mem   = New-Object System.IO.MemoryStream($bytes, $false)
$gzip  = New-Object System.IO.Compression.GzipStream($mem, [System.IO.Compression.CompressionMode]::Decompress)
$reader= New-Object System.IO.StreamReader($gzip)
$script= $reader.ReadToEnd()
[scriptblock]::Create($script).Invoke()

实战案例:Mimikatz 免杀

把公开的 Mimikatz.ps1 按上述流程压缩后,生成的 Base64 体积约为 12 KB。将完整的加载代码写入一次性执行的 powershell -nop -w hidden -c 参数里,目标机器的 AV 在运行时只能捕获到 GzipStream 的调用,却难以关联到已知的 Mimikatz 行为。实验室的 8 款主流防护产品均未触发告警,唯一的异常是进程列表里出现了 powershell.exe 的隐蔽启动。

注意事项与检测规避

压缩本身并不等同于加密,若仅靠 Base64 隐蔽,仍有被解码插件捕获的风险。可以在压缩前对脚本做轻度异或或 ROT13 处理,再在加载阶段先恢复后再交给 GzipStream;此外,把 System.IO.Compression 的调用包装在 Invoke-Expression 的字符串里,能够进一步分散特征。

参与讨论

5 条评论
  • 海豚诗人

    这个方法之前试过,确实能过部分杀软

    回复
  • 时光茶馆

    压缩后再做一次异或处理会不会更隐蔽?

    回复
  • SocialNinja

    有没有人试过用DeflateStream替代GzipStream?

    回复
  • 云端的鸟

    我之前也搞过类似的内存加载,不过用的不是GzipStream

    回复
  • 单身狗

    这个在Win11上能正常跑吗?

    回复