二維碼之QR碼生成原理與損壞修復


二維碼基礎知識

一提到二維碼,我們就會想起生活中處處都能見到的“二維碼”,比如收款碼、付款碼、微信名片等等。但是嚴格意義來講,這些碼只是眾多二維碼中的一種,叫做QR碼,也是現在我們使用最廣泛的一種二維碼。

二維碼又稱二維條碼,是用某種特定的幾何圖形按一定規律在平面(二維方向上)分布的、黑白相間的、記錄數據符號信息的圖形;在代碼編制上巧妙地利用構成計算機內部邏輯基礎的“0”、“1”比特流的概念,使用若干個與二進制相對應的幾何形體來表示文字數值信息,通過圖象輸入設備或光電掃描設備自動識讀以實現信息自動處理:它具有條碼技術的一些共性:每種碼制有其特定的字符集;每個字符占有一定的寬度;具有一定的校驗功能等。同時還具有對不同行的信息自動識別功能、及處理圖形旋轉變化點。

二維碼的種類有很多,除了我們常見的QR碼外,使用比較廣泛的還有:PDF417、DM、漢信碼等。有興趣的可以自行查閱

QR 碼的格式與生成原理

QR碼的尺寸

首先,我們先說一下QR碼一共有40種尺寸。官方叫做版本Version(版本)。
Version 1是21x21的矩陣;
Version 2是25x25的矩陣;
Version 3是29x29的矩陣...
每增加一個version,就會增加4的尺寸(或者稱單位,看后面就會理解),公式是:(V-1)*4 + 21(V是版本號) 最高Version 40,(40-1)*4+21 = 177,所以最高是177 x 177的正方形。
鑒於大家平時都已經習慣稱呼QR碼為二維碼,我們后面的內容也就不一直強調QR碼,而是稱為二維碼。大家只要明白,我們后面所說的二維碼,都是指QR碼即可。

QR碼的格式

QR碼格式示例如下:
英文版:
image

中文版:
image

定位圖案

Position Detection Pattern(位置探測圖形)是定位圖案的一種,就是每個二維碼都有的左上、左下和右上三個角的“回”字形的標志。用於標記二維碼的矩形大小。這三個定位圖案有白邊叫Separators for Postion Detection Patterns。之所以三個而不是四個,因為三個就足以標識一個矩形了,用四個反而多余,且會使得能夠表示的數據空間變小,掃描器在進行二維碼掃描的時候會根據這三個定位標識符來更正二維碼的坐標,方便進行掃描。這塊區域的尺寸固定,無論是哪個版本的二維碼,他的尺寸都是7*7的模塊。
image

Alignment Patterns(校正圖形) 只有在Version 2以上(包括Version2)的二維碼中需要這個東西,同樣是為了定位用的。它的尺寸也是固定的,為5*5的模塊。
image

Timing Patterns(定位圖形)也是用於定位的,是一單位寬的黑白交替點帶,由黑色點起始和結束。原因是二維碼有40種尺寸,尺寸過大了后需要有根標准線,不然掃描的時候可能會掃歪了。

格式信息

Format Information 存在於所有的版本中,用於存放一些格式化數據的,通過讀取這部分的內容,可以知道當前二維碼的糾錯等級、掩碼類別。主要內容為“糾錯等級(2bit)+ 掩碼類別(3bit)+ BCH code(10bit,用於糾錯)”,然后這15個bits還要與101010000010010做XOR操作,主要是為了如果選用了00的糾錯級別和000的Mask,從而造成全部為白色,這會增加掃描器的圖像識別的困難。
糾錯等級的比特表示:
image
為了增強二維碼的容錯能力,保證在一定的損壞范圍內,不會影響數據的讀取,共設計了兩個區域來存放兩條一模一樣的格式信息。
這15個bit在format information區域內的分布以及順序如下(下圖中數字的順序就是這15個bit的存放順序,應當注意的是這些數字表示的是位的高低,也就是當獲取到格式信息15個bit長度的二進制字符串時,左邊為高位,右邊為低位,所以最左側的二進制數字應該在14的位置,最右側的二進制數字應該在0的位置):
image

