深入解析System.IO.Compression.GzipStream在无文件攻击中的应用
venom的powershell免杀技术分析
当安全研究人员谈论“无文件攻击”(Fileless Attack)时,通常指的是那些不依赖在磁盘上写入可执行文件的攻击技术。这类攻击的隐蔽性极强,因为传统杀毒软件和EDR产品严重依赖文件扫描。而在.NET框架中,有一个看似平凡无奇的类——System.IO.Compression.GzipStream,正悄然成为构建这类攻击的绝佳工具。它并非设计用于恶意目的,但其数据流处理能力,恰恰为攻击载荷在内存中的“隐身”提供了完美的技术支撑。
GzipStream:不只是个压缩工具
从官方文档看,GzipStream 是用于压缩和解压缩GZIP格式数据的流。它的典型用法是读写文件或网络流。但在攻击者眼中,它的价值在于其构造函数:GzipStream(Stream stream, CompressionMode mode)。关键在于,这个 Stream 参数可以是任何流,尤其是 MemoryStream。
这就打开了一扇门。攻击者可以将经过GZIP压缩的恶意代码(例如PowerShell脚本、.NET程序集)以Base64字符串的形式嵌入到加载器脚本中。加载器运行时,通过 Convert.FromBase64String 将字符串还原为字节数组,注入到 MemoryStream,再通过 GzipStream 在内存中实时解压。整个过程中,原始的恶意载荷从未以明文或可执行文件的形式接触磁盘。
一个典型的技术栈
- 载荷准备:将PowerShell脚本或.NET二进制文件进行GZIP压缩,然后转换为Base64字符串。这个字符串就是最终的“运输形态”。
- 加载器:一个轻量级的PowerShell或C#程序。它的核心就是那几行调用
GzipStream和MemoryStream的代码。 - 执行:加载器在目标系统上运行,在内存中重建、解压并执行载荷。对于PowerShell,常用
[scriptblock]::create()来执行解压后的代码;对于.NET程序集,则使用Assembly.Load()。
为什么安全产品难以检测?
这种手法的规避能力源于几个层面。首先,Base64编码虽然简单,但结合GZIP压缩后,原始代码的静态特征(如特定函数名、字符串)被彻底打乱,简单的字符串匹配引擎很难生效。其次,GzipStream 是微软官方、完全合法的类库,其行为本身不具有恶意性,行为检测引擎难以将其与正常软件更新或数据解压区分开。
更关键的是“无文件”特性。整个恶意逻辑的组装和执行都发生在进程的内存空间里。除非EDR具备深度内存扫描和运行时行为分析能力,并能将内存中解压出的代码片段与已知威胁关联起来,否则很容易漏过。攻击者甚至可以将加载器代码进一步混淆,或者通过合法的系统管理工具(如PsExec、WMI)来触发,进一步融入“背景噪音”。
实战中的变体与限制
在实践中,攻击者不会止步于简单的加载。他们会引入更多技巧。例如,将Base64字符串分段存储,在运行时拼接,以避免长字符串触发告警。或者,不使用Base64,而将压缩后的字节数组直接以字节形式硬编码在脚本中,减少编码解码步骤。
然而,这种方法也有软肋。它依赖脚本宿主的可用性(如PowerShell是否被禁用或受约束)。此外,内存中的行为最终会暴露,高级的EDR可以通过挂钩关键.NET API(如 Assembly.Load)或监控PowerShell的脚本块日志(Script Block Logging)来捕获异常活动。2022年的一项行业分析就指出,尽管无文件攻击数量在上升,但具备完整内存取证和运行时检测能力的安全平台,其检出率已超过70%。
说到底,System.IO.Compression.GzipStream 在无文件攻击中的应用,是一场典型的“工具中性”的体现。它像一把手术刀,在外科医生手中能救人,在恶意攻击者手中则成为隐蔽的凶器。防御者需要理解这种技术原理,将检测重心从静态文件特征,转向动态的行为链条和内存中的异常活动模式。毕竟,在内存的战场上,真正的对抗才刚刚开始。

参与讨论
这招真够隐蔽的。
看完后才知道Gzip还能这么玩。
压缩解压居然成了黑客工具,笑死。
如果目标机器禁用了PowerShell,这种GzipStream方式还能用吗?
我之前也用MemoryStream配合Gzip来跑脚本,调试挺头疼的。
其实只要把Base64分块写成数组形式,能进一步规避字符串长度告警。