NEEPU Sec 2023 公开赛 二维码修复计划 wp

二维码修复计划

考点:二维码格式,reedsolo

打开附件:

在这里插入图片描述

二维码的大小为 33x33,版本为V=(N-17)/4,所以这是版本 4。

可以看到左上和右上角的格式信息111110110101010

查表知纠错等级为L,掩码模式为2。
Format and Version String Tables - QR Code Tutorial (thonky.com)

在这里插入图片描述

将已知信息填入到qrazybox中,选择对应格式和版本信息
QRazyBox - QR Code Analysis and Recovery Toolkit (merricx.github.io)

在这里插入图片描述

在这里插入图片描述

根据QR Mask Patterns Explained,2 号掩码有公式(列) mod 3 == 0。列号从0开始,所以我们要将0,3,6,9, … ,30列的位与1异或。

可以直接在qrazybox上选择2号掩码填充即可
在这里插入图片描述

二维码数据读取有固定的顺序,大致如下
在这里插入图片描述

用ps比划一下:
在这里插入图片描述

蓝色数字1-33是目前能读出的数据,先看1,2 01000010,10000110

0100指示编码方式,即字节模式

在这里插入图片描述

编码方式后面是数据长度,根据版本和编码方式可以确定数据位数的长度

在这里插入图片描述

所以0100后面8位00101000指示了长度信息,转换成十进制就是40,也就是说数据总长度为40个字节,先将前33个能读出的数据解码一下

data = '''
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?'''.split()

data = ''.join(data)[12:]
for k in ['0','1']:
    d=data.replace('?',k)
    for i in range(0, len(d), 8):
        print(chr(int(d[i:i+8], 2)), end='')
    print()

#https://cowtransfer.com/s/57c77

得到一个打不开的网址,去cowtransfer.com看了一下正常的分享链接刚好是40位

已知数据有31位,加上最后一位能观察出来是6,也就是已知32位,需要想办法恢复原始数据,这时黄色数字2-60块的信息就派上用场了

因为这时数据容量并未达到上限,所以需要添加一定的补齐符,即11101100 00010001的重复,具体可以生成一个二维码对比一下

from PIL import Image
import qrcode
 
def main():
    qr = qrcode.QRCode(version=4,error_correction=qrcode.constants.ERROR_CORRECT_L,box_size=15,border=4)
    #version:值为1~40的整数,控制二维码的大小
    #error_correction:控制二维码的错误纠正功能。可取值下列4个常量: 
    '''
    qrcode.constants.ERROR_CORRECT_X:
        1. X=L时,大约7%或更少的错误能被纠正。 
        2. X=M(默认)时,大约15%或更少的错误能被纠正。
        3. X=Q时,25%以下的错误会被纠正。
        4. X=H时,大约30%或更少的错误能被纠正。
    '''
    #box_size:控制二维码中每个小格子包含的像素数。
    #border:控制边框(二维码与图片边界的距离)包含的格子数(默认为4)
    #向二维码中添加信息
    qr.add_data("https://cowtransfer.com/s/57c77????????6")
    img = qr.make_image()
    img.save(r'...\test.png')

main()

恢复出补齐符后大概是这样

在这里插入图片描述

根据二维码数据读取的顺序将所有数据(掩码处理后的)读出来:

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? ???????? ???????? ????????
???????? ???????? ???????? ???????? ????0011 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? ????????

根据纠错特性表,可知version4-L的纠错码字有20个,在L级别,它可以恢复不超过7%的损坏的字节,但是Reed-Solomon的纠错能力很强,如果它知道错误在哪里,那么纠错能力就强得多,所以我们要想办法恢复一部分码字使得未知的数据降到20个字节这个最低要求。

在这里插入图片描述

目前得到的数据含有未知比特的共有23个码字,但其中有2个码字未知比特数为1,可以尝试排列组合,剩下的未知码字满足20个字节的最低要求。

解题代码如下:

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? ???????? ???????? ????????
???????? ???????? ???????? ???????? ????0011 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? ????????'''.split()

def solve(qr_bytes):
    b = bytearray()
    erasures = []
    for i, bits in enumerate(qr_bytes):
        if '?' in bits:
            erasures.append(i)
            b.append(0)
        else:
            b.append(int(bits, 2))
    mes= reedsolo.rs_correct_msg(b, 20 ,erase_pos=erasures)
    data=list(mes[0])
    data=[bin(i).lstrip('0b').zfill(8) for i in data]
    data = "".join(data)[12:]
    data=[chr(int(data[i:i+8], 2)) for i in range(0, len(data), 8)]
    print(''.join(data))

a=['01110010','01110011']
b=['11101000','11101011']
c=['10110110','10110111']
for i in range(2):
    for j in range(2):
        for k in range(2):
            qr_bytes[32]=a[i]
            qr_bytes[80]=b[j]
            qr_bytes[98]=c[k]
            solve(qr_bytes)

#https://cowtransfer.com/s/57c7711d010646

进入网页链接下载flag文件即可

Neepu{R7c0v3r1ng_qRc0de_1s_p@rT_0F_Th2_pL4n}

参考文章:
二维码之QR码生成原理与损坏修复 (cnblogs.com)
CTFSHOW-36D杯: ez-qrcode | Byxs20’s Blog
QR Code Tutorial - Thonky.com
使用 Python 生成二维码

工具网站:
QRazyBox - QR Code Analysis and Recovery Toolkit (merricx.github.io)
在线PS图片处理工具 (uupoop.com)

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值