版本信息

Version Information 在>= Version 7的版本中,預留兩塊3*6的區域存放一些版本信息。

數據碼字和糾錯碼字

除了上述的那些地方,剩下的地方存放 Data Code 數據碼字 和 Error Correction Code 糾錯碼字。我們后面就簡稱數據碼和糾錯碼,就是最前面兩張圖的深灰色區域,一般數據都是從右下角開始填充,先填充數據碼,數據碼填充完畢之后再填充糾錯碼,以version1為例,數據的填充順序,是這樣的:
image

當然,隨着版本的升高,會有越來越多的校正圖形摻雜在其中,這樣的話,數據填充可能就不是這么規矩的矩形了,但是總體的填充順序不會大變化,都是先右后左的順序。具體的可參考官方的文檔。

數據編碼與編碼流程

QR碼支持的數據編碼

  • Numeric mode(數字編碼),從0到9。
  • Alphanumeric mode(字符編碼),包括0-9,大寫的A到Z(沒有小寫),以及符號“$ % * + – . / : 空格”。
  • Byte mode (字節編碼),可以是0-255的ISO-8859-1字符。
  • Kanji mode (日文編碼),也是雙字節編碼。
  • Extended Channel Interpretation (ECI) mode 主要用於特殊的字符集。並不是所有的掃描器都支持這種編碼。
  • Structured Append mode 用於混合編碼,也就是說,這個二維碼中包含了多種編碼格式。
  • FNC1 mode 這種編碼方式主要是給一些特殊的工業或行業用的。比如GS1條形碼之類的。

下表是每個模式的編碼相對應的“編號”。
image
因為種類較多較復雜,而且為了方便大家理解,我們在這里值選擇數字編碼和字符編碼舉例,其它的編碼,有興趣的朋友可以查看官方文檔。

示例一:

Numeric mode 數字編碼,僅支持對從0到9的數字進行編碼,也就是使用這個編碼的二維碼,掃描出來的內容只會是一串數字。如果需要編碼的數字的個數不是3的倍數,那么,最后剩下的1或2位數會被轉成4或7bits,則其它的每3位數字會被編成長度為10位的二進制數,最后將這些二進制數據連接起來並在前面加上編碼模式的編號和字符計數指示符(就是表示了被編碼的信息有多少個字符),字符計數指示符的長度取決於編碼的模式和所要編成二維碼的版本,在數字編碼中,字符計數指示符如下表對應的有10、12或14位:
image

比如在Version 1的尺寸下,糾錯級別為H(表示糾錯等級為“高”,糾錯級別我們會在下面講到)的情況下,我們要編碼的內容為:01234567

>   (1). 按照每3個字符一組,把上述數字分成三組: 012 345 67
>   (2). 把三位數的轉成10bit二進制:012 轉成 0000001100;345 轉成 0101011001;兩位數的67 轉成7bit的二進制: 1000011。
>   (3). 把這3個二進制串起來: 0000001100 0101011001 1000011
>   (4). 把數字的個數(字符計數指示符)轉成二進制 (version 1,糾錯等級H,編碼模式為數字編碼,所以字符計數指示符的長度是10bit): 8個數字的二進制是 0000001000
>   (5). 把數字編碼的標志0001和第4步的字符計數指示符的編碼加到前面: 0001 0000001000 0000001100 0101011001 1000011

示例二:

Alphanumeric mode 字符編碼(也叫字母數字編碼)。包括0-9,大寫的A到Z(沒有小寫),以及符號“$ % * + – . / : 空格”。這些字符會映射成一個字符索引表。如下所示(兩個表,中英文對照):(其中的SP是空格,Char是字符,Value是其索引值)編碼的過程是把字符轉換為索引值,之后兩兩分組,按照特殊的算法轉換成十進制的數值(這個算法,雖然簡單,但是也是相當有想法,將前面的數值乘以45,加上后面的數值,組成一個十進制的數,這樣在后期解碼的時候,只用逆運算,用最后這個十進制的數除以45,得到的商和余數,就是原來的兩個數),最后轉成11bits的二進制,如果最后有一個落單的,那就轉成6bits的二進制。而字符計數指示符需要根據不同的Version尺寸編成9,11或13個二進制(如上表)。
image
image

