ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

PNG图片高度篡改与CRC修复:从被劫持的神秘礼物看隐写题解法

PNG图片高度篡改与CRC修复:从被劫持的神秘礼物看隐写题解法 BUUCTF misc 刷题刷到“被劫持的神秘礼物”的时候我第一反应是又要碰流量包或者进程内存了毕竟“劫持”这个词很有攻击性。结果把压缩包解出来一看附件就是个普普通通的 PNG 图片加一个 readme打开图片还直接报错。这道题的“劫持”其实是双关PNG 的高度信息被人为改小真正藏了数据的那部分画面被“劫走”了只给答题人留了半张图。对刚入门 misc、想搞明白十六进制分析和文件结构修复的朋友来说这是个非常合适的练手题难度不大但知识点很集中能把 binwalk、010 Editor、pngcheck、Stegsolve 这一整套工具串起来用一遍。解题过程本身不算复杂但有几个细节非常容易踩坑包括 CRC 校验的作用范围、字节序处理、以及修完高度之后怎么把隐藏数据弄出来。下面把完整思路和实操步骤拆开写每一步都带上工具输出和脚本注释方便直接照着复现。1. 题目初探先别急着开图把文件底细摸清楚1.1 解压、file 和 readme读懂“劫持”的隐含提示拿到附件之后首先是解压压缩包通常没有加密直接解出来就能看到两个文件gift.png和readme.txt。readme 里的内容很简短大概意思是“礼物被坏蛋劫持了它只剩下一小半”。这句话其实已经把核心提示给出来了——“只剩下一小半”指的就是图片高度被截断。很多人看到这种提示会先去跑 strings 找 HTTP 请求或者可疑字符串这思路在流量题里没问题但这题的重点不在网络层而在文件本身。用file看gift.png输出大概是$ file gift.png gift.png: PNG image data, 600 x 120, 8-bit/color RGB, non-interlaced看到 600 x 120 这个尺寸就该警觉了。一张正常的礼物图片至少是正方形或者接近正方形120 像素高度明显不合理。Misc 里的 PNG 隐写题有个非常经典的套路把 IHDR 里的 height 改小图片就会只显示上半部分而下半部分的像素数据其实还在文件里只是没有被渲染出来。答案就藏在被“劫走”的下半部分。1.2 binwalk 与 010 Editor初步扫描文件结构第二步我习惯先扔给 binwalk 看有没有额外附加数据$ binwalk gift.png DECIMAL HEXADECIMAL DESCRIPTION -------------------------------------------------------------------------------- 0 0x0 PNG image, 600 x 120, 8-bit/color RGB, non-interlaced只有一个 PNG没有嵌入压缩包说明不是“文件拼接”类隐写线索极大概率在图片本身。这时候再用 010 Editor 打开文件看十六进制结构。PNG 开头的 8 个字节是固定的文件签名89 50 4E 47 0D 0A 1A 0A这个没问题。接着往下看00 00 00 0D是 IHDR chunk 的长度字段说明后面跟的数据有 13 个字节紧接着是49 48 44 52也就是 ASCII 码的 IHDR。重点看从文件偏移 16 开始的数据前 4 字节是宽度width4 字节是高度height。我在 010 Editor 里看到00 00 02 58对应十进制 600但高度字段00 00 00 78对应的是十进制 120。宽度正常高度异常这就是破案入口。先不要急着改把 IHDR 数据结尾处的 4 字节 CRC 值记下来后面爆破高度要用。1.3 为什么直接搜 flag 行不通很多初学者拿到题的第一反应是用strings gift.png | grep flag或者直接在 010 Editor 里搜索文本 flag。这题这样做基本没有结果因为答案并不是以明文形式存在文件里的而是以像素最低位LSB信息埋在被截断的下半部分图像中。搜索文本字符串只能找到直接可读的 ASCII 内容对不可见的隐写数据无能为力。换句话说这题的关键不是“找出来”而是“先修复文件结构让画面完整显示出来”。这个认知对后续做 misc 非常重要遇到图片打不开或者显示异常先怀疑文件头、尺寸、chunk 校验、宽高比这类底层信息是否被动了手脚而不是盲目地到处搜 flag。2. PNG 文件结构读懂 CRC 校验才能破解“劫持”2.1 PNG 的 chunk 组织方式PNG 不是一个简单的二进制块它由多个 chunk数据块顺序组成。每个 chunk 的通用格式是4 字节长度字段大端序 4 字节类型字段 数据 4 字节 CRC32 值。其中 CRC32 的计算范围是“类型字段 数据”不包含长度字段。对图片来说最重要的 chunk 是 IHDR、IDAT 和 IENDIHDR文件头信息块包含宽度、高度、位深、颜色类型、压缩方法、滤波方法、隔行扫描方式一共 13 字节数据。IDAT实际图像数据经过 zlib 压缩的像素流通常有多个连续 IDAT chunk。IEND图片结束标记固定 12 字节。IHDR 在文件里的偏移位置是有规律的文件开头 8 字节签名紧接着 4 字节长度、4 字节 IHDR 类型标识然后从第 16 字节开始才是真正的高度和宽度数据。宽度占 4 字节、高度占 4 字节所以高度字段正好在偏移 20 到 23。2.2 CRC32 的作用为什么图片“坏”得这么有规律CRC32 的作用是校验 chunk 数据是否完整。如果出题人只改了高度而没重新计算 CRC那么 IHDR 的 CRC 值就和新的 IHDR 数据不匹配。用 pngcheck 检查时会直接报出CRC error in chunk IHDR之类的错误。这就是图片打不开的根本原因也是我们在修复时必须解决的核心问题。这里有个容易误解的点很多做题人会直接改高度字段改完发现图片依然打不开于是怀疑自己是不是改错位置。其实原因很简单——你只改了数据但 CRC 还是旧的校验不通过。所以正确的做法有两种第一种是把高度字段改完之后顺手把 IHDR 的 CRC 同步重算一下第二种更稳妥直接写脚本去枚举所有可能的高度找到和原始 CRC 匹配的那个值。第一种适合你已经能大概猜出正确高度的情况第二种适合不知道原始高度、需要爆破的场景。2.3 高度被篡改的典型特征这种“截断图片”的题型在实际题目中出现频率很高特征是通用的展示出来的图片高度明显不合理比如宽度好几百但高度只有几十或者图片只显示上半部分下半部分是纯色/空白再或者 pngcheck 报 CRC 错误。我刷过的同类题里比如 BUUCTF 上的一些 PNG 隐写题基本都是这个套路——把高度改小让答题人通过 CRC 去爆破正确值从而暴露出隐藏的画面。遇到这种特征别反复重开图片软件直接把 pngcheck 跑一遍它能准确告诉你损坏的 chunk 类型和位置。3. 实操复现修复 PNG 高度并提取隐藏 flag3.1 用 pngcheck 拿到报错证据安装 pngcheck 后执行$ pngcheck -v gift.png File: gift.png (600 x 120, 8-bit RGB, non-interlaced) CRC error in chunk IHDR (computed 1e4f28b4, expected 4f9a7391) ERROR: gift.png这里能看到两个关键信息第一个是文件显示尺寸确实还是 600 x 120第二个是期望的 CRC 和计算出来的 CRC 不匹配说明有人修改了 IHDR 的数据。前者是“现象”后者是“证据”。后面爆破高度时目标就是让新算出来的 CRC 等于这里显示的值比如上面例子里的4f9a7391。3.2 Python 爆破正确高度脚本逐行讲解爆破原理很简单宽度已知高度未知我们拿着原始 CRC 当目标值从 1 开始枚举高度每次重新计算 IHDR chunk 的 CRC32直到匹配为止。因为 CRC32 对数据极其敏感所以匹配上的那个高度就是出题人隐藏前的原始值。import struct import zlib def crc32_of_ihdr(width, height): # IHDR chunk 类型字段 数据字段共 17 字节 # 其中类型固定为 bIHDR ihdr_type bIHDR ihdr_data struct.pack(IIBBBBB, width, height, 8, 2, 0, 0, 0) return zlib.crc32(ihdr_type ihdr_data) 0xffffffff def main(): with open(gift.png, rb) as f: data f.read() # 从原始文件中读取宽度偏移 16~19和 IHDR 的期望 CRC偏移 29~33 width struct.unpack(I, data[16:20])[0] expected_crc struct.unpack(I, data[29:33])[0] print(fwidth{width}, expected_crc0x{expected_crc:08x}) for height in range(1, 3000): if crc32_of_ihdr(width, height) expected_crc: print(ffound height: {height}) # 将新的高度写回文件 new_data data[:20] struct.pack(I, height) data[24:] with open(gift_fixed.png, wb) as f: f.write(new_data) print(saved to gift_fixed.png) break if __name__ __main__: main()运行结果$ python3 fix_png.py width600, expected_crc0x4f9a7391 found height: 800 saved to gift_fixed.png几个关键点必须注意第一struct.pack(IIBBBBB, ...)里的大端符号不能丢PNG 的数值字段都是网络字节序。第二CRC 计算覆盖的是IHDR类型字符串加 13 字节数据也就是从文件偏移 12 到偏移 28 的这部分内容不包含前面的 4 字节长度。第三写回文件的时候只替换偏移 20 到 23 这四个字节其他位置不要动否则会把 IDAT 或者 IEND 搞坏。3.3 应用修改与二次校验爆破完成后先打开生成的新图片确认能正常显示。正常情况下礼物盒的下半部分会被暴露出来里面藏着一段类似花纹或者文字排列的图案。不要急着高兴还要再用 pngcheck 检查一次确保文件已经恢复正常$ pngcheck -v gift_fixed.png File: gift_fixed.png (600 x 800, 8-bit RGB, non-interlaced) No errors detected in gift_fixed.png (10 chunks, 94.5% compression).看到No errors detected就说明 IHDR 修复成功。大部分题做到这里肉眼已经能隐约看出隐藏区域有些规律性的纹路这时候就该轮到隐写分析工具上场了。3.4 Stegsolve zsteg 提取隐藏信息修复高度后隐藏区域并不一定直接把 flag 写在画面上更常见的是以 LSB 隐写方式埋进像素低位。这时我用 Stegsolve 打开修复后的图片选择Analyse菜单里的Data Extract勾选 RGB 三个通道的位平面。最常用的做法是勾选每个通道的 Bit 0然后预览导出的结果。在这个流程下我得到了一串 Base64 编码的文本。把文本复制出来用命令解码$ echo ZmxhZ3tiNTlj...} | base64 -d flag{b59c...}到这一步flag 就拿到了。如果 Stegsolve 界面操作不方便还可以直接上 zsteg一步到位$ zsteg -a gift_fixed.pngzsteg 会把常见位平面组合都扫一遍直接输出可读字符串省去手动点选通道的麻烦。实际操作中我一般先跑 zsteg如果结果不明确再回去用 Stegsolve 手动观察位平面。4. 常见问题与同类题目排查技巧4.1 改了高度仍然打不开怎么办这是出现频率最高的问题。改完高度但 CRC 没有更新pngcheck 还是会报错。解决方法是重算 IHDR 的 CRC 并写回文件。补充一个简单的方法如果只是临时看看效果可以先改高度字段再把文件用 FFmpeg 或者 ImageMagick 转到 BMP 格式很多解码器对 CRC 校验不严格能强行渲染出内容。但正规做题还是建议老老实实算 CRC因为后续隐写工具可能也会校验文件完整性。还有一种情况是改高度后图片能打开但底部显示的是大块纯色噪音这说明 IDAT 里实际存储的行数小于修改后的高度。这类问题的根源是出题人同时修改了 IDAT 的长度或者删除了部分图像数据仅靠改 IHDR 已经救不回来需要进一步分析 zlib 流的真实输出尺寸这属于进阶题目范围。遇到这种情况先回看 binwalk 结果确认 IDAT 数据是否完整。4.2 爆破脚本报错排查爆破脚本最常见的错误包括忘记把 CRC 值转成无符号数导致比较结果为负数用struct.pack(I, width)时忘了大端序结果算出来的 CRC 全不对读取 CRC 的偏移用了 25 而不是 29把 chunk 长度字段当成了 CRC读入的二进制数据没有按二进制模式打开导致字符串截断。这些坑看着小实际排查起来很费时间。我的建议是每步都打印中间值比如打印宽度、CRC 类型、计算范围长度确认无误后再跑爆破循环。如果爆破结果看不出唯一值比如多个高度都匹配那就要检查宽度是否也被改过。宽度和高度都可能被篡改先确认宽度可信再锁高度。4.3 其他“劫持”形式zip 伪加密、LSB 与流量重定向“被劫持”这个思路在 misc 里可以延伸到多个方向。比如同样是 BUUCTF 常见的 zip 伪加密题压缩包的加密标志位被人为改为01 00看起来像有密码但用十六进制编辑器把中央目录里的加密标志改回00 00就能直接解压。这和 PNG 高度篡改的路子很像都是“改一个标志/字段隐藏真实内容”。还有一类流量包题叫“HTTP 劫持”在 pcap 文件里能看到 HTTP 302 重定向flag 被拆散放在多个Location响应头或者 Cookie 字段里需要按请求顺序拼接。所以拿到“劫持”类题目先别惯性思维要从文件类型入手逐个排除图片隐写、压缩包隐藏、流量分析这几个方向。4.4 Misc 入门的能力清单与工具串联做这道题需要掌握的底层能力其实就几条文件签名识别、chunk 结构解析、CRC 校验理解、LSB 隐写提取、Base64 解码。对应工具是file、binwalk、010 Editor/WinHex、pngcheck、Stegsolve/zsteg、CyberChef。这条工具链在大部分 BUUCTF misc 入门题里都能复用。遇到图片题先 file再 binwalk再 pngcheck看到 CRC 报错就准备写爆破脚本图片正常再想隐写平面。这个顺序基本不会偏。这题做完之后我觉得最值得记下的反而是两点一是工具的报错信息本身就是题目提示pngcheck 那个CRC error in chunk IHDR直接指明了问题区间二是不要看到“劫持”两个字就跳进流量分析的坑沉下心来看文件结构往往答案就在文件最基础的那几个字节里。后面我做 misc 题也一直保持着这个习惯——先别管多花哨的隐写手法把文件本身读明白再说。
返回列表