[CISCN 2023 初赛]puzzle 解析

打开文件包给了一堆拼图碎片,由于文件数量高达2880张,这里不考虑gaps的方式进行修正拼图

(因为跑了也只会把gaps跑冒烟)

tmp类型的拼图,因为tmp文件特性在文件头的位置会有其在原图片上的位置坐标

 

 于是,我们可以通过坐标+图片分辨率的方式(在得到最后一块拼图的坐标后,将其与它的分辨率大小相加)就能得到原图片的大小

*方便我们创建同等大小的画布

于是我们书写脚本对其进行处理(参考大佬的wpNSSCTF | 在线CTF平台)

import os
files = os.listdir('C:\\Users\\Lenovo\\Desktop\\puzzle\\tmp4\\')
size = []
for file in files:
    with open('C:\\Users\\Lenovo\\Desktop\\puzzle\\tmp4\\'+file,'rb') as fr:
        data = fr.read()
    x = int.from_bytes(data[6:8],'little') #读入tmp文件的自带坐标
    y = int.from_bytes(data[8:10],'little')#little意思为小数据类型,即储存数据时前面的数据储存在高位
    width = int.from_bytes(data[0x12:0x16],'little') #读入图片宽度
    height = 100
    size.append([(y,x),(height,width)]) #以(文件坐标位置)(文件像素大小)为一组数据写入size数组中
sorted_size = sorted(size,key=lambda x:x[0]) #按照第一位进行排序,即(y,x)排序
img_height = sorted_size[-1][0][0] + sorted_size[-1][1][0] #对最后一张图片的像素和坐标进行相加处理,得到最终拼图的宽和高
img_width = sorted_size[-1][0][1] + sorted_size[-1][1][1]
print(img_height,img_width)

然后我们得到原本图片大小

4000 7200

 得到原本图片之后我们就可以选择对图片进行拼接,脚本如下

from PIL import Image,ImageOps
import os
exp = Image.open('C:\\Users\\Lenovo\\Desktop\\puzzle\\tmp4\\99765296563.bmp')
image = Image.new(exp.mode,(7200, 4000)) #mode函数保留原本图片文件的色彩格式
files = os.listdir('C:\\Users\\Lenovo\\Desktop\\puzzle\\tmp4\\')
for file in files:
    with open('C:\\Users\\Lenovo\\Desktop\\puzzle\\tmp4\\'+file,'rb') as fr:
        data = fr.read()
    x = int.from_bytes(data[6:8],'little')#读取tmp坐标
    y = int.from_bytes(data[8:10],'little')
    height=int.from_bytes(data[0x16:0x1A],'little',signed=True) # signed:选True、Flase表示是否要区分二进制的正负数含义。即是否要对原二进制数进行原码反码 补码操作
    img = Image.open("C:\\Users\\Lenovo\\Desktop\\puzzle\\tmp4\\" + file)
    if height < 0:
        img=ImageOps.flip(img)
        print('已翻转:',file)
    image.paste(img,(x,y))
image.save('C:\\Users\\Lenovo\\Desktop\\flag.bmp')

 需要注意的是,部分图片给的高度参数是负数,这样会导致拼出来的图片会颠倒过来

[CISCN 2023 初赛]puzzle 解析_第1张图片

我们对其进行反转处理后即可得到原本图片

[CISCN 2023 初赛]puzzle 解析_第2张图片

 之后对分成3部分的flag进行分别解析

1.lsb解密

        打开stegsolve,选择lsb除alpha外的0位,得到第一部分flag

[CISCN 2023 初赛]puzzle 解析_第3张图片

2.二进制

        猜测部分图片的高度被修改与出题人的意图有关。因为有着高度正负的区别,猜想是二进制。于是对图片输入的高度进行提取,并按照拼图的顺序进行排列

import os
from Crypto.Util.number import long_to_bytes
files = os.listdir('C:\\Users\\Lenovo\\Desktop\\puzzle\\tmp4\\')
s=[]
for file in files:
    with open('C:\\Users\\Lenovo\\Desktop\\puzzle\\tmp4\\'+file,'rb') as fr:
        data = fr.read()
    x = int.from_bytes(data[6:8],'little')#读取tmp坐标
    y = int.from_bytes(data[8:10],'little')
    height=int.from_bytes(data[0x16:0x1A],'little',signed=True)
    bindata='0' if height<0 else '1' #将高度数据转换为二进制的0,1
    s.append([(y,x),bindata])
s=sorted(s,key=lambda x:x[0])#存入数据组,按坐标进行排序
b_flag=''
for i in s:
    b_flag+=i[1]#读入二进制数据
print(long_to_bytes(int(b_flag,2)))#输出第二部分flag

 最终得到

b'          2nd_paRT_15_reVeRSe_bMp_         .--. .- -..\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff'

除去第二部分flag外,我们还得到第3部分flag的摩斯密码提示

[CISCN 2023 初赛]puzzle 解析_第4张图片

 pad一般指pad()函数,即填充函数

考虑到bmp文件特性,bmp是按⾏绘制的,每⾏数据都需要为4的倍数,当像素数据不满⾜这个条件时,会⾃动填充相应字节的0。而在这道题中出题人很明显修改了这个填充的值,我们需要把这个值按照拼图的排列方式提取出来。

import os
files = os.listdir('C:\\Users\\Lenovo\\Desktop\\puzzle\\tmp4')
my_data = []
for file in files:
    with open('C:\\Users\\Lenovo\\Desktop\\puzzle\\tmp4\\'+file,'rb') as fr:
        data = fr.read()
    x = int.from_bytes(data[6:8],'little')
    y = int.from_bytes(data[8:10],'little')
    width = int.from_bytes(data[0x12:0x16],'little',signed=True)
    padding_size = 0 if 4- 3*width%4 == 4 else 4- 3*width%4 #计算填充字节
    img_data_size = 3*width#计算数据字节
    length = len(data[54:])#⽂件头占54字节
    img_data = data[54:]
    padding_data = b''#声明是二进制字符串
    for i in range(img_data_size,length,padding_size+img_data_size):
        padding_data += img_data[i:i+padding_size]
    my_data.append([(y,x),padding_data])#先排横坐标,再排纵坐标
sorted_data = sorted(my_data,key=lambda x:x[0])#按拼图顺序排序
padding_msg = b''
for v in sorted_data:
    padding_msg += v[1]
with open('C:\\Users\\Lenovo\\Desktop\\c_flag.jpg','wb') as fw:
    fw.write(padding_msg)

最后输出的16进制数开头为ffd8,是一个jpg文件,于是我们直接输出为一个jpg文件即可

得到

[CISCN 2023 初赛]puzzle 解析_第5张图片

 3rd_parT_1s_paddINGINGING}

将3部分flag进行拼接得到最终flag

flag{f1R5T_part_1s_LSB_sTeG0_2nd_paRT_15_reVeRSe_bMp_3rd_parT_
1s_paddINGINGING}

 此题结束

(感谢大佬的wpNSSCTF | 在线CTF平台让我大开眼界。同样也吃了没有见过bmp拼图这种类型题目的亏,这次算是长见识了)

你可能感兴趣的:(数学建模,全国大学生信安创新实践赛,安全,web安全)