Writeup 4 CSS CTF Semester 2 2026 - Lamp Drill
题目信息
- 题目名称:Lamp Drill
- 分值:15
- 难度:Beginner
- 类型:Hardware / Reverse Engineering
- 题目描述:
Warm-up. No spaces.
Flag Format: CSSCTF{...}
These artists may help (Cost: 5 points)
- Offonoff
- Logical (Cost: 15 points)
A black filled lamp is 1. An empty circle is 0.
- 附件 :
lampDrill.svg
解题思路
1. 分析提示
题目给了两个提示:
提示 1(5 分):Offonoff
Offonoff 是一个韩国音乐组合,但在这里它是个双关:
- Off = 关 = 逻辑 0
- On = 开 = 逻辑 1
- "Off on off" = 0 1 0
这暗示我们要关注逻辑电平(0 和 1)。
提示 2(15 分):Logical
A black filled lamp is 1. An empty circle is 0.
这直接告诉我们编码方式:
- 黑色实心圆 = 1
- 白色空心圆 = 0
2. 分析 SVG 电路图
打开 lampDrill.svg,可以看到一张逻辑电路图:
顶部:逻辑门真值表
| 输入 A | 输入 B | 输出 |
|---|---|---|
| 1 (黑) | 1 (黑) | 1 (黑) |
| 1 (黑) | 0 (白) | 0 (白) |
| 0 (白) | 1 (黑) | 0 (白) |
| 0 (白) | 0 (白) | 0 (白) |
这完全符合 AND 门的真值表:只有两个输入都为 1 时,输出才为 1。
下方:3 行电路
每行有 8 个盒子 ,每个盒子里有 2 个圆(输入),盒子之间用箭头连接。
每个盒子是一个独立的 AND 门。
3. 提取数据
用脚本从 SVG 中提取所有圆形节点的坐标和填充色:
python
import re
from collections import defaultdict
with open('lampDrill.svg', 'r') as f:
content = f.read()
circles = []
pattern = r'<path d="M ([\d.]+) ([\d.]+)\s*C[^"]*"\s*clip-path="[^"]*"\s*style="fill: (#[0-9a-f]+); stroke: #[0-9a-f]+; stroke-width: 1.6'
for m in re.finditer(pattern, content):
x = float(m.group(1))
y = float(m.group(2))
fill = m.group(3)
val = 1 if fill == '#1c1915' else 0
circles.append((x, y, val))
rows = defaultdict(list)
for x, y, val in circles:
rows[round(y)].append((x, val))
for row_key in sorted(rows.keys()):
if row_key < 200:
continue
row = sorted(rows[row_key], key=lambda t: t[0])
values = [v for x, v in row]
print(f"y={row_key}: {values}")
输出:
y=281: [1, 0, 1, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 1, 1]
y=385: [0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1]
y=489: [0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1]
4. 计算 AND 输出
每行 16 个输入,每 2 个一组送入 AND 门,得到 8 位输出。
第 1 行(y=281):
| 输入对 | AND 输出 |
|---|---|
| 1, 0 | 0 |
| 1, 1 | 1 |
| 1, 1 | 1 |
| 0, 1 | 0 |
| 0, 0 | 0 |
| 1, 0 | 0 |
| 1, 1 | 1 |
| 1, 1 | 1 |
输出:0 1 1 0 0 0 1 1 = 01100011 = 0x63 = 'c'
第 2 行(y=385):
| 输入对 | AND 输出 |
|---|---|
| 0, 1 | 0 |
| 1, 1 | 1 |
| 1, 1 | 1 |
| 1, 1 | 1 |
| 1, 0 | 0 |
| 0, 1 | 0 |
| 1, 1 | 1 |
| 1, 1 | 1 |
输出:0 1 1 1 0 0 1 1 = 01110011 = 0x73 = 's'
第 3 行(y=489):
| 输入对 | AND 输出 |
|---|---|
| 0, 0 | 0 |
| 1, 1 | 1 |
| 1, 1 | 1 |
| 1, 1 | 1 |
| 1, 0 | 0 |
| 0, 1 | 0 |
| 1, 1 | 1 |
| 1, 1 | 1 |
输出:0 1 1 1 0 0 1 1 = 01110011 = 0x73 = 's'
5. 拼接结果
三行输出对应的 ASCII 字符:
c + s + s = css
6. 最终 Flag
CSSCTF{css}
解题脚本
python
import re
from collections import defaultdict
with open('lampDrill.svg', 'r') as f:
content = f.read()
circles = []
pattern = r'<path d="M ([\d.]+) ([\d.]+)\s*C[^"]*"\s*clip-path="[^"]*"\s*style="fill: (#[0-9a-f]+); stroke: #[0-9a-f]+; stroke-width: 1.6'
for m in re.finditer(pattern, content):
x = float(m.group(1))
y = float(m.group(2))
fill = m.group(3)
val = 1 if fill == '#1c1915' else 0
circles.append((x, y, val))
rows = defaultdict(list)
for x, y, val in circles:
rows[round(y)].append((x, val))
result = ""
for row_key in sorted(rows.keys()):
if row_key < 200:
continue
row = sorted(rows[row_key], key=lambda t: t[0])
values = [v for x, v in row]
outputs = []
for i in range(0, len(values), 2):
if i+1 < len(values):
outputs.append(values[i] & values[i+1])
if len(outputs) == 8:
byte = int(''.join(map(str, outputs)), 2)
result += chr(byte)
print(f"y={row_key}: {outputs} -> {chr(byte)}")
print(f"Flag: CSSCTF{{{result}}}")
输出:
C:\Users\Administrator\AppData\Local\Programs\Python\Python314\python.exe exp.py
y=281: [0, 1, 1, 0, 0, 0, 1, 1] -> c
y=385: [0, 1, 1, 1, 0, 0, 1, 1] -> s
y=489: [0, 1, 1, 1, 0, 0, 1, 1] -> s
Flag: CSSCTF{css}
进程已结束,退出代码为 0
关键点总结
- 编码:黑圆 = 1,白圆 = 0(提示 2)
- 逻辑门 :顶部示例确认是 AND 门(只有 1,1 输出 1)
- 数据提取:从 SVG 中按 y 坐标分组提取圆形节点
- 计算:每行 16 个输入 → 8 个 AND 输出 → 1 字节 ASCII
- 结果 :3 行得到
c、s、s→css
最终 Flag:
CSSCTF{css}
Rambo
2026年国庆节
🎉🎉🎉