CTF新手必看:用WinHex和Python搞定PNG/JPG图片隐写(附实战脚本)
CTF图片隐写实战从损坏文件到Flag提取的完整指南当你第一次拿到一张看似损坏的图片时那种既困惑又兴奋的感觉正是CTF竞赛的魅力所在。本文将带你体验完整的图片隐写分析流程——从识别文件类型、修复文件结构到最终提取隐藏信息。不同于传统的理论讲解我们将通过一个连贯的实战案例使用WinHex和Python脚本一步步解决问题。1. 初识文件隐写当图片拒绝打开时上周我遇到一张名为broken_image.dat的文件——没有扩展名无法用任何图片查看器打开。这正是典型的CTF Misc题目开场。作为新手你需要培养的第一个技能就是文件类型识别。1.1 使用WinHex查看文件签名所有文件都有特定的签名Magic Number位于文件开头几个字节。用WinHex打开文件后我注意到开头几个十六进制值00 00 00 00 50 4E 47 0D 0A 1A 0A对比常见图片格式签名PNG:89 50 4E 47 0D 0A 1A 0AJPG:FF D8 FF E0GIF:47 49 46 38显然这个文件缺少PNG的标准签名头89但包含PNG的特征字符。这种情况通常意味着文件头被故意删除或修改文件可能包含多个数据结构的组合需要手动修复文件头才能正常读取1.2 修复损坏的PNG文件头在WinHex中执行以下操作按CtrlN新建文件在起始位置输入标准PNG头89 50 4E 47 0D 0A 1A 0A从原文件复制剩余数据CtrlA全选后CtrlC复制在新文件中粘贴(CtrlV)保存为.png格式注意某些情况下可能需要调整字节顺序。如果修复后仍无法打开尝试在签名后添加空白字节或调整数据偏移量。2. 深入PNG结构IHDR块与CRC校验成功修复文件头后图片仍然显示异常——只有一片纯色区域。这说明文件结构可能还存在其他问题。PNG文件由多个数据块(Chunk)组成最重要的是IHDR块包含图片的元信息。2.1 解析IHDR块结构用WinHex查找IHDR字符串对应十六进制49 48 44 52其完整结构如下偏移量长度(字节)描述示例值04块长度00 00 00 0D44块类型(IHDR)49 48 44 5284图片宽度00 00 01 00124图片高度00 00 00 80165其他参数...214CRC校验...在我的案例中发现高度值异常小0x80128像素而图片实际内容应该更高。修改这个值可能导致CRC校验失败因此需要同步更新CRC。2.2 Python自动修复脚本手动计算CRC比较繁琐使用以下Python脚本可以自动完成import zlib import struct def fix_png_dimensions(filename, new_widthNone, new_heightNone): with open(filename, rb) as f: data f.read() ihdr_pos data.find(bIHDR) - 4 if ihdr_pos 0: raise ValueError(Not a valid PNG file) # 读取原始宽高 width, height struct.unpack(II, data[ihdr_pos8:ihdr_pos16]) # 更新宽高 if new_width: width new_width if new_height: height new_height # 重新计算CRC ihdr_data data[ihdr_pos4:ihdr_pos8] struct.pack(II, width, height) data[ihdr_pos16:ihdr_pos21] new_crc zlib.crc32(ihdr_data) 0xFFFFFFFF # 写回文件 f.seek(ihdr_pos8) f.write(struct.pack(II, width, height)) f.seek(ihdr_pos21) f.write(struct.pack(I, new_crc)) # 使用示例将高度调整为1024像素 fix_png_dimensions(repaired.png, new_height1024)运行脚本后图片显示恢复正常并在底部区域发现了FlagCTF{PNG_CRC_MASTER}。3. JPG文件分析隐藏在EXIF中的数据另一类常见题目使用JPG格式。与PNG不同JPG的隐写更多利用其元数据(EXIF)和特殊的编码结构。3.1 检查EXIF信息使用Python的Pillow库可以轻松提取EXIFfrom PIL import Image from PIL.ExifTags import TAGS def print_exif(image_path): img Image.open(image_path) exif_data img._getexif() if not exif_data: print(No EXIF data found) return for tag_id, value in exif_data.items(): tag_name TAGS.get(tag_id, tag_id) print(f{tag_name:25}: {value}) print_exif(suspicious.jpg)在某个CTF题目中这个方法发现了异常的UserComment字段包含Base64编码的字符串Q1RGe0VYSUZfSElEREVOX0RBVEF9解码后得到Flag。3.2 修复损坏的JPG文件遇到头部损坏的JPG文件时可以使用特征值修复标准JPG以FF D8 FF开头文件结束标记为FF D9使用WinHex查找这些特征值我曾遇到一个案例文件开头被添加了多余字节。通过搜索FF D8定位真实起始位置后删除前面多余数据即可修复。4. 高级技巧组合分析与自动化工具真正的CTF题目往往需要多种技术组合使用。以下是我总结的检查清单基础检查文件签名验证strings命令查找可见字符binwalk检测嵌入文件元数据分析EXIF信息PNG块结构文件尾部附加数据视觉分析使用StegSolve检查不同位平面调整亮度/对比度分离颜色通道自动化脚本示例import os import subprocess from PIL import Image def analyze_image(filepath): print(f\nAnalyzing {os.path.basename(filepath)}...) # 1. 检查文件类型 file_output subprocess.run([file, filepath], capture_outputTrue, textTrue).stdout print([File Type], file_output.strip()) # 2. 提取字符串 strings_output subprocess.run([strings, filepath], capture_outputTrue, textTrue).stdout if len(strings_output) 1000: print([Strings] Found, len(strings_output.splitlines()), lines) else: print([Strings]\n, strings_output) # 3. 检查EXIF try: img Image.open(filepath) exif img._getexif() print([EXIF], {k:v for k,v in exif.items() if isinstance(v, (str, int))} if exif else No EXIF) except: print([EXIF] Unable to read) # 4. 检查LSB try: pixels img.load() lsb_bits [] for y in range(min(10, img.height)): for x in range(min(10, img.width)): r,g,b pixels[x,y][:3] lsb_bits.extend([r1, g1, b1]) print([LSB Sample], .join(map(str, lsb_bits[:30])) ...) except: pass analyze_image(final_challenge.png)在最近一次比赛中这套方法帮助我发现了一个隐藏在图片LSB中的ZIP文件最终通过分析压缩包内的文本文件获得了Flag。记住CTF图片隐写就像数字考古——需要耐心、系统性的检查以及一点创造力。