二维码(QRCODE)纠错容错位读取修复操作

一次CTF比赛的杂项解题遇到了一道和二维码相关的题目,但是利用扫描工具扫描不出来结果,随后查阅了二维码的相关资料,定位到了问题出在二维码的校验码上。需要读取二维码的纠错容错位,然后比对两个纠错容错码,最后修复二维码纠错位扫码找到结果。

二维码是一个仅支持校验和纠错的编码,对于纠错功能比较直观的感受就是遮挡了一部分非关键区块之后二维码还可以扫出结果。在计算机网络课程里面多多少少都会提到校验码、纠错码的概念,简单啰嗦一下。校验码是能发现信息代码中存在错误的编码算法,最常见的是CRC算法,在文件校验、网络校验中都有应用。纠错码除了能检查到错误还可以一定程度纠正错误情况保证不出意外的编码,海明码(Hamming Code)是常见的纠错码。

二维码纠错信息存储的位置

二维码纠错信息所在的位置(以25*25大小的二维码为例,不同版本有差异),第一个纠错串信息在左上角定位符附近:

(8, 0),(8, 1),(8, 2),(8, 3),(8, 4),(8, 5),(8, 7),(8, 8),(7, 8),(5, 8),(4, 8),(3, 8),(2, 8),(1, 8),(0, 8)

第二个纠错串信息在右下角定位符附近:

(24, 8),(23, 8),(22, 8),(21, 8),(20, 8),(19, 8),(18, 8),(8, 17),(8, 18),(8, 19),(8, 20),(8, 21),(8, 22),(8, 23),(8, 24)

注意,这里两个纠错字符串的信息必须一致才能进行纠错操作。测试很多工具扫码遇到两个纠错字符串不一致时会忽略,从而识别不出二维码内容。

二维码纠错容错等级

二维码的纠错等级,容错等级。纠错等级是指容错率的大小,按照容错率从小到大可分L(<7%),M(<15%),Q(<25%),H(<30%)。容错率也叫纠错率。纠错率指的就是二维码能被正常扫描时允许被遮挡的最大面积占总面积的比率,确保二维码在被遮挡部分面积后仍能被正常扫描。

纠容错等级 纠错串
L0 111011111000100
L1 111001011110011
L2 111110110101010
L3 111100010011101
L4 110011000101111
L5 110001100011000
L6 110110001000001
L7 110100101110110
M0 101010000010010
M1 101000100100101
M2 101111001111100
M3 101101101001011
M4 100010111111001
M5 100000011001110
M6 100111110010111
M7 100101010100000
Q0 011010101011111
Q1 011000001101000
Q2 011111100110001
Q3 011101000000110
Q4 010010010110100
Q5 010000110000011
Q6 010111011011010
Q7 010101111101101
H0 001011010001001
H1 001001110111110
H2 001110011100111
H3 001100111010000
H4 000011101100010
H5 000001001010101
H6 000110100001100
H7 000100000111011

解题代码

图片本身有白色外边,先进行裁边,返回图片实际像素数量和一个信息编码位的比例。

def position(stdsize=25):
    from PIL import Image
    
    qr = Image.open('key.png')
    x, y = qr.size
    start = x
    end = 0
    for i in range(x):
        for j in range(y):
            if qr.getpixel((i, j)) != 1:
                start = min(i, j, start)
                end = max(i, j, end)
    img2 = qr.crop((start + 1, start + 1, end + 1, end + 1))
    proportion = (end - start) // stdsize
    return img2, proportion

还原信息到当前二维码25*25大小的数组上,并输出当前二维码的两个纠错容错等级。

dic = {
        "L0": "111011111000100",
        "L1": "111001011110011",
        "L2": "111110110101010",
        "L3": "111100010011101",
        "L4": "110011000101111",
        "L5": "110001100011000",
        "L6": "110110001000001",
        "L7": "110100101110110",
        "M0": "101010000010010",
        "M1": "101000100100101",
        "M2": "101111001111100",
        "M3": "101101101001011",
        "M4": "100010111111001",
        "M5": "100000011001110",
        "M6": "100111110010111",
        "M7": "100101010100000",
        "Q0": "011010101011111",
        "Q1": "011000001101000",
        "Q2": "011111100110001",
        "Q3": "011101000000110",
        "Q4": "010010010110100",
        "Q5": "010000110000011",
        "Q6": "010111011011010",
        "Q7": "010101111101101",
        "H0": "001011010001001",
        "H1": "001001110111110",
        "H2": "001110011100111",
        "H3": "001100111010000",
        "H4": "000011101100010",
        "H5": "000001001010101",
        "H6": "000110100001100",
        "H7": "000100000111011"}
l1 = [
    (8, 0),
    (8, 1),
    (8, 2),
    (8, 3),
    (8, 4),
    (8, 5),
    (8, 7),
    (8, 8),
    (7, 8),
    (5, 8),
    (4, 8),
    (3, 8),
    (2, 8),
    (1, 8),
    (0, 8)]
l2 = [
    (-1, 8),
    (-2, 8),
    (-3, 8),
    (-4, 8),
    (-5, 8),
    (-6, 8),
    (-7, 8),
    (8, -8),
    (8, -7),
    (8, -6),
    (8, -5),
    (8, -4),
    (8, -3),
    (8, -2),
    (8, -1)]


def reshape(img2, sub_step):
    sub_step = step
    result = []
    result2 = []
    x, y = img2.size
    for i in range(0, x, sub_step):
        tmpl = []
        tmpl2 = []
        for j in range(0, y, sub_step):
            tmpl.append(1 - img2.getpixel((j, i)))
            tmpl2.append(img2.getpixel((j, i)))
        result.append(tmpl)
        result2.append(tmpl2)
    f1 = ''.join([str(result[i[0]][i[1]]) for i in l1])
    f2 = ''.join([str(result[i[0]][i[1]]) for i in l2])
    for k, v in dic.items():
        if v == f1:
            print(f1, k)
        if v == f2:
            print(f2, k)
    return result2

最后有两种办法,可以暴力枚举纠错容错等级或者用较大的纠错容错等级替换纠错容错位修复图片(测试用L4可以扫出来),最后调用zxing扫描输出结果。

def fix(result2):
    import numpy as np
    import matplotlib.pyplot as plt
    import zxing

    for a, b in enumerate(dic['L4']):
        result2[l1[a][0]][l1[a][1]] = 1 - int(b)
        result2[l2[a][0]][l2[a][1]] = 1 - int(b)
    gray_array = np.array(result2)
    plt.imshow(gray_array, cmap='Greys_r')  # 转灰度图
    plt.axis('off')  # 不显示坐标轴
    plt.imsave('final.png', gray_array, cmap='Greys_r')
    
    new_qr = zxing.BarCodeReader()
    pw = new_qr.decode('final.png')
    pw = pw.parsed
    print(pw)


a1, a2 = position()
a3 = liupi(a1, a2)
fix(a3)

你可能感兴趣的:(CTF,安全研究,python)