2023CISCN MISC Puzzle

发布时间 2023-05-30 12:30:53作者: WXjzc

虽然没有参加,但是这道题我比较感兴趣,bmp拼图,听其他师傅一说,我就感觉有印象,一查发现与22年的春秋杯PINTU类似

要拼图,先要知道原图的宽高,给出的图片宽是不等的,需要我们去计算一下

files = os.listdir('./tmp4')
size = []
for file in files:
    with open('./tmp4/'+file,'rb') as fr:
        data = fr.read(0x1A)
    x = int.from_bytes(data[6:8],'little')
    y = int.from_bytes(data[8:10],'little')
    width = int.from_bytes(data[0x12:0x16],'little')
    height = 100
    size.append([(y,x),(height,width)])
sorted_size = sorted(size,key=lambda x:x[0])
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

这样可以计算得出原图的宽为7200,高为4000,接下来就是根据bmp的保留数据来拼图

from PIL import Image
import os
exp = Image.open('./tmp4/99765296563.bmp')
image = Image.new(exp.mode,(7200, 4000))

files = os.listdir('./tmp4')
for file in files:
    with open('./tmp4/'+file,'rb') as fr:
        data = fr.read(0x1A)
    x = int.from_bytes(data[6:8],'little')
    y = int.from_bytes(data[8:10],'little')
    img = Image.open("./tmp4/"+file)
    image.paste(img,(x,y))
image.save('flag.bmp')

得到图片,不过发现部分内容不正确

仔细观察可以发现这部分图片其实是水平翻转了,这就涉及到一个点,在bmp中,如果一张图的高度为负数,那么这张图片的绘制顺序就会颠倒,从而达到翻转的效果,以刚刚生成的这张图片为例,当将高度修改为负数时,图片颠倒

因此,在拼图时,肯定有小图的高度是负数,需要将这些图片翻转后再拼图

from PIL import Image
from PIL import ImageOps
import os
exp = Image.open('./tmp4/99765296563.bmp')
image = Image.new(exp.mode,(7200, 4000))

files = os.listdir('./tmp4')
for file in files:
    with open('./tmp4/'+file,'rb') as fr:
        data = fr.read(0x1A)
    x = int.from_bytes(data[6:8],'little')
    y = int.from_bytes(data[8:10],'little')
    img = Image.open("./tmp4/"+file)
    height = int.from_bytes(data[0x16:0x1A],'little',signed=True)
    if height < 0:
        img = ImageOps.flip(img) #翻转图片
    image.paste(img,(x,y))
image.save('flag.bmp')

得到图片

第一部分lsb最低位隐写flag{f1R5T_part_1s_LSB_sTeG0_

出题人既然刻意修改了部分图片的高度,那么肯定是有更深的意图的,这些小图的高度分为正负,正好对应了二进制,因此需要按照拼图的顺序将二进制内容输出

from PIL import Image
import os
from Crypto.Util.number import long_to_bytes
files = os.listdir('./tmp4')
msg = []
for file in files:
    with open('./tmp4/'+file,'rb') as fr:
        data = fr.read(0x1A)
    x = int.from_bytes(data[6:8],'little')
    y = int.from_bytes(data[8:10],'little')
    height = int.from_bytes(data[0x16:0x1A],'little',signed=True)
    bin_data = '0' if height < 0 else '1'
    msg.append([(y,x),bin_data])#先排横坐标,再排纵坐标
sorted_msg = sorted(msg,key=lambda x:x[0])#按拼图顺序排序
s = ""
for v in sorted_msg:
    s += v[1]
print(long_to_bytes(int(s,2)))
#2nd_paRT_15_reVeRSe_bMp_

第三部分则是填充字节,bmp是按行绘制的,每行数据都需要为4的倍数,当像素数据不满足这个条件时,会自动填充相应字节的0,出题人修改了这个填充的数据,需要按照拼图的顺序提取这些数据

在写入文件前,可以尝试打印,打印时可以发现文件头是ffd8,典型的jpg文件

from PIL import Image
import os
files = os.listdir('./tmp4')
my_data = []
for file in files:
    with open('./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('1.jpg','wb') as fw:
    fw.write(padding_msg)
#3rd_parT_1s_paddINGINGING}

因此得到最终flagflag{f1R5T_part_1s_LSB_sTeG0_2nd_paRT_15_reVeRSe_bMp_3rd_parT_1s_paddINGINGING}