【Misc】PNG宽高修改 - PNG图片宽高CRC爆破
引言
在CTF竞赛的MISC(杂项)题型中,PNG图片的宽高修改是高频考点之一。这类题目往往通过隐藏图片的真实尺寸,结合CRC校验或文件结构特性来干扰选手获取关键信息。本文将从文件结构解析、宽高修改原理、工具三个方面,深入探讨PNG图片的宽高修改技术,并附带具体操作示例。
一、PNG文件结构与宽高存储原理
PNG文件的固定头为 89 50 4E 47 0D 0A 1A 0A
,用于标识文件类型。关键数据块
IHDR位于文件头之后,存储图像的元数据。
图像元数据包括:
- 宽度:IHDR块的第16-19字节(4字节,大端序存储)
- 高度:IHDR块的第20-23字节(4字节,大端序存储)
- 其他参数:色深、颜色等值。
在第二行(0010H)打头4字节就是宽度,紧随其后的就是高度。图示宽度为00 00 04 8A
,高度为00 00 01 22
。将16进制转换为10进制得到宽度为:1162,高度为290。
每个数据块末尾包含了4字节的CRC32校验码
,用于验证数据块(包括IHDR类型码和宽高数据)的完整性。若直接修改宽高值而未更新CRC,图片将无法正常显示。但是某些图片浏览软件会忽视CRC32校验,直接显示修改后的值。
图中选中的蓝色底色字体(00 18 BF 68
)则为此处IHDR的CRC32校验码。
校验码的计算方式参考:
import zllib
zllib.crc32(IHDR+widht+height+后部字符)
其实IHDR位一直从图中的49 48 44 52
一直到00 18 BF
的前面。
二、修改宽高
你可以直接使用010Editor修改宽度位置或高度位置。
上图是一个已经包含FLAG的图片,我们需要修改高度造成FLAG的不显示。
原图值是00 00 01 82
,也就是386px,我们设置为200px,也就是00 00 00 C8
。
保存后,再次观察图片:
图片的下半部分已经被隐藏。
三、恢复宽高
当我们拿到这样一个Misc题目时,我们需要首先了解原图大概在多少。但是其实盲目修改,只要范围不是很大,也可以成功。
例如我直接改为00 00 01 C8
图片中同样也是会包含FLAG的,下部分黑色区域则是没有图像内容导致的。
我们使用Brute_Crack_PNG的GUI工具,可以轻松还原图片。
很明显当前PNG图片是高度位置不符,所以应该爆破高度,我们按照上图所示在工具中勾选爆破高度模式
,点击开始爆破。
不出1秒,就找到了有效高度。
点击保存文件,即可保存修改后的图片。
修复的分毫不差。
工具地址:https://github.com/Moxin1044/Brute_Crack_PNG
为了效率,目前使用Go编写该项目,基本代码和原理可以参考master分支中的python版本,具体代码如下:
import zlibdef png_crc32_crack_wh(file_name):with open(file_name, 'rb') as f:hexdata = f.read().hex()PNG_data = hexdata[:16]if PNG_data == "89504e470d0a1a0a":IHDR = bytes.fromhex(hexdata[24:32])width = int(hexdata[36:40], 16)height = int(hexdata[44:48], 16)str2 = bytes.fromhex(hexdata[48:58])crc32 = int(hexdata[58:66], 16)add_num = 20000 # 最大宽高,合理修改快速出flagfor w in range(width, width + add_num):for h in range(height, height + add_num):width_bytes = w.to_bytes(4, 'big')height_bytes = h.to_bytes(4, 'big')if zlib.crc32(IHDR + width_bytes + height_bytes + str2) == crc32:return f"PNG图片宽度:{ hex(w)} | {w}\nPNG图片高度:{hex(h)} | {h}"if zlib.crc32(IHDR + width_bytes + height_bytes + str2) == crc32:breakelse:return "可能不是PNG文件,或文件头有修改。\nPNG文件头:89504e470d0a1a0a"if __name__ == "__main__":filename = "test.png"print(png_crc32_crack_wh(filename))