从第一次MISC出题开始——

起因

升入大二之后的第一次招新赛让我有了出题给别人做的机会,那就从这道题(SSSCTF 2023 MISC–未经禁止,允许垂钓!)的出题思路开始说起吧。

image-20231028115658334.png

这道题是一道稍微有些奇怪的图片隐写题。起因是我从体育馆回到宿舍路过湖边的时候看到了这个标语牌“未经允许,禁止垂钓”,但是脑回路不正常的我却将其读成了“未经禁止,允许垂钓”,这便成了一个笑话。回去之后,我在用010editor看hex数据解题的时候突然发现整整齐齐的hex数据似乎也可以被像我读那个标语牌一样重新排列,于是就有了出题思路。

题目解析

image-20231028115937884

我将一个转换成base64编码的flag保存成了png图片的形式,并通过脚本将其中的hex数据进行了以32为单位的重排,重排规律也就和我乱读标语牌的顺序一样,前16位读两个,后16位再读两个,如果用010editor每行16的形式来看的话就是第一排读两个第二排再读两个往后推进。而这题的解题过程也很简单,就是把这一段数据写一个把数据重新逆向排列好的脚本之后保存为文件打开即可,这题出题的本意也是为了让新生熟悉010editor的用法和锻炼新生的脚本写作能力。

而为了降低写脚本难度和降低脑洞要求,我将经过重排的flag数据通过添0变成了32倍数长度的整齐形式,并在原png数据后添了一大块FF来隔开后放在后面。所以如果使用010editor打开这题,也能在一块很显眼的FF数据库后面看到很显眼的被打散掉的PNG文件头和IHDR信息头。

image-20231028120927524.png

不过除此之外,毕竟这题的名字是叫“未经禁止,允许垂钓”,而且这题的description我也写成了

来到世界最美湖,涌泉湖!

太美丽了,涌泉湖。

哎呀,这怎么不让钓鱼?就吊就吊!

“未经禁止,允许垂钓!”

还是看看远处的泡面桶吧。

这种搞笑格式,于是便藏了两个fake flag。

Fake Flag#1 宽高隐写

PNG图片隐写中,最常见也最烂大街的一种隐写方式就是宽高隐写。宽高隐写的原理利用了PNG本身的性质,即会在文件头的IHDR信息中保存该图片的宽高像素,并以此为依据来读取图片内容中的其他信息并最终排列成图片。而多数图片查看器并不会对IHDR本身进行校验,所以可以手动将IHDR信息中的图片高度改小,来达到将图片的下半部分隐藏起来的效果(但Kali Linux会提示CRC错误,图片损坏)。

但PNG图片格式对于IHDR数据被修改也有一定的检测方式,IHDR信息的最后四位是校验位,它会对前面的数据通过CRC-32算法计算出校验值并比对,来检测是否被修改。如果使用了宽高隐写但没有对CRC值进行处理,那么使用010editor打开的时候也会提示CRC Mismatch

image-20231028122432041.png

这也是宽高隐写的一个重要特征,从而我们可以很快的识别出宽高隐写,也可以从CRC-32校验的原理中写脚本来爆破出正确的宽高值。

以下是一个示例脚本,来自CSDN-png图片的结构和crc校验

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

import binascii
import struct


crcbp = open("文件名.png", "rb").read() #打开图片
crc32frombp = int(crcbp[29:33].hex(),16) #读取图片中的CRC校验值
print(crc32frombp)

for i in range(4000): #宽度1-4000进行枚举
for j in range(4000): #高度1-4000进行枚举
data = crcbp[12:16] + \
struct.pack('>i', i)+struct.pack('>i', j)+crcbp[24:29]
crc32 = binascii.crc32(data) & 0xffffffff
# print(crc32)
if(crc32 == crc32frombp): #计算当图片大小为i:j时的CRC校验值,与图片中的CRC比较,当相同,则图片大小已经确定
print(i, j)
print('hex:', hex(i), hex(j))

但部分比较有坏水的出题人会在进行宽高隐写的同时把CRC校验值也修改掉,从而使其无法被爆破,但这道题中我没有那么坏,只是把fake flag的base64拼接到原图片的下面并且把高度改小了100,就算被骗到了其实也没有多少损失。

image-20231028123938083.png

Fake Flag#2 图种(文件拼接)

早年间贴吧常见的在签名档里面藏文件的操作其实也是对PNG文件格式的利用。PNG文件格式比较与众不同的一点是,它会在图片数据结束之后写一个文件尾,这之后的数据不会去读取,无论写什么也对前面的图片没有影响。因此可以在PNG文件头的后面拼接上其他文件,比较常见的是放一个ZIP压缩包进去。

image-20231028125233754.png

在PNG的文件尾IEND之后能很清楚的看见PK字样(ZIP文件头)

而解决这种隐写的方式也很简单,在 kali linux 下有一个强大的工具 binwalk

1
sudo apt-get install binwalk 

安装好之后使用

1
binwalk -e 文件名

即可对拼接起来的文件进行分离

如果 binwalk 分离不出,还可以使用 foremost 工具

1
sudo apt-get install foremost

或者使用 dd 进行手动分离

1
dd if=源文件 of=输出文件 count=123(一共取多少位)skip=456(开头跳过的块) bs=1