天天看點

二維碼(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)