前言
在打 NEEPU Sec 2023 的时候,对这道题充满着好奇,通过面向百度搜索以及群友大佬的 WriteUp 终于解开了我的谜团,也让我更深了解到了二维码这玩意(
题目
过程
比赛期间,我试过 binwalk、010Editor、Stegsolve 等均没用,因为他就是一张普普通通的丢失了一部分的 QR 码。先是把左下角 Qrazybox 填补上,把定位线给补上,就得到了下面这个样子
通过以下这幅图展示的 QR 码的格式
通过 QR 码尺寸 = (V - 1) * 4 + 21
,其中 V 为版本号,可以得出该 QR 码的版本为 4 。
通过上图读出格式信息 111110110101010
通过 Format and Version String Tables 可以得出纠错等级为 L ,掩码类别为 2 。
之后将这个二维码丢到 QrazyBox 中通过 Tools->Extract QR Information
可以提取出流(即 Data Blocks)
["01000010","10000110","10000111","01000111","01000111","00000111","00110011","10100010","11110010","11110110","00110110","11110111","01110111","01000111","00100110","00010110","11100111","00110110","01100110","01010111","00100010","11100?1?","0?1?????","?1?1?1?0","?1?1?0?0","11110111","00110010","11110011","01010011","01110110","00110011","01110011","0111001?","????????","????????","????????","????????","????????","????????","????????","???????1","01100000","11101100","00010001","11101100","00010001","11101100","00010001","1110110?","????????","????????","????????","????????","????????","????????","????????","???????0","00010001","11101100","00010001","11101100","00010001","11101100","00010001","1110110?","????????","????????","????????","????????","????????","????????","????????","???????0","00010001","11101100","00010001","11101100","00010001","11101100","00010001","1110100?","????????","????????","????????","????????","????????","???????0","11110100","00001000","01101001","1101011?","????????","????????","????????","???????1","00000101","11001001","10111011","1011011?","????????"]
并且通过 Decoded data
得出 **https://cowtransfer s/57c70**
(这个之后会用到)
通过开头的 0100
可以得出数据编码为 字节编码 ,即先将字符串转为 ISO 8859-1 或 UTF-8,之后将字符串的每个字符为一组转为长度为 8bit 的二进制数据,通过文档可以得知其 字符计数指示器 的长度为 8bit (即 00101000
转十进制为 40
),故可知有效字符总共 40 位。通过在 Error Correction Code Words and Block Information 可以得知 4-L 的码字总数为 80 ,纠错码字数为 20 。
通过目前可读的数据进行整理(对比 ASCII 表)可得
01101000 h
01110100 t
01110100 t
01110000 p
01110011 s
00111010 :
00101111 /
00101111 /
01100011 c
01101111 o
01110111 w
01110100 t
01110010 r
01100001 a
01101110 n
01110011 s
01100110 f
01100101 e
01110010 r
00101110 .
0?1?0?1? c 01100011
?????1?1 o 01101111
?1?0?1?1 m 01101101
?0?01111 / 00101111
部分问号内容可通过 https://cowtransfer.com/ 可知为 .com/
,补全即可获得以下新的数据流( 加粗文本 为填补内容 .com
, 斜体内容 为 有效字符(共 40 位))
["01000010","10000110","10000111","01000111","01000111","00000111","00110011","10100010","11110010","11110110","00110110","11110111","01110111","01000111","00100110","00010110","11100111","00110110","01100110","01010111","00100010","11100110","00110110","11110110","11010010","11110111","00110010","11110011","01010011","01110110","00110011","01110011","0111001?","????????","????????","????????","????????","????????","????????","????????","???????1","01100000","11101100","00010001","11101100","00010001","11101100","00010001","1110110?","????????","????????","????????","????????","????????","????????","????????","???????0","00010001","11101100","00010001","11101100","00010001","11101100","00010001","1110110?","????????","????????","????????","????????","????????","????????","????????","???????0","00010001","11101100","00010001","11101100","00010001","11101100","00010001","1110100?","????????","????????","????????","????????","????????","???????0","11110100","00001000","01101001","1101011?","????????","????????","????????","???????1","00000101","11001001","10111011","1011011?","????????"]
有效字符后接结束符(4bit,0000),之后便是补偿码(16bit,1110110000010001),于是下一步就是将补偿码补上可得出以下新的数据流( 加粗文本 为填补内容 .com
, 斜体内容 为 有效字符(共 40 位))
["01000010","10000110","10000111","01000111","01000111","00000111","00110011","10100010","11110010","11110110","00110110","11110111","01110111","01000111","00100110","00010110","11100111","00110110","01100110","01010111","00100010","11100110","00110110","11110110","11010010","11110111","00110010","11110011","01010011","01110110","00110011","01110011","0111001?","????????","????????","????????","????????","????????","????????","????????","???????1","01100000","11101100","00010001","11101100","00010001","11101100","00010001","11101100","00010001","11101100","00010001","11101100","00010001","11101100","00010001","11101100","00010001","11101100","00010001","11101100","00010001","11101100","00010001","11101100","00010001","11101100","00010001","11101100","00010001","11101100","00010001","11101100","00010001","11101100","00010001","11101100","00010001","11101100","00010001","1110100?","????????","????????","????????","????????","????????","???????0","11110100","00001000","01101001","1101011?","????????","????????","????????","???????1","00000101","11001001","10111011","1011011?","????????"]
此时含有 ? 的字数有 23 个,其中 数组[32]、[80]、[90] 均却一位,因此可以通过爆破来减少 3 个以来满足纠错码字数 20 ,又因为采用二进制,因此有 2^3 = 8 种情况。
import reedsolo
reedsolo.init_tables(0x11d)
qr_bytes = ["01000010","10000110","10000111","01000111","01000111","00000111","00110011","10100010","11110010","11110110","00110110","11110111","01110111","01000111","00100110","00010110","11100111","00110110","01100110","01010111","00100010","11100110","00110110","11110110","11010010","11110111","00110010","11110011","01010011","01110110","00110011","01110011","0111001?","????????","????????","????????","????????","????????","????????","????????","???????1","01100000","11101100","00010001","11101100","00010001","11101100","00010001","11101100","00010001","11101100","00010001","11101100","00010001","11101100","00010001","11101100","00010001","11101100","00010001","11101100","00010001","11101100","00010001","11101100","00010001","11101100","00010001","11101100","00010001","11101100","00010001","11101100","00010001","11101100","00010001","11101100","00010001","11101100","00010001","1110100?","????????","????????","????????","????????","????????","???????0","11110100","00001000","01101001","1101011?","????????","????????","????????","???????1","00000101","11001001","10111011","1011011?","????????"]
for i in range(8):
qr_bytes[32] = qr_bytes[32][:7] + str(('{:03b}'.format(i))[:1])
qr_bytes[80] = qr_bytes[80][:7] + str(('{:03b}'.format(i))[1:2])
qr_bytes[90] = qr_bytes[90][:7] + str(('{:03b}'.format(i))[2:3])
print(qr_bytes[32], qr_bytes[80], qr_bytes[90])
b = bytearray()
erasures = []
for j, bits in enumerate(qr_bytes):
if '?' in bits:
erasures.append(j)
b.append(0)
else:
b.append(int(bits, 2))
rmes, recc, errata_pos = reedsolo.rs_correct_msg(b, 20, erase_pos=erasures)
flag = ''
for k in range(1, 42): # 有效字符 40 个
flag += ('{:08b}'.format(rmes[k]))[:4]
if len(flag) == 8:
print(chr(int(flag, 2)), end='')
flag = ('{:08b}'.format(rmes[k]))[4:]
print()
就可以得出 flag 力,flag 就在这个 you did it.zip 内
感谢
- 二维码之QR码生成原理与损坏修复
- 二维码の数据编码Data Encoding
- 群友大佬的 WriteUp
- Reed Solomon