ctfshow-misc入门

ctfshow_misc入门

想了一下还是觉得先把这里写完,后面可能着重去 re 或者 pwn 了?

不过如果可以,我还是希望 misc + re 两手抓

misc1

附件打开后就是一个flag:
ctfshow{22f1fb91fc4169f1c9411ce632a0ed8d}

misc2

拿到一个txt文件,文件头是 89 50 4E 47 ,所以将后缀改为 png 即可。

head

misc2

ctfshow{6f66202f21ad22a2a19520cdd3f69e7b}

misc3

是一个 bpg 图片文件,下载 Honeyview 即可查看。

ctfshow{aade771916df7cde3009c0e631f9910d}

misc4

拿到了多个附件,用 010 查看文件头可知是后缀名被修改。
相关文件头:

1
2
3
4
5
6
png: 89 50 4E 47 0D 0A 1A 0A
jpg: FF D8 FF
bmp: 42 4D
gif: 47 49 46 38 39 61
tif: 49 49 2A 00
webp: 52 49 46 46 XX XX XX XX 57 45 42 50

根据对应文件头修改后缀即可得到分为多段的 flag 。

ctfshow{4314e2b15ad9a960e7d9d8fcff902da}

misc5

尾部隐写。将图片放入 010 中,搜索 ctf 即可找到:

flag

ctfshow{2a476b4011805f1a8e4b906c8f84083e}

misc6

与 misc5 相同,在 010 中搜索 ctf 即可在中部位置找到flag:

flag

ctfshow{d5e937aefb091d38e70d927b80e1e2ea}

misc7

与 misc5、misc6 一样

flag

ctfshow{c5e77c9c289275e3f307362e1ed86bb7}

misc8

在 010 中看不出端倪,于是用 binwalk 检查一下,发现有两个 png 文件。

binwalk

所以用 foremost 分离一下:

foremost

得到两张图片,其中一张为 flag 。

misc8

ctfshow{1df0a9a3f709a2605803664b55783687}

这题目直接用 010 把前面一张图片数据删掉后保存,应该也可以。

misc9

放入 010 后搜索即可拿到。

使用 exiftool 也可以找到flag

ctfshow{5c5e819508a3ab1fd823f11e83e93c75}

misc10

先用 010 没找到什么信息,于是用 binwalk 看一看,发现有两个 zlib 文件,

于是 binwalk -e misc10.png 拿出来。

zlib

查看 10ES 文件即得flag:

flag

ctfshow{353252424ac69cb64f643768851ac790}

misc11

依旧是先用 010 找东西,发现找不到。

binwalk 得到两个 zlib ,但是提取之后发现文件里没有 flag 。

exiftool 也得不到什么有用的信息。

考虑到附件是一个 png 图片,尝试使用 tweakpng 。

tweakpng

发现它有两个 IDAT 块,将第一个 IDAT 块删除之后,图片出现了 flag :

flag

misc12

hyw, 用 tweakpng 打开有一堆 IDAT 块,一个个删除到第 8 个之后,图片出现了 flag 。

idat

flag

misc13

用 010 后在其中找到了 被隔开的 flag ,隔一个字母提取字符后得到 flag :

ctfshow

ctfshow{ae6e3ea48f518b7e42d7de6f412f839a}

misc14

是一个 jpg 文件。

010 里面没有太多信息。

binwalk 查看:

misc14

发现多张照片。

唔,使用 binwalk -e misc14.jpg 提取失败了,

foremost 提取的文件夹只有 1 张图片 ?

干脆直接在 010 里面搜索 FF D8 , FF D9 ,把前面的图片全部删了,拿到最后的 flag 。

flag

有些人的 wp 中使用的是 dd if=misc14.jpg of=1.jpg skip=2103 bs=1 指令

该指令的意思是:

把 misc14.jpg 从第 2103 个字节开始,直到文件末尾的数据复制到 1.jpg

