虽然没有参加,但是这道题我比较感兴趣,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}