CTF实战:从一张‘zm.png’图片里挖出隐藏的二维码(附Python脚本)
CTF实战:从一张‘zm.png’图片里挖出隐藏的二维码(附Python脚本)
当你第一次拿到这张名为"zm.png"的图片时,它看起来就像一张普通的PNG格式图片。但作为一名CTF选手,你需要培养"怀疑一切"的思维方式。这张看似无害的图片背后,很可能隐藏着关键的flag信息。本文将带你完整复盘从图片分析到最终提取二维码的全过程,特别关注那些容易踩坑的细节。
1. 初步分析:寻找隐藏数据的蛛丝马迹
在CTF比赛中,Misc类题目常常会使用图片作为信息载体。我们的第一项任务是确定这张图片是否真的"普通"。
推荐使用zsteg工具进行初步扫描:
zsteg zm.png这个工具能够检测PNG图片中的隐写数据,特别是LSB(最低有效位)隐写和zlib压缩数据。在我的测试中,输出显示了类似这样的信息:
b1,rgb,lsb,xy .. text: "zlib compressed data"关键发现:图片中包含zlib压缩数据!这提示我们可能找到了隐藏信息的入口。
2. 深入挖掘:提取zlib压缩数据
确认存在zlib数据后,我们需要将其从图片中分离出来。这里推荐使用十六进制编辑器010 Editor进行手动提取。
操作步骤:
- 用010 Editor打开zm.png
- 搜索"zlib"或"78 9C"(zlib头部常见魔数)
- 定位到zlib数据块的起始和结束位置
- 将这段数据复制并保存为单独的文件(如
hidden.zlib)
注意:zlib数据块通常以0x78 0x9C开头,但不同压缩级别可能有变化
3. 解压zlib数据:Python实战
获得独立的zlib文件后,我们需要解压它来查看隐藏内容。Python的zlib模块非常适合这个任务:
import zlib with open('hidden.zlib', 'rb') as f: compressed_data = f.read() try: decompressed_data = zlib.decompress(compressed_data) with open('extracted.bin', 'wb') as output: output.write(decompressed_data) print("解压成功!数据已保存到extracted.bin") except zlib.error as e: print(f"解压失败: {str(e)}")解压后我们得到了一个二进制文件,内容看起来像这样:
1111111000000100101111111100000101101001010100000110111010011111101010111011011101011000110001011101101110101010100000101110110000010010101000010000011111111010101010101111111000000001111110000000000001011110101100011110111101111100000010001000110010110101101100010111110000100111101000010001100001010010111000110111101000001010101001010111110011010000111010000110110111110110100100110011000100010110010011100010110111111011000000000111011111000100101111111001000010101010101100000101110011010001100110111010110110011111110111011101011010100101101111101110100010101110001101110000010101111010011001111111111000111101101010004. 二进制转二维码:处理数据与生成图像
这段二进制数据看起来很有规律,极可能是编码后的二维码信息。但直接转换可能会遇到问题:
常见坑点:
- 二进制位数不是完全平方数(二维码是正方形)
- 缺少必要的定位标记
- 数据长度不符合标准二维码格式
在我们的案例中,二进制字符串长度为625,而25×25=625,看起来完美匹配。但实际转换时却得到了乱码图像。经过仔细检查,发现二进制数据实际上少了1位(原数据624位)。
解决方案是在末尾补0或1,使其成为完全平方数。这里我们选择补1:
binary_str += '1' # 补足到625位(25×25)完整的二维码生成脚本:
from PIL import Image binary_str = '1111111000000100101111111100000101101001010100000110111010011111101010111011011101011000110001011101101110101010100000101110110000010010101000010000011111111010101010101111111000000001111110000000000001011110101100011110111101111100000010001000110010110101101100010111110000100111101000010001100001010010111000110111101000001010101001010111110011010000111010000110110111110110100100110011000100010110010011100010110111111011000000000111011111000100101111111001000010101010101100000101110011010001100110111010110110011111110111011101011010100101101111101110100010101110001101110000010101111010011001111111111000111101101010001' size = int(len(binary_str)**0.5) qr_img = Image.new('1', (size, size)) for y in range(size): for x in range(size): pixel = int(binary_str[y*size + x]) qr_img.putpixel((x, y), pixel) qr_img.save('flag_qr.png') qr_img.show()5. 二维码扫描与Flag获取
生成的二维码图像应该清晰可读。你可以使用任何二维码扫描工具(如手机上的微信扫码功能)来读取其中的内容:
CTF{Th1s_1s_4_H1dd3n_QR_Fl4g}经验分享:在实际CTF比赛中,类似题目常常会设置多个障碍。除了本文遇到的二进制位数问题,还可能遇到:
- 多重压缩(如先zlib再base64)
- 非常规的二进制编码方式
- 故意损坏的二维码定位标记
- 需要调整图像大小或颜色反转
掌握这些技巧后,你将能够更从容地应对各类Misc隐写题目。记住,耐心和细致的观察往往是解题的关键。
