[MISC] QR code wp

0

这题一直做到比赛结束都没出…然后又做了近两个小时才拿到flag。

0x01

一道400分的MISC,下载文件下来是一个3gp后缀的类似gif一样,每一帧都显示出一部分,提取出来是一张完整的图片,上半部分是半个二维码,下半部分写着“后面没有咯”。

0x02

先把半个二维码顺时针旋转90度,然后我拿出photoshop,打开标尺和网格…开始还原,历经近1小时,因为ps不太熟练,现学的,成品如图:

0x03

没了思路,于是开始google,找到题目出处,seccon ctf 2014qr easy这题。 然后找到当时的wp: https://yous.be/2014/12/07/seccon-ctf-2014-qr-easy-write-up/ 下面就是复现了。

0x04

首先明确本题的QR code大小是29×29,所以是version 3。 wikipedia上有v3的结构图: https://commons.wikimedia.org/wiki/File:QR_Ver3_Codeword_Ordering.svg 这里对应的format info很重要,但我们这半个只能看到后一半的二进制串11100111。 这里可以查表: the list of all format information strings 结果是对应ECC Level为H,Mask Pattern为2。 然后查表: QR Mask Patterns Explained 得知Mask Number为2对应的掩码是(column) mod 3 == 0,如图:

0x05

由此得到raw D1-D26(别问怎么得到的,我是一个一个0和1手写的):


D1  = 0b00010100
D14 = 0b10110001
D2  = 0b00100001
D15 = 0b01100001
D3  = 0b00110011
D16 = 0b00100101
D4  = 0b11000110
D17 = 0b00110100
D5  = 0b00010110
D18 = 0b10110101
D6  = 0b11011101
D19 = 0b01011110
D7  = 0b00011110
D20 = 0b00111100
D8  = 0b10100001
D21 = 0b00010000
D9  = 0b11000001
D22 = 0b00000100
D10 = 0b01010101
D23 = 0b01010101
D11 = 0b01010011
D24 = 0b00100111
D12 = 0b00110101
D25 = 0b11010001
D13 = 0b10100001
D26 = 0b10111001

按此顺序一一对应的掩码是这样的:


01010101
01010101
01010101
01010101
01010101
00000000
00000000
00000000
00000000
00000000
10101010
10101010
10101010
10101001
01010101
01010101
01010101
01010000
00000000
00000000
00000000
00000000
00000000
00000001
01010101
01010101

做一次异或,得到真正的数据(已经按D1-D26的顺序重新排列了):


D1  = '01000001'
D2  = '01110100'
D3  = '01100110'
D4  = '11000110'
D5  = '00010110'
D6  = '01110111'
D7  = '10110100'
D8  = '11110100'
D9  = '10010100'
D10 = '01010101'
D11 = '01010011'
D12 = '00110101'
D13 = '11110100'
D14 = '11100100'
D15 = '00110100'
D16 = '00100101'
D17 = '00110100'
D18 = '10110101'
D19 = '11110100'
D20 = '10010101'
D21 = '01000101'
D22 = '01010100'
D23 = '01010101'
D24 = '00100111'
D25 = '11010000'
D26 = '11101100'

0x06

下面就是解码了,搞清楚结构就可以了,mode indicatorscharacter count indicator,以及四种编码模式各自的特点,这里是byte mode encoding。 附上解码的ruby脚本:


data = '01000001' \
       '01110100' \
       '01100110' \
       '11000110' \
       '00010110' \
       '01110111' \
       '10110100' \
       '11110100' \
       '10010100' \
       '01010101' \
       '01010011' \
       '00110101' \
       '11110100' \
       '11100100' \
       '00110100' \
       '00100101' \
       '00110100' \
       '10110101' \
       '11110100' \
       '10010101' \
       '01000101' \
       '01010100' \
       '01010101' \
       '00100111' \
       '11010000' \
       '11101100'
alphanumeric = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:'.chars
 
def read(str, size)
    str.slice!(0, size)
end
 
def kanji(num)
    if num >= 0x1740
        (0xC140 + num / 0xC0 * 0x100 + num % 0xC0)
            .chr(Encoding::Shift_JIS).encode(Encoding::UTF_8)
    else
        (0x8140 + num / 0xC0 * 0x100 + num % 0xC0)
            .chr(Encoding::Shift_JIS).encode(Encoding::UTF_8)
    end
end
 
loop do
    case mode = read(data, 4)
    when '0010' # Alphanumeric
        count = read(data, 9).to_i(2)
        (count / 2).times do
            chunk = read(data, 11).to_i(2)
            print alphanumeric[chunk / 45]+ alphanumeric[chunk % 45]end
        print alphanumeric[read(data, 11).to_i(2)] if count.odd?
    when '0100' # Byte
        count = read(data, 8).to_i(2)
        count.times do
            print read(data, 8).to_i(2).chr
        end
    when '1000' # Kanji
        count = read(data, 8).to_i(2)
        count.times do
            print kanji(read(data, 13).to_i(2))
        end
    when '0000' # Terminate
        break
    else
        fail "Unhandled mode #{mode}"
    end
end

0x07

得到flag:

Leave A Reply

苏ICP备16066660号-1

苏公网安备 32011502010432号