从PNG文件头到CRC碰撞:手把手教你修复一张被‘篡改’的CTF图片

张开发
2026/4/21 6:47:05 15 分钟阅读

分享文章

从PNG文件头到CRC碰撞:手把手教你修复一张被‘篡改’的CTF图片
从PNG文件头到CRC碰撞手把手教你修复一张被篡改的CTF图片当你兴致勃勃地打开一张CTF竞赛中的PNG图片时却发现它无法正常显示——这很可能遇到了经典的图片尺寸篡改题型。作为CTF Misc方向的基础题型这类挑战考察的是对PNG文件结构的理解与二进制修复能力。本文将带你化身数字取证专家从文件头分析到CRC校验碰撞完整还原修复过程。1. PNG文件结构探秘PNG文件由多个数据块(chunk)组成每个块包含长度、类型、数据和校验码四部分。当我们用WinHex或010 Editor打开被篡改的图片时首先看到的是89 50 4E 47 0D 0A 1A 0A // PNG文件头签名 00 00 00 0D // IHDR块长度(13字节) 49 48 44 52 // IHDR标识 00 00 03 13 // 图像宽度(787像素) 00 00 01 F4 // 图像高度(500像素) 08 06 00 00 00 // 色深颜色类型压缩方法等 DA 5A 4A 50 // CRC32校验码关键点在于IHDR块中的宽高字段。攻击者往往会修改这两个4字节数值导致图片无法显示但忽略了CRC校验码与宽高的强关联性。CRC32算法会对从IHDR到最后一个参数的所有数据进行校验计算任何篡改都会导致校验失败。2. 手工验证CRC冲突假设我们检测到图片实际宽度被改为600像素(0x00000258)但原始CRC值0xDA5A4A50保持不变。此时可以验证CRC冲突import binascii # 被篡改的IHDR块数据 modified_data bIHDR\x00\x00\x02\x58\x00\x00\x01\xF4\x08\x06\x00\x00\x00 current_crc binascii.crc32(modified_data) 0xffffffff print(f当前CRC: {hex(current_crc)}) print(f文件记录CRC: 0xda5a4a50) print(f是否匹配: {current_crc 0xda5a4a50})运行后将显示CRC不匹配证实图片已被篡改。此时我们需要通过暴力破解找出正确的宽高组合。3. CRC碰撞实战脚本通过遍历可能的宽高值并计算CRC可以找到能通过校验的正确组合。以下是优化后的Python脚本import binascii import struct from concurrent.futures import ThreadPoolExecutor def check_crc(width, height, target_crc): data bIHDR struct.pack(i, width) struct.pack(i, height) b\x08\x06\x00\x00\x00 crc binascii.crc32(data) 0xffffffff return (width, height) if crc target_crc else None def brute_force_crc(target_crc, max_dim2000): with ThreadPoolExecutor() as executor: futures [] for w in range(1, max_dim): for h in range(1, max_dim): futures.append(executor.submit(check_crc, w, h, target_crc)) for future in futures: result future.result() if result: return result return None # 从文件读取CRC值 with open(corrupted.png, rb) as f: f.seek(29) # 跳转到CRC位置 file_crc int.from_bytes(f.read(4), byteorderbig) print(f开始CRC碰撞目标值: {hex(file_crc)}) width, height brute_force_crc(file_crc) print(f破解成功正确尺寸: {width}x{height})该脚本采用多线程加速实测在普通PC上可在几分钟内完成1000x1000范围内的搜索。对于CTF竞赛中常见的尺寸范围(通常5000px)效率完全足够。4. 文件修复与验证获得正确尺寸后用十六进制编辑器修改对应字段用010 Editor打开文件跳转到0x12偏移量IHDR宽度起始位置将4字节宽度值修改为破解结果同样方法修改高度值保存文件修复完成后图片应能正常显示。为验证修复效果可以使用pngcheck工具pngcheck -v fixed.png正确的输出应显示IHDR CRC OK且无任何错误警告。如果图片仍无法打开可能需要检查文件头签名是否完整(8字节)IDAT数据块是否损坏文件结尾标记(IEND)是否存在5. 进阶技巧与优化对于大型图片或需要快速解题的场景可以优化爆破策略宽度优先搜索观察大多数CTF题目只修改宽度可固定高度进行单维度搜索def quick_width_scan(target_height, target_crc): for w in range(1, 10000): data bIHDR struct.pack(i, w) struct.pack(i, target_height) b\x08\x06\x00\x00\x00 if (binascii.crc32(data) 0xffffffff) target_crc: return w return None已知比例搜索当图片明显被压扁或拉长时可按比例缩小搜索范围def ratio_based_search(target_crc, ratio1.5, delta0.2): for w in range(1, 3000): h_min int(w / (ratio delta)) h_max int(w / (ratio - delta)) for h in range(h_min, h_max): data bIHDR struct.pack(i, w) struct.pack(i, h) b\x08\x06\x00\x00\x00 if (binascii.crc32(data) 0xffffffff) target_crc: return (w, h)6. 实际CTF案例解析以某次竞赛中的broken_image.png为例使用pngcheck检测发现问题broken_image.png CRC error in chunk IHDR (computed 38d82c82, expected 18d82c82)提取IHDR块的CRC值0x18d82c82运行优化后的爆破脚本target_crc 0x18d82c82 result brute_force_crc(target_crc) print(result) # 输出 (800, 600)修改文件后成功显示隐藏的flagCTF{CRC32_c0ll1s10n_1s_fun}7. 防御性编程建议为防止自己的题目被轻易破解出题者可考虑同时修改多个IHDR参数如色深宽高使用非常规图片尺寸增加爆破难度在IDAT块中设置额外的校验机制结合其他隐写技术如LSB增加复杂度对于参赛者建议建立自己的工具库将常见操作封装成函数def patch_png_size(filename, width, height): with open(filename, rb) as f: # 定位到宽度字段 f.seek(0x12) f.write(struct.pack(i, width)) # 定位到高度字段 f.seek(0x16) f.write(struct.pack(i, height))掌握PNG文件结构与CRC校验原理不仅能解决CTF竞赛中的图片题更能深入理解文件格式验证机制。当遇到损坏图片时不妨先检查IHDR块——答案可能就藏在那些十六进制数字中。

更多文章