您好,欢迎来到爱问旅游网。
搜索
您的当前位置:首页png文件格式分析

png文件格式分析

来源:爱问旅游网
png⽂件格式分析

png⽂件格式分析

写在前⾯

在写这个东西写到⼀半的时候,突然发现CTFWiki已经有这篇相对正规的⽂章了。对PNG⽂件格式的分析⽹上相对⽐较多,所以分析的⽐较菜,表哥们轻喷。

PNG⽂件结构

PNG⽂件格式

PNG⽂件格式很简单,对于⼀个PNG⽂件来说,主要是开头固定的字节(⼜叫做⽂件头,⽂件署名域,标识符等等)和三组以上的PNG数据块按照特定的顺序组成,其中,最基本的PNG⾄少包含以下部分:

⽂件头 IHDR IDAT IEND

其中,⽂件头的hex为:

50 4E 47 0D 0A 1A 0A

这⼀部分主要是考察对各类媒体类型(mime type)的识别⽐较多。重要的是,正确判断⼀个图⽚的⽂件格式⼗分重要,如 GIF ⾥⾯有帧信息,⽽JPG ⾥⾯却没有,PNG 图⽚有通道信息,⽽ JPG 也没有。单凭后缀进⾏错判会导致处理的时候⽂件报错。(补充:)

同时由于⽂件种类多⽽复杂,通过背诵⽂件头实现对应图⽚⽂件格式便显得⼗分困难。根据⽹上资料可知,python库中存在imghdr模块可以直接识别⽂件,同⽬录下命令⾏⾥敲⼊指令:

python -m imghdr file1

即可识别,结果会返回⽂件格式。

PNG数据块(Chunk)

PNG定义了两种类型的数据块:关键数据块(critical chunk),是标准的数据块;辅助数据块(ancillary chunks),是可选的数据块。关键数据块定义了四个标准数据块。分别是IHDR、PLTE、IDAT、IEND,后⾯我们会分别提⼀下这四块。其中PLTE仅与索引彩⾊图像有关,在⿊⽩PNG图⽚是作为可选数据块存在的,但是本⾝是标准数据块之⼀。

对于每⼀个数据块,其数据结构⼜是统⼀的,每个数据块由Length(4bytes,指定数据块数据域的长度),Chunk Type Code(4bytes,数据块类型码,由ASCII字母组成), Chunk Data(数据块数据,储存按照CTC指定的数据),CRC(4byte,循环冗余检测,检测是否有错误的循环冗余码)组成。CRC计算是通过CTC和CD得到的。CRC通过触发以及余数原理进⾏错误侦测。附:

同时这⾥提供⼀下正常图⽚CRC校验值的计算⽅法:

import zlib

with open('d0430db27b8c4d3694292d9ac5a55634.png','rb') as image_data:

bin_data=image_data.read()

#截取待计算的字符串即IHDR中去除前四字节(length)和后四字节(CRC)

data = bytearray(bin_data[12:29])

#使⽤函数计算

crc32key = zlib.crc32(data)

print(hex(crc32key))

PNG数据块结构

IHDR

⽂件头数据块IHDR(header chunk):包含PNG⽂件中储存的图像数据的基本信息。位于⽂件头之后的第⼀个数据块。⽂件头数据块是由13个字节组成的:

域名称字节数说明

Width4bytes图像宽度,以像素为单位

Height4bytes图像⾼度,以像素为单位

Bit depth1byte图像深度

Color Type1byte颜⾊类型

Compression method1byte压缩⽅法(LZ77派⽣算法)

Filter method1byte滤波器⽅法

Interlace method1byte隔⾏扫描⽅法

"c":"图像深度:索引彩⾊图像:1,2,4或8

灰度图像:1,2,4,8或16

真彩⾊图像:8或16

颜⾊类型:

0:灰度图像, 1,2,4,8或16

2:真彩⾊图像,8或16

3:索引彩⾊图像,1,2,4或8

4:带α通道数据的灰度图像,8或16

6:带α通道数据的真彩⾊图像,8或16

隔⾏扫描⽅法:

