不只是扫一扫:用Python玩转CTF中的二维码生成与花式隐写(Stegosaurus/PIL实战)
在CTF竞赛的Misc类题目中,二维码和隐写技术堪称黄金搭档。但大多数解题攻略止步于工具使用,鲜少深入技术原理与自动化实现。本文将用Python代码实战,带你从二进制数据解析到动态生成二维码,再到Stegosaurus工具的深度应用,构建完整的解题技术链。
1. 二维码的底层逻辑与Python生成实战
二维码的本质是二进制矩阵的艺术。理解其编码规则,才能应对CTF中各种"魔改"题型。以经典的25x25二维码为例,其核心结构包含:
- 定位图案:三个角落的"回"字形方框
- 时序图案:黑白相间的导航线
- 数据区:实际存储信息的模块
- 纠错码:Reed-Solomon编码的冗余数据
from PIL import Image def create_qr_matrix(data_str, size=25): """将01字符串转换为二维码矩阵""" matrix = [] for i in range(size): row = [int(data_str[i*size + j]) for j in range(size)] matrix.append(row) return matrix def draw_qr(matrix, cell_size=10): """可视化二维码矩阵""" img = Image.new('RGB', (len(matrix[0])*cell_size, len(matrix)*cell_size), 'white') pixels = img.load() for y in range(len(matrix)): for x in range(len(matrix[0])): color = (0, 0, 0) if matrix[y][x] else (255, 255, 255) for dy in range(cell_size): for dx in range(cell_size): pixels[x*cell_size+dx, y*cell_size+dy] = color return img # 示例:手工构造的25x25二维码数据 sample_data = "1111111000100001101111111100000101110010110100000110111010100000000010111011011101001000000001011101101110101110110100101110110000010101011011010000011111111010101010101111111000000001011101110000000011010011000001010011101101111010101001000011100000000000101000000001001001101000100111001111011100111100001110111110001100101000110011100001010100011010001111010110000010100010110000011011101100100001110011100100001011111110100000000110101001000111101111111011100001101011011100000100001100110001111010111010001101001111100001011101011000111010011100101110100100111011011000110000010110001101000110001111111011010110111011011" qr_matrix = create_qr_matrix(sample_data) draw_qr(qr_matrix).show()注意:实际CTF题目中,二维码数据可能隐藏在IDAT块、文件末尾或像素LSB中,需要先进行数据提取和清洗
2. 从二进制到二维码的自动化流水线
CTF中的二维码往往不会直接给出图片文件,而是隐藏在各类数据格式中。以下是典型处理流程:
数据提取
- PNG文件:解析IDAT块,使用zlib解压
- 流量包:过滤HTTP传输,提取二进制流
- 文本文件:转换十六进制或Base64编码
格式转换
- 处理字节序问题
- 修正损坏的文件头尾
- 转换数据维度(如将一维数组转为二维矩阵)
可视化生成
- 处理非标准尺寸(如625位数据可能是25x25矩阵)
- 添加定位图案等必要元素
import zlib import struct def extract_idat(png_path): """从PNG文件中提取异常IDAT块""" with open(png_path, 'rb') as f: data = f.read() # 查找IDAT标记(0x49444154) idat_pos = data.find(b'IDAT') - 4 length = struct.unpack('>I', data[idat_pos:idat_pos+4])[0] return data[idat_pos+8:idat_pos+8+length] def decode_zlib(data): """解压zlib压缩数据""" return zlib.decompress(data) # 实战示例 idat_data = extract_idat('hidden_qr.png') raw_data = decode_zlib(idat_data) qr_matrix = create_qr_matrix(bin(int.from_bytes(raw_data, 'big'))[2:].zfill(625))3. Stegosaurus:Python字节码隐写的黑科技
Stegosaurus工具利用Python字节码(.pyc)文件的特性实现隐写:
| 特性 | 说明 |
|---|---|
| 无效空间利用 | 参数占位字节存储Payload |
| 编码密度低 | 平均每100字节pyc可隐藏5字节 |
| 运行时透明 | 不影响原程序执行逻辑 |
| 抗检测性强 | strings等工具无法直接发现 |
安装与基础使用:
git clone https://github.com/AngelKitty/stegosaurus.git cd stegosaurus python stegosaurus.py -p "secret_message" target.pycCTF实战案例:
- 从题目给出的.pyc文件中提取隐藏信息:
python stegosaurus.py -x challenge.pyc- 隐藏解题脚本到提供的.pyc中:
# 生成包含Payload的.pyc with open('payload.txt', 'w') as f: f.write('print("FLAG{hidden_in_pyc}")') !python stegosaurus.py -p payload.txt sample.pyc -o infected.pyc4. 综合实战:从数据碎片到flag的完整链条
假设获得一个经过多重处理的文件,解题步骤如下:
文件分析
import binascii def file_signature(file_path): with open(file_path, 'rb') as f: return binascii.hexlify(f.read(8)).decode() print(file_signature('mystery.bin')) # 输出:89504e470d0a1a0a → PNG文件数据修复与提取
def repair_png(broken_data): # 添加缺失的IEND块 return broken_data + bytes.fromhex('0000000049454e44ae426082') with open('mystery.bin', 'rb') as f: fixed_data = repair_png(f.read()[8:-4]) # 去除错误头尾二维码生成与解码
from pyzbar.pyzbar import decode qr_img = draw_qr(extract_matrix_from_idat(fixed_data)) result = decode(qr_img) print(result[0].data.decode()) # 输出隐藏的flag或下一步提示进阶处理(如遇Python字节码)
import subprocess # 使用Stegosaurus提取隐藏信息 output = subprocess.check_output(['python', 'stegosaurus.py', '-x', 'hidden.pyc']) print(output.split(b'Extracted payload:')[-1].decode().strip())
在实战中,可能需要组合多种技术。例如某次比赛中,我们需要:
- 先修复损坏的PNG文件头
- 提取异常的IDAT块数据
- 将zlib解压后的二进制转为二维码
- 扫描二维码获得.pyc文件
- 最后用Stegosaurus提取出flag
这种技术链条的构建能力,正是区分普通选手与高手的核心要素。掌握这些技能后,面对各类二维码隐写题目时,你将拥有系统化的解题思路,而非盲目尝试各种工具。