在Version 1的尺寸下,糾錯級別為H的情況下,對“AC-42”進行編碼:

>   (1). 從字符索引表中找到 AC-42 這五個字條的索引 (10,12,41,4,2)
>   (2). 兩兩分組: (10,12) (41,4) (2)
>   (3). 把每一組轉成11bits的二進制:第一組(10,12),計算公式 10*45+12 = 462 轉成 00111001110 ;第二組(41,4),計算41*45+4 = 1849 轉成 11100111001 ;第三組(2) 等於 2 轉成 00001 。
>   (4). 把這些二進制連接起來:00111001110 11100111001 000010
>   (5). 把字符的個數(字符計數指示符)轉成二進制 (Version 1-H為9 bits ): 5個字符,5轉成二進制就是 000000101
>   (6). 在頭上加上編碼標識 0010 和第5步的字符計數指示符編碼: 0010 000000101 00111001110 11100111001 000010

結束符和補齊符

以上述示例一為基礎,在編碼結束后,我們得到了如下編碼:
image

然后,我們還要加上結束符,表示真正的額數據已經結束。
image

按每組8個bit分組,如果所有的編碼加起來不是8個倍數我們還要在后面加上足夠的0,比如上面一共有45個bit,所以,我們還要加上3個0,然后按8個bits分好組:

00010000 00100000 00001100 01010110 01100001 10000000

再然后,就是補齊符(padding bytes),如果添加上結束符之后還沒有達到我們最大的bits數的限制,我們還要加一些補齊碼(Padding Bytes),Padding Bytes就是重復這兩個bytes:11101100 00010001。(使用這兩個字節的主要原因是,為了防止在填入數據時出現大片的深色或淺色區域,對掃描器產生干擾,使得二維碼難以正常掃描),至於要補多少個補齊符,需要查看文檔中相應的字符數和數據容量對應表,由於版本比較多,我們不一一列出,在官方文檔中,我們示例相對應的是表7-表11。我們以下表為例:
image

從表中,我們可以知道,version1-H的數據容量為9個數據碼字(每個數據碼字為8位),而我們上面已經有了6個數據碼字,所以要補充三個8bit,補充完畢如下:

00010000 00100000 00001100 01010110 01100001 10000000 11101100 00010001 11101100

上面的每一組數據為一個數據碼字,Data Codewords,現在也只是原始數據,還需要對其加上糾錯碼。

糾錯碼

上面我們提到了糾錯級別,Error Correction Code Level,二維碼中有四種級別的糾錯(從低到高為L、M、Q、H),這就是為什么有人在二維碼的中心位置加入圖標,也依舊能夠掃描的原因。(就是二維碼殘缺量不超過所對應的糾錯等級允許的范圍時,使用掃描工具依舊能掃描出內容的原因)。糾錯等級對應的容錯范圍如下:
image

即只要二維碼的損壞面積沒有超過這個范圍,理論上是可以恢復損壞的數據的。
至於糾錯碼是如何計算的,這涉及到里德-所羅門糾錯算法(Reed-Solomon error correction),里德-所羅門碼是定長碼。這意味着一個固定長度輸入的數據將被處理成一個固定長度的輸出數據。在最常用的(255,223)里所碼中,223個里德-所羅門輸入符號(每個符號有8個位元)被編碼成255個輸出符號。大多數里所錯誤校正編碼流程是成體系的。這意味着輸出的碼字中有一部分包含着輸入數據的原始形式。符號大小為8位元的里所碼迫使碼長(編碼長度)最長為255個符號。標准的(255,223)里所碼可以在每個碼字中校正最多16個里所符號的錯誤。由於每個符號事實上是8個位元,這意味着這個碼可以校正最多16個短爆發性錯誤。
里德-所羅門碼,如同卷積碼一樣,是一種透明碼。這代表如果信道符號在隊列的某些地方被反轉,解碼器一樣可以工作。解碼結果將是原始數據的補充。但是,里所碼在縮短后會失去透明性。在縮短了的碼中,“丟失”的比特需要被0或者1替代,這由數據是否需要補足而決定。(如果符號這時候反轉,替代的0需要變成1)。於是乎,需要在里所解碼前對數據進行強制性的偵測決定(“是”或者“補足”)。

