前言
本题为:CTFSHOW的36D杯 misc ez-qrcode
和攻防世界的qr-easy解法基本一模一样。
1.第一眼
用ps稍微处理一下:
此二维码的大小为 29x29,版本V的大小为N × N,N = 17 + 4V,所以这是版本 3。
2.格式信息
图中两处红色方框,表示二维码的格式信息,前面7位,后面8位。
后面8位的格式信息为:11100111
,前7位我们不清楚,所以我们的信息字符串为:???????11100111
所有格式信息字符串的列表
ECC 级别 | 掩码图案 | 类型信息比特 |
---|---|---|
L | 0 | 111011111000100 |
L | 1 | 111001011110011 |
L | 2 | 111110110101010 |
L | 3 | 111100010011101 |
L | 4 | 110011000101111 |
L | 5 | 110001100011000 |
L | 6 | 110110001000001 |
L | 7 | 110100101110110 |
M | 0 | 101010000010010 |
M | 1 | 101000100100101 |
M | 2 | 101111001111100 |
M | 3 | 101101101001011 |
M | 4 | 100010111111001 |
M | 5 | 100000011001110 |
M | 6 | 100111110010111 |
M | 7 | 100101010100000 |
Q | 0 | 011010101011111 |
Q | 1 | 011000001101000 |
Q | 2 | 011111100110001 |
Q | 3 | 011101000000110 |
Q | 4 | 010010010110100 |
Q | 5 | 010000110000011 |
Q | 6 | 010111011011010 |
Q | 7 | 010101111101101 |
H | 0 | 001011010001001 |
H | 1 | 001001110111110 |
H | 2 | 001110011100111 |
H | 3 | 001100111010000 |
H | 4 | 000011101100010 |
H | 5 | 000001001010101 |
H | 6 | 000110100001100 |
H | 7 | 000100000111011 |
根据二维的信息字符串???????11100111
再结合所有格式信息字符串的列表或者上图表格。
很容易得到这个二维码的ECC级别为H和掩码模式为2。
3.去掉掩码
掩码号 | 如果下面的公式对于给定的行/列坐标为真,则切换该坐标处的位 |
---|---|
0 | (行 + 列) mod 2 == 0 |
1 | (行) mod 2 == 0 |
2 | (列) mod 3 == 0 |
3 | (行 + 列) mod 3 == 0 |
4 | (floor(行 / 2) + floor(列 / 3)) mod 2 == 0 |
5 | ((行 * 列) mod 2) + ((行 * 列) mod 3) == 0 |
6 | ( ((行 * 列) mod 2) + ((行 * 列) mod 3) ) mod 2 == 0 |
7 | ( ((行 + 列) mod 2) + ((行 * 列) mod 3) ) mod 2 == 0 |
根据QR Mask Patterns Explained,也就是上面的表格,2 号掩码有公式(列) mod 3 == 0
。注意列号是从0开始的,所以我们要切换坐标为0,3,6,9, … ,27的列的位。
二维码也有固定的图案,所以我们只需要切换数据部分的位即可。查看数据区和位顺序。
(通过wikipedia.org)
所以,原始的 D1-D26 是:
D1 = 00010100
D14 = 10100010
D2 = 00110011
D15 = 00010001
D3 = 00110011
D16 = 01110110
D4 = 11000110
D17 = 01010111
D5 = 00010110
D18 = 01000100
D6 = 11011101
D19 = 00101100
D7 = 00011111
D20 = 01011110
D8 = 00011100
D21 = 00000010
D9 = 00100001
D22 = 01100110
D10 = 00110110
D23 = 01010111
D11 = 00010110
D24 = 11010000
D12 = 11100110
D25 = 11101101
D13 = 10110011
D26 = 01000100
去掉掩码后,(列) mod 3 == 0
D1 = 01000001
D14 = 11110111
D2 = 01100110
D15 = 01000100
D3 = 01100110
D16 = 01110110
D4 = 11000110
D17 = 01010111
D5 = 00010110
D18 = 01000100
D6 = 01110111
D19 = 10000110
D7 = 10110101
D20 = 11110111
D8 = 01000110
D21 = 01010111
D9 = 01110100
D22 = 00110110
D10 = 00110110
D23 = 01010111
D11 = 00010110
D24 = 11010000
D12 = 11100110
D25 = 11101100
D13 = 11100110
D26 = 00010001
4.数据解码
解码有模式指示符:
0001
:数字模式(每 3 位 10 位)0010
:字母数字模式(每 2 个字符 11 位)0100
:字节模式(每个字符 8 位)1000
:汉字模式(每个字符 13 位)0111
: ECI 模式
字符计数指示符跟在模式指示符之后。
- 版本 1-9
- 数字模式:10 位
- 字母数字模式:9位
- 字节模式:8位
- 汉字模式:8位
- 版本 10–26
- 数字模式:12位
- 字母数字模式:11 位
- 字节模式:16位
- 汉字模式:10位
- 版本 27–40
- 数字模式:14位
- 字母数字模式:13 位
- 字节模式:16位
- 汉字模式:12位
查看每种模式的编码过程:
让我们从上面的数据 D1-D26 开始:
data = '01000001' \
'01100110' \
'01100110' \
'11000110' \
'00010110' \
'01110111' \
'10110101' \
'01000110' \
'01110100' \
'00110110' \
'00010110' \
'11100110' \
'11100110' \
'11110111' \
'01000100' \
'01110110' \
'01010111' \
'01000100' \
'10000110' \
'11110111' \
'01010111' \
'00110110' \
'01010111' \
'11010000' \
'11101100' \
'00010001'
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
Ruby在线网站运行即可得到
flag{TgCannotGetHouse}
Python代码同样也没有问题:
# D1-D26:
data = ['01000001', '01100110', '01100110', '11000110', '00010110', '01110111', '10110101', '01000110', '01110100', '00110110', '00010110', '11100110', '11100110', '11110111', '01000100', '01110110', '01010111', '01000100', '10000110', '11110111', '01010111', '00110110', '01010111', '11010000', '11101100', '00010001']
data = "".join(data)
data = data[4:]
for i in range(0, len(data), 8):
print(chr(int(data[i:i+8], 2)), end="")
运行效果如下:
flag{TgCannotGetHouse}
拜拜!
5.参考网站:
https://yous.be/2014/12/07/seccon-ctf-2014-qr-easy-write-up/
https://www.zhihu.com/question/65253283