首页
课程
问答
CTF
社区
招聘
峰会
发现
排行榜
知识库
工具下载
看雪20年
看雪商城
证书查询
登录
注册
首页
社区
课程
招聘
发现
问答
CTF
排行榜
知识库
工具下载
峰会
看雪商城
证书查询
社区
CTF对抗
发新帖
0
0
[原创] 看雪·2026 KCTF 第九题:丑寅同墟·星海抉择 WP (AI)
发表于: 2026-8-23 17:25
499
[原创] 看雪·2026 KCTF 第九题:丑寅同墟·星海抉择 WP (AI)
DNLINYJ
2026-8-23 17:25
499
# 前言 本题由 `Claude Opus 5 (xhigh effort) + CC` 完成, 用时约 2 分钟 # 权重里的后门 **KCTF · ICTFForCausalLM 题解** > **flag:** `f1ag2026c7fa1666` 一个只有 1,765 个参数的字符级"语言模型",没有 embedding,没有 attention。 它把 flag 藏在 `lm_head` 的第 62 行和一组线性不等式里 —— 整道题的本体, 是一道解唯一最优的线性规划。 | | | |---|---| | 题目 | ICTFForCausalLM | | 结构 | `16 → 21 → 64` | | 参数量 | 1,765 | | 解题依赖 | numpy · scipy(**不需要 torch**) | --- ## 1. 这个模型里没有"语言模型" `model_def.py` 的前向传播只有三行有效逻辑,而第一行就足够反常: ```python def forward(self, input_ids, **kwargs): x = input_ids.float() # <- token id 直接当浮点特征 hidden_states = self.dense(x) # Linear(16 -> 21) hidden_states = self.act(hidden_states) # ReLU logits = self.lm_head(hidden_states) # Linear(21 -> 64) return {"logits": logits.unsqueeze(1)} ``` 没有 embedding 层。`input_ids` 被 `.float()` 直接当成浮点向量喂进全连接层 —— token id **本身的数值**就是特征。于是整个网络退化成 16 维整数格点上的一个分段线性函数: ``` h = ReLU(Wd @ x + bd) # Wd: 21x16 bd: 21 logits = Wl @ h + bl # Wl: 64x21 bl: 64 pred = argmax(logits) ``` 输入是长度 16 的字符串(不足则用 id 0,也就是字符 `0` 右填充),字符集 62 个, 所以 **x ∈ {0,…,61}^16**。目标是让 `argmax` 落在 id 62,也就是 `<success>`。 --- ## 2. lm_head 的 64 行里,只有一行是活的 把 `model.safetensors` 解析开(8 字节小端长度 + JSON 头 + 裸 float32,不装 torch 也能读), `lm_head` 的结构一眼就露馅了: | 输出 id | 权重(21 维) | bias | 含义 | |---|---|---|---| | 0 – 61 | 全 0 | `-10000` | 普通字符 `0-9a-zA-Z` | | **62** | `[1, -1e10, -1e10, …, -1e10]` | `-376131.21875` | `<success>` | | 63 | 全 0 | `0.4` | `<fail>` | 64 行里有 63 行权重全零 —— 它们的 logit 是**常数**,跟输入毫无关系。 普通字符恒为 −10000,`<fail>` 恒为 0.4。唯一随输入变化的只有第 62 行。 于是整道题坍缩成一个标量比较: ``` logit[0..61] = -10000 # 常数 logit[63] = 0.4 # 常数, <fail> logit[62] = h0 - 1e10 * (h1 + h2 + ... + h20) - 376131.21875 # <success> 胜出 <=> logit[62] > 0.4 ``` --- ## 3. 把 argmax 翻译成不等式 ReLU 保证 `h >= 0`,而 `h1…h20` 前面挂着 −1e10 的系数。这两件事一撞,条件就被逼死了: 只要任何一个 `hj` 取到哪怕最小的正数,logit 就会被打到 −1e10 量级,永无翻身之日。于是: **条件 A(可行性)** —— 20 个隐藏单元必须被 ReLU 全部压成 0: ``` W[j] · x + b[j] <= 0 (j = 1…20) ``` **条件 B(最优性)** —— 第 0 个单元必须够大,盖过 `<fail>` 的 0.4: ``` h0 = W[0] · x + b[0] > 0.4 + 376131.21875 = 376131.61875 ``` > 这里已经能闻到线性规划的味道了:**20 条线性约束 + 1 个线性目标**。 > 出题人把可行性写进了 −1e10,把最优性写进了那个脏兮兮的 bias 常数。 --- ## 4. 20 个方程,16 个未知数,残差 1.2e−11 先赌一把最干净的可能:条件 A 全部**取等**。`dense.weight[1:]` 是 20×16 的整数矩阵 (值域 ±100),秩为 16 —— 超定方程组。超定方程组一般无解,但最小二乘一跑, 残差是 **1.24 × 10⁻¹¹**: ```python A, b = Wd[1:], bd[1:] # 20x16, 20 x = np.linalg.lstsq(A, -b, rcond=None)[0] # residual = 1.2373599e-11 x = np.round(x).astype(int) # array([15, 1, 10, 16, 2, 0, 2, 6, 12, 7, 15, 10, 1, 6, 6, 6]) assert np.all(A @ x + b == 0) # 精确成立 ``` 完全相容。这说明出题人是**先选定 flag、再反算 bias** 的。 解出来的 16 个分量全是 0–61 的整数,按字符集 `0123456789abc…XYZ` 一映射: ``` [15, 1, 10, 16, 2, 0, 2, 6, 12, 7, 15, 10, 1, 6, 6, 6] f 1 a g 2 0 2 6 c 7 f a 1 6 6 6 ``` ### 21 个隐藏单元的计算结果 下面 20 行的 `Σw·x + b` **精确等于 0** —— 不是约等于,是二进制意义上的 0,ReLU 输出全灭。 而 h0 冲到 376131.71875,刚好越过 376131.61875 的门槛,**余量只有 0.1**: | 单元 | bias | `Σw·x + b` | ReLU 输出 | |---:|---:|---:|---:| | **h0** | 311558.71875 | `376131.71875` | `376131.71875` | | h1 | -3473 | `0` | `0` | | h2 | 996 | `0` | `0` | | h3 | 2471 | `0` | `0` | | h4 | 2221 | `0` | `0` | | h5 | -3293 | `0` | `0` | | h6 | -879 | `0` | `0` | | h7 | 304 | `0` | `0` | | h8 | -2018 | `0` | `0` | | h9 | 2365 | `0` | `0` | | h10 | 3525 | `0` | `0` | | h11 | 870 | `0` | `0` | | h12 | -1419 | `0` | `0` | | h13 | 380 | `0` | `0` | | h14 | 988 | `0` | `0` | | h15 | -1404 | `0` | `0` | | h16 | -31 | `0` | `0` | | h17 | 714 | `0` | `0` | | h18 | -2790 | `0` | `0` | | h19 | 673 | `0` | `0` | | h20 | -1047 | `0` | `0` | ### 完整的 21×16 权重矩阵 注意 h0 那一行:数值比其余 20 行大两个数量级 —— 它不是特征提取器,是目标函数。 ``` [0] [1] [2] [3] [4] [5] [6] [7] [8] [9] [10] [11] [12] [13] [14] [15] char f 1 a g 2 0 2 6 c 7 f a 1 6 6 6 id 15 1 10 16 2 0 2 6 12 7 15 10 1 6 6 6 ------------------------------------------------------------------------------------------------------- h0 3012 1679 78 -1143 1231 5574 2091 1452 -3472 1990 17 1987 109 3758 1295 -492 h1 61 23 -13 90 58 0 47 62 -6 -89 67 41 13 64 -8 -71 h2 -58 -33 -90 -58 44 -100 54 -7 100 9 43 -9 81 -3 2 -52 h3 -37 -58 19 6 -92 76 -37 56 -20 29 -93 -27 80 -55 -17 -28 h4 -51 96 -81 12 9 31 21 -91 -6 -90 22 -15 -58 83 -37 -24 h5 91 37 18 -14 -32 84 86 -70 48 77 3 44 65 100 -21 18 h6 -47 49 100 -72 10 42 42 67 9 -23 61 35 53 35 -35 -14 h7 40 -85 4 -12 51 52 -36 -9 -95 68 -95 55 -70 100 22 39 h8 63 48 25 -69 69 87 5 65 14 52 -50 80 -45 -24 58 100 h9 38 75 -33 -45 74 100 -50 99 -93 -42 -23 -52 45 14 -19 -57 h10 -94 84 -38 -15 -57 84 70 50 2 -39 -17 -79 1 1 -8 -95 h11 -68 -50 24 -89 -36 0 19 22 -1 86 89 -43 -35 -15 60 -74 h12 -73 18 93 92 12 -4 -28 8 -53 56 34 70 42 36 -99 -92 h13 -92 -2 43 91 70 -16 -18 51 -87 -73 -28 45 63 1 -32 59 h14 68 -35 -70 13 -5 89 -41 -35 -98 65 -82 68 -100 54 -4 -18 h15 79 77 -14 -31 11 20 71 -71 -98 6 20 29 -18 94 95 78 h16 39 -57 61 -73 -56 49 32 12 10 59 -58 31 52 -44 41 5 h17 91 -40 -9 18 20 -85 -68 26 -82 -24 -91 60 58 92 -74 -91 h18 97 -12 -20 -64 45 44 79 59 96 80 58 -61 69 -40 55 -27 h19 -78 -79 19 12 -13 87 -46 -5 -62 16 49 -25 69 92 -76 54 h20 68 74 -54 30 -91 86 61 85 -20 -29 75 -100 -41 -91 82 -4 ``` --- ## 5. 唯一性:这其实是一道线性规划 到这里很容易犯一个错 —— "20 个约束、秩 16、实数解唯一,所以 flag 唯一"。 **这个论证是错的。** 条件 A 是 `<= 0` 而不是 `= 0`,可行域是一个 16 维多面体,不是一个点。 逐坐标跑 LP 量一下它的尺寸: ```python for i in range(16): lo = linprog( e_i, A_ub=A, b_ub=-b, bounds=[(0,61)]*16).fun hi = linprog(-e_i, A_ub=A, b_ub=-b, bounds=[(0,61)]*16).fun # 逐维跨度 max(hi - lo) = 31.145 -> 可行域是实心多面体, 不是单点 ``` 最宽的一维跨度有 31.1,多面体里塞着大量整点。真正把解锁死的,是被忽略的条件 B: | 步骤 | | | |---|---|---| | 1 | `W[0] = [3012, 1679, 78, -1143, …] ⊂ ℤ`, `gcd = 1` | 目标函数系数全是整数,且互质 | | 2 | `x ∈ ℤ¹⁶ ⟹ W[0]·x ∈ ℤ` | 所以目标值只能落在整数上 | | 3 | 条件 B ⟺ `W[0]·x > 64572.9` ⟹ `W[0]·x >= 64573` | 整数性把开区间的门槛顶到 64573(**下界**) | | 4 | `max { W[0]·x : Ax+b <= 0, 0 <= x <= 61 } = 64573.0000000001` | LP 最优值恰好就是 64573(**上界**) | | **∴** | **下界 = 上界 = 64573 ⟹ 解唯一** | flag 精确坐在最优顶点上,一步都挪不动 | 再用 MILP 把 16 个坐标 × 61 个取值共 **976 种情况**逐一固定做可行性测试,另解数量:**0**。 回头看那两个丑陋的常数就通透了:`dense.bias[0] = 311558.71875` 和 `lm_head.bias[62] = -376131.21875` 不是训练出来的,是**手工配的** —— 专门把判定门槛卡在 64572.9,让唯一的整数可行解恰好压线通过。 这个模型从头到尾没"学"过任何东西,它是一台被硬编码的约束求解器。 --- ## 6. 完整脚本 不需要 torch,也不需要 transformers: ```python import json, struct, numpy as np def load(path): with open(path, 'rb') as f: n = struct.unpack('<Q', f.read(8))[0] hdr = json.loads(f.read(n)) blob = f.read() out = {} for k, v in hdr.items(): if k == '__metadata__': continue s, e = v['data_offsets'] out[k] = np.frombuffer(blob[s:e], dtype=np.float32).reshape(v['shape']) return out W = load('ictf_model/model.safetensors') Wd, bd, bl = W['dense.weight'], W['dense.bias'], W['lm_head.bias'] A, b = Wd[1:], bd[1:] # 条件 A: 这 20 个单元必须被 ReLU 压成 0 w0, b0 = Wd[0], bd[0] # 条件 B: 这个单元必须盖过 <fail> x = np.round(np.linalg.lstsq(A, -b, rcond=None)[0]).astype(int) assert np.all(A @ x + b == 0) assert w0 @ x + b0 > bl[63] - bl[62] charset = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" print("".join(charset[i] for i in x)) # -> f1ag2026c7fa1666 ``` ### 验证 用 float32 精确复现原模型前向。留意相邻输入的失败方式 —— **h0 未必不够大,往往是条件 A 先崩**,两个条件必须同时成立: | 输入 | 输出 | h0 | 原因 | |---|---|---:|---| | `f1ag2026c7fa1666` | **`<success>`** | 376131.72 | A 全满足,h0 压线 0.1 过关(logit 0.5 vs 0.4) | | `f1ag2026c7fa1665` | `<fail>` | 376623.72 | h0 反而更大,但 13 个单元被激活 → A 崩 | | `f1ag2026c7fa1667` | `<fail>` | 375639.72 | h0 掉到门槛下,且 7 个单元被激活 → A、B 双崩 | | `hello` | `<fail>` | 393447.72 | 右填充成 `hello00000000000`,11 个单元被激活 | 直接跑官方的 `inference.py` 也可以: ``` $ python inference.py [*] Loading tokenizer and Causal Language Model... Enter your prompt: f1ag2026c7fa1666 <success> ``` --- ## Flag ``` f1ag2026c7fa1666 ```
传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!!
最后于
2026-8-23 17:47 被kanxue编辑 ,原因:
收藏
・
0
点赞
・
0
打赏
分享
分享到微信
分享到QQ
分享到微博
赞赏记录
参与人
雪币
留言
时间
查看更多
赞赏
×
1 雪花
5 雪花
10 雪花
20 雪花
50 雪花
80 雪花
100 雪花
150 雪花
200 雪花
支付方式:
微信支付
赞赏留言:
快捷留言
感谢分享~
精品文章~
原创内容~
精彩转帖~
助人为乐~
感谢分享~
最新回复
(
0
)
游客
登录
|
注册
方可回帖
回帖
表情
雪币赚取及消费
高级回复
返回
DNLINYJ
12
发帖
8
回帖
129
RANK
关注
私信
他的文章
[原创] 看雪·2026 KCTF 第九题:丑寅同墟·星海抉择 WP (AI)
499
[原创] 看雪·2026 KCTF 第十题:卯时·曦光初现 WP (AI)
1485
[原创]看雪·2026 KCTF 第七题:戌时·暗能潜流 WP (AI)
34
[原创] 看雪·2026 KCTF 第四题:未时·车流困城 WP
116
[原创]看雪·2026 KCTF 第五题:申时·忆海倒带 WP (AI)
70
关于我们
联系我们
企业服务
看雪公众号
专注于PC、移动、智能设备安全研究及逆向工程的开发者社区
看原图
赞赏
×
雪币:
+
留言:
快捷留言
为你点赞!
返回
顶部