這兩段話是我抄的,什么意思我也不懂,但我們有現成的python模塊來運算出糾錯碼——python的reedsolo模塊,我們只要會用就行了,數學好的朋友可以去研究具體算法的實現。

那么,哪個版本應該生成幾個糾錯碼?我們可以對照官方文檔中的糾錯特性表,表13-表22。以下表為例:
image

以版本1-H為例進行解釋,從表中,我們可以清晰的知道,糾錯碼字數應該為17個,糾錯的塊數為1(表示這個版本要編碼的數據只會分為一個數據塊),(26,9,8)表示,這個版本的二維碼總共可以存放26個碼字,但是這26個碼字中,有9個碼字為數據碼字,17個為糾錯碼字(8*2+1=17),8位糾錯容量。每個表的下方附有注釋信息:
image

這也是為什么糾錯碼字數為r2,當后面有一個箭頭時,表示r2之后還要加1。
在給數據碼字添加糾錯碼時,還有對數據碼字分塊的操作,因為version1的二維碼對數據碼字只分一個塊,不夠明顯,所以我們采用網上的我所參考過的一個例子(現在不太容易找出處了,時間太久遠了):
image

這個例子使用的是Version 5 Q糾錯等級的二維碼,從表中得知需要4個糾錯塊(2,2表示糾錯塊有兩2組,每組2個),頭一組的兩個Blocks中各15個數據碼字加上各18個糾錯碼字。
因為二進制寫起來會讓表格太大,所以,都用了十進制來表示,我們簡述一下下表是如何生成的:在將數據編碼之后,每8位分開,形成我們需要的數據碼字,然后再將數據碼字按照規定分成15:15:16:16的四組,分別進行糾錯碼的運算,生成的糾錯碼字數都是18。
image

最終將這些碼字穿插放置。但是也不是隨意穿插,是數據碼字與數據碼字穿插,糾錯碼字與糾錯碼字穿插。
對於數據碼字:把每個塊的第一個數據碼字先拿出來排列好,然后再取第一塊的第二個,如此類推。上述示例中的數據碼字Data Codewords如下:
image

>   我們先取第一列的:67, 246, 182, 70
>   然后再取第二列的:67, 246, 182, 70, 85,246,230 ,247
>   如此類推:67, 246, 182, 70, 85,246,230 ,247 ……… ………,38,6,50,17,7,236

對於糾錯碼,也是一樣:
image

和數據碼取的一樣,得到:213,87,148,235,199,204,116,159,…… …… 39,133,141,236

然后,再把這兩組放在一起(糾錯碼放在數據碼之后)得到:

67, 246, 182, 70, 85, 246, 230, 247, 70, 66, 247, 118, 134, 7, 119, 86, 87, 118, 50, 194, 38, 134, 7, 6, 85, 242, 118, 151, 194, 7, 134, 50, 119, 38, 87, 16, 50, 86, 38, 236, 6, 22, 82, 17, 18, 198, 6, 236, 6, 199, 134, 17, 103, 146, 151, 236, 38, 6, 50, 17, 7, 236, 213, 87, 148, 235, 199, 204, 116, 159, 11, 96, 177, 5, 45, 60, 212, 173, 115, 202, 76, 24, 247, 182, 133, 147, 241, 124, 75, 59, 223, 157, 242, 33, 229, 200, 238, 106, 248, 134, 76, 40, 154, 27, 195, 255, 117, 129, 230, 172, 154, 209, 189, 82, 111, 17, 10, 2, 86, 163, 108, 131, 161, 163, 240, 32, 111, 120, 192, 178, 39, 133, 141, 236

Remainder Bits(剩余位)

最后再加上Reminder Bits,對於某些Version的QR,上面的還不夠長度,還要加上Remainder Bits,比如:上述的5Q版的二維碼,還要加上7個bits,Remainder Bits加零就好了。關於哪些Version需要多少個Remainder bit,可以參看官方文檔的表一(這里列出一部分)。
image

