当前位置: 首页 > news >正文

图片结构 - voasem

图片分析简介

图像文件有多种复杂的格式,可以用于各种涉及到元数据、信息丢失和无损压缩、校验、隐写或可视化数据编码的分析解密,都是 Misc 中的一个很重要的出题方向。涉及到的知识点很多(包括基本的文件格式,常见的隐写手法及隐写用的软件),有的地方也需要去进行深入的理解。

元数据(Metadata)[¶]

元数据(Metadata),又称中介数据、中继数据,为描述数据的数据(Data about data),主要是描述数据属性(property)的信息,用来支持如指示存储位置、历史数据、资源查找、文件记录等功能。

元数据中隐藏信息在比赛中是最基本的一种手法,通常用来隐藏一些关键的 Hint 信息或者是一些重要的如 password 等信息。

这类元数据你可以 右键 --> 属性 去查看, 也可以通过 strings 命令去查看,一般来说,一些隐藏的信息(奇怪的字符串)常常出现在头部或者尾部。

接下来介绍一个 identify 命令,这个命令是用来获取一个或多个图像文件的格式和特性。

-format用来指定显示的信息,灵活使用它的-format` 参数可以给解题带来不少方便

[format各个参数具体意义](https://www.imagemagick.org/script/escape.php)

PNG数据块(Chunk)

1.数据块构成结构

image-20250429205158314

2.PNG数据块

数据块格式(统一数据结构)

image-20250429224155341

数据块类型

​ PNG定义了两种类型的数据块:

  • 关键数据块(critical chunk),这是标准的数据块
  • 辅助数据块(ancillary chunks),这是可选的数据块。(可有可没有)

关键数据块定义了4个标准数据块,每个PNG文件都必须包含它们,PNG读写软件也都必须要支持这些数据块

数据块中有 4 个关键数据块:

  1. 文件头数据块 IHDR(header chunk):包含有图像基本信息,作为第一个数据块出现并只出现一次。
  2. 调色板数据块 PLTE(palette chunk):必须放在图像数据块之前。
  3. 图像数据块 IDAT(image data chunk):存储实际图像数据。PNG 数据允许包含多个连续的图像数据块。
  4. 图像结束数据 IEND(image trailer chunk):放在文件尾部,表示 PNG 数据流结束。

一.PNG图像标识符

PNG文件头

(根据PNG文件的定义来说,其文件头位置总是由位固定的字节来描述的)

十进制数 137 80 78 71 13 10 26 10
十六进制数 89 50 4E 47 0D 0A 1A 0A

一堆(n个)数据块就是一个数据流

以下是四个关键数据块

二.IHDR数据块(13个字节)

文件头数据块IHDR(header chunk)

  • 包含有PNG文件中存储的图像数据的基本信息

  • 作为第一个数据块出现在PNG数据流中,且一个PNG数据流中只能有一个文件头数据块。

文件头数据块由13字节组成,它的格式如下表所示。

域的名称 字节数 说明
Width 4 bytes 图像宽度,以像素为单位
Height 4 bytes 图像高度,以像素为单位
Bit depth 1 byte 图像深度: 索引彩色图像:1,2,4或8 灰度图像:1,2,4,8或16 真彩色图像:8或16
ColorType 1 byte 颜色类型:0:灰度图像, 1,2,4,8或16 2:真彩色图像,8或16 3:索引彩色图像,1,2,4或8 4:带α通道数据的灰度图像,8或16 6:带α通道数据的真彩色图像,8或16
Compression method 1 byte 压缩方法(LZ77派生算法)
Filter method 1 byte 滤波器方法
Interlace method 1 byte 隔行扫描方法

隔行扫描方法

0:非隔行扫描

1: Adam7(由Adam M. Costello开发的7遍隔行扫描方法)

image-20250429223740401

三.PLTE(调色板数据块)

调色板数据块PLTE(palette chunk)包含有与索引彩色图像(indexed-color image)相关的彩色变换数据,它仅与索引彩色图像有关,而且要放在图像数据块(image data chunk)之前

PLTE数据块是定义图像的调色板信息,PLTE可以包含1~256个调色板信息,每一个调色板信息由3个字节组成:

颜色 字节 意义
Red 1 byte 0 = 黑色, 255 = 红
Green 1 byte 0 = 黑色, 255 = 绿色
Blue 1 byte 0 = 黑色, 255 = 蓝色

因此,调色板的长度应该是3的倍数,否则,这将是一个非法的调色板。

对于索引图像,调色板信息是必须的,调色板的颜色索引从0开始编号,然后是1、2……,调色板的颜色数不能超过色深中规定的颜色数(如图像色深为4的时候,调色板中的颜色数不可以超过2^4=16),否则,这将导致PNG图像不合法。

真彩色图像和带alpha通道数据的真彩色图像也可以有调色板数据块,目的是便于非真彩色显示程序用它来量化图像数据,从而显示该图像。

四.IDAT(图像数据块)

图像数据块IDAT(image data chunk):它存储实际的数据,在数据流中可包含多个连续顺序的图像数据块。

IDAT存放着图像真正的数据信息,因此,如果能够了解IDAT的结构,我们就可以很方便的生成PNG图像。

  • 储存图像像素数据

  • 在数据流中可包含多个连续顺序的图像数据块

  • 采用 LZ77 算法的派生算法进行压缩

  • 可以用 zlib 解压缩

    eg:如下图

    • 00 00 00 D3 数据长为211字节(211(只是第三个的)+12(头、标&校验))

      ​ 211---16*n+R

    • 49 44 41 54 IDAT标识(想找IDAT的位置就find十六进制49 44 41 54,无空格也行)

    • 78 9C…… 压缩的数据,LZ77派生压缩方法
    • DA 12 06 A5 CRC校验

** IDAT 块只有当上一个块充满时,才会继续一个新的块

------->用 pngcheck 去查看此 PNG 文件

λ .\pngcheck.exe -v sctf.png
File: sctf.png (1421461 bytes)chunk IHDR at offset 0x0000c, length 131000 x 562 image, 32-bit RGB+alpha, non-interlacedchunk sRGB at offset 0x00025, length 1rendering intent = perceptualchunk gAMA at offset 0x00032, length 4: 0.45455chunk pHYs at offset 0x00042, length 9: 3780x3780 pixels/meter (96 dpi)chunk IDAT at offset 0x00057, length 65445zlib: deflated, 32K window, fast compressionchunk IDAT at offset 0x10008, length 65524
...chunk IDAT at offset 0x150008, length 45027chunk IDAT at offset 0x15aff7, length 138chunk IEND at offset 0x15b08d, length 0
No errors detected in sctf.png (28 chunks, 36.8% compression).

可以看到,正常的块的 length 是在 65524 的时候就满了,而倒数第二个 IDAT 块长度是 45027,最后一个长度是 138,很明显最后一个 IDAT 块是有问题的,因为他本来应该并入到倒数第二个未满的块里.

利用 python zlib 解压多余 IDAT 块的内容,此时注意剔除 长度、数据块类型及末尾的 CRC 校验值

import zlib
import binascii
IDAT = "789...667".decode('hex')
result = binascii.hexlify(zlib.decompress(IDAT))
print result

五.IEND(文件尾)(12个字符)

图像结束数据IEND(image trailer chunk):它用来标记PNG文件或者数据流已经结束,并且必须要放在文件的尾部。

如果我们仔细观察PNG文件,我们会发现,文件的结尾12个字符看起来总应该是这样的:

00 00 00 00 49 45 4E 44 AE 42 60 82

不难明白,由于数据块结构的定义,IEND数据块的长度总是0(00 00 00 00,除非人为加入信息),数据标识总是IEND(49 45 4E 44),因此,CRC码也总是AE 42 60 82。

3*4 长度+数据标识+CRC码

辅助数据块

(除关键数据块外都是辅助数据块)

  • 背景颜色数据块 bKGD(background color)
  • 基色和白色度数据块 cHRM(primary chromaticities and white point),所谓白色度是指当 R=G=B=最大值 时在显示器上产生的白色度
  • 图像 γ 数据块 gAMA(image gamma)
  • 图像直方图数据块 hIST(image histogram)
  • 物理像素尺寸数据块 pHYs(physical pixel dimensions)
  • 样本有效位数据块 sBIT(significant bits)
  • 文本信息数据块 tEXt(textual data)
  • 图像最后修改时间数据块 tIME (image last-modification time)
  • 图像透明数据块 tRNS (transparency)
  • 压缩文本数据块 zTXt (compressed textual data)
    image-20250429223740401

image-20250429223948510

图像示例

1.PNG文件头标识

image-20250429211130284

2.IHDR数据块

image-20250429212053560

  • 00 00 00 0D 说明IHDR头块长为13

  • 49 48 44 52 IHDR标识

  • 00 00 01 00 图像的宽,256像素

  • 00 00 01 00 图像的高,256像素

  • 04 色深,2^4=16,即这是一个16色的图像(也有可能颜色数不超过16,当然,如果颜色数不超过8,用03表示更合适)

  • 03 颜色类型,索引图像

  • 00 PNG Spec 规定此处总为0(非0值为将来使用更好的压缩方法预留),表示使压缩方法(LZ77派生算法)

  • 00 同上总为0

  • 00 非隔行扫描

  • 36 21 A3 B8 CRC校验

    CRC校验原理

    划分为组,每组kbit

    设数据M=101001(此时k=6)

    用二进制的模二运算进行2^n*M的运算(即在M后面添加n个0)

    除数P 商Q 余数R
    image-20250508160353720

CRC校验代码如下:

import java.util.zip.CRC32;public class CrcTest {public static void main(String[] args) {byte[] checkData = new byte[]{0x49,0x48,0x44,0x52,0x00,0x00,0x00, 0x08,0x00,0x00,0x00, 0x08,0x04,0x03,0x00,0x00,0x00};CRC32 crc32 = new CRC32();  crc32.update(checkData);long value = crc32.getValue();byte[] intToBytes = longToBytes(value);String bytesToHexString = bytesToHexString(intToBytes);System.out.println(bytesToHexString);}public static byte[] longToBytes(long value)   {   byte[] src = new byte[4];  src[0] = (byte) ((value>>24) & 0xFF);  src[1] = (byte) ((value>>16)& 0xFF);  src[2] = (byte) ((value>>8)&0xFF);    src[3] = (byte) (value & 0xFF);       return src;  } //将字节数组按16进制输出public static String bytesToHexString(byte[] src){StringBuilder stringBuilder = new StringBuilder("");if (src == null || src.length <= 0) {return null;}for (int i = 0; i < src.length; i++){int v = src[i] & 0xFF;String hv = Integer.toHexString(v);if (stringBuilder.length() != 0) {stringBuilder.append(",");}if (hv.length() < 2) {stringBuilder.append(0);}stringBuilder.append(hv);}return stringBuilder.toString();}}

3.可选数据块image-20250429214831111

可选数据块sBIT,颜色采样率,RGB都是256(2^8=256)

image-20250429221912698

4.PLTE调色板数据块

image-20250429221354386

这里是调色板信息

  • 00 00 00 27 说明调色板数据长为39字节,即13个颜色数(从0开始)
  • 50 4C 54 45 PLTE标识
  • B7 00 34 颜色0
  • FF 99 00 颜色1
  • …… ……
  • FF FF 00 最后一个颜色,12
  • 48 29 75 2C CRC校验

()其他示例:
image-20250429221732274

5.IDAT数据部分

image-20250429222748112

  • 00 00 00 D3 数据长为211字节(211(只是第三个的)+12(头、标&校验))

    211---16* n+R--- 16*13+3

  • 49 44 41 54 IDAT标识(想找IDAT的位置就find十六进制49 44 41 54,无空格也行)

  • 78 9C…… 压缩的数据,LZ77派生压缩方法

  • DA 12 06 A5 CRC校验

6.IEND文件尾

image-20250429214928937

IEND数据块,这部分正如上所说,通常都应该是

00 00 00 00 49 45 4E 44 AE 42 60 82

至此,我们已经能够从一个PNG文件中识别出各个数据块了。由于PNG中规定除关键数据块外,其它的辅助数据块都为可选部分,因此,有了这个标准后,我们可以通过删除所有的辅助数据块来减少PNG文件的大小。(当然,需要注意的是,PNG格式可以保存图像中的层、文字等信息,一旦删除了这些辅助数据块后,图像将失去原来的可编辑性。)

删除了辅助数据块后的PNG文件,现在文件大小为147字节,原文件大小为261字节,文件大小减少后,并不影响图像的内容。参考:打造自由换色的png图片类。

<文件不同,校验码也不一样>

CRC校验例题image-20250430192105818

脚本(不知道是不是所有脚本都一样)

import os
import binascii
import structmisc = open("misc4.png","rb").read()for i in range(1024):data = misc[12:16] + struct.pack('>i',i)+ misc[20:29]crc32 = binascii.crc32(data) & 0xffffffffif crc32 == 0x932f8a6b:print i

pngcheckimage-20250430200616582

LSB

LSB 全称 Least Significant Bit,最低有效位。

PNG 文件中的图像像数一般是由 RGB 三原色(红绿蓝)组成,每一种颜色占用 8 位,取值范围为 0x000xFF,即有 256 种颜色,一共包含了 256 的 3 次方的颜色,即 16777216 种颜色。

而人类的眼睛可以区分约 1000 万种不同的颜色,意味着人类的眼睛无法区分余下的颜色大约有 6777216 种。

LSB 隐写就是修改 RGB 颜色分量的最低二进制位(LSB),每个颜色会有 8 bit,LSB 隐写就是修改了像数中的最低的 1 bit,而人类的眼睛不会注意到这前后的变化,每个像素可以携带 3 比特的信息。

image-20250430201053426

stepic用法

Stepic - aldeid

JPG数据段

段结构

  • JPG是由一个一个段构成的

  • 所有数据都是高位在前

  • JPEG 是有损压缩格式,将像素信息用 JPEG 保存成文件再读取出来,其中某些像素值会有少许变化。在保存时有个质量参数可在 0 至 100 之间选择,参数越大图片就越保真,但图片的体积也就越大。一般情况下选择 70 或 80 就足够了
  • JPEG 没有透明度信息

image-20250504151019219

段标识 段类型 段长度 段数据
1字节 1字节 2字节 (段长度-2)
FF
(一定存在) (一定存在) (不包含段标识、段类型)
  • 有些段没有长度描述也没有内容,只有段标识和段类型。文件头和文件尾均属于这种段。
  • 段与段之间无论有多少 FF 都是合法的,这些 FF 称为「填充字节」,必须被忽略掉。

常见段类型

image-20250504140725029

段结构包括:

段标识|段类型|段长度|端数据

段标识
1字节,固定为FFH,H表示16进制

段类型
1字节

文件头SOI

名称 SOI 标记码 D8

文件尾EOI

名称 EOI 标记码 D9

帧开始(标准JPEG)

名称 SOF0 标记码 C0
名称 SOF1 标记码 C1
C0与C1在一个文件里只会存在一种,下面以C0为例,C1同样操作。

FF C0H段数据分析
偏移位置 字节长度 含义
0x00 1 标志(固定08H)
0x01~0x02 2 Y轴分辨率
0x03~0x04 2 X轴分辨率
0x05 1 组件数量(固定03H)
0x06~0x14 9 组件数据

我们查找

FF C0

image-20250504142745939

后面两个字节00 11表示段长度,将十六进制转为十进制,可以知道后面有15个字节的数据(段长度占了2字节,并且不包括段标识和段类型)
那么我们可以看到后面的数据

标志08
1个字节

image-20250504142920981

这个标志固定是08

y轴分辨率

2字节

image-20250504144353636

计算一下,

image-20250504144417019

与实际的分辨率(高度)一样

image-20250504144437112

x轴分辨率

2字节

image-20250504144452368

这两字节表示x轴分辨率,计算一下

与实际上文件的属性是一样的

image-20250504144508580

组件数量03

image-20250504144550469

这1个字节固定为03

从03之后的9个字节,都为组件数据,且每个组件数据为3个字节。

组件大小和数量都为固定的。

定义Huffman表(霍夫曼表)DHT

名称 DHT 标记码 C4

固定4个FF C4,通常用来表示图片的亮度和色度,其中2个记录亮度,2个记录色度

通常一个图片中有四个霍夫曼表段:亮度的直流量(DC)、亮度的交流量(AC)、色度的直流量(DC)、色度的交流量(AC)

FF C4H段数据结构分析

image-20250504150141778

从其他图片复制霍夫曼表,会导致图片颜色或亮度错误

FF DA -- 数据区的开始

字节

image-20250504141837638

这个红色框框里的两个数表示一个字节

高位在前

什么是高位在前?
例如现实生活中1234,高位在前则表示12 34

而winhex的都是低位在前,在后面会讲到段长度,如00 10,低位在前则表示10 00

12 34 56

56 34 12

例子:更改宽度和高度来恢复图片

GIF

文件头

固定长6个字节

image-20250504200737109

逻辑屏幕描述符

7字节

宽 高 压缩字节

image-20250504200821038

  • 1C 02 宽

  • C8 01 高

​ 在GIF文件格式中,所有的多字节值均以小端格式(little-endian format,从最后一个字节开始往前保存)存储。比如在我们看来 0A 00,这个值实际写作 000A,也就是十进制的10。

​ 宽0X1C02是2*16^3+ 0 *16^2+12 *16+1=8385

  • F7 压缩字节 -----》转成二字节 1111 0111

    • 第一个bit 标志位 ——表示全局颜色列表是否存在(1存在 0不存在)
    • 接下来三个bit ——表示图像调色板中每个颜色的原色所占用的Bit数(颜色分辨率)

    ​ (011表示占用4个Bit,111占用8个Bit)——即最多只能占用8个bit

    ​ 调色板最多只包含由24-Bit颜色中选出的256个颜色(实际有很多优化方案能提高颜色分辨率,如加入局部调色板)

    • 第五个Bit为标志位 ——表示颜色列表排序方式

      若为1,表示颜色列表是按照颜色在图像中出现的频率降序排列。0则相反

    • 最后三个bit ——表示全局颜色列表大小

​ 计算方法是2^N+1 ,其中N为这三个Bit的二进制数值。

eg: 111 2**(7+1)=256

011 2**(3+1)=16

  • FF ——背景色在全局颜色列表中的索引

若无全局颜色列表则此字节无效。

在GIF的图像数据中,没有被指定颜色的像素会被背景色填充

  • 00 ——像素的宽高比

大多数时候这个值都是0,若值为N, 则图像的宽高比:

aspectRatio = (pixelAspectRatio + 15) / 64

全局颜色列表

由前面的逻辑屏幕描述符可知,全局颜色列表的大小是256,每个颜色占三个字节,按照RGB排列,所以它占有256*3个字节。

GIF格式可以拥有global color table,或用于针对每个子图片集,提供local color table。每个color table由一个RGB(就像通常我们见到的(255,0,0)红色 那种)列表组成。

正如之前说到的,global color table的长度为2 ^ (N + 1),因此这个表占用 3 * 2 ^ (N + 1)个字节。

数据流中,颜色是按照列表中的索引存储的。

image-20250504204941811

image-20250504204925571

应用程序扩展21 FF

开头21 FF

GIF中扩展块都以0x21开始,后一个字节是扩展标签,标识扩展用途。
应用程序扩展的标签是0xFF,它包含有应用程序的标识信息和应用程序数据。

其中 Netscape 应用程序扩展常用于控制GIF的动画循环次数。Netscape 扩展长19个字节,前14个是应用程序的ACSII信息,后四个是数据子块,用于指定GIF的循环次数, 按无符号整型存储,0表示无限循环。

图像控制扩展21 F9

用于指定透明度以及控制动画

图形控制扩展块属于”89a”版本的定义。

它在一个图像数据块的最前端,用来指定图像的透明度与动画属性。

图形控制扩展的开端两字节是0x21F9,其中0x21表示这是一个扩展,F9表示扩展用于图形控制。

第三个字节是块的大小(它到结束符之间的数据)。

第四个字节是压缩字段,前三个Bit保留,四到六Bit是disposal method(处理方法),倒数第二个位是user input flag(用户输入标志),最后一位transparent color flag(透明色标志)

第四、五个字节是图像控制扩展后面的图像的动画时间(delay time),以无符号整型(unsigned格式)存储。

第六个字节是透明色索引,之后是块结束符0x00。

image-20250504210506855

图像描述符

每个image都以一个image descriptor block(图像描述块)作为开头,这个块固定为10字节。

图像描述符位于GIF中每一个图像数据的前端,由0x2C开始,长度固定为10个字节。

第一个字节是图像描述符的标识0x2c

后面八个字节表示图像的frame(left, top, width, height),用来在动画中局部更新图像。每个值都是2字节的,无符号小端格式。

最后一个是压缩字节,主要是关于局部颜色列表的信息,其中第二个Bit表示图像的存储方式是交织还是连续。

首位是local color table flag(局部颜色表标志)。设置为1表示允许你指定图片数据使用一个不同的颜色表(跟在后面)以取代全局颜色表。

第二个位是interlace flag(隔行扫描标志)。隔行扫描方式会改变图像渲染到屏幕上的方式,从而减少烦人的视觉闪烁(类似垂直同步)。隔行扫描在显示器上的效果是,第一轮扫描先立即模糊地显示图像,随后再一轮将其填充锐化。这种方式会让人们感觉更舒服,因为它可以让人们模糊地意识到即将显示的东西是什么,而不是等待像素点被一行一行地填充绘制。要支持这种显示方式,图片的扫描行需要以一种特定的顺序来存储,需要分为4个部分,每个部分都是一个完整的模糊显示,通过4次显示加成使得图片越来越清晰,最终完全呈现。

image-20250504210639855

局部颜色列表

如果上面的局部颜色列表标志位为1,那么局部颜色列表会排列在图像描述符后面,它只对紧跟在它之后的图像数据有效。如果局部颜色列表标志位为0,那么图像数据将使用全局颜色列表索引颜色。局部颜色列表的大小计算方法和像素颜色格式与全局颜色列表相同。

图像数据

GIF的图像数据是经过LZW压缩的二进制流,通过解码可以将其按照颜色列表中的颜色进行像素填充。第一个字节是LZW最小编码大小,用来进行数据解码。第二个字节是图像数据的大小,之后的都是图像数据,直到块结束符。

http://www.wxhsa.cn/company.asp?id=6703

相关文章:

  • ESP32做AP,ESP8266做station,遥控
  • 实用指南:25年高联:一试填空题解析(下篇)
  • Spring AOP 面向切面编程 - 浪矢
  • jvm内存泄漏的排查tips总结
  • IPA
  • Chromium历史版本下载方式
  • 【ACM出版】第三届物联网与云计算技术国际学术会议 (IoTCCT 2025)
  • 2025年最全 Wiki 管理工具测评:ONES、Confluence、Notion......哪个更适合你?
  • 鼠你爱称重
  • 详细介绍:用户争夺与智能管理:定制开发开源AI智能名片S2B2C商城小程序的战略价值与实践路径
  • PlorarD(WEB中等)
  • 神经网络稀疏化设计构架方式和原理深度解析
  • 天下拍拍卖系统:二方系统也能扩展三方平台功能
  • express使用redis
  • day07 课程
  • 111
  • 排序实现java - 教程
  • .net core 发布到 iis 步骤
  • kylin SP2安装mysql8.4.5
  • 微信社群机器人接口
  • C++的枚举类
  • Revit二次开发 钢筋生成API(一)
  • 方法
  • 详细介绍:PHP基础-语法初步(第七天)
  • 如何通过Python SDK 删除 Collection
  • maven项目连接DM数据库和基本sql使用
  • 【中国计算机学会CCF主办】第六届人工智能、大数据与算法国际学术会议(CAIBDA 2026)
  • 图片 - voasem
  • 面试时让你设计一个“朋友圈点赞”功能测试,如何回答才出彩?
  • 企训宝教育培训微信小程序系统