0:⾮隔⾏扫描

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

其中我们关注的是前8字节内容,也就是Width和Height。通过对图⽚⾼度和宽度的修改隐藏原本应该显⽰的flag是最为常见的⽅法。⽐较常见的就是CRC爆破和简单计算猜测原图⽚宽度,在下⾯我可能会提⼀下。

PLTE

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

颜⾊字节意义

red10 = ⿊⾊, 255 = 红

green10 = ⿊⾊, 255 = 绿

blue10 = ⿊⾊, 255 = 蓝

由于三原⾊的原因,调⾊板数据块的长度应该是三的倍数,否则就是⼀个⾮法调⾊板。真彩⾊的 PNG 数据流也可以有调⾊板数据块,⽬的是便于⾮真彩⾊显⽰程序⽤它来量化图像数据,从⽽显⽰该图像。这⼀块考察的相对较少,所以暂时没啥要提的。

但是也不是不能提⼀下。我们可以通过修改PLTE数据块修改图⽚的颜⾊。不过我搜到的资料中很少是对调⾊板修改,更多是对数据中的RGB进⾏修改。我个⼈偏向于这种修改是对IDAT块的修改,⽽不是PLTE层⾯。

IDAT

图像数据块IDAT(image data chunk),这部分储存实际的数据,⽽且在数据流中可以多个连续存在。

储存图像像数数据

数据留种可以包含多个连续顺序的IDAT

采⽤LZ77算法的派⽣算法进⾏压缩

可以⽤zlib解压缩

只有单个数据块充满时,才会填充下⼀个数据块

相对重要的时最后两条,尤其是最后⼀条。IDAT单个数据块不充满表⽰此处的数据块出现错误,⽽错误⼤多数时隐写导致的,这也是我们获取flag的⼀个⼩⼩的突破⼝。

关于zlib解压缩这⼀部分,PNG⽂件是“可选”zlib解压缩,不代表只有zlib⼀种。zlib不是⼀个压缩算法,我们说到“zlib压缩数据”更多的⼀是是指代⼀种压缩数据的存放格式。我们可以使⽤python提供的zlib库对其进⾏处理。我尝试了zlib字符串的解压缩,在这⾥提供代码以及⼀篇⽂章

#导⼊zlib包

import zlib

#⽬标字符串

message = 'abcd1234'

#字符串压缩

compressed = zlib.compress(message)

#字符串解压

decompressed = zlib.decompress(compressed)

print 'original:', repr(message)

print 'compressed:', repr(compressed)

print 'decompressed:', repr(decompressed)

这⼀块牵扯⽐较多的就是LSB隐写相关的,还有数据插⼊。⼀般来说数据插⼊说明这个只是⼀个跳板,下⾯还有其他隐写……这两块我们稍微提⼀下。

LSB 全称 Least Significant Bit,最低有效位。PNG ⽂件中的图像像数⼀般是由 RGB 三原⾊(红绿蓝)组成,每⼀种颜⾊占⽤ 8 位,取值范围为 0x00 ⾄ 0xFF,即有 256 种颜⾊,⼀共包含了 256 的 3 次⽅的颜⾊,即 16777216 种颜⾊。

⽽⼈类的眼睛可以区分约 1000 万种不同的颜⾊,意味着⼈类的眼睛⽆法区分余下的颜⾊⼤约有 6777216 种。

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

"c":"我们假设⼀个像素块的RGB是(11011010,10010110,10010101),那么,我们修改它末尾的bit的时候,⼈眼看来⾊调基本上是没有变化的。说⽩了,就是⼈类视觉冗余没有办法识别相近的⾊素块导致信息的隐藏,LSB隐写见过的两个⽐较常见的⽅向有藏⼆维码和ASCII码。本⼈能⼒不⾜,在这⾥附上IEND

图像结束数据 IEND(image trailer chunk):它⽤来标记 PNG ⽂件或者数据流已经结束,并且必须要放在⽂件的尾部。⽂件结尾的⼗⼆个字符看起来应该是:

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