掩碼(也叫掩模)

編碼的步驟是完成了,但是要想生成一個完整的二維碼,還需要先將現在所擁有的數據填入提前准備的空白模板后,選擇一個合適的掩碼,將原模板的數據與掩碼進行異或運算,最后,再將format information填進去就生成了二維碼。

掩碼存在的意義:二維碼是要拿來掃描的,而掃描怕的就是無法清晰地分辨出編碼信息的每一位。要是二維碼中黑白點數量不均,或是空間分布不均都會導致大色塊區域的出現,而大色塊區域的出現會增加掃描時定位的難度,從而降低掃描的效率。更嚴重的情況下,如果數據填入后碰巧出現了功能性標識,比如定位標識的圖樣,還會干擾正常功能性標識的作用,導致QR碼無法掃描。
在計算機科學中,掩碼就是一個二進制串,通過和數據進行異或運算來變換數據。在QR碼中,掩碼也是通過異或運算來變換數據矩陣。QR碼的掩碼就是預先定義好的矩陣。QR標准通過生成規則定義了八個數據掩碼(最后一個的編號,應該是111而不是110,圖片錯了):
image

前面的三位二進制的數據就是每個模式掩碼相對應的編號,這個信息也是要填入format information中的。具體的掩碼圖片是這樣的:
image

從這個圖我們就可以直觀的看到每種掩碼的模板樣子,以掩碼2(編號為010)為例,公式 j mod 3 = 0 就是表示從左邊開始數,能被3整除的列,都要取逆(黑塊變白塊,白塊變黑塊),當然二維碼的固定格式區域的信息是不用取逆的,所以要使用掩碼2,需要取逆的列數為:0、3、6、9…..。
image

當然,官方規定在進行異或時,原始的數據模板要與每個掩碼模板進行異或運算后,要進行如下的規則進行計分(處罰),最后選擇分數最低的一個作為最佳的掩碼選擇。這里我們只做了解,不深入。
image

手繪二維碼

在這一小節,我計划用excel手動畫出一個二維碼,目標是使用手機能夠掃描出“HELLO”。

准備固定格式的填充模板

利用前面所學到的內容,就已經足夠我們手動來描繪出一個簡單的二維碼,我們要畫的是一個version1且容錯等級為H的二維碼,從上面的基礎知識中知道,版本1的二維碼尺寸為2121,所以我們整理出一個2121的表格(最好將每一單位的模塊都調整成正方形),並提前畫好version1二維碼中的固定格式部分。如下圖,是一個version1版本的二維碼的所有固定格式。
image

進行數據編碼

接下來就是對數據進行編碼,由於進行編碼的內容“HELLO”,全部為大寫字母,且不存在特殊符號。所以這里選用字符編碼是最方便的。
image

根據前面的示例,可以很簡單的知道如何來運算得到二進制的數據:

1. 從字符索引表中找到“HELLO”相對應的值。(17,14,21,21,24)
2. 兩兩分組(17,14),(21,21),24
3. 把每一組轉換成11bit的二進制:(17,14):17*45+14=779=0b01100001011 ;(21,21):21\*45+21=966=0b01111000110 ;24:0b011000
4. 把這些二進制連接起來:01100001011 01111000110 011000
5. 把字符的個數轉成二進制,5個字符(“HELLO”),000000101
6. 再加上字符編碼的標識0010,加起來就是0010 000000101 01100001011 01111000110 011000
7. 再加上結束符0000:0010 000000101 01100001011 01111000110 011000 0000
8. 按8bit重排,並補齊位數為8的倍數:00100000 00101011 00001011 01111000 11001100 00000000
9. 添加補齊碼:00100000 00101011 00001011 01111000 11001100 00000000 11101100 00010001 11101100

計算糾錯碼

根據糾錯特性表來進行分塊和添加糾錯碼:
image

由表我們知道,碼字總數為26,糾錯碼字數為17,再加上我們之前編碼好的數據9個8bit,剛好是26.

計算糾錯碼:
image

碼字整理

整理一下得到的碼字:

