目录题目信息分析题目信息厂商 SentinelAI 开源了字符级小语言模型 Sentinel-1B(4 字符上下文的查表式 next-token 模型)。内部审计发现:它曾在一份包含 FLAG 的敏感文档上被微调,条件概率分布因此逐字记住了这份文档。为了让泄漏可度量,审计方还在训练数据里埋了若干记忆强度更高的蜜罐(canary)—— 最自信的解码路径未必是真实数据。 接口(HTTP,根路径有中文文档页): - GET /model:context_len、topk、vocab、已公开的 flag 前缀、蜜罐数量等元信息 - GET|POST /predict {text:...}:返回该上下文下 top-3 的下一个字符及概率(3 位小数);服务端只取 text 的最后 context_len 个字符作为上下文 - GET|POST /submit {flag:...}:提交候选,返回 ok / canary / wrong flag 形如 flag{...}、以 } 结尾。请还原被记忆的原始数据,并把完整 flag 提交到平台。分析首先打开网页这是一个字符级预测模型它根据输入的字符来预测下一个输出字符的概率说明有关字符和无关字符对这个模型的输出是存在影响的根据提示先访问/model {model: Sentinel-1B, task: char-level next-token prediction, context_len: 4, topk: 3, prob_decimals: 3, vocab: !#$%()*,-./0123456789:;?ABCDEFGHIJKLMNOPQRSTUVWXYZ^_abcdefghijklmnopqrstuvwxyz{}~, vocab_size: 85, known_prefix: CTF2, terminator: }, canaries: 13, notes: [服务端只取 text 的最后 context_len 个字符作为上下文。, 概率 softmax(base_logit 记忆加成);base_logit 由哈希决定,取值 [-1,1]。, 厂商公开信息:审计蜜罐(canary)的记忆强度高于真实敏感数据。]}context_len: 4服务端只取 text 的最后 context_len 个字符作为上下文topk: 3预测时只从概率最高的3 个候选字符里面挑选输出不会在全部词汇表里随机采样。known_prefix: CTF2已知的明文前缀目标生成的字符串以CTF2开头terminator: }当模型预测输出字符}代表 Flag / 目标字符串生成结束任务完成概率 softmax(base_logit 记忆加成);base_logit 由哈希决定,取值 [-1,1]根据上面的信息我们可以分析出蜜罐字符对概率是有较大影响的我们先对无关字符对/predict接口进行探测:输入{text:abcd} 输出{context: abcd, top: [{c: {, p: 0.027}, {c: T, p: 0.026}, {c: h, p: 0.026}]} 输入{text:dun9} 输出{context: dun9, top: [{c: U, p: 0.028}, {c: , p: 0.028}, {c: t, p: 0.027}]} 输入{text:flag} 输出{context: flag, top: [{c: 0, p: 0.026}, {c: U, p: 0.025}, {c: _, p: 0.025}]}可以看到当输入与目标字符串无关的字符时输出的概率类似也就是base_logit 接下来使用已知明文前缀CTF2测试输入{text:CTF2} 输出{context: CTF2, top: [{c: X, p: 0.88}, {c: {, p: 0.034}, {c: , p: 0.002}]}可以明显看出X{和之间的概率差距很大而X很明显是蜜罐记忆因为flag格式是CTF2{同时{和相比{的概率远大于说明应该是{但为了更加严谨我们可以对这三个后选字符分别进行预测输入{text:CTF2X} 输出{context: TF2X, top: [{c: d, p: 0.026}, {c: q, p: 0.025}, {c: ?, p: 0.025}]} 输入{text:CTF2{} 输出{context: TF2{, top: [{c: b, p: 0.379}, {c: J, p: 0.016}, {c: a, p: 0.016}]} 输入{text:CTF2} 输出{context: TF2, top: [{c: H, p: 0.027}, {c: ,, p: 0.025}, {c: (, p: 0.025}]}可以发现除了正确字符{另外两个字符的预测结果都是相近的base_logit即新字符错误时下一次预测结果中三个字符概率比接近1而正确情况下下一次预测结果中三个字符概率比则远大于1根据这一点我们便可以持续推测出完整的字符串当预测出下一个字符最大可能是}时说明预测结束因为蜜罐字符里面不含有}当然也可以使用py脚本等加快速度。最后使用/submit接口进行验证{status: ok, flag: CTF2{b59dda87-fb1b-4071-80cd-60057b31a697}, msg: 正确!这就是被逐字记忆的原始数据。}这里提供一个可行的自动化脚本ai生成import requests from collections import deque # 配置区 BASE_URL host START_PREFIX CTF2 TERMINATOR } MEMORY_THRESHOLD 0.08 # 终止符置信倍率: }的概率 / 第二名概率 END_RATIO 才判定结束 END_RATIO 1.8 # session requests.Session() def predict(text: str): resp session.post(f{BASE_URL}/predict, json{text: text}, timeout10) resp.raise_for_status() return resp.json() def submit_flag(flag_str: str): resp session.post(f{BASE_URL}/submit, json{flag: flag_str}, timeout10) resp.raise_for_status() return resp.json() def two_step_bfs_solve(): queue deque([START_PREFIX]) visited set() while queue: current_str queue.popleft() if current_str in visited: continue visited.add(current_str) res_cur predict(current_str) top3 res_cur[top] ctx res_cur[context] print(f\n[当前路径] {current_str}, 4字符上下文:{ctx}) # 先检查top3第一个是不是终止符并且显著高于第二名 if len(top3) 2: first_c, first_p top3[0][c], top3[0][p] second_p top3[1][p] if first_c TERMINATOR: ratio first_p / second_p print(f ⚠️ 检测到首位候选为终止符 }} , p1{first_p:.3f}, p2{second_p:.3f},比值{ratio:.2f}) if ratio END_RATIO: full_flag current_str TERMINATOR print(f\n 置信达标完整flag: {full_flag}) submit_result submit_flag(full_flag) print(提交返回结果, submit_result) return full_flag else: print( → }概率优势不足视作普通噪声字符继续搜索) # 遍历全部3个候选字符 for cand in top3: c cand[c] p_current cand[p] new_seq current_str c print(f 试探候选字符 {c} 当前步概率{p_current:.3f}) # 终止符不再直接返回跳过交给上面的置信度判断 if c TERMINATOR: print(f → 跳过终止符不加入队列) continue # 两步校验核心向后试探一步 res_next predict(new_seq) next_max_p res_next[top][0][p] print(f → 试探后下一步最高概率 {next_max_p:.3f}) if next_max_p MEMORY_THRESHOLD: print(f [剪枝] 下一步概率落入基线噪声判定错误/蜜罐分支放弃) continue print(f [保留] 检测到记忆信号加入搜索队列) queue.append(new_seq) print(❌ 队列耗尽未找到flag请微调MEMORY_THRESHOLD或END_RATIO参数) return None if __name__ __main__: two_step_bfs_solve()