IEND 数据块的长度总是00 00 00 00,数据标识总是 IEND 49 45 4E 44,因此,CRC 码也总是AE 42 60 82。

这个地⽅遇到的是图⽚叠图⽚,也就是很粗暴的将两张图⽚的数据块叠在⼀起,这样的⽂件只能打开,在⼯具中是报错的。但是在读取数据的时候读取到IEND读取停⽌,此时只显⽰第⼀张图⽚。这样相对粗暴的完成了隐写的⽬的。

PNG图⽚隐写

⽂件类型判断

CRC爆破

在这⾥CRC爆破我选⽤的是BUUOJ ⼤⽩。tweakpng中提⽰校验码与实际校验码出现差别,⼀般这种情况下是擅⾃修改宽⾼没有修改校验码导致的。这个时候我们以原本校验码为准,进⾏CRC爆破。

import struct

import binascii

from Crypto.Util.number import bytes_to_long

img = open("dabai.png

for i in range(0xFFFF):

stream = img[12:20] + struct.pack('>i', i) + img[24:29]

crc = binascii.crc32(stream)

#crc = binascii.crc32(stream) & 0xFFFF

#if crc == 0x8e14dfcf:

if crc == bytes_to_long(img[29:33]):

print(hex(i))

(友情提醒,crypto库安装后运⾏代码⼤概率还会出现⽆crypto库的⽩给情况,减少⽩给⼈⼈有责)

⾼度遍历 0~0xFFFF。此外,因为img[12:20]等输出的结果是bytes, 所以这⾥需要利⽤struct对整型数据格式化, 将其打包为字节流。另外格式符i意味着4字节的整型。默认⼩端输出加'改为⼤端输出。

得出结果在010editor修改即可。

不过这种题⽬制作的时候或多或少有⼀点点bug,就是有的时候凭⼿感直接输⼊⾼度或者宽度也可以观察到隐藏的那部分图⽚,这样看来好像不⽤很正规的办法也能解出来(?

未填充满数据块

还有⼀些删除错误数据块就可以了的习题。

LSB隐写

题⽬⽰例我选⽤的是BUUOJ的LSB,这道题⽐较简单,⽽且是牵扯到⼆维码。这个地⽅我们是stegsolve⼯具,这⾥附上

使⽤stegsolve打开,在Red plane 0、Grenn plane 0、Blue plane0通道发现图⽚的上⽅好有东西,Analyse->Data Extract稍微调整⾄⼀下,发现这是⼀张图⽚,Save Bin保存为flag.png。打开发现是⼆维码,扫码出flag。

IEND⽂件叠⽂件

misc8是⽂件叠加⽂件的典型。我们先⽤binwalk进⾏检查,发现其中叠加了⼀个png⽂件。分离图⽚⽂件分为两种,⼀种是⽤foremost或者binwalk直接分离,另外⼀种是WinHex⼿动分离。

第⼀种:在Linux中通过指令binwalk misc8.png查看,发现存在PNG⽂件。offset是32,也就是说跳过前⽅32块偏移后,后⽅为我们所要的。

通过dd指令:

$ dd if=misc8.png of=misc8a.png skip=32 bs=1

其中,if是⽬标⽂件,of是输出⽂件,skip是跳过偏移数⽬,bs设置每次读写块的⼤⼩为1字节。

第⼆种:直接⾁眼⼿动分离了。有些费时费⼒,不太建议⽤这种⽅法。png⽂件的IEND应该是00 00 00 00 49 45 4E 44 AE 42 60 82,我们在WinHex中通过⾁眼发现确实是有多的png。通过16进制⽂本定位,在offset F2C处已经出现上述字符块,⽽到结尾也存在同样的字符块。我们选中后半部分,编辑->复制选块->⾄新⽂件,将⽂件后缀填好。打开发现flag。

因篇幅问题不能全部显示,请点此查看更多更全内容

Copyright © 2019- awee.cn 版权所有 湘ICP备2023022495号-5

违法及侵权请联系:TEL:199 1889 7713 E-MAIL:2724546146@qq.com

本站由北京市万商天勤律师事务所王兴未律师提供法律服务