CTF实战:巧用文件结构修复图片宽高
1. CTF图片宽高修改实战入门第一次参加CTF比赛时遇到一张打不开的图片题目我完全摸不着头脑。后来才知道这类图片隐写题目往往通过修改图片文件结构中的关键参数来隐藏信息。最常见的套路就是篡改图片的宽高值让图片无法正常显示需要选手手动修复才能获取flag。JPG和PNG作为最常用的图片格式它们的文件结构就像乐高积木一样由多个标准化的模块组成。比如JPG文件以0xFFD8开头PNG文件以固定的8字节签名开头。理解这些基础结构就是解决图片宽高修改题目的第一步。2. JPG文件结构与宽高修改2.1 JPG文件格式深度解析JPG文件就像一本精心编排的相册每个章节都有特定的标记码引导。最重要的SOF0Start of Frame标记就是存放宽高信息的黄金位置。它的结构是这样的标记代码0xFFC0固定 数据长度2字节 精度1字节通常为08 图像高度2字节 图像宽度2字节 颜色分量数1字节 颜色分量信息可变长度实战中遇到过这样一个案例用hex编辑器打开题目图片搜索FFC0标记后发现宽度被设为0000。这就是典型的宽高篡改手法——将实际尺寸清零导致图片无法渲染。2.2 三步搞定JPG宽高修复定位关键标记使用010 Editor或WinHex搜索FFC0读取原始数据FFC0后第5-8字节就是高度和宽度注意大端序修改数值比如将异常的0000改为实际的0280640像素with open(corrupted.jpg, rb) as f: data f.read() sof0_pos data.find(b\xff\xc0) height data[sof0_pos5:sof0_pos7] width data[sof0_pos7:sof0_pos9] print(f原始宽高{int.from_bytes(width,big)}x{int.from_bytes(height,big)})记得修改后要检查SOF0段的CRC校验。有次比赛我就因为没校验改完图片还是打不开白白浪费半小时。3. PNG文件结构与爆破技巧3.1 PNG文件头与IHDR块PNG的文件结构更像集装箱货轮每个数据块chunk都有明确的标签。关键尺寸信息存放在IHDR块中这个块的结构非常规范长度4字节固定13 类型4字节IHDR 宽度4字节 高度4字节 位深1字节 颜色类型1字节 压缩方法1字节 滤波器1字节 交错方法1字节 CRC4字节遇到过最狡猾的题目是把IHDR块的CRC校验值也改了导致常规编辑器直接报错。这时候就需要用到CRC爆破技术。3.2 自动化爆破宽度实战当遇到CRC校验正确的篡改时可以写脚本爆破实际宽度import binascii import struct with open(flag.png, rb) as f: data f.read() ihdr data[12:29] # 获取IHDR块内容 for w in range(1000): # 假设宽度在1000像素内 # 重建IHDR块替换宽度字段 new_ihdr ihdr[:4] struct.pack(i, w) ihdr[8:] # 计算CRC32注意包含块类型和内容 crc binascii.crc32(bIHDR new_ihdr[4:13]) 0xffffffff if crc int.from_bytes(ihdr[13:17], big): print(f爆破成功实际宽度{w}) break这个脚本我在三场不同比赛中都用到过最快的一次只用了2秒就爆破出正确宽度。记得保存原始文件有次我直接在内存中修改导致源文件损坏不得不重新下载题目附件。4. 高级技巧与避坑指南4.1 双重校验陷阱去年某CTF出了道阴险题目不仅改了宽高还同时在APP1段Exif信息里存了假尺寸。用常规方法修改后某些图片查看器仍会读取Exif里的错误尺寸。解决方法是用exiftool彻底清除Exif数据exiftool -all corrupted.jpg4.2 尺寸与压缩的关联修改JPG宽度时要注意SOF0里的采样因子在颜色分量信息中会影响实际显示比例。有次我把800x600改为1600x300结果图片显示异常扭曲就是因为没同步调整采样因子。4.3 工具链推荐010 Editor二进制编辑神器有现成的JPG/PNG模板pngcheck快速验证PNG文件结构HxD轻量级十六进制编辑器Pillow库Python处理图片的最后防线from PIL import Image try: img Image.open(modified.jpg) img.verify() # 验证图片完整性 except Exception as e: print(f图片校验失败{str(e)})5. 实战案例复盘去年一道真题给出显示为100x100的图片但flag明显被截断。用脚本读取真实尺寸发现实际是100x800修改高度后果然显示完整二维码。关键点在于先用file命令确认是PNG24格式发现IHDR显示的100x100与CRC校验匹配怀疑存在IDAT块数据异常最终发现题目在IDAT块后追加了隐藏数据这种多层嵌套的题目就需要结合hex编辑和Python脚本分析import zlib with open(challenge.png,rb) as f: data f.read() idat_pos data.find(bIDAT) idat_data data[idat_pos4:idat_pos8] # 解压缩IDAT数据 decompressed zlib.decompress(data[idat_pos8:-12]) print(len(decompressed)) # 通过数据量反推真实高度