CTBUCTF2025 Misc方向 Writeup
666和999的故事
出题人:G3rling
难度:Easy
题目描述:
Misc之旅从这里开始……
拿到题目附件打开,将字符缩小,可以依稀看到01组成的是一个类似于二维码一样的东西
让GPT写一个脚本进行转化,将666转化为黑色像素,999转化成白色像素
from PIL import Image
def read_pattern_from_file(file_path):
with open(file_path, 'r') as file:
lines = [line.strip() for line in file.readlines() if line.strip()]
# 按3个字符一组分割每行的数字
pattern = []
for line in lines:
pattern.append([line[i:i+3] for i in range(0, len(line), 3)])
width = len(pattern[0]) # 每行的数字组数
height = len(pattern) # 行数
return pattern, width, height
def create_image_from_pattern(pattern, width, height):
pic = Image.new("1", (width, height)) # 使用 "1" 模式创建黑白图像
for y in range(height):
for x in range(width):
# 将"999"转换为白色(255),"666"转换为黑色(0)
pixel_value = 255 if pattern[y][x] == '999' else 0
pic.putpixel((x, y), pixel_value)
pic.show()
pic.save("flag.png")
file_path = "attachment.txt" # 替换为你的txt文件路径
try:
pattern, width, height = read_pattern_from_file(file_path)
print(f"图像尺寸: {width}x{height}")
create_image_from_pattern(pattern, width, height)
except ValueError as e:
print(f"错误: {e}")
这里还可以直接用记事本进行一个小小的非预期,缩到最小即可看到Flag
转化后得到一张二维码,扫码得到Flag
ctbuctf{The_Misc_Begins_Here_0a5cda}
Do you know SSTV?
出题人:G3rling
难度:Mid
题目描述:
什么是"SSTV"?,第一个"S"代表“Slow”,意为“缓慢的”;第二个"S"代表“Scan”,意为“扫描”;"S"代表“Slow”,意为“缓慢的”;不然就“To Vanish“吧
根据题目可以知道这道题是SSTV(慢扫描电视)
慢扫描电视(Slow-scan television),简称SSTV,是业余无线电爱好者的一种主要图片传输方法,慢扫描电视通过无线电传输和接收单色或彩色静态图片,且完全不依赖网络。
这里通过配置虚拟声卡采集电脑内的声音,借助SSTV接收软件显示出图像(MMSSTV接收出来的是有问题的,需要换一个其他的接收软件,这里使用的是RX-SSTV)
得到Flag
ctbuctf{N0thing_1s_impossible}
Ez_Base64
出题人:G3rling
难度:Mid
题目描述:
Think carefully.This Base64 is very simple.
尝试解码几排,发现内容都是重复的
但是编码后的内容存在不同,这就涉及到Base64的编码原理了
Base64 是一种将二进制数据编码为 ASCII 字符的编码方式,其核心原理是将 3 字节(24 位)的二进制数据分割为 4 个 6 位的片段,再将每个 6 位片段映射到预定义的 64 个可打印 ASCII 字符上。但是如果编码内容不为3字节的整数倍,那么就会出现特殊情况
拿A的编码作为例子
可以看到为了凑成3字节组,编码时将会在二进制数据后补0。二进制数据中标为黄色的0是可以隐藏信息,因为在黄色位置将默认的0修改为其他二进制数,在解码时不会影响其解码结果。
根据这个原理写出解密脚本
file_path = "attachment.txt"
file = open(file_path,'r')
a = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
flag_temp = ''
while True:
text = file.readline()
text = text.replace("\n", "")
if not text:
break
if text.count('=') == 1:
flag_temp = flag_temp + \
str('{:02b}'.format((a.find(text[len(text)-2])) % 4))
if text.count('=') == 2:
flag_temp = flag_temp + \
str('{:04b}'.format((a.find(text[len(text)-3])) % 16))
file.close()
flag = ""
temp = len(flag_temp)
temp = temp//8*8
for i in range(0,temp,8):
flag = flag + chr(int(flag_temp[i:i+8],2))
print(flag)
得到Flag
ctbuctf{Base64_1s_Fun_0a1304}
书读万遍其意自现
出题人:G3rling
难度:Hard
题目描述:
这是一本答案之书,随便翻开一页,他会给你想要的答案
本题设置了两种做法,第一种是常规解,这道题主要考察词频分析,在收集到5w次左右词频会趋于稳定
from pwn import *
flags = []
char_count = {}
io = remote('ctf.ctbu.edu.cn', 32940)
for _ in range(50000):
io.sendlineafter(b'you want >', b'1')
io.recvuntil(b'and saw: ')
char = io.recv(1).decode('utf-8')
if char in char_count:
char_count[char] += 1
else:
char_count[char] = 1
sorted_char_count = sorted(char_count.items(), key=lambda item: item[1], reverse=True)
for char, count in sorted_char_count:
print(f"Character: {char}, Frequency: {count}")
for char, count in sorted_char_count:
print(char, end='')
io.close()
另外一种是Pwn的解法,设置了一个整形溢出(这里应该先弄个溢出的,很多同学都走了这个解法)
from pwn import *
io = remote('ctf.ctbu.edu.cn', 32940)
io.sendlineafter(b'want >', str((ord('C')+0xdeadbeef)-(1<<32)).encode())
io.sendlineafter(b'what?', str((ord('T')+0xdeadbeef)-(1<<32)).encode())
io.sendlineafter(b'pwner??', str((ord('B')+0xdeadbeef)-(1<<32)).encode())
io.sendlineafter(b'so crazy!',str((ord('U')+0xdeadbeef)-(1<<32)).encode())
io.interactive()
因为Flag头中有重复的英文单词,需要修改一下统计得到的Flag头
ctbuctf{CAYLESsaNDdOMoRe}
Png_Aio
出题人:G3rling
难度:Hard
题目描述:
小小PNG,拿下!
首先对图像宽高进行爆破,得到Flag1
使用zsteg查看LSB通道,发现Flag2
同时可以看到存在一个zlib,继续查看图片的IDAT块
可以看到在中部出现了断层,将第一个到长度为42628的IDAT块删除后对宽高进行爆破,得到Flag3
综上所述
ctbuctf{Look_H1gher_And_DeEeper}
vivo50保卫战:决战星期四
出题人:G3rling
难度:Baby
题目描述:
爷爷最近想起在家里存放着一张 vivo50 代金券,这张代金券被锁在一个保险箱里,但他忘记了密码。而更让人头疼的是,这个保险箱的锁并不是普通的密码锁,而是一个特殊的 Brainfuck 编写的程序,你能帮助爷爷破解保险箱密码,让爷爷在星期四前拿到 vivo50 代金券吗?
👉 第一位打开保险箱的同学请联系G3rling领取vivo50代金卷
考点是Brainfuck,为了模拟保险箱逐位破解的效果,带了一点侧信道爆破味儿
from pwn import *
charset = '0123456789abcdefghijklmnopqrstuvwxyz'
def bf_encode_string(s):
return ''.join(['+' * ord(c) + '.[-]' for c in s])
io = remote("ctf.ctbu.edu.cn", 32975)
password = "ctbuctf{"
while not password.endswith('}'):
for ch in charset:
io.recvuntil(b'Enter the password')
attempt = password + ch + "}"
bf_code = bf_encode_string(attempt)
io.sendlineafter(b'>> ', bf_code.encode())
response = b""
while b"\nW" not in response: # 只获取Wrong Password的首字母,节省时间
response += io.recv(1)
if password.encode() in response:
print(response)
if b"\nC" in response:
password += ch + "}"
print(f"Final password: {password}")
exit(0)
if attempt.encode() in response: # 只有ch正确时才会处理},当接收到@时证明ch正确
password += ch
print(f"Found next password: {password}")
break
Zip_Master
出题人:G3rling
难度:Baby
题目描述:
想必新生赛你已经学会基本操作了,是不是很简单呢?那让我们来练习一下前面的操作,快来试试这个吧~
G3rling把flag.txt压缩后放在了桌面上,但是附件遭到了损坏,很多关键部分都被删除了。
据G3rling回忆能够记得这些信息:压缩软件为Bandizip 7.06
压缩方式为Deflate
被压缩文件的最后修改时间为2025/05/02 10:32:28
文件CRC为 6BB27AB2
原始大小为46
扩展字段长度为36
外部文件属性为32
当前磁盘上的中央目录记录数为1
中央目录总条目数为1
中央目录的总大小为90
中央目录相对于第一个entry的起始位置为81请根据以上信息修复压缩包,得到flag.txt
因为今年好几次比赛都遇到了修复压缩包的题目,这里直接拉通
Zip文件结构
zip文件结构由三部分组成:压缩文件源数据区 + 压缩源文件目录区 + 压缩源文件目录结束标志
文件源数据区
字段名称 | 字段描述 |
---|---|
frSignature | ZIP文件头,固定值504B0304 |
frVersion | 解压所需的 pkware 版本 |
frFLags | 全局方式位标记,最低位的1bit是1表示加密,是0表示未加密 |
frCompression | 压缩方法(具体值如Store、Deflate等) |
frFileTime | 被压缩文件的最后修改时间 |
frFileDate | 被压缩文件的最后修改日期 |
frCrc | 文件压缩前 CRC-32 的值 |
frCompressedSize | 被压缩文件压缩后的大小 |
frUncompressedSize | 被压缩文件压缩前的大小 |
frFileNameLength | 被压缩文件的文件名长度 |
frExtraFieldLength | 扩展字段长度(具体值如0等) |
frFileName | 被压缩文件的文件名 |
frData | 被压缩文件压缩后的数据 |
文件目录区
字段名称 | 字段描述 |
---|---|
deSignature | 签名,通常是504B0102 |
deVersionMadeBy | 制作于哪个 pkware 版本 |
deVersionToExtract | 解析该目录需要的版本号,与数据区frVersion的值一致 |
deFLags | 一般标志位,与数据区frFlags的值一致 |
deCompression | 压缩方法,与数据区frCompression的值一致 |
deFileTime | 被压缩文件的最后修改时间,与数据区中对应字段的值一致 |
deFileDate | 被压缩文件的最后修改日期,与数据区中对应字段的值一致 |
deCrc | 文件压缩前 CRC-32 的值 |
deCompressedSize | 被压缩文件压缩后的大小 |
deUncompressedSize | 被压缩文件压缩前的大小 |
deFileNameLength | 被压缩文件的文件名长度 |
deExtraFieldLength | 扩展字段长度(与数据区的对应值可能不一致) |
deFileCommentLength | 文件备注长度 |
deDiskNumberStart | 文件起始位置所在的磁盘编号(早期磁盘很小的情况下,压缩包可能跨磁盘) |
deInternalAttributes | 内部文件属性 |
deExternalAttributes | 外部文件属性 |
uint deHeaderOffset | 本目录指向的entry相对于第一个entry的起始位置,单位byte,这也就限制了ZIP文件最大也就是4G了,再大就无法定位了 |
deFileName | 被压缩文件的文件名 |
deExtraField | 扩展字段 |
文件目录结束
字段名称 | 字段描述 |
---|---|
elSignature | 签名,通常是504B0506 |
elDiskNumber | 当前磁盘编号 |
elStartDiskNumber | 中央目录起始磁盘编号 |
elEntriesOnDisk | 当前磁盘上的中央目录记录数 |
elEntriesInDirectory | 中央目录总条目数 |
elDirectorySize | 中央目录的总大小 |
elDirectoryOffset | 中央目录相对于第一个entry的起始位置 |
elCommentLength | 注释长度 |
char elComment | 压缩包注释 |
根据以上材料对附件进行修复,附上完整十六进制数据(大意了,被GPT秒了)
50 4b 03 04 14 00 00 00 08 00 0e 54 a2 5a b2 7a b2 6b 2b 00 00 00 2e 00 00 00 08 00 00 00 66 6c 61 67 2e 74 78 74 4b 2e 49 2a 4d 2e 49 ab 0e cf cf cf 2f 07 a2 f8 c8 fc fc d2 c4 f8 a8 cc 82 78 df c4 e2 92 54 20 28 52 7c bf a7 51 11 44 d4 02 00 50 4b 01 02 14 00 14 00 00 00 08 00 0e 54 a2 5a b2 7a b2 6b 2b 00 00 00 2e 00 00 00 08 00 24 00 00 00 00 00 00 00 20 00 00 00 00 00 00 00 66 6c 61 67 2e 74 78 74 0a 00 20 00 00 00 00 00 01 00 18 00 63 2f 46 71 0a bb db 01 aa 05 16 a7 0a bb db 01 b2 7b f4 08 0a bb db 01 50 4b 05 06 00 00 00 00 01 00 01 00 5a 00 00 00 51 00 00 00 00 00
解压得到Flag
ctbuctf{Wooowoow_Yooua_Zip_Masteeeer!!!!!}