前言
一个更好的方法与本文章方法不一样,都可以用来参考。
解题
下载附件得到一个无后缀文件

发现头部被修改。
如果想知道如何判断,可以参考:
CTF必知:以一次MISC题目完整分析png图片16进制分别代表什么(干货满满!)
python
data = open("steganography_challenge","rb").read()
print("Header:",data[:35].hex())
idx = data.find(b"\x89PNG")
print("PNG at offset:",idx)

到35字节的地方有PNG的IDAT。
删除前面的字节后。
用zsteg功能扫描一下。

前面的因为跳过的报错不用管,只需要注意最后一条118行无效滤波字节 254。这个警告说明 PNG 滤波字节被修改了。
说明图片身高造假,就是要修改一下图片的高。

把高改成00 00 00 76(118)即可。
如果想知道如何判断,可以参考:
CTF必知:以一次MISC题目完整分析png图片16进制分别代表什么(干货满满!)

提取成功。

解压后每个压缩包都加密了。
但是其中6个和密码有关的只有4个字节。
CRC爆破。
可以用代码:
python
import binascii, struct, itertools, string
# 从各 pass*.zip 头中读取 CRC32
crcs = {
"pass1": 0xce70d424,
"pass2": 0xf90c8a70,
"pass3": 0xff3fe4bb,
"pass4": 0x242a5387,
"pass5": 0x9a27098e,
"pass6": 0xd3f6df9f,
}
# 枚举所有 4 字节可打印 ASCII,匹配 CRC32
charset = "".join(chr(i) for i in range(32, 127))
crc_to_plain = {}
for combo in itertools.product(charset, repeat=4):
candidate = "".join(combo).encode()
c = binascii.crc32(candidate) & 0xffffffff
if c in set(crcs.values()):
crc_to_plain[c] = candidate.decode()

这4个是CRC32。如果想知道如何判断,可以参考:
CTF必知:以一次MISC题目完整分析png图片16进制分别代表什么(干货满满!)
脚本:
python
#!/usr/bin/env python3
import binascii
import itertools
import string
from concurrent.futures import ThreadPoolExecutor
crc_dict = {
'ce70d424': 'pass1',
'f90c8a70': 'pass2',
'ff3fe4bb': 'pass3',
'242a5387': 'pass4',
'9a27098e': 'pass5',
'd3f6df9f': 'pass6'
}
targets = {int(k, 16): v for k, v in crc_dict.items()}
chars = "".join(chr(i) for i in range(32, 127)) # 修复 Bug
results = {}
lock = __import__('threading').Lock()
def check_batch(batch):
"""检查一批组合"""
local_results = {}
for p in batch:
s = "".join(p)
crc = binascii.crc32(s.encode()) & 0xffffffff
if crc in targets:
local_results[targets[crc]] = s
return local_results
def main():
print("开始 CRC32 碰撞爆破...")
print(f"目标 CRC 数:{len(targets)}")
print(f"字符集大小:{len(chars)}")
print(f"总组合数:{len(chars)**4:,}")
print()
# 分批次处理
all_combos = itertools.product(chars, repeat=4)
batch_size = 100000
batch = []
completed = 0
with ThreadPoolExecutor(max_workers=8) as executor:
futures = []
for combo in all_combos:
batch.append(combo)
completed += 1
if len(batch) >= batch_size:
futures.append(executor.submit(check_batch, batch.copy()))
batch = []
if completed % 1000000 == 0:
print(f"进度:{completed:,}/{len(chars)**4:,}")
# 处理剩余
if batch:
futures.append(executor.submit(check_batch, batch))
# 收集结果
for future in futures:
for name, s in future.result().items():
if name not in results:
results[name] = s
print(f"✓ {name} = {s}")
# 检查是否完成
if len(results) == 6:
print("\n" + "="*50)
pwd = "".join([results[f'pass{i}'] for i in range(1, 7)])
print(f"🎉 密码:{pwd}")
print("="*50)
return pwd
print(f"\n只找到 {len(results)}/6 个")
return None
if __name__ == '__main__':
main()

或者直接用ZipCracker。

最终得到:
python
pass is c1!xxtLf%fXYPkaA
解压后得到:

存在零宽字符隐写。
python
txt = open("flag.txt",encoding="utf-8").read()
ZWSP = "\u200b"
ZWNJ = "\u200c"
bits = []
for char in txt:
if char == ZWSP:
bits.append("0")
elif char == ZWNJ:
bits.append("1")
flag = ""
for i in range(0,len(bits) - 7 ,8):
flag += chr(int("".join(bits[i:i+8]),2))
print(flag)
得到dart{bf4100d9-cc8d-48f6-a095-54cbfad189e1}。