2026.8.10(7)【Misc】找到呆唯

📌 题目信息

项目内容
题目名称找到呆唯
题目来源青少年CTF S1·2026 公益赛(投稿题)
题目类型Misc
子分类压缩包爆破 / JPEG隐写 / Base64解码
考点字典爆破、JPEG COM 注释段隐写、多重 Base64、二维码
难度⭐⭐⭐
最终 Flagflag{iam_here!!!}

题面:呆唯走丢了,找到她,她会告诉你 flag 的。

🛠️ 使用工具

  • unzip / fcrackzip:压缩包密码爆破
  • Python3:Base64 解码、JPEG 段解析、提取隐藏数据
  • zbarimg:二维码扫描识别
  • binwalk / file:文件类型判断

📝 解题思路

题目给两个附件:

  • 提示.zip加密,需爆破密码)
  • which_kind_of_Base64.txt(Base64 文本,/9j/ 开头 → 实为 JPEG)

流程分三步:

  1. 爆破压缩包:结合提示"喜欢符号和数字",符号+数字组合爆破,得到密码 1/45*,解出工具提示;
  2. 解码 Base64 → JPEG → 提取隐写:Base64 解码出 JPEG,其 COM 注释段(FF FE 里藏了另一段 Base64,解码后提取出隐藏的 PNG(二维码);
  3. 扫码:识别二维码得到 flag。

第 1 步:爆破压缩包密码,并解码“可能用到的工具.txt”

提示.zip 加密,zip 内有个目录叫「我喜欢符号和数字哦」,提示密码只含符号和数字。用字典/掩码爆破(符号集如 !@#$%^&*()_+-=/? × 数字 0-9),命中密码:

1/45*

用密码解压,得到 提示/可能用到的工具.txt

解码 txt,得到隐写工具链接(关键提示):

可能用到的工具.txt 里是「就 这 ¿」和「不 会 吧 ?」两种词组的重复(字间有空格)。这是两态(二进制)编码,所以可以转换成0/1编码,转换后尝试 8 位一组转换成ASCII,但本题比较特殊,不是 8 位一组——经尝试,正确分组是 9 位一组:

import re
s = open('可能用到的工具.txt', encoding='utf-8').read()
# 提取两种词组的顺序(去掉空格)
tokens = re.findall(r'就这|不会吧', s.replace(' ', ''))
# 就这→0, 不会吧→1
binseq = ''.join('0' if t == '就这' else '1' for t in tokens)
print(len(binseq))          # 468, 468//9 = 52, 整除

# 9位一组转字符
out = ''.join(chr(int(binseq[i:i+9], 2)) for i in range(0, len(binseq), 9))
print(out)


解码结果:

https://tools.cmdragon.cn/zh/apps/steganography-tool

这个链接是 CMDragon 的「文件隐写工具」——把文件隐藏到 JPG 图像的在线工具。这就是解题工具提示

⚠️ 易错点:这组两态文本不是标准 8 位 ASCII 二进制,用 8 位分组会得到乱码;正确是 9 位一组(468 = 9×52)。所以看到"两种词重复"先别急着按常规二进制解,先算总位数能否整除、多试几种位宽(7/8/9)。

⚠️ 工具说明:这个在线工具在 JPG 的注释/字段里写入隐藏文件并用自定义格式标记(本题里是 STEGO37880: 头 + base64)。


第 2 步:Base64 解码 which_kind_of_Base64.txt 得到 JPEG 图

由于题目提示了JPG 图像隐写工具网站,而且which_kind_of_Base64.txt 开头是 /9j/——标准 Base64 的 JPEG 头(/9j/ 解码 = \xff\xd8\xff)。因此用标准 Base64 解码:

import base64
raw = open('which_kind_of_Base64.txt','rb').read().strip()
data = base64.b64decode(raw)          # 标准 Base64
open('img.jpg','wb').write(data)      # 得到 JPEG
# 文件头 ffd8fffe ... 说明是 JPEG


第 3 步:解析 JPEG 的 COM 注释段(FF FE)

exiftool 是干什么的:

exiftool 是一个功能强大的命令行工具,主要用来读取 / 写入 / 编辑各种文件的元数据(metadata)。核心能力:

  1. 读元数据:图片(EXIF、GPS、拍摄参数)、视频、PDF、Office 文件等的内置信息
  2. 写/改元数据:改拍摄时间、作者、GPS 坐标等
  3. 解析文件内部结构:包括 JPEG 的各个段(APP0/APP1/EXIF/COM 等)
  4. CTF 里最有用的:快速暴露藏在元数据/注释段里的字符串(比如这里的 STEGO37880 base64)
    它支持几百种文件格式,是取证/CTF 的常规武器之一。
exiftool img.jpg

关键发现:COM 段内容以 STEGO37880: 开头(这就是那个JPG 图像隐写工具网站的关键特征!!),后面紧跟一大段 Base64(UAAAAH...)。

⚠️ JPEG 结构速记:

  • FF D8 文件头
  • FF E0 APP0(JFIF)
  • FF E2 APP2(ICC)
  • FF FE COM 注释段 ← 常用来藏数据
  • FF DB 量化表、FF C4 Huffman 表、FF DA SOS(之后是图像数据)

第 4 步:提取 STEGO37880 后的隐藏 PNG

STEGO37880 是隐写工具的标识/头。把它后面的 Base64 解码,得到 P\x00\x00\x00 + JSON 元数据 + 隐藏的 PNG

# -*- coding: utf-8 -*-
"""读取当前目录JPEG的COM(FF FE)注释段, 提取STEGO37880后的base64解码为PNG"""
import base64

# ---------- 第一步: 读取jpg并解析COM段 ----------
jpg_name = "img.jpg"           # 改成你的jpg文件名
with open(jpg_name, 'rb') as f:
    data = f.read()
print(f"JPEG大小: {len(data)} 字节")
# 遍历JPEG段, 找 FF FE (COM) 段
com_content = None
i = 2
while i < len(data) - 1:
    if data[i] != 0xFF:
        i += 1
        continue
    marker = data[i+1]
    if marker == 0xD8 or marker == 0xD9 or 0xD0 <= marker <= 0xD7:
        i += 2
        continue
    seglen = (data[i+2] << 8) | data[i+3]   # 段长度(大端,含自身2字节)
    if marker == 0xFE:      # COM 注释段
        com_content = data[i+4 : i+2+seglen]
        print(f"找到 COM 段 @偏移{i}, 长度={seglen}")
        break
    i += 2 + seglen
    if marker == 0xDA:
        break
if com_content is None:
    print("没有找到 COM(FF FE) 段")
    exit()
print("COM内容开头:", com_content[:50])

# ---------- 第二步: 提取 STEGO37880: 后的 base64 ----------
idx = com_content.find(b'STEGO37880')
if idx == -1:
    print("没找到 STEGO37880 标记")
    exit()
b64 = com_content[idx+len(b'STEGO37880'):]
b64 = b64.decode(errors='replace').strip()
if b64.startswith(':'):
    b64 = b64[1:]            # 去掉冒号
print("base64字符串长度:", len(b64))

# ---------- 第三步: base64解码, 提取PNG ----------
pad = (4 - len(b64) % 4) % 4
d = base64.b64decode(b64 + '='*pad)     # 补padding
print("解码长度:", len(d))
png_sig = d.find(b'\x89PNG\r\n\x1a\n')  # 找PNG签名
if png_sig == -1:
    print("没找到PNG签名")
    exit()
png = d[png_sig:]
# 裁剪到 IEND 结束(避免尾部CRC错)
iend = png.rfind(b'IEND')
if iend >= 0:
    png = png[:iend+12]
open("qr.png", 'wb').write(png)
print(f"已保存二维码: qr.png (长度 {len(png)})")

# ---------- 第四步(可选): 直接用pyzbar扫 -----------------
try:
    from PIL import Image
    from pyzbar.pyzbar import decode
    im = Image.open("qr.png")
    res = decode(im)
    for r in res:
        print("QR内容:", r.data.decode())
except Exception as e:
    print("扫码步骤(可选)跳过:", e)


说明:

  • 解码开头:b'P\x00\x00\x00{"name":"haha.png","type":"image/png","size":28326,...}' —— 是一段 JSON 头 + 尾随的 PNG 二进制。
  • 提取出的 PNG 是 400×400 的二维码图

第 5 步:扫码获取 Flag

zbarimg(或手机扫码)识别二维码图获取flag。


✅ 最终 Flag

flag{iam_here!!!}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值