[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')

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

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

 之后对分成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,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的摩斯密码提示

 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拼图这种类型题目的亏,这次算是长见识了)

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
单词搜索迷宫(Word Search Puzzle)问题是一个经典的算法问题,其输入是一个二维的字符数组和一组单词,目标是找出字符数组网格中的所有单词。这些单词可以是水平的、垂直的或者是任意的对角线方向,所以需要查找8个不同的方向。解决这个问题的一种常见方法是使用回溯算法,具体步骤如下: 1. 遍历二维字符数组,对于每个字符,以其为起点开始搜索,搜索的方向包括水平、垂直和对角线方向。 2. 对于每个搜索到的单词,将其记录下来。 3. 重复步骤1和2,直到遍历完整个二维字符数组。 下面是一个使用C#语言实现的单词搜索迷宫算法的示例代码: ```csharp class WordSearchPuzzle { private char[,] grid; private HashSet<string> words; public WordSearchPuzzle(char[,] grid, HashSet<string> words) { this.grid = grid; this.words = words; } public void Solve() { int rows = grid.GetLength(0); int cols = grid.GetLength(1); for (int i = 0; i < rows; i++) { for (int j = 0; j < cols; j++) { Search(i, j, new StringBuilder()); } } } private void Search(int row, int col, StringBuilder sb) { if (row < 0 || row >= grid.GetLength(0) || col < 0 || col >= grid.GetLength(1)) { return; } sb.Append(grid[row, col]); string word = sb.ToString(); if (words.Contains(word)) { Console.WriteLine("Found '{0}' at [{1}, {2}] to [{3}, {4}]", word, row, col, row - sb.Length + 1, col - sb.Length + 1); } if (word.Length < 3) { Search(row + 1, col, sb); Search(row - 1, col, sb); Search(row, col + 1, sb); Search(row, col - 1, sb); Search(row + 1, col + 1, sb); Search(row - 1, col - 1, sb); Search(row + 1, col - 1, sb); Search(row - 1, col + 1, sb); } sb.Remove(sb.Length - 1, 1); } } // 使用示例 char[,] grid = new char[,] { {'t', 'h', 'i', 's'}, {'w', 'a', 't', 's'}, {'o', 'a', 'h', 'g'}, {'f', 'g', 'd', 't'} }; HashSet<string> words = new HashSet<string>() { "this", "two", "fat", "that" }; WordSearchPuzzle puzzle = new WordSearchPuzzle(grid, words); puzzle.Solve(); ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值