打开文件包给了一堆拼图碎片,由于文件数量高达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 = 100size.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')
需要注意的是,部分图片给的高度参数是负数,这样会导致拼出来的图片会颠倒过来
我们对其进行反转处理后即可得到原本图片
之后对分成3部分的flag进行分别解析
1.lsb解密
打开stegsolve,选择lsb除alpha外的0位,得到第一部分flag
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,1s.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的摩斯密码提示
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文件即可
得到
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拼图这种类型题目的亏,这次算是长见识了)