>   數據碼字:32,43,11,120,204,0,236,17,236
>   糾錯碼字:109,149,156,18,217,41,246,36,42,84,46,225,190,218,251,27,196

數據填充

得到所有的碼字之后,就是簡單但是麻煩的數據填充了:
由於我們制作的是version1的二維碼,所以不需要進行數據塊的排序。直接按照如下的圖片進行數據塊的填充(先填充數據碼字,再填充糾錯碼字)。
image

如:填充完第一個第二個數據碼字的時候,應該是這個樣子的:
image

全部填充完畢如下圖,(剩下的粉紅色的部分,是格式信息的位置,我們到后面再填充):
image

將相應的format information信息填充進相應的區域。

將其填充進format information數據區(注意填充順序0-14所對應的是二進制的低位到高位)。
image

與掩碼進行異或運算

我們前面提到,我們使用的是掩碼2,也就是下面這個掩碼:
image

j mod 3 = 0 意思就是從左往右數,凡是能夠被3整除的列,顏色都要取反(黑變白,白變黑)。我們將要進行反色的列標注出來(這里我是用的是在對應的列下方標粉色)。注意:功能性區域的不用取逆,就是一開始准備的固定格式的地方和之后的format information區域的數據,不用取逆。
image

手繪完成

OK,大功告成。現在用我們的手機就可以掃描了:
image

MMA2015-MISC400-qr二維碼恢復挑戰

學以致用,復現MMA2015-MISC400-qr的二維碼恢復挑戰的解題步驟,原版write-up地址為:
https://github.com/pwning/public-writeup/blob/master/mma2015/misc400-qr/writeup.md
我的恢復思路也是跟着wp來的。

注:python官方下載的reedsolo模塊版本為0.3,不是很好用,所以我們這次使用write-up中推薦的版本,下載解壓后運行python setup.py install即可。

題目分析

題目給出的二維碼如下圖:
image

根據對QR碼的了解,知道這是一個25*25的二維碼,也就是version2的二維碼,從它能看見的部分我們可以得到format information的一部分信息:

??????011011010

獲取格式信息

對照下面這個網址所給出的對應表,可以知道這個二維碼使用了什么編碼模式和使用了哪一個掩碼

>   https://www.thonky.com/qr-code-tutorial/format-version-tables\#list-of-all-format-information-strings

image

經對照可知:

- 完整的format information信息應該是:010111011011010
- 且可以得到的信息還有該二維碼使用的掩碼為:6,對應的計算公式:( (ij) mod 2 + (ij) mod 3 ) mod 2 = 0
- 所對應的糾錯等級為:Q

將被遮擋的固定信息部分以及format information信息補充完整。
image

獲取原始碼字

與相對應的掩碼進行異或運算,得到原始的數據中的一部分數據碼字和糾錯碼字。這個掩碼對應的公式比較麻煩,且不容易計算,好在官方的文檔中有提供各個掩碼相對應的圖案,如下圖就是掩碼6相對應的圖案,如果不會計算公式,可以手動畫一個與題目大小一樣的掩碼圖,其對應的小圖案大小是不變的,不同版本的二維碼,只是小圖案的數量不同:
image

將掩碼應用到我們補充完的二維碼上,翻轉與掩碼中深色區域相對應的區域的顏色,並用灰色將format information覆蓋,方便讀取數據,最后得到的如下圖:
image

從右下角開始,按下圖的蛇形順序讀取數據碼字和糾錯碼字的信息,至於不同區域塊的信息讀取順序,可以參考官方文檔。
image
image
image

且相對應的數據塊分布應該如下圖所示:
image

將全部可讀的信息讀取出來:

00100000 10100010 10111000 00111010 01011001 10011010 10001000 ????????
???????? ???????? ???????? ???????? ???????? ???????? ???????? ??00000?
1??????? ???????? ???????? ???????? ???????? ??010001 00100100 1???????
???????? ???????? ?????010 11000011 10000000 10101100 00010000 11100100
10101010 10011001 01100110 ???????? ???????? ?????0?0 0000000? ????????
???????? ???????? ???????? ????????

