📌 题目信息
| 项目 | 内容 |
|---|---|
| 题目名称 | qr |
| 题目来源 | 青少年CTF S1·2026 公益赛 |
| 题目类型 | Misc |
| 子分类 | 二维码还原 / 像素点阵 |
| 考点 | 二进制点阵 → 二维码图片 → 扫码 |
| 难度 | ⭐⭐ |
| 最终 Flag | flag{56876aae7cb7b98a3756bac05c6b6675} |

🛠️ 使用工具
- Python3 + PIL(Pillow):读取二进制点阵并按行列绘制二维码
- OpenCV(py)
QRCodeDetector:对生成的二维码图片扫码提取 flag - 也可用手机/任意扫码工具识别
📝 解题思路
附件 qr.txt 里是一串 0/1,本质是二维码的像素点阵数据(每个 0/1 代表一个黑白模块)。思路分三步:
- 把 0/1 按行列排成方形矩阵;
- 按矩阵绘制成二维码图片(0=黑模块、1=白背景);
- 用扫码工具识别图片,读出 flag。
一步直达:在记事本里把字体缩放到最小,然后扫描二维码即可得到flag。
第 1 步:解析点阵,确定边长
qr.txt 内容是 0/1,注意它是用空格 + 换行分隔的:
0 0 0 0 0 0 0 0 ...
0 0 0 0 0 0 0 0 ...
...
直接 read().strip() 会把空格也算进长度,导致边长算错。必须先去掉所有空白,再算边长:
bits = ''.join(open('qr.txt').read().split()) # 去空格和换行
print(len(bits)) # 40000 => 200 x 200
import math
print(int(len(bits)**0.5)) # 200
⚠️ 易错点(本份文件的关键):如果直接
read().strip()不去空格,长度会是 79999,平方根算出 282,完全错误。一定要先split()去掉所有空白,得到纯 0/1 串 40000 = 200²,边长才是 200。
第 2 步:按矩阵绘制二维码
用 PIL 新建一张 1(黑白)模式的图,逐像素按 0/1 上色:这里把 1 画成黑色模块(也可反过来,只要能扫出来):
from PIL import Image
def qrcode(txt_path, output_path, module_size=10):
with open(txt_path, 'r') as f:
bits = ''.join(f.read().split()) # 关键:去掉空格/换行
size = int(len(bits) ** 0.5) # 200
img_size = size * module_size
img = Image.new('1', (img_size, img_size), 1) # 背景白(1)
for y in range(size):
for x in range(size):
idx = y * size + x
if idx < len(bits) and bits[idx] == '1':
# 把这一模块画成黑块
for dy in range(module_size):
for dx in range(module_size):
img.putpixel((x*module_size+dx, y*module_size+dy), 0)
img.save(output_path)
print("边长:", size)
qrcode("qr.txt", "qrcode.png", module_size=10)
⚠️ 注意:
module_size是每个模块的放大倍数(这里 10),让二维码足够大、便于扫码。Image.new('1', ...)用'1'模式(1 位黑白)即可。
运行后得到 qrcode.png(2000×2000 黑白二维码图)。
第 3 步:扫码获取 Flag
直接用 OpenCV 内置的二维码检测器解码:
import cv2
img = cv2.imread("qrcode.png")
det = cv2.QRCodeDetector()
flag, _, _ = det.detectAndDecode(img)
print(flag)
输出:
flag{56876aae7cb7b98a3756bac05c6b6675}
✅ 最终 Flag
flag{56876aae7cb7b98a3756bac05c6b6675}

【Misc】qr&spm=1001.2101.3001.5002&articleId=163926056&d=1&t=3&u=d336bfb84bb54cd985d83e3d992c8638)
237

被折叠的 条评论
为什么被折叠?



