前言

NCTF告一段落了,战况还可以。


有趣的是看到最终名单有自己,我寻思自己这么低的分数也能被记录上,细看才发现是校内名单。信息搞错了,还真高兴的那么几秒。


在比赛结束前20分钟最后做出来qrssssssss这题!所以特此来记录下misc的qrssssssss和qrssssssss_revenge这两题做题的过程

qrssssssss_revenge

(第一道题有非预期解,可以通过 按时间先后顺序 进行排序 逐个扫描再重复尝试能得到flag,所以这里以第二道题qrssssssss_revenge为例)

下载附件压缩包解压得到一堆二维码图片


看到这么多二维码第一眼反应就是用pyzbar库批量扫描,所以先尝试批量扫描看看什么东西

os批量处理图片名,然后再用pyzbar库扫描 提取二维码内容。

from pyzbar import pyzbar
from PIL import Image
import cv2
import os

PATH = './qrssssssss_revenge'

os.chdir(PATH)

text = ''
for i in os.listdir():
    qrcode = cv2.imread(i)
    data = pyzbar.decode(qrcode)
    text += data[0].data.decode('utf-8')

print(text)
303bdc03bc3e3d84bdd2{4}}T84f7dfb3Tf86Ff67fe30e07d6N3f3C{479dc8eebbde3df2F6d6e3322bdNef34T81db6fF66e7{1}F3eb49f108Cd30646f{f049dc}4T3363Fd32F4bbb3N234CCd09c8Te6ee86bN4T1dTe}323bd367Fd9f2ddf4f4b4d9cTCf{b}3F244b2dd60CcT3242{74d3db26e{b0be}fb}9b116C{3T}9F49T}e3324db42b03eb46eN62Nbb0T684443706bfe202318CN66NCee24c{89e74ef428FCb491d}4dc{eb2{7cF13Fb4dcNT3673ef26fd6Cdbd37e9N33NdfcNc12Tcbf4418Cd14bdef34TC}939}b4{1ffddc{0ff13}ddddb49}8F394N{24bb72}b7Fdb2db4fb{63T43dCN346f1e383c66dF7eb6e42b{4b21d4N222C70N8beb244df6fCFb

得到的结果是乱的,但能发现N C T F { 这些flag的关键字符,说明flag的搞到但是顺序是错乱的。

后面尝试词频分析,但也不是,每个字符都是均匀的分布。

问了出题人,得知我们并不需要处理扫描后的字符串,我们需要处理扫描图片的一个顺序。正确的顺序能直接扫描得到flag

同时题目给出了提示:


LMQH 代表的是二维码的纠错级别,所以这里猜测我们还要去分析每张二维码的纠错级别,将同一类别的二维码放到一起 然后再进行处理。

至于如何去分析,我参考了网上的文章关于二维码的构造,参考:http://www.2wm.cn/article/article-12


我们能够观察红色区域的4个方块的黑白情况来分析纠错级别。

level L:1、2都为黑色
level M:1为黑色,2为白色
level Q:1为白色,2为黑色
level H:1、2都为白色

所以这里思路就有了,因为每张图片的大小都是一样,所以可以用PIL模块提取每张图片的红色区域的坐标对应的颜色,再添加到列表里。
再与我们先定义好的每个纠错级别的列表进行比较,如果一样就能确定纠错级别,最后把同一级别的列表的二维码逐一扫描识别 打印结果。

二维码中每个方块的大小是20px,所以这里用脚本定义了下,红色区域的坐标也通过方块的数量来找。

from pyzbar import pyzbar
from PIL import Image
import cv2
import os


PATH = './qrssssssss_revenge'


block_size = 20
width,height = 660,660


def Errorlevel(images): #获取纠错级别
    level_list = ['1111','1010','0101','0000'] #L M Q H
    L,M,Q,H = [],[],[],[]
    for img in images:
        level = ''
        m = Image.open(img)  
        h = block_size * 8 + block_size // 2 # + block_size //2 是为了找方块中心区域识别
        for i in range(0,block_size*2,block_size):
            w = i + block_size//2
            rgb = m.getpixel((w,h))
            if rgb == 255:
                level += "0"
            else:
                level += "1"
        w = block_size * 8 + block_size // 2
        for i in range(0,block_size*2,block_size):
            h = height - (i + block_size//2)
            rgb = m.getpixel((w,h))
            if rgb == 255:
                level += "0"
            else:
                level += "1"
        if level in level_list:
            eval('LMQH'[level_list.index(level)]).append(img)
    return [L,M,Q,H]


os.chdir(PATH)


image_names = []
text = ''
for i in os.listdir():
    image_names.append(i)


for level in Errorlevel(image_names):
    text = ''
    for img in level:
        qrcode = cv2.imread(img)
        data = pyzbar.decode(qrcode)
        text += data[0].data.decode('utf-8')
    print(text)

运行结果

2{TT6FNC{F62NT16F{1F1C6{T6F2FN2CCT6NT1TFTC{F2CT2{2{116C{TFT22N6NT6621CN66NC{2FC1{2{F1FNT666CNNN12T1C1TC{1{1FN{2F2{TCN16F{1N2CNCF
0d03e3d8d8f73870073f378ee3de38dfe73f08d300d33d3d08ee83d37dfff3037d30ef3330ee08370fe08e8e7ef87373f733ff8e3f0f3ddd837733d3837e708f
33b34b44b63649bb32632b466b4946494334bbb3496b432b692b49b24464246bb9b9494b436242324944b49b3239b44399b4b9944bb2bb6366b6424b222bb26b
cbcd}}dffffeeddcdedfdefb}effc}cede}dfd44dc}bddcdbeb}}}}edbb4bb44becd}4dcecb4dcefddbdedccc4dbdf4}}fddcf}d4}}dbd4fb44fecdebd4e44df

能发现运行结果中,虽然顺序还是乱的 但是看第一行都包含了 N C T F { 那就说明这个思路没错,每一个列表都有flag的一部分 但列表内还是乱的。

比赛时做到这里就蒙了,再次询问出题人寻求帮助,发现其实还疏忽了一个东西:掩码

参考文章:https://www.bilibili.com/read/cv13270189


掩码和纠错级别它们被记录在二维码的格式信息中。

所以我们还需按照掩码的编号进行排先后顺序,这样就能搞定顺序问题,从而得到flag。

为了验证,我们可以根据上面的脚本 将纠错级别为L的图片名打印出来,然后找出来扫描内容为N的图片,通过扫描工具查看其掩码是否为0(0为掩码0-7中的起始)


同样可以扫描内容为C的二维码,其掩码是1。那就说明我们思路没错,现在只需获取掩码进行排序就能解决当前的问题。

如何获取原二维码的掩码??

网上大多的资料是告诉我们如何将可读的二维码通过掩码转换成我们看到的二维码,而我们得到的是处理过后的二维码。

可以参考这个视频,关于掩码讲的很清晰:https://www.huxiu.com/article/398385.html


拿上面的二维码做例子

首先,我们得到一个二维码 可以看其左上角码头下方的5个数据块得到信息


能看出其纠错级别为L,掩码是101,因为经过处理 所以这里的掩码是不准确的。

按视频中所讲,我们只需11101与10101异或,异或得到的数据即为原二维码的掩码。(这里拿101和101异或也可以)

异或后的结果0b11101 ^ 0b10101 = 01000,得到掩码为000 转换十进制是0也就是掩码0

符合扫描结果。

因此现在的思路就是通过识别每张二维码的掩码(三位块数据),再与101进行异或,就能得到得到原掩码,最后对掩码进行排序就能得到正确顺序的flag

脚本同样是用PIL识别三位块的颜色 黑白转换数据(二维码中黑代表1,白代表0),然后通过异或计算得到掩码,将掩码作为列表的下标把内容添加到一个长度为8的列表中,最后拼接列表内容 返回一个纠错级别的内容。循环纠错级别 拼接内容 得到flag

from PIL import Image
from pyzbar import pyzbar
import os
import cv2




PATH = './qrssssssss_revenge' #_revenge


block_size = 20
width,height = 660,660


def Mask(image): #获取二维码的掩码
    res = ['']*8
    for k in image:
        img = Image.open(k)
        qrcode = cv2.imread(k)
        data = pyzbar.decode(qrcode)
        text = data[0].data.decode('utf-8')
        mask = ''
        h = block_size * 8 + block_size // 2
        for i in range(0,block_size*3,block_size):
            w = block_size * 2 + i + block_size//2
            rgb = img.getpixel((w,h))
            if rgb == 255:
                mask += "0"
            else:
                mask += "1"
        #得到的二进制数据 与 101 异或得到原掩码
        mask_ = int(mask,2) ^ 0b101
        res[mask_] = text
        result = ''.join(res)
        print(k,mask,text)
    return result


def Errorlevel(images): #获取纠错级别
    level_list = ['1111','1010','0101','0000'] #L M Q H
    L,M,Q,H = [],[],[],[]
    for img in images:
        level = ''
        m = Image.open(img)  
        h = block_size * 8 + block_size // 2
        for i in range(0,block_size*2,block_size):
            w = i + block_size//2
            rgb = m.getpixel((w,h))
            if rgb == 255:
                level += "0"
            else:
                level += "1"
        w = block_size * 8 + block_size // 2
        for i in range(0,block_size*2,block_size):
            h = height - (i + block_size//2)
            rgb = m.getpixel((w,h))
            if rgb == 255:
                level += "0"
            else:
                level += "1"
        if level in level_list:
            eval('LMQH'[level_list.index(level)]).append(img)
    return [L,M,Q,H]


def main(image_names):
    images = Errorlevel(image_names) #创建同一纠错列表
    flag = ''
    for n in images:
        flag += Mask(n) #按照掩码排序
    print(f'[+] {flag}')


os.chdir(PATH)


image_names = []
for i in os.listdir():
    image_names.append(i)


main(image_names)

运行得到FLAG


qrssssssss:

总结

学到了二维码的纠错级别和掩码的内容,以及还原掩码的方式。这些也只是二维码的冰山一角,这一块还需慢慢探索。