2. Misc-图片隐写-LSB隐写
任务目标
给定一张PNG图片,其中通过LSB(最低有效位)隐写方式在RGB通道的每个字节最低位嵌入了信息。你需要编写脚本提取这些隐藏位,将其重组为字节流,并还原出隐藏的文本内容或图片文件。
环境依赖
本案例使用Python 3,需要安装Pillow库(用于图像读取)和numpy(可选,用于加速位运算)。安装命令:
pip install pillow numpy
实现原理
LSB隐写将秘密信息的每个bit写入载体图像每个像素RGB分量值的最低位。提取时,只需读取每个颜色分量的最低位,按顺序拼接成bit流,每8个bit组成一个字节,即可还原原始数据。本案例提供两种还原模式:文本模式(直接按UTF-8解码)和图片模式(将提取的字节保存为PNG文件)。
关键完整代码
from PIL import Image
import numpy as np
import os
def extract_lsb(image_path, output_mode='text', output_path=None):
"""
从PNG图片中提取LSB隐写数据
:param image_path: 载体图片路径
:param output_mode: 'text' 或 'image'
:param output_path: 输出文件路径(image模式必填)
:return: 提取的原始字节串
"""
img = Image.open(image_path).convert('RGB')
pixels = np.array(img)
# 提取所有像素RGB值的最低位
lsb_bits = pixels & 1
# 将bit矩阵展平为一维数组(按行优先,R,G,B顺序)
flat_bits = lsb_bits.flatten()
# 每8个bit组成一个字节
byte_count = len(flat_bits) // 8
extracted_bytes = bytearray()
for i in range(byte_count):
byte = 0
for j in range(8):
byte = (byte << 1) | flat_bits[i * 8 + j]
extracted_bytes.append(byte)
# 去除末尾的零填充(隐写时通常以连续8个0字节作为结束标记)
# 查找最后一个非零字节位置
last_nonzero = len(extracted_bytes) - 1
while last_nonzero >= 0 and extracted_bytes[last_nonzero] == 0:
last_nonzero -= 1
data = bytes(extracted_bytes[:last_nonzero + 1])
if output_mode == 'text':
try:
text = data.decode('utf-8')
print("提取的文本内容:")
print(text)
except UnicodeDecodeError:
print("数据不是有效UTF-8文本,尝试以图片模式输出")
if output_path:
with open(output_path, 'wb') as f:
f.write(data)
print(f"已保存为文件: {output_path}")
elif output_mode == 'image':
if not output_path:
raise ValueError("image模式必须指定output_path")
with open(output_path, 'wb') as f:
f.write(data)
print(f"提取的图片已保存至: {output_path}")
return data
if __name__ == "__main__":
# 使用示例:从stego.png中提取文本
extract_lsb("stego.png", output_mode='text')
# 如果提取的是图片,使用以下方式:
# extract_lsb("stego.png", output_mode='image', output_path="hidden.png")
运行与验证
准备测试图片:若你手头没有隐写图片,可用以下代码生成一张包含隐藏文本的测试图:
from PIL import Image
import numpy as np
def embed_lsb(text, output_path="stego.png", size=(100, 100)):
"""将文本嵌入随机图片的LSB中"""
data = text.encode('utf-8') + b'\x00' * 8 # 添加结束标记
bits = ''.join(f'{byte:08b}' for byte in data)
bit_len = len(bits)
img = Image.new('RGB', size, (255, 255, 255))
pixels = np.array(img)
flat = pixels.flatten()
for i in range(min(bit_len, len(flat))):
flat[i] = (flat[i] & 0xFE) | int(bits[i])
img = Image.fromarray(flat.reshape(size[0], size[1], 3))
img.save(output_path)
print(f"已生成含隐写信息的图片: {output_path}")
# 生成测试图片
embed_lsb("CTF{LSB_Staganography_Success}", "stego.png")
验证步骤:
- 运行上述生成脚本创建测试图片
stego.png; - 运行提取脚本:
python extract_lsb.py(假设保存为extract_lsb.py); - 观察控制台输出,应显示:
CTF{LSB_Staganography_Success}。
通过条件:提取出的文本与嵌入内容完全一致,或提取出的图片文件能正常打开且内容正确。
常见问题
- 提取乱码:可能隐写时未添加结束标记,或数据本身非文本。可尝试以
image模式输出为文件,用file命令检查文件类型。 - 图片尺寸过小:若载体图片像素数少于隐藏数据位数,信息将无法完整嵌入。确保图片足够大(如100x100可容纳约3750字节)。
- 通道顺序:本实现按RGB顺序提取,若隐写时使用BGR或其他顺序,需相应调整代码。