从第一次拿到一张需要检查隐写信息的 PNG 图片开始,zsteg 就一直是我在 Kali 环境里离不开的工具。简单说,zsteg 是一个专门用来检测 PNG 和 BMP 图片中隐藏数据的命令行工具,它能看到肉眼看不到的 LSB 隐写痕迹,也能识别很多藏在图片像素里的文件片段。如果你参与 CTF 的图片隐写题、或者在做数字取证时需要快速排查一张图片是否被做过手脚,那这篇关于 zsteg 的安装与使用记录,应该能让你省下不少折腾时间。我会从零开始,把我在 Kali 中安装 zsteg 的完整过程、常用参数、实战命令以及踩过的坑全部摊开来讲。
1. zsteg 是什么,能检测什么
1.1 从一道 CTF 题说起
记得有一次参加线下的内部赛,有一道隐写题给了一张风景照片,文件大小 2MB,分辨率正常,打开看也完全没有异常。很多人在 exiftool 里翻了半天,用 strings 搜了一圈也没有线索。后来有人跑了一下zsteg -a,几秒种就发现图片的蓝色通道最低位里藏了一段字符串,顺着那段字符串又找到了下一步的压缩包密码。那道题之后,我养成了一个习惯:凡是遇到图片,先交给 zsteg 扫一遍再说。
类似这种场景在很多取证和渗透测试里也会出现。攻击者经常会把一些文本、密钥、甚至恶意脚本藏进图片里,用来绕过内容检测。zsteg 这类工具就是用来对付这种手法的。它不需要知道隐藏信息的具体格式,只要图片被写入的方式属于它支持的隐写类别,很大概率能被它扫描出来。
1.2 zsteg 的工作原理
zsteg 的核心原理并不神秘。常见的图片隐写,最基础的一种是 LSB 隐写。PNG 和 BMP 这类无损图片,每个像素的颜色由红色、绿色、蓝色三个通道构成,有些还带透明度通道。每个通道的值是一个字节,比如红色从 0 到 255。理论上,如果我把某个像素红色值从 200 改成 201,肉眼几乎无法察觉,但二进制最低的一位发生了变化。LSB 就是 Least Significant Bit,最低有效位。把大量隐藏信息依次替换到这些最低位上,人眼看不出来,但程序完全可以读出来。
zsteg 做的就是扫描这些最低位,并且它比一般的脚本更聪明。它会自动尝试不同的通道组合,比如只取蓝色通道、同时取红绿蓝三个通道、或者把透明度通道也带上;也会尝试不同位顺序,LSB 是从低位向高位读,MSB 是从高位向低位读。它还会对提取出来的位流做常见编码分析,比如 ASCII 码、UTF-8、甚至压缩文件头或者图片文件头。也就是说,它不仅能告诉你“这里看起来有东西”,还能直接告诉你“这里藏的是一个 ZIP 文件”或者“这是一段可读字符串”。
1.3 zsteg 与同类工具的区别
经常有人把 zsteg 和 binwalk、steghide 混在一起用,但它们解决的问题完全不同。我整理过一个粗对比表,供你参考:
| 工具 | 主要定位 | 适用格式 | 能干什么 |
|---|---|---|---|
| zsteg | LSB 及其他位隐写检测 | PNG、BMP | 扫描并提取嵌入在像素低位的数据 |
| binwalk | 文件结构和嵌入文件检测 | 任意文件 | 从文件尾部或中间嗅探签名,分离压缩包、固件等 |
| steghide | 隐写加密写入与提取 | JPEG、BMP、WAV、AU | 需要密码,适用于已知用 steghide 隐藏的场景 |
| exiftool | 元数据查看与修改 | 几乎所有图片格式 | 查看可编辑 EXIF 等元数据,不适合底层隐写 |
| strings | 提取可打印字符 | 任意 | 只找文本,发现不了二进制隐藏信息 |
所以,当一张图片没有任何元数据异常、用 strings 也没看到奇怪字符串时,zsteg 是很有力的补充。它针对的是像素级别的隐写,而不是文件层面的拼凑。
2. Kali 环境下安装 zsteg 的三种方式
2.1 安装前确认基础环境
Kali 本身预装的 Ruby 环境比较完整,这对 zsteg 是个好消息,因为 zsteg 是基于 Ruby 的开源工具。在开始之前,我一般先执行两条命令确认 Ruby 和 gem 是否可用:
ruby -v gem -v如果输出版本号,说明环境正常。如果提示找不到命令,则先安装 Ruby:
sudo apt update sudo apt install ruby-dev build-essential zlib1g-devruby-dev 和构建工具非常重要,因为部分依赖需要编译本地扩展。我在一个精简版 Kali 上试过跳过这一步,结果 zsteg 安装时直接报编译错误。
2.2 方式一:gem 一行命令安装
zsteg 发布为 RubyGem,所以最直接的安装方式就是:
sudo gem install zsteg让 gem 把 zsteg 及其依赖一起装好。这个命令会从 RubyGems 源拉取最新版本,安装完成后执行zsteg --help就能看到帮助信息。如果系统里已经有 zsteg,想升级到最新版,可以用:
sudo gem update zsteg这里想提醒一件事:如果你使用了国内的 RubyGems 镜像源,有可能因为同步不及时导致安装失败或者版本落后。这时候可以先查看当前源:
gem sources如果看到不是官方源,再考虑切回官方源,或者使用一家你信任且更新及时的镜像。怎么切换网上都有很多参考资料,核心原则是确保能拉到最新的 zsteg gem 包。
2.3 方式二:从源码编译安装
某些情况下,你可能需要修改 zsteg 源码,或者 gem 源一直拉不下来,那就可以直接从 Git 仓库拉源码编译。zsteg 的源码在代码托管平台上就能找到,大致操作如下:
git clone https://github.com/某仓库地址/zsteg.git cd zsteg sudo rake install这里的“某仓库地址”需要你自行确认,我一般通过搜索“zsteg gem”就能找到。源码方式的好处是,你可以拿到最新开发版本,甚至直接在源码里加打印日志,方便自己理解它到底在扫描什么。缺点是需要手动安装依赖,比如ruby-zsteg依赖zsteg的底层 gem 包。如果缺依赖,rake install会报错,根据报错用 gem 安装缺失的依赖即可。
2.4 方式三:Docker 方式
如果你不想污染当前 Kali 环境,或者你用的是非 Kali 系统,也可以拉一个包含 Kali 工具的 Docker 镜像,在容器里运行 zsteg。比如:
docker run --rm -it -v $(pwd):/data kali:v1 bash apt update && apt install -y ruby-dev zlib1g-dev gem install zsteg cd /data zsteg -a your.png这种方式适合需要临时取证或跨平台工作的场景。我个人在紧急比赛中用过一次,因为镜像没预装 zsteg,现场安装依然花了半分钟,但也还算顺利。
2.5 安装后的验证
无论用哪种方式,装完以后第一件事是执行:
zsteg --help正常会输出一屏参数说明。如果提示找不到命令,先检查 gem 的 bin 目录是否在 PATH 中。执行gem environment可以查看 Ruby 的安装目录,比如 Ruby 2.x 的 bin 目录可能在/var/lib/gems/2.7.0/bin,需要将它加入 PATH。另一种验证方式是直接扫一张图,最简单的办法是自己生成一张纯色 PNG,然后运行zsteg -a 1.png,如果能跑完而不报错,说明核心功能正常。
3. zsteg 常用参数与使用姿势
3.1 最简单的命令
最基本的用法是:
zsteg image.png这条命令会扫描默认通道,输出可能存在的隐藏数据。但实际场景中,我几乎不会只用默认参数,因为默认配置会漏掉一些特殊情况。比如有些题目会把信息藏在蓝色通道,而默认扫描可能不会把所有通道都覆盖到。要做到更全面,通常会用:
zsteg -a image.png-a表示尝试所有已知的方法和通道,扫描范围更广,输出也会更详细。缺点是速度慢一些,但对单张图片来说几乎可以忽略不计。
3.2 常用参数速查表
我用得最多的参数都整理在这个表里了:
| 参数 | 作用 | 示例 |
|---|---|---|
-a | 用所有已知方法扫描 | zsteg -a image.png |
-v | 输出详细调试信息 | zsteg -v image.png |
-E | 从指定通道提取数据 | zsteg -E "b1,bgr,lsb" image.png |
-c | 手动指定通道,如 r、g、b、a | zsteg -c b image.png |
-o | 指定位顺序:lsb 或 msb | zsteg -o msb image.png |
-b | 指定从第几位开始读取 | zsteg -b 1 -o lsb image.png |
-l | 限制最大提取字节数 | zsteg -l 64 image.png |
-C | 禁用彩色输出,便于脚本解析 | zsteg -C image.png |
其中-E是提取模式,后面跟的通道描述格式很重要。像b1,bgr,lsb的意思是:从 b 位开始读 1 个比特,按 bgr 通道顺序,采用 lsb 方式。如果对格式不熟,可以先跑zsteg -a,输出里会包含形如b1,bgr,lsb的提示,直接拿来放到-E后面提取即可。
3.3 不同场景下的参数组合
场景一:快速判断图片有没有问题。
zsteg -a suspect.png场景二:扫描结果提示某个通道有异常,想只提取那个通道的内容。
zsteg -E "b1,bgr,msb" suspect.png场景三:感觉图片颜色有些奇怪,怀疑是 MSB 隐写。
zsteg -o msb -c r suspect.png场景四:只需要提取前几字节,判断隐藏数据是什么类型。
zsteg -l 16 -a suspect.png这些命令看起来不长,但关键是要理解每个参数的含义。我的经验是:先无脑跑-a,再根据输出缩小范围,最后用-E提取具体数据。这也是最高效的流程。
4. 实战:用 zsteg 从 PNG 中提取隐藏 flag
4.1 准备一张测试图片
为了验证 zsteg 是否正常工作,我会自己构造一张带隐写的图片,而不使用网上现成的样例,这样我能完全知道隐藏内容是什么,从而判断工具输出是否正确。
我用 Python 的 PIL 库来生成一张纯白图片和一张有自然过渡色的小图:
from PIL import Image import random img = Image.new("RGB", (200, 200), (255, 255, 255)) img.save("test.png")这是一张全白图片,理论上看起来什么都没有。但全白像素有一个特点:像素值都在 255,最低位全是 1。如果我在最低位写入数据,在颜色上会变成 254 或 255 的细微差别,肉眼完全看不出来。
4.2 手工嵌入 LSB 信息
接着我写一个简单的 LSB 嵌入脚本。为了贴近真实 CTF 场景,我在隐藏数据前加上一些标记字符串,比如模拟一个文本内容:
from PIL import Image img = Image.open("test.png") pixels = img.load() message = "flag{zsteg_install_usage_test}" binary = ''.join(format(ord(c), '08b') for c in message) binary = binary.replace('01', '10') # 为了演示稍微变换,不做特殊处理 idx = 0 for y in range(200): for x in range(200): if idx < len(binary): r, g, b = pixels[x, y] pixels[x, y] = (r & 0xFE | int(binary[idx]), g, b) idx += 1 else: break img.save("test.png")这里我选择了把信息嵌入每个像素红色通道的最低位。实际嵌入过程可能使用多种通道,但总体思路一致:取一个字节,把最低位替换成隐藏信息的一个 bit。如果你的 Python 环境没有 PIL,先执行pip install pillow。
4.3 运行 zsteg 提取
保存图片后,进入 Kali 终端,先执行最普通的扫描:
zsteg test.png由于默认扫描不会覆盖所有通道,这里可能看不到完整信息。接着执行:
zsteg -a test.png输出中会看到类似这样的内容:
test.png b1,r,lsb,msb -> "flag{zsteg_install_usage_test}"zsteg 会报告隐藏位置和提取出的字符串。如果你是第一次用,突然看到自己能读取自己藏进去的那串字符串,会非常有成就感。
4.4 结果分析与人工验证
如果 zsteg 已经输出了明文,那一切放心。但更常见的情况是,它输出了一堆看起来乱码的内容,比如二进制文件头或者 base64。这时候不要无脑复制,应该先分析输出的十六进制内容,判断它是什么文件类型。
比如输出是:
b1,bgr,lsb -> "\x50\x4B\x03\x04..."看到\x50\x4B是PK,说明隐藏数据是一个 ZIP 压缩包。接下来就要用-E把这段数据提取出来,保存成文件再分析:
zsteg -E "b1,bgr,lsb" test.png > hidden.zip zipinfo hidden.zip这个步骤是把 zsteg 从“检测工具”升级成“取证利器”的关键。所以我的实操流程永远是:先检测,定位通道;提取二进制流;根据文件头判断类型;再保存文件进一步处理。
5. 踩坑记录与常见问题排查
5.1 gem 安装失败的解决办法
我最初在某旧版本 Kali 上安装 zsteg 时,遇到了/usr/include/ruby-2.7/ruby.h not found之类的错误,原因是缺少 Ruby 开发头文件。解决办法很简单,先确保安装了:
sudo apt install ruby-dev如果依然报错,再看是不是缺少 zlib:
sudo apt install zlib1g-dev还有一类权限问题:如果不用 sudo 直接gem install zsteg,会被拒绝写入系统目录。我之前因为嫌 sudo 麻烦,试过gem install --user-install zsteg,但装完以后命令不在 PATH 里,反而更麻烦。所以老老实实用sudo gem install zsteg是最省心的。
5.2 图片明明有隐写却检测不出来
zsteg 只支持 PNG 和 BMP 两种无损格式图片。如果你拿到的是一张 JPEG,它大概率检测不出 LSB 信息,因为 JPEG 是有损压缩,像素在保存时已经被改过了,隐写数据很容易被破坏。所以遇到 JPG 图片时,先查元数据、先看文件尾部,再考虑用其他工具。
即使图片是 PNG,也可能检测不出来,原因大概率出在信息隐藏的位序和通道上。有些隐写工具会默认用 MSB,或者只把信息藏在某个特定通道。此时手动指定参数试试:
zsteg -a --msb channel.png或者通过-c把所有通道轮流扫一遍:
for ch in r g b a; do zsteg -c $ch channel.png; done如果还是没结果,可以怀疑这段隐写数据使用了加密或压缩,比如 stego 工具自带的加密机制。zsteg 对未经处理的明文数据最敏感,对高熵加密数据的识别率会明显下降。
5.3 内存不足与性能问题
zsteg 会把图片数据读取到内存里分析,超大图片或高分辨率图片可能导致内存占用过高。我处理过一张 12000×8000 的卫星图,直接跑zsteg -a时系统内存几乎被吃满。这种情况下可以先用图像处理软件缩小图片尺寸,或者先裁剪局部区域分析。另外,尽量在干净的 Kali 环境中运行,避免同时开太多重型应用。
5.4 误报一箩筐,如何判断真假
zsteg 的输出会有一些“看似有内容但实际是图片本身的噪声”。比如全白图片的 LSB 全是 1,分析出来的位流有可能被误判为可打印字符串。这时候不能只看有没有字符串,还要结合上下文判断。
我的校验方法是:把提取出的内容保存下来,用file命令看真实类型;如果是纯文本,就观察是不是连续可读;如果是文件头,就解压验证 CRC。如果只是几个零散 ASCII 字符,大概率是误报。还有一个技巧是:用 zsteg 扫描一张干净的、无隐写的同类图片作为基线,对比输出。如果两者都出现同样的“异常”,那基本可以断定是图片本身的结构特征,不是隐写。
6. 我的使用心得与扩展思路
6.1 使用习惯
现在我在 Kali 里做图片检查时,默认流程已经固定下来:先exiftool看元数据,再strings扒一次文本,然后立刻zsteg -a。如果 zsteg 报告隐藏数据,先用-E提取,再根据文件头判断下一步。这个过程熟练后,一张图不到一分钟就能完成初步排查。
另外我会把 zsteg 的输出重定向到文件,方便回看。比如:
zsteg -a image.png | tee zsteg_report.txt这样做的好处是,当图片很多时,可以批量扫描后统一分析,而不是在终端里一屏一屏翻。批量扫描我一般配合一个简单的循环:
for img in *.png; do echo "=== $img ===" zsteg -a "$img" | grep -E -i "flag|cipher|txt|zip|rar|PNG|BMP" || true done这只是一个粗糙的过滤,但很实用。它会突出显示含有关键字样的输出,减少人工排查时间。
6.2 和其他工具配合的完整流程
zsteg 不是万能的,很多隐藏数据需要多工具配合才能找到。我遇到的最大一个坑是图片里藏了一个压缩包,但 zsteg 只能检测出像素低位有数据,无法直接导出完整的压缩包。这时候需要把 zsteg 提取的内容与 binwalk 结合。
具体做法是先提取可疑数据到文件:
zsteg -E "b1,bgr,lsb" image.png > extracted_data file extracted_data binwalk extracted_data如果 binwalk 发现了嵌套文件,就可以用binwalk -e自动分离。另外,如果图片携带了 EXIF 信息,exiftool 也能补充不少线索。还有 steghide 适用于已知密码的隐写场景,密码可能藏在图片文件名或题目描述里。这些工具配合起来,基本覆盖了绝大多数图片隐写的处理方式。
6.3 最后再分享一个小技巧
我个人在实际操作中有一个体会:zsteg 的-v参数看起来只是输出调试信息,但在图片扫描结果异常时非常有用。它会打印出正在尝试的每个通道和位顺序,帮你看懂它到底在做什么,甚至能根据调试信息自己推断新的提取思路。
比如有一段调试输出显示“trying bgr lsb...”,但最终没有输出任何内容。这时可以手动尝试-E "b1,bgr,msb",说不定能把隐藏数据翻出来。因为 zsteg 的自动扫描集合并不等于全部可能组合,手动调整参数往往能发现意外结果。
另外,如果你经常参加比赛或做取证分析,建议写一个小的自定义脚本,把 zsteg、binwalk、exiftool 串起来。我自己的做法是:传入一个图片路径,脚本自动跑完整流程,并把所有输出存到一个目录里。这样不仅方便复盘,也能在紧急场景下快速响应。工具不会每次都直接给你答案,但一套可靠的处理流程会。