盡可能多地恢復數據

根據官方文檔的糾錯特性表,可知version2-Q的糾錯碼字數有22個,數據碼字數也有22個,在Q級別,它可以恢復不超過25%的損壞的字節,但是我們只有16個完整的字節,即超過63%的字節丟失,但是Reed-Solomon的糾錯能力很強,如果它知道錯誤在哪里,那么糾錯能力就強得多,可以糾正多達兩倍的擦除。但是這個比例也才只到50%,還不能直接把丟失的數據恢復出來。所以我們要想辦法恢復一部分字節使得達到所擁有的完整數據有22個字節這個最低要求。
image

我們先將獲得的可讀取數據整理一下:

0010:【編碼模式=字符編碼(字母數字模式)】
000010100:【9個bit長度的字符計數標識符=20個字符】
01010111000:【FL】
00111010010:【AG】
11001100110:【 I】
1010001000?:【S?】

我們計算一下,22個數據碼字,就是176個bit,而字符計數標識符表示總共有20個字符被編碼,在編碼的時候分為10組,每組11個bit,所以4+9+10*11=123個bit,也就是真正儲存信息的數據碼字共有123個bit,123/8=15余3,也就是結束符在第16個字節碼,所以在第16個字節碼的第4位開始,加上4個0(結束符),又因為8bit重組時需要補充為8的倍數,8-3-4=1,所以還需要加1個0。這時候總共也就16個數據碼字,22-16=6,所以還要加上6個字節的補齊碼,最終獲得的數據碼字的內容如下:

00100000 10100010 10111000 00111010 01011001 10011010 10001000 ????????
???????? ???????? ???????? ???????? ???????? ???????? ???????? ??000000
11101100 00010001 11101100 00010001 11101100 00010001

這樣,我們就手動恢復了6個字節的數據,此時我們丟失的碼字就只剩下22個了,正好達到了最低的要求。我們就可以使用糾錯碼恢復原本的數據。

編寫腳本恢復

編寫腳本利用python的reedsolo模塊進行糾錯(腳本文件已經存在與step3文件夾下)。

import sys
import reedsolo

reedsolo.init_tables(0x11d)

qr_bytes = '''00100000
10100010
10111000
00111010
01011001
10011010
10001000
????????
????????
????????
????????
????????
????????
????????
????????
??000000
11101100
00010001
11101100
00010001
11101100
00010001
00100100
1???????
????????
????????
?????010
11000011
10000000
10101100
00010000
11100100
10101010
10011001
01100110
????????
????????
?????0?0
00000000
????????
????????
????????
????????
????????'''.split()

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))
print erasures
print type(b)
for i in b :
	print '%x' % i

mes, ecc = reedsolo.rs_correct_msg(b, 22,erase_pos=erasures)
for c in mes:
	print '{:08b}'.format(c)

得到全部的信息:

00100000 10100010 10111000 00111010 01011001 10011010 10001000 00101111
10000110 11100010 10110110 10011001 01001010 11000011 00010101 00000000
11101100 00010001 11101100 00010001 11101100 00010001

拆分和解碼

>   0010 [編碼模式=字符編碼]
>   000010100 [字符長度=20]
>   01010111000 ["FL"]
>   00111010010 ["AG"]
>   11001100110 [" I"]
>   10100010000 ["S "]
>   01011111000 ["G+"]
>   01101110001 ["JQ"]
>   01011011010 ["GA"]
>   01100101001 ["H:"]
>   01011000011 ["FW"]
>   00010101000 ["3X"]
>   0000 [結束符]
>   0 [8bit重組時補充的bit]
>   11101100 00010001 [補齊碼]
>   11101100 00010001
>   11101100 00010001

二維碼恢復挑戰-二維碼恢復工具

二維碼恢復工具:qrazybox
工具下載地址:https://github.com/Merricx/qrazybox/tree/master/js

聲明:
本次使用的題目是某次CTF比賽的題目,但是具體是哪次比賽的,我也不是很清楚。只是某天在群里划水的時候,間接得到了題目的文件,所以嘗試進行了解答;由於是之后進行的回憶,自己也懶得再做一遍了,所以只能依靠當時解題時的一些零碎數據來拼湊一下完整的過程(我當時是手工做的,很費時間)。

