EZStego
EZStego 是一种极其经典且巧妙的隐写算法,专门针对索引颜色模式(Indexed Color)的图像(如 GIF,或前文提到的 Color Type 为 3 的 PNG 文件)设计。
在进行文件隐写分析时,一个关键的认知是:EZStego 并不会造成任何通常意义上的文本文件损坏或格式异常,它完全遵循原图像的主文档结构。 它的隐蔽性建立在巧妙操纵合法的 PLTE(调色板)和像素数据流之上,而非破坏文件架构。
以下是 EZStego 的核心原理及底层逻辑剖析:
1. 传统 LSB 隐写的痛点
在 RGB 真彩色图像中,常规的 LSB(最低有效位)隐写直接修改像素的 R、G、B 字节的最后一位。因为只改变了 1 个单位的色值,人眼无法察觉。
但在索引图像中,图像数据块(如 PNG 的 IDAT)存储的不是具体的 RGB 值,而是调色板中的索引号(0~255)。
如果直接对索引号进行 LSB 修改(例如将索引 4 改为 5),在调色板中,索引 4 可能对应深黑色,而索引 5 可能对应亮红色。仅仅 1 个比特的改动,会导致屏幕上出现极其明显的杂色噪点(视觉异常)。
2. EZStego 的核心算法:亮度排序映射
EZStego 为了解决上述问题,引入了一个非常优雅的算法流程。它的核心思想是:找到视觉上最接近的颜色来进行替换。
具体步骤如下:
步骤一:提取并量化调色板
程序首先读取图像合法的调色板结构(例如 PNG 中的 PLTE 数据块),提取出所有的 RGB 颜色表。
步骤二:计算亮度并建立有序映射(关键步)
在内存中(不修改原文件调色板结构),EZStego 会根据色彩空间距离或亮度公式对所有颜色进行排序。通常使用的亮度(Luminance)公式为:
\(Y = 0.299R + 0.587G + 0.114B\)
经过排序后,程序会得到一个逻辑上连续且渐变的颜色列表 \(L\)。在这个有序列表 \(L\) 中,相邻的两个颜色(比如 \(L[10]\) 和 \(L[11]\))在视觉上是极其相似的,人眼难以分辨。
步骤三:秘密数据嵌入
程序开始遍历图像的像素数据区。假设当前要隐藏秘密数据的一个比特位 b(值为 0 或 1):
-
读取当前像素在原调色板中的索引,找出其对应的 RGB 颜色 \(C\)。
-
在排序后的列表 \(L\) 中找到颜色 \(C\) 的位置,记为其逻辑序号 \(P\)。
-
检查序号 \(P\) 的最低有效位(LSB)是否与秘密比特
b一致:-
一致: 保持原样,不作修改。
-
不一致: 将序号 \(P\) 修改为 \(P'\)(通常是 \(P+1\) 或 \(P-1\),确保替换后的颜色仍在相邻位置)。
-
-
取出 \(L[P']\) 对应的新颜色 \(C'\)。
-
在原调色板中找到 \(C'\) 对应的原始索引号,并用这个新索引号覆写图像数据块中的原索引号。
3. 文档结构层面的最终形态
经过上述处理后,最终生成的图像文件结构呈现以下特征,这也是为什么单纯检查文件损坏无法察觉异常的原因:
-
PLTE数据块完全合法: 调色板的物理顺序根本没有改变,也没有被注入任何多余数据。 -
文件大小不变: 数据块长度严格遵循规范。
-
视觉无损: 由于替换的是亮度最相近的颜色,肉眼观察图像(甚至用简单的色彩直方图分析)都极难发现异常。
4. 逆向分析与数据提取思路
针对这种基于主文档结构的隐写,如果需要编写 Python 等脚本进行自动化提取,逻辑是嵌入过程的严格逆向:
-
解析主文档: 从目标文件中分离出调色板数据和像素索引流。
-
重建排序表: 使用相同的亮度公式(\(Y = 0.299R + 0.587G + 0.114B\))对提取出的调色板在内存中重新进行排序,复原出有序列表 \(L\)。
-
遍历提取:
-
依次读取图像数据区中的每一个像素索引。
-
根据索引查出 RGB 颜色。
-
在有序列表 \(L\) 中定位该颜色的逻辑序号 \(P\)。
-
提取 \(P\) 的最低有效位(
P & 1)。
-
-
组装数据: 将提取出的所有比特位按 8 位一组拼接成字节流,即可还原出隐藏的二进制数据(如 Flag 或加密压缩包)。
索引模式的参考结构: