前言

在打 NEEPU Sec 2023 的时候,对这道题充满着好奇,通过面向百度搜索以及群友大佬的 WriteUp 终于解开了我的谜团,也让我更深了解到了二维码这玩意(

题目

flag

过程

比赛期间,我试过 binwalk、010Editor、Stegsolve 等均没用,因为他就是一张普普通通的丢失了一部分的 QR 码。先是把左下角 Qrazybox 填补上,把定位线给补上,就得到了下面这个样子

flag2

通过以下这幅图展示的 QR 码的格式

qrcode_format

通过 QR 码尺寸 = (V - 1) * 4 + 21,其中 V 为版本号,可以得出该 QR 码的版本为 4

qrcode_format_information

通过上图读出格式信息 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 内

感谢