misc15

给了一个 bmp 文件,但是解法是朴实无华的放入 010 搜索。

flag

misc16

binwalk 后发现一个奇怪的东西放在图片里面:

DD4

于是提取后打开 DD4 文件拿到 flag :

flag

misc17

用 binwalk 发现一个 bzip2 文件,提取出来之后发现 flag ,竟然没有 flag !
把提取出来的东西放在 010 里面看了一下,结尾竟然是 IEND 嘛 。

exiftool 并没有东西。

在 tweakpng 中发现多个 IDAT 块,但是删了没用 ?

删了没用考虑合并,把全部 IDAT 块合并,然后重新 binwalk 。

提取出来的东西 还是没有 flag !

仔细检查提取的文件,发现 D6E 实际上是 png 的文件头,改后缀名之后,终于拿到 flag 了。

D6E

这道题目看了 wp ,实在是没想到合并 IDAT 块,而且有些师傅的 wp 说直接 binwalk 提取就可以了,我怎么感觉写的不是一道题目 qwq 。还有些师傅说可以用 zsteg 做,我还没有试过。

misc18

实际上用 010 打开之后搜索就能找到。

这里我用 exiftool 做的,也是直接就能看到。

flag

ctfshow{325d60c208f728ac17e5f02d4cf5a839}

misc19

一样, 010 里面直接搜就行,分成了两段。

我也是用的 exiftool ,清晰一些。

flag

misc20

exiftool 之后看评论,flag 是个谐音:

flag

ctfshow{c97964b1aecf06e1d79c21ddad593e42}

misc21

依旧是 exiftool :

misc21

我们可以看到serial number后面有一串数字,这就是我们要找的,但是这不是flag,我们将此转换成ASCII码值,得到这个(hex(X&Ys))。

根据这个提示我们需要依次提取X、Y值(3902939465、2371618619、1082452817、2980145261),我们可以看出这四串数字都是十进制数,我们再将这些数字依次转换成十六进制数,然后我们依次拼接也就是ctfshow{e8a221498d5c073b4084eb51b1a1686d}

misc22

010 里面实际上能找到两个 FF D8 ,但是由于第二个 jpg 文件被加在 exif 元数据里面,binwalk 识别不出来。

这里可以用 010 删数据,也可以用 exiftool -ThumbnailImage -b /misc22.jpg > 123.png 提取缩略图。

flag

misc23

用 exiftool 之后在里面找到 flag 相关线索:

misc23

ctfshow{}, UnixTimestamp, DECtoHEX, getflag

unix时间戳转成16进制

flag{03425649ea0e31938808c0de51b70ce6a}

misc41

这题目真气笑了。不愧是愚人节特供。

在 010 里面用 hex 搜索 F001 ,高亮部分就是 flag 。

flag!!!

misc24

根据提示,flag在图片上面,猜想要更改图片高度。

更改 struct BITMAPINFOHEADER bmih / LONG biHeight 的值:

height

原来的图片出现 flag:

flag

misc25

还是更改图片高度,flag 在图片下方:

flag

misc 26

改了图片高度之后发现要拿到图片实际高度:

flag

用脚本爆破:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import zlib
import struct
import sys

file_path = sys.argv[1]

with open(file_path, 'rb') as f:
all_b = f.read()
crc32key = int(all_b[29:33].hex(), 16)
data = bytearray(all_b[12:29])
n = 4096

for w in range(n):
width = bytearray(struct.pack('>i', w))
for h in range(n):
height = bytearray(struct.pack('>i', h))
for x in range(4):
data[x+4] = width[x]
data[x+8] = height[x]

crc32result = zlib.crc32(data)
if crc32result == crc32key:
print(f"w: {w}, h: {h}")
print(f"hex w: {hex(w)}, hex h: {hex(h)}")
sys.exit(0)

得到真实高度:

height

misc27

也是改高度

misc28

