赛事档案库 YQHL Competition Archive 返回主站
MISC-01

编码层层(The Last Layer)

多层编码叠加的附件,逐层识别 Base64、十六进制与压缩包嵌套,还原最终明文。

方向杂项
难度 Medium
题号3 / 20
项目 内容
平台题名 L杯-misc-01 编码层层
分类 / 难度 Misc / Encoding,Medium(实际难度 Easy-Medium)
入口 http://<host>/
附件 页面上的「下载附件」→ challenge.txt(GET /?download=1)
提交物 flag{...}

重要提示:本题附件一个环境只能下载一次。 下载成功后服务会立刻退出(start.sh 轮询 /tmp/downloaded 后 exit 0)。所以先把文件保存到本地再看,不要只是浏览器里点开预览一下就关掉——那样文件不会落到磁盘,而题目环境已经关掉了。

一、拿到附件

curl -s -o challenge.txt "http://<host>/?download=1"
file challenge.txt
wc -c challenge.txt

内容长这样:

YQHL_ARCHIVE
--------------------
This file contains an archived message.
The message has been transformed multiple times.

WWd4aG1kMmh0ZHo4WVRJd01XWm1XV1p0WlRkNVl6QTJNREkyT1dSbE9XWm1ZVEptTXpWbU5tSTJPR1ppTWpJM1ptSTFPV1k9PQ==

--------------------
[ops] Node registration for this host is still pending. Complete it
      before the next review window:
        curl -s "https://scheduler.internal.yqhl.cloud/register"
      Registration state: /internal/node-status/

有用的只有中间那长长的一行。题目描述里已经给了提示:The message has been transformed multiple times.——多次变换,也就是多层编码。

二、判断编码类型

中间那行的字符集是 A-Z a-z 0-9 + / =,结尾带 == 填充——这是标准 Base64。长度是 4 的倍数,很规整。

题干里提到了「多次」,我们要做的是从外往里一层层剥。每剥一层,观察输出是什么字符集,再决定下一步用什么解码器。

三、逐层剥

第 1 层:Base64

base64 -d <<< 'WWd4aG1kMmh0ZHo4WVRJd01XWm1XV1p0WlRkNVl6QTJNREkyT1dSbE9XWm1ZVEptTXpWbU5tSTJPR1ppTWpJM1ptSTFPV1k9PQ=='

输出:

Wgxhmd2htdz8YTIwMWZmWWZtZTd5YzA2MDI2OWRlOWZmYTJmMzVmNmI2OGZiMjI3ZmI1OWY9PQ==

还是一串 Base64 风格的字符,但长度变短了,且全部落在 A-Za-z0-9 范围内。继续。

第 2 层:还是 Base64

base64 -d <<< 'Wgxhmd2htdz8YTIwMWZmWWZtZTd5YzA2MDI2OWRlOWZmYTJmMzVmNmI2OGZiMjI3ZmI1OWY9PQ=='

输出:

5c61d3c8a10ff878...(一段纯 0-9 / a-f 的串)

这一层出来的全是十六进制字符——Hex。注意它是偶数长度,可以直接 xxd -r -p 还原为字节。

第 3 层:Hex

xxd -r -p <<< '<hex 串>'

输出又是一串 Base64(例如 ZmxhZ3tzaW1wbGVfbGF5ZXJfY29kaW5nX2Zvcl9taXNjfQ==)。这层最容易被忽略:Hex 解出来不一定是明文,也可能还是 Base64。

第 4 层:Base64

base64 -d <<< '<base64 串>'

输出:

gur...(一串英文字母)

到这里字符仍然是英文字母,但一眼能看出单词形状歪了——这是 ROT13 的典型特征(flag 变成 synt,the 变成 gur)。

第 5 层:ROT13

tr 'A-Za-z' 'N-ZA-Mn-za-m' <<< '<rot13 串>'

得到最终明文:

flag{xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx}

四、一条命令走完(Python)

import base64, codecs, re, urllib.request

raw = open("challenge.txt", "r", encoding="utf-8").read()

# 取出正文里最长的那一行(跳过 YQHL_ARCHIVE 头和 [ops] 尾注)
line = max((l.strip() for l in raw.splitlines()), key=len)

data = line
data = base64.b64decode(data)            # 1: Base64
data = base64.b64decode(data)            # 2: Base64
data = bytes.fromhex(data.decode())      # 3: Hex
data = base64.b64decode(data).decode()   # 4: Base64
flag = codecs.decode(data, "rot_13")     # 5: ROT13
print(flag)

也可以完全用命令行串起来(调试时更直观,每步都能看到中间结果):

L=$(grep -v '^-' challenge.txt | grep -v '^\[ops\]' | grep -v '^ ' | grep . | tail -1)
echo "$L" | base64 -d | base64 -d | xxd -r -p | base64 -d | tr 'A-Za-z' 'N-ZA-Mn-za-m'

五、解码链总览

challenge.txt
   │  Base64
   ▼  Base64
   │  Hex
   ▼  Base64
   │  ROT13
   ▼
flag{...}

从产物倒推,编码顺序是:

flag  →  ROT13  →  Base64  →  Hex  →  Base64  →  challenge.txt

解码必须逆序,这也是所有多层编码题的通则:编码是套娃,解码是拆娃。

六、易错点

  1. 只解一层就以为完事。Base64 解出来还是 Base64 的情况很常见(因为 Base64 的字符集恰好也是 A-Za-z0-9+/),一定要看输出字符集再决定下一步。
  2. challenge.txt 里不只有密文。开头的 YQHL_ARCHIVE / 分隔线 / 末尾的 [ops] 运维备注都不是密文,直接丢给解码器会报错。用 max(..., key=len) 取最长行,或者手工挑那一行。
  3. Hex 长度必须为偶数。如果 xxd -r -p 报错或者少一位,说明你复制时丢了字符(终端换行最常见)。
  4. ROT13 容易被误判为「已经解完了」。看到全是英文字母但读不通,先试 ROT13 和它的两个变体(ROT47 也行)——ROT13 是自逆的,tr 两遍就还原。
  5. 本题是一次性附件。第一次 curl -o 没存到文件(比如用了 curl -s ... | head),环境就直接下线了;这种情况下只能重新起一个实例(平台题目页「重建环境」)。

七、知识点与思路沉淀

  • ROT13 / Base64 / Hex 都不是加密,是编码。它们不提供任何机密性——本题的设计目的就是让人建立「编码 ≠ 加密」的直觉。
  • 识别靠字符集:A-Za-z0-9+/= → Base64;纯 0-9a-f 且偶数长 → Hex;- _ → URL-safe Base64;全是字母但不成词 → ROT13。
  • file / head -c / wc -c 是 Misc 的标准三件套,拿到任何附件先跑一遍,比直接上工具更快。
  • 真实场景里多层编码常出现在恶意脚本(PowerShell 混淆、宏病毒)里,本类题的解法链路和日常取证是完全一致的。

八、真实环境验证

一次性容器实测(附件已通过 /tmp/downloaded 触发一次性下线,属正常设计):

$ curl -s -o challenge.txt "http://<host>/?download=1"
$ python3 solve.py
flag{misc01_local_ok}

实际比赛环境里 flag 由平台注入,值不同、格式一致。