> QRazyBox 是一個基於 Web 的應用程序(工具包),用於分析和恢復損壞的二維碼。
> QRazyBox 允許您通過使用類似 Paint 的編輯器重新繪制和重建二維碼來恢復二維碼。
> 它還提供了幾個子工具來幫助您更快、更高效地分析和恢復。

總體來說,這是一個輔助類型的工具,可以幫助你更好的分析損壞的QR碼。

題目分析

這個題目的文件如下:
image

可以看到損壞的程度並不高(至少比上一道題少),我們先嘗試手工讀取內容,根據前面的知識,我們可以得知:
image

那么可以嘗試將它畫在excel表格中,並將缺失的定位點和格式信息補上:
image

整理已知信息

手動解碼的步驟我們就不在這里描述了,我們直接放出解碼時需要的內容:

>   版本:3
>   格式信息:101000100100101
>   糾錯等級: M 
>   掩碼編號: 1 

恢復初始數據

計算掩碼的行數與列數(python):

for row in range(29):
	for column in range(29):
		if(( ((row * column) % 2) + ((row * column) % 3) ) % 2 == 0):
			print(str(row)+'--'+str(column)

手動將掩碼反色的點返回來:
image

讀取數據碼字

01000001 11000111 10100110 10100110 00110111
01000110 01100111 10110111 10010011 00000111
01010101 11110110 01100011 00010110 11100110
01000101 11110110 11010110 01010101 11110011
00010110 11100101 11110111 00010111 00100110
00110011 00000110 01000011 00110111 11010000
???????? ???????? ???0?1?1 ?0?1?0?1 ?1?0?1?0
?0?1?0?1 11101100 00010001 11101100 00010???
???????? ???????? ???????? ???????? ????????
???????? ???????? ??????0? 0?1?1?1? 0?1?0?1?
0?101000 01110011 10011000 10100010 10011011
11010110 ???????? ???????? ???????? ????????
?1?01101 11001101 01101011 ???????? ????????
???????? ???????? ???????? ?0?10100 ????????
???????? ???????

整理信息

>   0100 = 編碼模式 8-bit Byte
>   00011100 = 計數器 28

跟前面讀取的數據比對一下,可以知道前28個碼字都是完整的,這就意味着我們都不需要使用糾錯碼來糾錯,直接讀取就行了。

腳本讀取

最后的解密代碼:

s = '''01111010 01101010 01100011 0111
01000110 01100111 10110111 10010011 00000111
01010101 11110110 01100011 00010110 11100110
01000101 11110110 11010110 01010101 11110011
00010110 11100101 11110111 00010111 00100110
00110011 00000110 01000011 00110111 1101'''


ss = s.replace(" ","").replace("\n","")
flag = ''
for i in range(0,28):
	flag += chr(int(ss[i*8:(i+1)*8],2))
print(flag)

將上面的代碼運行,即可得到最終的flag。

使用工具恢復

現在我們講如何使用工具:
從github上將qrzybox-master下載下來,將index.html文件拖入瀏覽器中打開,然后點擊“new project”--> “import from image”,然后將“恢復固定格式.png”導入:
image

然后點擊“tools”-->“Extract QR information”,即可恢復數據:
image

就這么簡單,當然這個工具還有很多其他的用處,就開大家自己去研究了。

參考文章

二維碼生成細節和原理:

https://zhuanlan.zhihu.com/p/21463650

https://coolshell.cn/articles/10590.html

官方文檔(中文版):

https://wenku.baidu.com/view/ef77275f312b3169a451a4a4.html?pn=50

里德-所羅門碼:

https://www.jianshu.com/p/8208aad537bb

https://en.wikiversity.org/wiki/Reed–Solomon_codes_for_coders#Encoding_outline

https://stackoverflow.com/questions/30363903/optimizing-a-reed-solomon-encoder-polynomial-division

MMA2015-MISC400-qr的write up

https://github.com/pwning/public-writeup/blob/master/mma2015/misc400-qr/writeup.md


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM