带*号的为复现(呜呜呜

Personal Vault

直接搜索flag{得到

c61664ca-b31f-423c-b1a1-11cc38e58ad1

flag{personal_vault_seems_a_little_volatile_innit}

The_Interrogation_Room

找到必胜策略

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Q01:  ( ( S1 ) == S4 ) == 0
Q02: ( ( ( ( S0 ) == S6 ) == 0 ) == S7 ) == 0
Q03: ( ( ( ( ( ( S1 ) == S2 ) == 0 ) == S6 ) == 0 ) == S7 ) == 0
Q04: ( ( S2 ) == S5 ) == 0
Q05: ( ( ( ( ( ( S2 ) == S3 ) == 0 ) == S4 ) == 0 ) == S7 ) == 0
Q06: ( ( ( ( ( ( ( ( ( ( S0 ) == S2 ) == 0 ) == S3 ) == 0 ) == S4 ) == 0 ) == S5 ) == 0 ) == S7 ) == 0
Q07: ( ( S5 ) == S7 ) == 0
Q08: ( ( S3 ) == S6 ) == 0
Q09: ( ( ( ( ( ( ( ( S1 ) == S3 ) == 0 ) == S5 ) == 0 ) == S6 ) == 0 ) == S7 ) == 0
Q10: ( ( ( ( ( ( S2 ) == S3 ) == 0 ) == S4 ) == 0 ) == S6 ) == 0
Q11: ( ( ( ( ( ( ( ( S0 ) == S2 ) == 0 ) == S3 ) == 0 ) == S5 ) == 0 ) == S7 ) == 0
Q12: ( ( ( ( ( ( ( ( ( ( S0 ) == S3 ) == 0 ) == S4 ) == 0 ) == S5 ) == 0 ) == S6 ) == 0 ) == S7 ) == 0
Q13: ( ( S0 ) == S4 ) == 0
Q14: ( ( S1 ) == S3 ) == 0
Q15: ( ( ( ( ( ( S0 ) == S1 ) == 0 ) == S4 ) == 0 ) == S7 ) == 0
Q16: ( ( ( ( S0 ) == S1 ) == 0 ) == S3 ) == 0
Q17: ( ( ( ( ( ( ( ( S2 ) == S3 ) == 0 ) == S5 ) == 0 ) == S6 ) == 0 ) == S7 ) == 0

拷打ai写脚本爆破

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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
#!/usr/bin/env python3
"""
exploit_interrogation_full.py

完整交互脚本:使用指定 17 条非自适应查询与审问服务交互,
自动 PoW -> 提问 -> 解码 -> 提交,并稳健判断每轮成功/失败。
"""

import socket
import sys
import time
import re
import itertools
import hashlib

# ------------------ 配置与问题矩阵 ------------------
QUESTIONS = [
"( ( S1 ) == S4 ) == 0",
"( ( ( ( S0 ) == S6 ) == 0 ) == S7 ) == 0",
"( ( ( ( ( ( S1 ) == S2 ) == 0 ) == S6 ) == 0 ) == S7 ) == 0",
"( ( S2 ) == S5 ) == 0",
"( ( ( ( ( ( S2 ) == S3 ) == 0 ) == S4 ) == 0 ) == S7 ) == 0",
"( ( ( ( ( ( ( ( ( ( S0 ) == S2 ) == 0 ) == S3 ) == 0 ) == S4 ) == 0 ) == S5 ) == 0 ) == S7 ) == 0",
"( ( S5 ) == S7 ) == 0",
"( ( S3 ) == S6 ) == 0",
"( ( ( ( ( ( ( ( S1 ) == S3 ) == 0 ) == S5 ) == 0 ) == S6 ) == 0 ) == S7 ) == 0",
"( ( ( ( ( ( S2 ) == S3 ) == 0 ) == S4 ) == 0 ) == S6 ) == 0",
"( ( ( ( ( ( ( ( S0 ) == S2 ) == 0 ) == S3 ) == 0 ) == S5 ) == 0 ) == S7 ) == 0",
"( ( ( ( ( ( ( ( ( ( S0 ) == S3 ) == 0 ) == S4 ) == 0 ) == S5 ) == 0 ) == S6 ) == 0 ) == S7 ) == 0",
"( ( S0 ) == S4 ) == 0",
"( ( S1 ) == S3 ) == 0",
"( ( ( ( ( ( S0 ) == S1 ) == 0 ) == S4 ) == 0 ) == S7 ) == 0",
"( ( ( ( S0 ) == S1 ) == 0 ) == S3 ) == 0",
"( ( ( ( ( ( ( ( S2 ) == S3 ) == 0 ) == S5 ) == 0 ) == S6 ) == 0 ) == S7 ) == 0"
]

POW_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"

# ------------------ 网络读写工具 ------------------
def recv_some(sock, timeout=2.0):
"""尝试读取一些数据;超时或无数据返回空字符串(不会抛异常)。"""
try:
sock.settimeout(timeout)
data = sock.recv(4096)
if not data:
return ""
return data.decode(errors='ignore')
except socket.timeout:
return ""
except Exception:
return ""

def recv_until_keywords(sock, keywords, total_timeout=12.0, per_recv=1.0):
"""
循环读取,直到缓冲包含任一 keywords(忽略大小写),或超时。
返回累积缓冲(字符串)。
"""
deadline = time.time() + total_timeout
buf = ""
low = ""
while time.time() < deadline:
chunk = recv_some(sock, timeout=per_recv)
if chunk:
buf += chunk
low = buf.lower()
for kw in keywords:
if kw.lower() in low:
return buf
else:
time.sleep(0.02)
return buf

def send_line(sock, s):
if isinstance(s, str):
s = s.encode()
if not s.endswith(b'\n'):
s += b'\n'
sock.sendall(s)

# ------------------ PoW 求解 ------------------
def solve_pow_from_banner(banner):
m = re.search(r"sha256\(XXXX\+([A-Za-z0-9]+)\)\s*==\s*([0-9a-f]{64})", banner)
if not m:
return None
tail = m.group(1)
target = m.group(2)
for p in itertools.product(POW_CHARS, repeat=4):
prefix = ''.join(p)
if hashlib.sha256((prefix + tail).encode()).hexdigest() == target:
return prefix
return None

# ------------------ 本地表达式评估(模拟查询结果) ------------------
def eval_expr_on_S(expr, S):
"""
在给定 S(长度8的布尔元组)下评估表达式 expr(QUESTIONS 中的格式)。
将 S0..S7 注入局部命名空间,禁用 __builtins__。
"""
locals_map = { f"S{i}": bool(S[i]) for i in range(8) }
# 让 0/1 在 eval 中照常使用
locals_map["0"] = 0
locals_map["1"] = 1
try:
return bool(eval(expr, {"__builtins__": None}, locals_map))
except Exception:
return False

# ------------------ 解析服务器回答 ------------------
def parse_bool_from_text(text):
if not text:
return None
if re.search(r"\bTrue\b", text, re.IGNORECASE):
return True
if re.search(r"\bFalse\b", text, re.IGNORECASE):
return False
m = re.search(r"\b([01])\b", text)
if m:
return m.group(1) == '1'
return None

# ------------------ 解码(暴力最邻近,允许≤2错) ------------------
def decode_S_from_observed(observed_bits):
best_dist = 999
best_vals = []
for val in range(256):
S = tuple(((val >> i) & 1) == 1 for i in range(8))
expected = [1 if eval_expr_on_S(q, S) else 0 for q in QUESTIONS]
dist = sum(1 for a,b in zip(expected, observed_bits) if a != b)
if dist < best_dist:
best_dist = dist
best_vals = [val]
elif dist == best_dist:
best_vals.append(val)
return best_vals, best_dist

# ------------------ 轮后等待与判定(稳健) ------------------
def wait_round_end_or_next(sock, timeout_total=12.0, per_recv=1.0):
"""
提交答案后调用,等待出现成功或失败提示。
成功(返回 True):
"The prisoner scowls as you expose his lies" + "Very well" + "ask your next round"
或者出现 "ask your question" / "Welcome to the interrogation"
失败(返回 False):
包含 "fell for" / "fell for my deception"
超时(返回 None)
"""
end_time = time.time() + timeout_total
buf = ""
while time.time() < end_time:
chunk = recv_some(sock, timeout=per_recv)
if chunk:
buf += chunk
low = buf.lower()
# 失败优先判断
if "you fell for" in low or "fell for my deception" in low or "you fell for my deception" in low:
print("[server - failure]:", buf.strip())
return False
# 成功判断(严格与宽松两种匹配)
if ("the prisoner scowls as you expose his lies" in low and
"very well" in low and "ask your next round" in low):
print("[server - success]:", buf.strip())
return True
if "the prisoner scowls as you expose his lies" in low:
print("[server - success fragment]:", buf.strip())
return True
if "very well, ask your next round" in low or "very well" in low:
print("[server - success fragment]:", buf.strip())
return True
if "ask your question" in low or "welcome to the interrogation" in low:
print("[server - next round prompt]:", buf.strip())
return True
else:
time.sleep(0.02)
print("[warn] wait_round_end_or_next: timed out. buffer:", buf.strip()[:300])
return None

# ------------------ 主交互流程 ------------------
def run(host, port):
sock = socket.create_connection((host, port), timeout=10)
try:
banner = recv_until_keywords(sock, ["sha256(XXXX+", "Give me XXXX", "Notice:", "Ask your question"], total_timeout=6.0)
if banner:
print(banner.strip())
# PoW
if "sha256(XXXX+" in banner:
prefix = solve_pow_from_banner(banner)
if prefix is None:
print("[!] PoW parse/solve failed.")
return
print("[*] PoW solved:", prefix)
# 等提示并发送
_ = recv_until_keywords(sock, ["Give me XXXX", ":"], total_timeout=3.0)
send_line(sock, prefix)
time.sleep(0.05)
more = recv_some(sock, timeout=1.0)
if more:
print(more.strip())
# 进入循环(最多 25 轮),直到失败或达到上限
round_count = 0
while True:
round_count += 1
print("\n=== Round", round_count, "===")
# 等待/打印初始提示
intro = recv_until_keywords(sock, ["Ask your question", "Welcome to the interrogation", "Notice:"], total_timeout=6.0)
if intro:
print(intro.strip())
observed = []
for idx, q in enumerate(QUESTIONS, start=1):
# 等待提问提示
prompt = recv_until_keywords(sock, ["Ask your question", "Ask your question:"], total_timeout=8.0)
if prompt:
try:
print(prompt.strip().splitlines()[-1])
except:
print(prompt.strip())
# 发送问题(保证 token 间空格)
print(f"Sending Q{idx:02d}: {q}")
send_line(sock, q)
# 读取回应
resp = recv_until_keywords(sock, ["Prisoner's response", "The prisoner smirks", "Ask your question", "Now reveal"], total_timeout=4.0)
if not resp:
resp = recv_some(sock, timeout=1.0)
if resp:
last = resp.strip().splitlines()[-1]
print(" <-", last)
else:
print(" <- (no response captured)")
val = parse_bool_from_text(resp)
if val is None:
more = recv_some(sock, timeout=1.0)
combined = (resp or "") + more
val = parse_bool_from_text(combined)
if val is None:
print("[!] Could not parse boolean response, defaulting to False")
val = False
observed.append(1 if val else 0)
# 读取 "Now reveal..." 提示并打印
tail = recv_until_keywords(sock, ["Now reveal the true secrets", "Now reveal"], total_timeout=3.0)
if tail:
print(tail.strip())
print("[*] Observed bits:", observed)
# 解码
best_vals, best_dist = decode_S_from_observed(observed)
print(f"[*] Best distance: {best_dist}, #candidates: {len(best_vals)} (show up to 8): {best_vals[:8]}")
if best_dist <= 2 and best_vals:
chosen = best_vals[0]
S_bits = [(chosen >> i) & 1 for i in range(8)]
elif best_vals:
# 若最小距离大于2,仍取最小距离候选的第一个作为回退
chosen = best_vals[0]
S_bits = [(chosen >> i) & 1 for i in range(8)]
print("[warn] best_dist >2, using best candidate as fallback")
else:
S_bits = [0]*8
print("[warn] no candidates found, submitting all zeros as fallback")
print("[*] Submitting S0..S7:", S_bits)
send_line(sock, " ".join(str(b) for b in S_bits))
# 等待并判断本轮成败
res = wait_round_end_or_next(sock, timeout_total=12.0)
if res is True:
print("[*] Round succeeded — continue to next round.")
# loop continues
elif res is False:
print("[!] Round failed — stopping.")
break
else:
# 如果超时未检测到 definitive 信息,再短读一次并尝试判断
extra = recv_some(sock, timeout=2.0)
if extra:
print("[server extra]:", extra.strip())
low = extra.lower()
if "you fell for" in low or "fell for my deception" in low:
print("[!] Server reports failure in extra read.")
break
if "ask your question" in low or "very well" in low or "welcome to the interrogation" in low:
print("[*] Detected next prompt in extra read; continuing.")
continue
# 最后保守退出
print("[!] No clear verdict after waiting — exiting.")
break
# 安全上限
if round_count >= 25:
print("[*] reached max configured rounds (25).")
break
finally:
try:
sock.close()
except:
pass

# ------------------ 入口 ------------------
if __name__ == "__main__":
if len(sys.argv) < 3:
print("Usage: python3 exploit_interrogation_full.py <host> <port>")
sys.exit(1)
host = sys.argv[1]
port = int(sys.argv[2])
run(host, port)

c5f6f4aa-bf4a-4fbb-9a78-a18d40f9939e

flag{0392e6f4-8628-49df-8de6-1373ad76573a}

谍影重重6.0*

好抽象的题目,喜欢我800mb的流量包吗

首先用命令将udp的包都提取出,

tshark -r capture.pcap -T fields -e data -Y "udp" > udp_data.txt

打开发现一堆fffffffff的,直接将data转音频发现有一堆静音,猜测是12字节rtp头后全为ffff的行导致的

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
import re

def process(input_path='udp_data.txt', output_path='udp_data_clean.txt'):
total = kept = removed_ff = skipped_short = 0
with open(input_path, 'r', encoding='utf-8', errors='ignore') as fin, \
open(output_path, 'w', encoding='utf-8') as fout:
for line in fin:
total += 1
# 清洗:仅保留十六进制字符
s = re.sub(r'[^0-9a-fA-F]', '', line.strip())
if not s:
continue
# 12字节 = 24个十六进制字符
if len(s) < 24:
skipped_short += 1
continue
try:
data = bytes.fromhex(s)
except ValueError:
# 非法十六进制,跳过
continue
# 去掉前12字节RTP头
payload = data[12:]
if not payload:
continue
hx = payload.hex()
# 去掉去头后全是f的行(例如 ffffff...)
if hx and set(hx.lower()) == {'f'}:
removed_ff += 1
continue
fout.write(hx + '\n')
kept += 1
print(f'总计: {total},输出: {kept},删除全f: {removed_ff},过短: {skipped_short}')

if __name__ == '__main__':
process()

去掉后,转音频

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
from binascii import unhexlify
import audioop
import wave

# 从 udp_clean.txt 读取 u-law 负载(每行一个十六进制 payload)
payloads = []
with open("udp_data_clean.txt", "r", encoding="utf-8", errors="ignore") as f:
for line in f:
s = line.strip()
if not s:
continue
try:
payloads.append(unhexlify(s))
except Exception:
continue # 非法 hex 行,跳过

# 合并所有 payload
alaw = b"".join(payloads)

# u-law 解码为 16-bit PCM
pcm = audioop.ulaw2lin(alaw, 2) # 2 bytes per sample

# 放大音量 (factor > 1.0 放大, < 1.0 降低)
amplification_factor = 100.0 # 可以调整,比如 5~50
pcm = audioop.mul(pcm, 2, amplification_factor)

# 输出 WAV
with wave.open("bbb1_amplified.wav", "wb") as wf:
wf.setnchannels(1)
wf.setsampwidth(2)
wf.setframerate(8000)
wf.writeframes(pcm)

print(f"已输出 bbb1_amplified.wav,共 {len(payloads)} 帧。")

写脚本用funasr识别文字,

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
49
from pydub import AudioSegment
from funasr import AutoModel

# 原始音频路径
input_audio = "bbb1_amplified.wav"
# 增强后的音频路径
enhanced_audio = "enhanced.wav"

# ----------------------------
# 1️⃣ 读取音频并提高音量
# ----------------------------
audio = AudioSegment.from_wav(input_audio)

# 归一化音量
audio = audio.normalize()

# 提高音量,例如 +10 dB
audio += 10

# 保存增强后的音频
audio.export(enhanced_audio, format="wav")
print(f"✅ 音频已增强并保存为 {enhanced_audio}")

# ----------------------------
# 2️⃣ FunASR 识别
# ----------------------------
# 加载模型(中文 Paraformer 模型为例)
model = AutoModel(
model="paraformer-zh",
vad_model="fsmn-vad", # 可选:自动分段
punc_model="ct-punc" # 可选:自动加标点
)

# 执行识别
text_result = model.generate(input=enhanced_audio, batch_size_s=300)

# ----------------------------
# 3️⃣ 输出结果
# ----------------------------
for i, r in enumerate(text_result, 1):
print(f"[片段 {i}] 识别结果:{r['text']}")

# 保存到文件
with open("asr_result.txt", "w", encoding="utf-8") as f:
for r in text_result:
f.write(r["text"] + "\n")

print("✅ 识别结果已保存到 asr_result.txt")

image-20251023181657809

得到密令651466314514271616614214660701456661601411451426071146666014214371656514214470

尝试使用这串数字解压缩包失败,观察发现字符的范围是 0-7,猜测这里要八进制转字符

用动态规划算法切成若干个长度为 2 或 3 的八进制段寻找可能的解:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
s = "651466314514271616614214660701456661601411451426071146666014214371656514214470"

def decode_octal(s):
n = len(s)

def dfs(i, path):
if i == n:
yield "".join(path)
return
for l in (2, 3):
if i + l <= n:
part = s[i:i+l]
val = int(part, 8)
if 32 <= val <= 126:
yield from dfs(i + l, path + [chr(val)])

return dfs(0, [])

# 打印所有可能的解
for decoded in decode_octal(s):
print(decoded)

image-20251023185101730

发现有且仅有一个解:5f3eb916bf08e610aeb09f60bc955bd8

得到

转文字

image-20251023185500182

image-20251023191209323

flag{2a97dec80254cdb5c526376d0c683bdd}

legacyOLED*

一个sr文件,尝试用7z解压得到三个文件

image-20251023165634294

询问gpt metadata的内容,

image-20251023165711395

得知是一个sigrok的逻辑分析仪采集文件,能用Pulseview分析

image-20251023165921888

AI说 这个可能是spi或者I2C的数据

image-20251023170151876

再根据软件中的信息确定I2C地址

image-20251023171220359

image-20251023171432313

上图的SDA为D0,SCL为D1

先提取出数据

sigrok-cli -i legacyOLED.sr -P i2c:sda=D0:scl=D1 > i2c_dump.txt

image-20251023173850193

再让ai写个脚本提取一下每一帧的内容

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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
from pathlib import Path
import numpy as np
from PIL import Image
import matplotlib.pyplot as plt
import os

# ========== 基础配置 ==========
FILE_PATH = r".\logic-1-1" # 修改为你的文件路径
SDA_BIT = 0 # D0 为 SDA
SCL_BIT = 1 # D1 为 SCL
WIDTH, HEIGHT = 128, 64 # OLED 尺寸
ADDR_OLED = 0x78 # SSD1306 的写地址
OUTPUT_DIR = "oled_frames" # 帧图片输出目录
os.makedirs(OUTPUT_DIR, exist_ok=True)

# ========== I²C 解码 ==========
def decode_i2c(raw):
scl_last, sda_last = 1, 1
msgs, in_msg = [], False
bitcount, byteval = 0, 0

for b in raw:
scl = (b >> SCL_BIT) & 1
sda = (b >> SDA_BIT) & 1

# START
if scl == 1 and sda_last == 1 and sda == 0:
in_msg, bitcount, byteval = True, 0, 0
msgs.append(("START", None))

# STOP
if scl == 1 and sda_last == 0 and sda == 1:
if in_msg:
msgs.append(("STOP", None))
in_msg = False

# 上升沿采样
if scl_last == 0 and scl == 1 and in_msg:
if bitcount < 8:
byteval = (byteval << 1) | sda
elif bitcount == 8:
ack = sda
msgs.append(("BYTE", byteval, ack))
byteval, bitcount = 0, -1
bitcount += 1

scl_last, sda_last = scl, sda
return msgs

# ========== 事务划分 ==========
def group_transactions(msgs):
txs, cur = [], None
for m in msgs:
if m[0] == "START":
cur = []
elif m[0] == "STOP":
if cur is not None:
txs.append(cur)
cur = None
elif m[0] == "BYTE":
if cur is not None:
cur.append((m[1], m[2]))
return txs

# ========== OLED 数据重建 ==========
def reconstruct_frames(txs):
W, H = WIDTH, HEIGHT
addr_mode = 0
col_start, col_end = 0, W - 1
page_start, page_end = 0, (H // 8) - 1
cur_col, cur_page = 0, 0

fb = np.zeros((H, W), dtype=np.uint8)
frames = []
frame_idx = 0

for tx in txs:
if len(tx) < 2 or tx[0][0] != ADDR_OLED:
continue
control = tx[1][0]

if control == 0x00: # 命令模式
i = 2
while i < len(tx):
cmd = tx[i][0]; i += 1
if cmd == 0x20 and i < len(tx): # addressing mode
addr_mode = tx[i][0]; i += 1
elif cmd == 0x21 and i + 1 < len(tx): # column range
col_start, col_end = tx[i][0], tx[i + 1][0]
cur_col, i = col_start, i + 2
elif cmd == 0x22 and i + 1 < len(tx): # page range
page_start, page_end = tx[i][0], tx[i + 1][0]
cur_page, i = page_start, i + 2
else:
pass

elif control == 0x40: # 数据模式
for j in range(2, len(tx)):
data_byte = tx[j][0]
if (0 <= cur_col <= col_end) and (0 <= cur_page <= page_end):
for bit in range(8):
pixel = (data_byte >> bit) & 1
y = cur_page * 8 + bit
if y < H:
fb[y, cur_col] = 255 if pixel else 0

# horizontal 地址模式
if addr_mode == 0:
cur_col += 1
if cur_col > col_end:
cur_col = col_start
cur_page += 1
if cur_page > page_end:
cur_page = page_start
# 保存一帧
frame_img = Image.fromarray(fb.copy())
frame_path = os.path.join(OUTPUT_DIR, f"frame_{frame_idx:04}.png")
frame_img.save(frame_path)
frames.append(frame_path)
frame_idx += 1
# vertical 地址模式
elif addr_mode == 1:
cur_page += 1
if cur_page > page_end:
cur_page = page_start
cur_col += 1
if cur_col > col_end:
cur_col = col_start
# 保存一帧
frame_img = Image.fromarray(fb.copy())
frame_path = os.path.join(OUTPUT_DIR, f"frame_{frame_idx:04}.png")
frame_img.save(frame_path)
frames.append(frame_path)
frame_idx += 1
return frames

# ========== 主流程 ==========
if __name__ == "__main__":
raw = open(FILE_PATH, "rb").read()
msgs = decode_i2c(raw)
txs = group_transactions(msgs)
print(f"解码出 {len(txs)} 个 I2C 事务")

frames = reconstruct_frames(txs)
print(f"共提取 {len(frames)} 帧图像,保存在 {OUTPUT_DIR} 目录中")

# 显示第一帧示例
if frames:
img = Image.open(frames[0])
plt.imshow(img, cmap="gray", vmin=0, vmax=255)
plt.axis("off")
plt.show()

这个比较完整,代码提取上边的黑白像素

image-20251023174558477

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
from PIL import Image

def image_to_binary_array(image_path, threshold=128):
"""
将图片转成黑白(1/0)数组
:param image_path: 图像路径
:param threshold: 灰度阈值(0~255,默认128)
:return: 二维 list,每个像素对应 0 或 1
"""
img = Image.open(image_path).convert('L') # 转灰度
w, h = img.size
binary = []
for y in range(h):
row = []
for x in range(w):
pixel = img.getpixel((x, y))
row.append(1 if pixel > threshold else 0)
binary.append(row)
return binary

def save_binary_array(binary, output_file):
"""
将二进制数组保存为文本文件
"""
with open(output_file, "w", encoding="utf-8") as f:
for row in binary:
f.write(''.join(map(str, row)) + "\n")

if __name__ == "__main__":
path = "oled_frame_1082.png" # 输入图像路径
output_file = "output.txt" # 输出文件路径

data = image_to_binary_array(path)
save_binary_array(data, output_file)

print(f"解码完成,黑白数据已保存到 {output_file}")

头部数据解binary

image-20251023175226680

得到flagqwb{Re41_Ma5te7-O5-S5Dl3o6_12C}

BlackPearl*

询问ai得到思路

image-20251023221618282

使用gnuradio来raw转wav看看

image-20251023230001627

猜测为摩斯电码,手敲一下

1
-.- .--- --.- -..- . .. .. ..--- .- ....- .- --.- .- -- ....- ... .-- -..- ... --.- ..- .- .. ..-. .- -.-- .- .- -.- .- .. -... --.- -.-. .- .- -... .... ..-. -.- . ....- ... ..-. -.- .- --.- -.. .... ... -.-- .- .- -... ..-. -.- .- .- --.- .- -.-. -- -..- .-- -.. ..--- .- .- --. .- .- .. -- --.. .-- --. -.-. --.. --.. --- --- .-. ....- .... .. -- .- -... .- .- -... --.- --... ...-- -..- ..- -.-. .--- --. -.- -..- --.. --.- .-. --. -.-. -... ...- --. --... .... --.- ..-. --- .-. .-- ....- .-- --. -.... -.. -..- -.- .- -.. -.-. -.- .-- ..-. .-. -- .-. ....- --- .. ...- .-. .... . -. ....- .... --.- -... -.- .--. --. -.. .-. . .--- .. -.-- . -... . --.- .--- ... -.-. .-.. --. -..- --.- ..- .- -.-- -.-. --.. -... .-- --- .--- ..... -... -.... ..-. .-.. --- .- -.-. -- .-- .--- --.. --... --... ...- ..-. ..-. ...-- .-- .-. .--- . ...-- .-- -..- ... -- .-.. . -..- .--- .- ..- -.-- --... -.... .-.. .... --.. -.-. .-- -.... .--- .--. -- --- --... -.... -.-. --. .... -.... -... .... . -... ... .--- -... .... --- ..... --. .- .... .... -. -... --. .. ..--- .- ....- --... -. -. -..- .-. --.- ..-. --.. ...-- ..--- .- --.- --- -..- --- ...- ... .-. .- -- -.-. --.- .. .- .-

解码

image-20251023225502140

得到一个rar包,发现需要密码。在频谱图,往后拉:

image-20251023231659799

image-20251023231800744

密钥:1d3c8c7582dd,解压出flagflag{fa61cde1c174b-79a8-5cb9-3ea464e3dbca}