关于图片的格式和隐写术
需要掌握的处理技术
- python PIL
- python struct 文件格式处理
- python zlib 提取png中IDAT块
图片Metadata
可以通过右键属性来查看。Windows的属性可能比较全,有一个备注栏。
可以通过strings命令查看,strings可以显示出文件中所有可打印的字符。
jpeg格式
- 有损压缩,像素信息变成jpeg会损失精度,可以自定义质量参数from0-100,jpeg没有透明度信息。
- 基本数据结构: 【段】【经压缩的图像数据】
段 的结构
- 段标识,1字节,数据固定为FF,是每个新段的开始
- 段类型,1字节,注意,FFD8和FFD9分别为JPG文件开始和结束标志
- 段长度,2字节,不包括段标识和段类型,包括段长度和段内容
- 段内容,2字节,小于等于65535字节
注意,段与段之间有多少FF都可以,这是填充字节,必须被忽略掉。
struct SOFx sof2
这里的 WORD Y_image和WORD X_image是图片的显示大小。会有题目修改这些参数来隐藏信息。
png格式
- 文件头固定,其他部分由三个或以上的数据块(Chunk)按顺序构成。
文件头89 50 4E 47 0D 0A 1A 0A
+ 数据块 + 数据块 + 数据块…… - 数据块:分为两种。关键数据块 critical chunk和辅助数据块ancillary chunk。关键数据块有的可选,有三个不可选,必须有,并且这些数据块在位置上也有规定。
数据块的规定可以参考CTFwiki中的这个【链接】 - 数据块的统一结构
- length 4字节 指定数据域长度
- chunk type code(类型码) 4字节,由ascii组成
- 数据 可变长度
- CRC 4字节 检测错误
注意,CRC是对类型码和数据进行运算得到的。
列举一些重要的数据块
1.IHDR,文件头数据块
只能在第一块。描述了基本信息,13字节组成,前八个字节是宽度和高度(单位是像素)。如果宽度和高度被修改了,会导致CRC不匹配,显示不出数据。解决办法是使用脚本进行爆破,枚举宽度和高度,使得CRC满足条件。
import os
import binascii
import struct
CRC = 0x932f8a6b
#可以手动读取文件的CRC,也可以编写脚本获取。
Your_file = "test.file"
#要修复的文件
misc = open(Your_file,"rb").read()
for i in range(1024):
data = misc[12:16] + struct.pack('>i',i)+ misc[20:29]
crc32 = binascii.crc32(data) & 0xffffffff
if crc32 == CRC:
print i
这个脚本的原理暂时还不清楚。。
2.调色板数据块 PLTE(palette chunk)
它包含有与索引彩色图像(indexed-color image)相关的彩色变换数据,它仅与索引彩色图像有关,而且要放在图像数据块(image data chunk)之前。真彩色的 PNG 数据流也可以有调色板数据块,目的是便于非真彩色显示程序用它来量化图像数据,从而显示该图像。
3.IDAT,图像数据块
png文件可包含多个连续的IDAT,可以用zlib解压缩。注意,只有上一块填满(65535)的情况下,才可以放到下一个块中。
python zlib 可以解压多余 IDAT 块的内容,此时注意剔除 长度、数据块类型及末尾的CRC校验值。
import zlib
import binascii
IDAT = "789...667".decode('hex')
result = binascii.hexlify(zlib.decompress(IDAT))
print result
4. IEND,图像结束数据块
最后一块,IEND 数据块的长度总是 00 00 00 00
,数据标识总是 IEND 49 45 4E 44
,因此,CRC 码也总是 AE 42 60 82
。所以IEND固定格式为:
00 00 00 00 49 45 4E 44 AE 42 60 82
最低有效位LSB
PNG由RGB三原色组成,每个颜色占用八位,即0x00-0xFF,共有256^3种颜色,而人眼分辨不了这么多,于是这些颜色的末位就可以用来隐写数据。一个色块可以隐写3bit的数据。(当然也可以使用更多的末位来隐写数据)
可以用Stegsolve这个工具来分析,此工具可以查看RGB任何通道任何bit的信息。
关于LSB隐写的不错的文章
GIF格式
GIF格式详解
基于颜色列表(存储的数据是该点的颜色对应于颜色列表的索引值),颜色最多只支持8位(256色)。
- 修改全局颜色列表可以让图像所有对应颜色发生变化,可能会在题目中出现。
文件内部分成许多块(控制块+数据块)。控制块控制图像的行为,数据块一般都是8bit的字符流,由控制块决定功能。
通过LZW压缩算法压缩图象数据来减少图象尺寸。
一个GIF文件的结构可分为文件头(File Header)、GIF数据流(GIF Data Stream)和文件终结器(Trailer)三个部分。文件头包含GIF文件署名(Signature)和版本号(Version);GIF数据流由控制标识符、图象块(Image Block)和其他的一些扩展块组成;文件终结器只有一个值为0x3B的字符(’;’)表示文件结束。
这个结构图很重要,请至少对它有个大概印象。
文件头(Header)6bytes
包括:GIF署名(Signature)和版本号(Version)
署名:三个字符GIF
版本号:87a 或 89a
GIF数据流
图形控制扩展(Graphic Control Extension)
可选的(需要89a版本),可以放在一个图象块(图象标识符)或文本扩展块的前面,用来控制跟在它后面的第一个图象(或文本)的渲染(Render)形式。
- 逻辑屏幕标识符(Local Screen Descriptor)
7个字节组成,定义了GIF图象的大小(Logical Screen Width & Height)、颜色深度(Color Bits)、背景色(Blackground Color Index)以及有无全局颜色列表(Global Color Table)和颜色列表的索引数(Index Count)。 - 全局颜色列表(Global Color Table)
全局颜色列表必须紧跟在逻辑屏幕标识符后面,每个颜色列表索引条目由三个字节组成,按R、G、B的顺序排列。 - 图象标识符(Image Descriptor)
一个GIF文件内可以包含多幅图象,一幅图象结束之后紧接着下是一幅图象的标识符,图象标识符以0x2C(‘,’)字符开始,定义紧接着它的图象的性质,包括图象相对于逻辑屏幕边界的偏移量、图象大小以及有无局部颜色列表和颜色列表大小,由10个字节组成。 - 局部颜色列表,可有可无,用来在本图像范围内替代全局颜色列表。
- 基于颜色列表的图象数据(Table-Based Image Data)
两部分组成:LZW编码长度(LZW Minimum Code Size)和图象数据(Image Data)。 - 图形文本扩展(Plain Text Extension)
可选的(需要89a版本),用来绘制一个简单的文本图象,这一部分由用来绘制的纯文本数据(7-bit ASCII字符)和控制绘制的参数等组成。
属于图形块。
应用程序扩展(Application Extension)
这是提供给应用程序自己使用的(需要89a版本),应用程序可以在这里定义自己的标识、信息等。
注释扩展(Comment Extension)
可选的(需要89a版本),可以用来记录图形、版权、描述等任何的非图形和控制的纯文本数据(7-bit ASCII字符),注释扩展并不影响对图象数据流的处理,解码器完全可以忽略它。存放位置可以是数据流的任何地方,最好不要妨碍控制和数据块,推荐放在数据流的开始或结尾。
文件结尾部分(Trailer)
这一部分只有一个值为0的字节,标识一个GIF文件结束。(固定值0x3B)
提取gif序列
from PIL import Image
gif = Image.open("test.gif")
for i,frame in enumerate(ImageSequence.Iterator(gif),1):
if frame.mode == "JPEG":
frame.save("%d.jpg"%i)
else:
frame.save("%d.png"%i)