[CISCN 2023 初赛]puzzle
1、要拼图,先要知道原图的宽高,给出的图片宽是不等的,先去计算一下
import os
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的保留数据来拼图
这就是拼图的关键点,别人说必须要为0,但是这个出题人就利用了不为0,也不会影响读取显示所以就可以利用这点了,bfReserved1 代表了 x,bfReserved2 代表了 y
x和y代表的是图像的左上角的位置,我们就根据这个还有高度是负数的情况是等于垂直翻转了,代码中遇到了高度是负数的,也就是等于-100的我们再做一个垂直翻转不就给翻转回来了。
bmp高度问题:
正常情况下:
实际本题情况:
因此需要垂直翻转
import os
import cv2
import numpy as np
new_img = np.zeros(shape=(4000, 7200, 3), dtype=np.uint8)
for root, dirs, files in os.walk("./tmp4"):
for file in files:
imgPath = os.path.join(root, file)
img = cv2.imread(imgPath, cv2.IMREAD_UNCHANGED)
row, col = img.shape[:2]
with open(imgPath, "rb") as f:
data = f.read(0x16+4)
x = int.from_bytes(data[6:8], byteorder="little", signed=False)
y = int.from_bytes(data[8:10], byteorder="little", signed=False)
if int.from_bytes(data[0x16:0x16+4], byteorder="little", signed=True) == -100:
img = cv2.flip(img, 0) # 垂直翻转一下
new_img[y:y+row, x:x+col] = img
cv2.imwrite("part1.png", new_img)
得到图片:
然后利用zsteg进行对bmp隐写分析
2、出题人既然刻意修改了部分图片的高度,那么肯定是有更深的意图的,这些小图的高度分为正负,正好
对应了二进制,因此需要按照拼图的顺序将二进制内容输出
import os
import libnum
from PIL import Image
# 7200, 4000
dic = {i: [] for i in range(4000 // 100)}
for root, dirs, files in os.walk("./tmp4"):
for file in files:
imgPath = os.path.join(root, file)
img = Image.open(imgPath)
with open(imgPath, "rb") as f:
data = f.read(0x16+4)
x = int.from_bytes(data[6:8], byteorder="little", signed=False)
y = int.from_bytes(data[8:10], byteorder="little", signed=False)
height = 0 if int.from_bytes(data[0x16:0x16+4], byteorder="little", signed=True) == -100 else 1
dic[y//100].append([x, height])
bin_str = ""
for key, values in dic.items():
values = sorted(values, key=lambda x: x[0])
for value in values:
bin_str += f"{value[-1]}"
# print(bin_str)
print(libnum.b2s(bin_str)) #二进制转字符
得到
3、除了第二段flag,还可以看到一段摩斯密码,解密后内容为pad,是对第三段flag隐藏方式的提示
第三部分则是填充字节,bmp是按行绘制的,每行数据都需要为4的倍数,当像素数据不满足这个条件
时,会自动填充相应字节的0,出题人修改了这个填充的数据,需要按照拼图的顺序提取这些数据。
在写入文件前,可以尝试打印,打印时可以发现文件头是 FF D8 ,典型的 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}