gif 改高度,和 png 差不多

misc29

也是 gif 改高度,把每一帧的高度即 image height 都改之后,能在第八帧拿到 flag 。

misc30

题目提示正确宽度是 950 ,把 bmp 图片宽度改一下就有 flag 了。

misc31

已知高度和文件,求实际宽度

但是是个 bmp 文件,也就是说不能用 CRC 来解了。

查了资料, bmp 规定,图片中每一行像素数据所占用的字节数,必须是 4 的倍数。

Row_Size = math.floor((Width * bpp + 31) / 32) * 4

所以用下面脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import sys
import os

file_path = sys.argv[1]

with open(file_path, 'rb') as f:
data = f.read()

actual_size = os.path.getsize(file_path)
data_offset = int.from_bytes(data[10:14], byteorder='little')
height = int.from_bytes(data[22:26], byteorder='little')
bpp = int.from_bytes(data[28:30], byteorder='little')

print(f"[*] 实际文件大小: {actual_size} 字节")
print(f"[*] 像素数据偏移: {data_offset}")
print(f"[*] 已知高度: {height}")
print(f"[*] 色深(bpp): {bpp}")
print("-" * 30)

found = False
# 爆破宽度
for w in range(1, 10000):
row_size = ((w * bpp + 31) // 32) * 4
expected_size = data_offset + row_size * height

# 匹配文件大小
if expected_size == actual_size:
print(f"[+] 找到精确匹配的宽度: {w} (十六进制: {hex(w)})")
found = True

if not found:
print("[-] 没有找到精确匹配的宽度!")
print("[-] 正在尝试忽略末尾可能的附加隐藏数据进行推算...")
print("-" * 30)
# 如果有附加数据,实际大小肯定大于预期大小。我们找最接近且不超过的。
# 由于我们不知道到底附加了多少数据,所以反过来推算:
# 假设剩余数据全都是像素,估算最大可能的 row_size
max_row_size = (actual_size - data_offset) // height
if max_row_size > 0:
for w in range(1, 10000):
row_size = ((w * bpp + 31) // 32) * 4
if row_size == max_row_size:
print(f"[?] 忽略末尾附加数据,推算可能的宽度: {w} (十六进制: {hex(w)})")

拿到宽度是 1082 ,最后得到 flag 。

misc32

这题就是 png 图片的宽度,用 CRC 就好了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import zlib
import struct
import sys

file_path = sys.argv[1]

with open(file_path, 'rb') as f:
all_b = f.read()
crc32key = int(all_b[29:33].hex(), 16)
data = bytearray(all_b[12:29])

for w in range(10000):
width = bytearray(struct.pack('>i', w))
for x in range(4):
data[x+4] = width[x]

crc32result = zlib.crc32(data)
if crc32result == crc32key:
print(f"w: {w}")
print(f"hex w: {hex(w)}")
sys.exit(0)

拿到宽度是 1044 ,修改后得到 flag 。

misc33

嗯,出题人把宽高都改了,要同时爆破宽高了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import zlib
import struct
import sys

file_path = sys.argv[1]

with open(file_path, 'rb') as f:
all_b = f.read()
crc32key = int(all_b[29:33].hex(), 16)
data = bytearray(all_b[12:29])

for w in range(1, 4096):
width = bytearray(struct.pack('>i', w))
for h in range(1, 4096):
height = bytearray(struct.pack('>i', h))
for x in range(4):
data[x+4] = width[x]
data[x+8] = height[x]

crc32result = zlib.crc32(data)
if crc32result == crc32key:
print(f"w: {w}, h: {h}")
print(f"hex w: {hex(w)}, hex h: {hex(h)}")
sys.exit(0)

得到:w: 978, h: 142

然后拿到 flag 。

misc34

这题出题人把 CRC 也改了,那我们需要考虑 IDAT 数据块

图片所有的像素数据都经过 Zlib 压缩后存在 IDAT 数据块里。只要我们把这些 IDAT 数据提取出来并解压,得到的就是原始的、未经压缩的像素数据总和。这个解压后的数据总长度与图片的宽度、高度有着绝对的数学关系 。
PNG 规定,每一行像素数据在开头都要加上 1 个字节的“滤波类型(Filter Type)”标志。所以,解压后数据总长度的公式为:

$$\text{Total Length} = \text{Height} \times (1 + \lfloor \frac{\text{Width} \times \text{bpp} + 7}{8} \rfloor)$$

因为解压后的总长度(Total Length)和每个像素占据的位数(bpp,可以从 IHDR 读出)都是已知的,我们只需要让宽度从 901 开始递增。算出每一行应该占用的字节数后,去去除总长度。如果能整除,除出来的结果就是高度!

脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import struct
import zlib
import sys

file_path = sys.argv[1]

with open(file_path, 'rb') as f:
data = f.read()

bit_depth = data[24]
color_type = data[25]

bpp_multiplier = {0: 1, 2: 3, 3: 1, 4: 2, 6: 4}
bpp = bit_depth * bpp_multiplier[color_type]

idat_data = bytearray()
offset = 8
while offset < len(data):
length = struct.unpack('>I', data[offset:offset+4])[0]
chunk_type = data[offset+4:offset+8]
if chunk_type == b'IDAT':
idat_data.extend(data[offset+8:offset+8+length])
elif chunk_type == b'IEND':
break
offset += 12 + length

raw_pixels = zlib.decompress(idat_data)
total_len = len(raw_pixels)

for w in range(901, 10000): //题目信息大于900
row_bytes = 1 + (w * bpp + 7) // 8
if total_len % row_bytes == 0:
h = total_len // row_bytes
print(f"w: {w}, hex w: {hex(w)}")
print(f"h: {h}, hex h: {hex(h)}")

得到:

w: 1123, hex w: 0x463
h: 173, hex h: 0xad

最后拿到 flag 。

misc35

啧,是 jpg 的,它没有之前那些能够严谨推出宽高的,只能用脚本生成多个图片观察。

固定一个比较大的高度值,防止被截断,然后测试宽度:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import os
import sys

file_path = sys.argv[1]

with open(file_path, 'rb') as f:
data = bytearray(f.read())

sof0_offset = -1
for i in range(len(data) - 1):
if data[i] == 0xFF and data[i+1] == 0xC0:
sof0_offset = i
break

if sof0_offset == -1:
sys.exit()

out_dir = "jpg_bruteforce"
if not os.path.exists(out_dir):
os.makedirs(out_dir)

fixed_height = 2000
data[sof0_offset + 5] = (fixed_height >> 8) & 0xFF
data[sof0_offset + 6] = fixed_height & 0xFF

for w in range(901, 2000):
data[sof0_offset + 7] = (w >> 8) & 0xFF
data[sof0_offset + 8] = w & 0xFF

out_path = os.path.join(out_dir, f"w_{w}.jpg")
with open(out_path, 'wb') as out_f:
out_f.write(data)

观察后得到宽度为 993 时,得到 flag 。

misc36

我去,是 gif 的,那只能和 jpg 一样了

不过 gif 需要修改每一帧的宽高:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import os
import sys

file_path = sys.argv[1]

with open(file_path, 'rb') as f:
original_data = bytearray(f.read())

out_dir = "gif_frames_bruteforce"
if not os.path.exists(out_dir):
os.makedirs(out_dir)

orig_w_b = original_data[6:8]
orig_h_b = original_data[8:10]

fixed_height = 2000
fh_b = bytes([fixed_height & 0xFF, (fixed_height >> 8) & 0xFF])

for w in range(920, 951):
data = bytearray(original_data)
fw_b = bytes([w & 0xFF, (w >> 8) & 0xFF])

data[6:8] = fw_b
data[8:10] = fh_b

for i in range(len(data) - 10):
if data[i] == 0x2C and data[i+5:i+7] == orig_w_b and data[i+7:i+9] == orig_h_b:
data[i+5:i+7] = fw_b
data[i+7:i+9] = fh_b

out_path = os.path.join(out_dir, f"w_{w}.gif")
with open(out_path, 'wb') as out_f:
out_f.write(data)

宽度是 941.

misc37

flag 被拆分成多段在 gif 画面中,提取每一帧画面:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import sys
import os
from PIL import Image

file_path = sys.argv[1]
img = Image.open(file_path)

out_dir = "gif_frames_extracted"
if not os.path.exists(out_dir):
os.makedirs(out_dir)

frame_num = 0
try:
while True:
img.seek(frame_num)
out_path = os.path.join(out_dir, f"frame_{frame_num}.png")
img.save(out_path)
frame_num += 1
except EOFError:
pass

即可拿到 flag 。

misc38

png 文件,常规的 010 发现这个图片很大,用 binwalk 打开里面一堆的 zlib

放到浏览器查看发现图片闪过 flag

查询资料发现这实际上是一个 APNG 文件,它能和 gif 一样存储动画,但是清晰度更高。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
import sys
import os
from PIL import Image

# 检查命令行参数
if len(sys.argv) < 2:
print("Usage: python extract_apng_frames.py <your_image.png>")
sys.exit(1)

file_path = sys.argv[1]

# 检查文件是否存在
if not os.path.exists(file_path):
print(f"File not found: {file_path}")
sys.exit(1)

try:
# 尝试打开 APNG 图片
img = Image.open(file_path)
except Exception as e:
print(f"Error opening image: {e}")
sys.exit(1)

# 创建输出目录
out_dir = "apng_frames_extracted"
if not os.path.exists(out_dir):
os.makedirs(out_dir)

print(f"[*] 正在分析 APNG 文件: {file_path}")
print(f"[*] 准备提取所有帧到目录: {out_dir}")

frame_num = 0
try:
while True:
# Pillow 的 seek 方法可以将“光标”移动到动画的某一帧
# 它同样适用于 APNG 格式
img.seek(frame_num)

# 为了安全提取,最好复制当前帧的数据,并确保它是一个带有完整 Alpha 通道的图像
# 有时直接 seek 并不解耦,导致保存错误
frame = img.copy()
if frame.mode != 'RGBA':
frame = frame.convert('RGBA')

out_path = os.path.join(out_dir, f"frame_{frame_num}.png")
frame.save(out_path)
print(f"[+] 成功提取第 {frame_num} 帧: {out_path}")

frame_num += 1

except EOFError:
# EOFError (End Of File) 表示 seek 到了不存在的帧,意味着动画已结束
print("-" * 30)
print(f"[*] 提取完成!共提取了 {frame_num} 帧画面。")
except Exception as e:
print(f"[-] 提取第 {frame_num} 帧时出错: {e}")

提取每一帧画面,拼接后得到 flag 。

misc39

是个 gif 文件,看不出什么异常。

!!!

gif 的延迟时间这里只有两种,370 毫秒和 360 毫秒,然后分别用 0,1 代替之后,ascii 转可见字符,得到 flag

!!!

想不到,这个让我幻视之前一道压缩包的题目了

就是压缩包的 deflate 方法只用了两种,然后也是转成 0,1 得到的flag

下面的脚本来自 ctfshow 的 qscf1234 的 wp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
from PIL import Image, ImageSequence

# 打开GIF文件
gif_path = r'misc39.gif'
delay_time_flag = ''
with Image.open(gif_path) as img:
global_delay = img.info['duration'] # 获取全局延迟,如果存在的话
print(f"Global delay: {global_delay} milliseconds")
# 获取GIF的迭代器
for frame in ImageSequence.Iterator(img):
# 获取当前帧的间隔(以毫秒为单位)
delay = frame.info['duration'] # 注意:在某些情况下,可能需要使用img.info['duration']
if delay:
# print(f"Frame delay: {delay} milliseconds")
if delay==370:
delay_time_flag += '1'
elif delay==360:
delay_time_flag += '0'
else:
print('异常')
else:
# 如果某些帧的延迟信息缺失,frame.info['duration']可能会返回None。在这种情况下,你可以使用默认的延迟值,通常为100毫秒。
# print(f"Frame delay: {100} milliseconds")
pass
print(delay_time_flag)
flag = ''
for i in range(0, len(delay_time_flag), 7):
# print(int(delay_time_flag[i:i+7], 2))
flag += chr(int(delay_time_flag[i:i+7], 2))
print(flag)

misc40

是 APNG 图片,和上一题差不多,拿到延迟分子转成可见字符。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import sys
import struct

file_path = sys.argv[1]

with open(file_path, 'rb') as f:
data = f.read()

result = ""
offset = 0

while True:
idx = data.find(b'fcTL', offset)
if idx == -1:
break

delay_num = struct.unpack('>H', data[idx+24:idx+26])[0]

try:
if delay_num > 0:
result += chr(delay_num)
except ValueError:
pass

offset = idx + 4

print(result)

结果:

å¥å¨ç¼åï¼çé½ä¸åctfshow{95ca0297dff0f6b1bdaca394a6fcb95b}

前面一串乱码发现实际上是一段套娃编码

对应的数是: 229, 165, 151, 229, 168, 131, 231, 188, 157, 229, 144, 136, 239, 188, 140, 231, 139, 151, 233, 131, 189, 228, 184, 141, 229, 129, 154

首先,把以上数字(Unicode编码10进制)都转换为16进制,在随波逐流里,(Unicode编码10进制)>>>(字符)>>>(16进制)

e5 a5 97 e5 a8 83 e7 bc 9d e5 90 88 ef bc 8c e7 8b 97 e9 83 bd e4 b8 8d e5 81 9a

三数为一组,字母变大写

E5A597 ,E5A883 ,E7BC9D ,E59088 ,EFBC8C ,E78B97 ,E983BD ,E4B88D ,E5819A

最后的文字:套娃缝合,狗都不做

misc42

png 文件,学乖了先看它是不是 APNG ,发现不是,于是常规的 binwalk ,010,tweakpng

发现它有一堆 IDAT 数据块,再结合题目说 flag 长度是 41 位,考虑 IDAT 长度隐写

把每个 IDAT 的长度拿到之后转为可见字符,得到 flag 。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import sys
import struct

file_path = sys.argv[1]

with open(file_path, 'rb') as f:
f.read(8)
result = ""

while True:
len_bytes = f.read(4)
if not len_bytes or len(len_bytes) < 4:
break

length = struct.unpack('>I', len_bytes)[0]
chunk_type = f.read(4)

if chunk_type == b'IDAT':
try:
result += chr(length)
except ValueError:
pass
elif chunk_type == b'IEND':
break

f.read(length + 4)

print(result)

得到å¿å¿1ctfshow{078cbd0f9c8d3f2158e70529f8913c65}

misc43

一番常规流程查看,发现 tweakpng 一直弹出 IDAT 块报错,所以可能是要 CRC32 拼接

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import sys
import struct

file_path = sys.argv[1]

with open(file_path, 'rb') as f:
f.read(8)
result = b""

while True:
len_bytes = f.read(4)
if not len_bytes or len(len_bytes) < 4:
break

length = struct.unpack('>I', len_bytes)[0]
chunk_type = f.read(4)

f.read(length)

crc_bytes = f.read(4)
result += crc_bytes

if chunk_type == b'IEND':
break

print(result.decode('ascii', errors='ignore'))

拿到:a.ctfshow{6eb2589ffff5e390fe6b87504dbc0892}B`