一、二维码概述
1.1 常见二维条码类型
| 类型 | 特点 |
|---|---|
| QR Code | 最主流,三定位图形,支持纠错 |
| Data Matrix |
https://jie.2weima.com/datamatrix.html |
| Aztec Code |
https://products.aspose.app/barcode/zh-hans/recognize/aztec#/recognized |
| PDF417 |
https://products.aspose.app/barcode/zh-hans/recognize/pdf417 |
| MaxiCode | 圆形,物流用 |
| 汉信码 | 国产标准 |
1.3 QR 码的核心特性
- 容量大:最多约 7089 个数字 / 4296 个字母 / 2953 个字节(版本 40)
- 容错强:最高可恢复约 30% 的污损
- 全方向扫描:任意角度(0°/90°/180°/270°)都能识别
- 黑白即可:不依赖颜色,便于印刷和隐写
二、QR 码结构详解
2.1 版本(Version)与模块(Module)
- QR 码有 1~40 个版本,版本号越大,矩阵越大。
- 版本 1 是 21×21 模块,之后每增加一个版本,边长增加 4 个模块。
- 公式:
边长 = 17 + 4 × 版本号- 版本 1 → 21×21
- 版本 2 → 25×25
- 版本 40 → 177×177
- 模块(module) 是最小单位,即一个黑白方块。
2.2 功能区域(一张图看懂)
1 | ┌──────────────────────────────────────┐ |
2.3 各功能区的作用
| 功能图形 | 数量/位置 | 作用 |
|---|---|---|
| 静区(Quiet Zone) | 四周空白,≥4 模块 | 分隔二维码与背景,帮助扫码器定位 |
| 定位图形(Finder Pattern) | 3 个(左上/右上/左下),7×7 回字形 | 判定方向和位置的核心 |
| 分隔符(Separator) | 定位图形外围一圈白色 | 隔离定位图形和数据区 |
| 时序图形(Timing Pattern) | 横向+纵向各一条,黑白交替 | 确定模块坐标和尺寸 |
| 校正图形(Alignment Pattern) | 版本2+,多个,5×5 | 大版本纠偏(透视/弯曲) |
| 格式信息(Format Info) | 2 份(冗余存放) | 记录纠错等级 + 掩码编号 |
| 版本信息(Version Info) | 版本7+,2 份 | 记录版本号 |
| 数据区 + 纠错区 | 剩余全部空间 | 实际内容 + 纠错码 |
记忆口诀:三定位、两时序、格式成双、版本成对、数据纠错填满。
三、编码原理
3.1 数据编码模式(Mode)
QR 码支持多种编码模式,前缀用 4 bit 模式指示符区分:
| 模式 | 指示符 | 字符集 | 位/字符 |
|---|---|---|---|
| 数字(Numeric) | 0001 | 0-9 | 3.33 |
| 字母数字(Alphanumeric) | 0010 | 0-9 A-Z 空格 $%*+-./: | 5.5 |
| 字节(Byte) | 0100 | ISO-8859-1 / UTF-8 | 8 |
| 汉字(Kanji) | 1000 | Shift JIS | 13 |
| 混合(ECI/其他) | 0111 等 | - | - |
CTF 里二维码内容大多是 Byte 模式(UTF-8) 或 字母数字模式。
3.2 纠错等级与 Reed-Solomon 纠错
QR 码用 Reed-Solomon(里德-所罗门)纠错码,在伽罗瓦域 GF(256) 上做多项式运算。
| 纠错等级 | 字母 | 可恢复比例 |
|---|---|---|
| Low | L | ≈7% |
| Medium | M | ≈15% |
| Quartile | Q | ≈25% |
| High | H | ≈30% |
RS 纠错的意义(CTF 关键):
- 纠错码冗余存储数据,所以二维码污损一部分仍能扫出。
- 这也意味着:你可以故意破坏/遮挡二维码的一部分,只要不超过纠错能力,内容不变——这是很多题的出法。
3.3 掩码(Mask)
编码时,为了避免出现「大面积同色块」导致扫码器误判(比如一片纯白/纯黑),数据区要和一个掩码图案做 XOR。
- 共 8 种掩码(编号 000~111),每种是固定的黑白图案公式。
- 编码器会试全部 8 种掩码,选「得分最高」(黑白分布最均匀、无大块、无易误读图形)的那个。
- 掩码编号记在格式信息里,解码时先读格式信息、再做对应掩码还原。
3.4 完整编码流程
1 | 1. 数据分析 → 选模式、版本、纠错等级 |
3.5 解码流程(逆向理解)
1 | 拍照/读图 |
四、方向判定机制
4.1 为什么二维码旋转不影响扫描
三个定位图形(Finder Pattern)分布在左上、右上、左下,右下角故意留空:
1 | ┌──────┬──────┐ |
扫码器流程:
- 找到这 3 个「回」字形图案
- 判断哪个角是空的 → 唯一确定方向
- 旋转/镜像纠正
- 再解码
所以 0°/90°/180°/270° 旋转都不影响结果,这是 QR 码设计的核心特性。
4.2 定位图形「回」字形的巧妙设计
回字形(◈ 形状:3×3 黑块 + 外围白圈 + 最外黑圈)有特殊性质:
- 无论从哪个方向看,黑白比例都是 1:1:3:1:1(沿任意穿过中心的水平线扫描)
- 这个比例在自然图像中几乎不出现 → 可以被快速、唯一地检测出来
4.3 利用方向判定的 CTF trick
标准二维码旋转不影响内容 → 如果一道题「换个角度扫出不同内容」,说明二维码被破坏了方向判定(镜像 / 定位图形被遮挡 / 多码叠放),导致扫码器「转正」出错,同一个图案被读成不同的数据。详见 [[#5.4 隐藏信息的各种手法]] 与 [[#八综合案例换表-base64–二维码]]。
五、CTF 中的二维码
5.1 考点分类总览
| 考点 | 说明 | 难度 |
|---|---|---|
| 二维码损坏/遮挡 | 涂掉一部分,需手动修复 | ★★ |
| 反色 | 黑白互换,直接扫不出 | ★ |
| 旋转 / 镜像 | 方向处理后再扫 | ★ |
| 拼接复原 | 二维码被切成碎片 | ★★~★★★ |
| 隐写(颜色/灰度/LSB) | 信息藏在像素里 | ★★★ |
| 多码叠放 | 多个二维码叠加,需分离 | ★★★ |
| 二维码内容加密 | 扫出来是密文(换表 base64 等) | ★★★ |
| 二维码藏密码 | 密码藏在二维码里,供后续解压 | ★★ |
5.2 基础处理:让扫不出的码能扫
① 反色(黑白互换)
1 | from PIL import Image, ImageOps |
② 旋转 / 镜像穷举
1 | from PIL import Image |
③ 补全定位图形(手动修复)
如果定位图形被涂掉,扫码器找不到方向就扫不出。做法:
- 用 PS / GIMP / 画图,照着另外两个完整的定位图形,把缺失的画回去
- 或者用脚本复制一个完好的定位图形贴过去
5.3 常见工具
| 工具 | 用途 |
|---|---|
pyzbar / zxing(Python) |
脚本扫码 |
ZBar(C 库,pyzbar 底层) |
扫码核心 |
zbarimg(命令行) |
直接扫图片 |
CyberChef |
在线各种编码/图像处理 |
Stegsolve |
通道分离(CTF 必用) |
| Photoshop / GIMP | 手动修复、通道操作 |
| 微信/支付宝扫码 | 快速验证(注意:会缓存、可能不认镜像/反色) |
| 草料二维码 / 在线生成器 | 生成对照二维码 |
5.4 隐藏信息的各种手法
① RGB 通道分离
二维码的「黑」不是纯黑,而是某个通道(R/G/B)有信息。用 Stegsolve 或脚本分离:
1 | from PIL import Image |
② 灰度分层 / 多阈值
图片看起来是二维码,但用了多个灰度级,不同阈值下是不同的码:
1 | from PIL import Image |
③ LSB 隐写
二维码的黑白方块里,LSB(最低有效位)藏了信息。用 Stegsolve 的 LSB 模式,或脚本提取最低位:
1 | from PIL import Image |
④ 多码叠放 / 错位
两个二维码(内容不同)以不同角度、透明度或颜色叠在一起。分离方法:
- 通道分离(两个码用不同颜色)
- 旋转分离(一个正、一个斜)
- 透明度/图层分离
⑤ 数据区篡改
直接改二维码的数据区(在纠错范围内),让扫出的内容变化。解题需还原原数据或用纠错恢复。
5.5 二维码内容加密
扫出来的二维码内容不是明文 flag,而是密文,需要进一步解码。最常见的结合:
- 换表 Base64(打乱 Base64 字符表)→ 见 [[#八综合案例换表-base64–二维码]]
- 凯撒 / ROT13 / 摩斯 / 栅栏等古典密码
- AES / RSA 等现代密码(配合密码学考点)
六、工具清单(速查)
6.1 扫码
1 | # 命令行 zbarimg(需安装 zbar) |
1 | import pyzbar.pyzbar as pyzbar |
6.2 生成二维码(对照 / 出题用)
1 | pip install qrcode[pil] |
1 | import qrcode |
6.3 图像处理
1 | pip install pillow opencv-python |
七、Python 实战脚本
7.1 一键穷举「旋转 + 镜像 + 反色」并扫码
1 | from PIL import Image, ImageOps |
7.2 通道分离 + 多阈值 + 扫码(组合拳)
1 | from PIL import Image, ImageOps |
7.3 换表 Base64 解码(二维码内容解密常用)
1 | import base64 |
八、综合案例:换表 Base64 + 二维码
典型真题(Base64Encryption,出题人 pjx1314)的完整流程,融合了「换表 Base64」和「二维码藏密码」两个考点。
8.1 题目结构
- 附件 1:
cipher.txt—— 一个 ZIP 文件的换表 Base64 密文 - 附件 2:
README.txt—— 明文已知,提供恢复换表的已知明文
8.2 解题全流程
1 | ① 识别换表 Base64(标准解不开 + 题目明示"字符表打乱") |
8.3 关键步骤代码
恢复换表(已知明文攻击):
1 | def recover(pts, enc='utf-8'): |
已知明文数据(文件头/尾是天然明文):
1 | d = [ |
二维码扫描(旋转/镜像穷举) 用 [[#71-一键穷举旋转–镜像–反色并扫码]] 即可。
8.4 本题考点小结
- 换表 Base64:已知明文(文件头尾)恢复换表,缺位爆破补全。
- 二维码藏密码:ZIP 有密码,密码藏在二维码里,需要「换个角度」扫。
- 二维码「换个角度」的原理:破坏/利用 QR 码的方向判定(3 定位图形 + 缺角),使同一个图案在不同方向被读成不同数据。
九、二维码解题思路速查
9.1 常见 trick 速记
| # | 思路 | 说明 |
|---|---|---|
| 1 | BMP 转二维码 | BMP 是未压缩位图,像素直接对应黑白,可逐像素重建成二维码图再扫 |
| 2 | 十六进制转 pyc | 给一串 hex,还原成 .pyc,用 uncompyle6 / pycdc 反编译,找生成二维码的源码 |
| 3 | 字符串制作二维码 | 给一串 0/1(或字符),长度是平方数,reshape 成方阵画成二维码 |
| 4 | 四个 TTL 值拼一个字节 | 网络包 TTL 字段编码二进制,提取特征位组合成字节 |
9.2 字符串(0/1)制作二维码
核心:二维码是方形,
0/1串长度必须是平方数(n²),才能reshape成 n×n 方阵。制作前先手动把长度补成平方数(补 0 或截断)。
1 | import numpy as np |
如果扫不出,把黑白色调转(
1 - arr)再扫一次。
9.3 BMP 转二维码
1 | from PIL import Image |
9.4 十六进制转 pyc
1 | # 1. hex 还原成文件 |
十、其他类型二维码
平时见得最多的是 QR Code,但实际还有很多其他类型的二维码。CTF 中偶尔出现,特征是「定位图形」和 QR Code 完全不同,扫码前要认清类型、选对工具。
10.1 类型总览
| 类型 | 定位特征 | 扫描工具 |
|---|---|---|
| Aztec Code | 中心「牛眼」定位(同心方块),无静区 | ZXing |
| DataMatrix | 左下 + 右上两条「L」形实线边界 + 两条虚线边界 | ZXing |
| GridMatrix | 网格矩阵(国产 GB/T 21049) | 专用工具 |
| MaxiCode | 圆形,中央 3 个同心圆,六边形模块 | ZXing |
| DotCode | 点阵组成,无实线边界 | ZXing |
| 汉信码 | 类似 QR,但定位块方向有特殊性 | 专用工具 |
| PDF417 | 堆叠式一维码(多条一维码叠起来) | ZXing / pyzbar |
10.2 各类型注意点
- 汉信码:左下角那块定位块的方向和另外几块不一样,有时候题目会把这块定位块反过来,导致扫不出,需要手动把它转回来。
- PDF417:出题人可能把图片反相(黑白互换)导致无法直接扫描。解决:把图拉进 StegSolve 或 PS 反相处理后再扫。
1 | from PIL import Image, ImageOps |
10.3 扫描工具选择
| 工具 | 支持类型 |
|---|---|
pyzbar(Python) |
主要 QR,部分 PDF417 |
ZXing(Java / zxing-cpp) |
QR、DataMatrix、Aztec、PDF417、MaxiCode、DotCode 等,最全 |
zbar |
QR、Code128 等一维 + 部分二维码 |
| 微信/支付宝 | 仅主流 QR |
遇到非 QR 的码,优先用 ZXing(在线版
zxing.org,或本地zxing-cpp的ZXingReader)。
十一、信号与图像处理中的变换方法(FFT / DFT / DCT / DWT)
CTF Misc 图像隐写高频考点。核心:把图像从空间域变换到频域,在频域里藏/看信息。
11.1 为什么需要变换:空间域 → 频域
- 空间域 = 像素值随位置变化;频域 = 把图像看成不同频率成分的叠加
- 低频 = 大块、平缓变化的区域(天空、背景、大面积纯色)→ 图像的整体轮廓
- 高频 = 急剧变化的区域(边缘、纹理、细节、噪声)→ 图像的细节
- FFT 把「整体」和「细节」拆开,CTF 就常在这两个层面藏信息
11.2 DFT 离散傅里叶变换(Discrete Fourier Transform)
一维公式(N 个采样点):
1 | X[k] = Σ_{n=0}^{N-1} x[n] · e^(-j·2π·k·n / N) (k = 0..N-1) |
x[n]:时域信号(输入)X[k]:频域结果(输出,复数)|X[k]|是幅度谱(各频率的强度),∠X[k]是相位谱
图像是二维信号,用二维 DFT:
1 | F(u,v) = Σ_x Σ_y f(x,y) · e^(-j·2π·(ux/M + vy/N)) |
f(x,y):像素值F(u,v):频域复数|F(u,v)|:幅度谱,通常取log显示(因为动态范围太大)
频谱图怎么看:
- 经过
fftshift(低频移到中心)后,中心 = 低频,四周 = 高频 - 中心很亮(低频能量大),四周暗(高频能量小)
11.3 FFT 快速傅里叶变换(Fast Fourier Transform)
FFT 只是 DFT 的快速算法,数学结果完全等价,不是另一种变换。
- 直接算 DFT 是 O(N²);FFT 用「分治 + 蝶形运算」降到 O(N log N)
- 工程上所有「傅里叶变换」实际都用 FFT 实现(NumPy 的
np.fft、OpenCV 的cv2.dft底层都是 FFT)
记忆:DFT 是「做什么」,FFT 是「怎么快速做」。CTF 里写代码直接用
np.fft.fft2。
11.4 DCT 离散余弦变换(Discrete Cosine Transform)
与 DFT 的区别:DCT 只用余弦分量(丢掉正弦/相位),结果是实数,而且有能量集中特性——大部分信息集中在少数低频系数上。
为什么重要:JPEG 压缩的核心就是 DCT:
- 图像分 8×8 小块
- 每块做 DCT → 64 个系数(左上角 = 直流/低频,右下角 = 高频)
- 量化:高频系数除以较大步长 → 很多变成 0(丢掉细节,实现压缩)
- 熵编码存储
CTF 关联:JPEG 的 DCT 系数上可以做隐写(改动高频系数藏数据,肉眼几乎看不出),经典工具 JSteg、F5、OutGuess。
11.5 DWT 离散小波变换(Discrete Wavelet Transform)
傅里叶的局限:只能告诉你「有哪些频率」,不能告诉你「某个频率出现在哪里」(丢失位置信息)。
小波的优势:同时定位频率和位置,多分辨率分析。
做法:一层层分解,每层把图像分成 4 个子带:
1 | ┌────────┬────────┐ |
- LL 继续下一层分解(递归),得到多级分解
- JPEG2000 的核心就是 DWT(比 JPEG 的 DCT 压缩率更高、无块效应)
11.6 四者对比
| 变换 | 输出 | 核心用途 | CTF 典型应用 |
|---|---|---|---|
| DFT | 复数(幅度+相位) | 频率分析 | 频谱隐写、滤波分离 |
| FFT | 同 DFT(更快) | 工程实现 | 同上 |
| DCT | 实数(能量集中) | JPEG 压缩 | JPEG 隐写(JSteg/F5/OutGuess) |
| DWT | 多级子带(时频定位) | JPEG2000 压缩 | 小波域隐写 |
11.7 CTF 应用与代码
① FFT 频谱隐写(最常见)
题目给一张图,肉眼看着正常,但信息藏在频域里。做法:做 FFT → 看幅度谱 → 频谱图里可能藏有文字/二维码/flag。
1 | import numpy as np |
如果频谱图里看到了文字/二维码,说明出题人在频域里埋了信息,逆变换回空间域前把它提取出来即可。
② 高通 / 低通滤波分离
- 低通:保留中心低频 → 得到模糊的轮廓图(大块信息)
- 高通:保留四周高频 → 得到边缘、细节、隐藏的噪点信息
1 | import numpy as np, cv2 |
③ DCT 隐写(JPEG)
1 | from scipy.fftpack import dct, idct |
更实用的做法是直接用现成工具:
steghide(DCT 隐写)、OutGuess、Jsteg、F5。题目如果是 JPEG 隐写,先试steghide extract -sf a.jpg。
④ DWT 隐写
1 | import pywt, cv2, numpy as np |
小波隐写题常把信息藏在 HH(对角高频)子带,或在 LL 子带里继续分解。
⑤ 常用 Python 库
1 | pip install numpy opencv-python scipy PyWavelets |
| 库 | 作用 |
|---|---|
numpy.fft |
FFT/DFT |
scipy.fftpack |
FFT、DCT |
cv2(OpenCV) |
dft、图像读写 |
PyWavelets(pywt) |
DWT |
十二、例题与参考链接
12.1 例题
| 题目 | 来源 | 考点 |
|---|---|---|
| qrssssssss | 2022 NCTF | QR 码标准 / 修复 |
| LmqHmAsk | 2023 羊城杯决赛 | 二维码隐写 |
| QRcode2 | GEEKCTF 2024 | QR 码标准 |
| 练假成真 | SHCTF 2024 week2 | 二维码伪造/修复 |
12.2 参考链接
- QR 码官方标准(ISO/IEC 18004):https://github.com/goodlunatic/ISO-IEC-18004-Standard
- TonyCrane CTF 二维码笔记:https://note.tonycrane.cc/ctf/misc/qrcode/
- QR 码原理(cnblogs):https://www.cnblogs.com/luogi/p/15469106.html
- 手搓二维码 crash:https://cabelis.ink/2023/01/16/crash-qrcode-by-hand/
关于 QR Code 标准,能出的考点极多,有时间建议直接读官方标准(上面的 GitHub 仓库存了一份)。
附:速记口诀
- 二维码结构:三定位、两时序、格式成双、版本成对、数据纠错填满
- 方向判定:三个定位缺一角,缺哪角定方向
- 扫不出先试:反色 → 旋转 → 镜像 → 补定位
- 扫出来是密文:换表 Base64 → 古典密码 → 现代密码
- 藏信息:通道分离 → 灰度分层 → LSB → 多码叠放