CodeShell.kr - Time 3

Challenge

题面只给一张镜像主题的配图,真正的附件要先从页面内嵌资源里取出来。三次一密本身是已知攻击,难点在于载荷要先从 PNG 的位平面里挖出来,而它自己会说明用法。

How about thinking backwards? Take it step by step.

倒着想怎么样?一步一步来。

1
https://codeshell.kr/challenges/time3/

Solution

配图是镜像主题的 emoji,页面没有附件下载链接,真正的数据在页面内嵌的 time-3-stego.png 里。

Step 1:用 zsteg 扫位平面,b1,r,lsb,xy 命中 ASCII 头:

1
2
$ zsteg -a assets/challenge-images/time-3-stego.png
b1,r,lsb,xy .. text: "TIME 3\nKNOWN PLAINTEXT 1 (ASCII):\nTHIS IS THE FIRST MESSAGE. ..."

Step 2:载荷自己描述了攻击方式:三条密文从第 0 字节起共用同一个 pad,并直接给出第一条消息的明文。

1
2
3
4
5
6
TIME 3
KNOWN PLAINTEXT 1 (ASCII):
THIS IS THE FIRST MESSAGE. THE KEY WAS REUSED THREE TIMES. THE SECOND AND THIRD MESSAGES SHARE THE SAME PAD FROM BYTE ZERO.
CIPHERTEXT 1 (hex): ff24d2af...(123 字节)
CIPHERTEXT 2 (hex): ...(92 字节)
CIPHERTEXT 3 (hex): ...(86 字节)

Step 3:多次一密:pad = C1 XOR P1,再用同一 pad 解 C2、C3。

1
2
P2: THE SECOND MESSAGE IS A DISTRACTION. USE THE SHARED PAD TO READ WHAT THE THIRD MESSAGE SAYS.
P3: THE THIRD MESSAGE SAYS CODESHELL{...}. SUBMIT ONLY THE FLAG.

Script

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
#!/usr/bin/env python3
"""CodeShell.kr — Time 3 (Stegano, 50p) solver.

The PNG carries a zsteg b1,r,lsb,xy payload whose ASCII header states the
setup: three messages encrypted with the SAME pad from byte zero, plus the
plaintext of message 1. XORing C1 with the known plaintext recovers the pad;
the same pad decrypts C2 and C3.

Usage:
uv run python solvers/time3.py
"""

import re
import subprocess
from pathlib import Path

PNG = "assets/challenge-images/time-3-stego.png"


def extract_payload(png=PNG):
"""Pull the R-channel LSB (b1,r,lsb,xy) plane via zsteg."""
out = subprocess.run(
["zsteg", "-e", "b1,r,lsb,xy", png],
capture_output=True, check=True,
).stdout
return out


def main():
raw = extract_payload()
text = raw.decode("latin1")
head = text[:4096]

known = re.search(r"KNOWN PLAINTEXT 1 \(ASCII\):\s*\n(.*?)\nCIPHERTEXT 1", head, re.S).group(1)
cts = [bytes.fromhex(h) for h in re.findall(r"CIPHERTEXT \d \(hex\):\s*\n([0-9a-f]+)", head)]
c1, c2, c3 = cts[0], cts[1], cts[2]
p1 = known.encode()

print(f"P1 len={len(p1)} C1={len(c1)} C2={len(c2)} C3={len(c3)}")
pad = bytes(a ^ b for a, b in zip(c1, p1))

for name, ct in (("P2", c2), ("P3", c3)):
pt = bytes(a ^ b for a, b in zip(ct, pad))
print(f"{name}: {pt!r}")


if __name__ == "__main__":
main()
CodeShell{third_time_was_the_charm_7f2a}