首页
社区
课程
招聘
[原创] 第九题:丑寅同墟·星海抉择
发表于: 2天前 35

[原创] 第九题:丑寅同墟·星海抉择

2天前
35

ICTFForCausalLM Writeup

题目概述

附件给出了一个自定义字符级模型 ICTFForCausalLM、模型权重以及推理脚本。模型接收一个最长 16 字符的字符串,并输出 <success> 或其他 token。目标是分析模型结构和权重,恢复隐藏的 flag。

最终结果:

模型输入:f1ag2026c7fa1666
flag:flag{f1ag2026c7fa1666}

1. 分析 tokenizer

tokenizer 使用如下字符表:

charset = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"

每个字符直接映射为它在字符表中的下标。输入不足 16 字符时会用编号 0 补齐,因此模型实际接收的是一个长度为 16 的整数向量:

x = [x0, x1, ..., x15], 0 <= xi <= 61

2. 分析模型结构

这个模型虽然名为 CausalLM,但没有 embedding、attention 等语言模型结构,实际只有两个全连接层:

x = input_ids.float()
h = ReLU(dense(x))
logits = lm_head(h)

写成数学形式即:

h = ReLU(Wx + b)
logits = W_head h + b_head

其中:

  • x 的维度为 16;
  • 隐藏层 h 的维度为 21;
  • 输出层有 64 个 token;
  • 编号 62 是 <success>
  • 编号 63 是 <fail>

3. 找到关键权重

读取 model.safetensors 后可以发现,绝大多数输出 token 的偏置都是 -10000,不会成为最终预测。真正需要比较的是 <success><fail>

<fail> 的输出层权重全为 0,偏置为 0.4,所以:

logit_fail = 0.4

<success> 对隐藏层第 0 项的权重为 1,对第 1 至 20 项的权重全部为 -1e10,偏置约为 -376131.22

logit_success = h0 - 1e10 * (h1 + h2 + ... + h20) - 376131.22

ReLU 保证所有 hi >= 0。只要 h1h20 中有任何一项为正,<success> 的 logit 就会被 -1e10 的权重压到极小。因此正确输入必须使:

h1 = h2 = ... = h20 = 0

观察权重可知,这些单元的线性部分在正确输入处被精确设置为 0。因此可以建立方程组:

W[1:21] x + b[1:21] = 0

这是一个包含 20 条方程、16 个未知数的超定线性方程组。系数矩阵的秩为 16,所以其精确解唯一。

4. 恢复输入

使用最小二乘求解方程组,再将浮点误差范围内的结果取整:

from safetensors.numpy import load_file
import numpy as np

charset = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
tensors = load_file("ictf_model/model.safetensors")

weight = tensors["dense.weight"].astype(np.float64)
bias = tensors["dense.bias"].astype(np.float64)

solution, residuals, rank, _ = np.linalg.lstsq(
    weight[1:], -bias[1:], rcond=None
)
token_ids = np.rint(solution).astype(int)

assert rank == 16
assert np.allclose(weight[1:] @ token_ids + bias[1:], 0)

secret = "".join(charset[i] for i in token_ids)
print(token_ids)
print(secret)

得到 token 编号:

[15, 1, 10, 16, 2, 0, 2, 6, 12, 7, 15, 10, 1, 6, 6, 6]

按照字符表反查:

15 -> f
 1 -> 1
10 -> a
16 -> g
 2 -> 2
 0 -> 0
 2 -> 2
 6 -> 6
12 -> c
 7 -> 7
15 -> f
10 -> a
 1 -> 1
 6 -> 6
 6 -> 6
 6 -> 6

拼接得到:

f1ag2026c7fa1666

此外,将 xi 限制为 tokenizer 的合法整数范围 0..61,对成功条件建立整数线性约束后逐位求最小值和最大值,16 个位置均固定为上述编号,说明合法输入中该解唯一。

5. 验证

将恢复出的字符串代回模型,隐藏层结果满足:

h1 = h2 = ... = h20 = 0
h0 = 376131.71875

于是:

logit_success = 0.5
logit_fail    = 0.4

argmax 最终选择编号 62,推理脚本输出:

<success>

Flag

flag{f1ag2026c7fa1666}

冰与火的战歌:Windows内核攻防实战高级班!从零到实战,融合AI与Windows内核攻防全技术栈,打造具备自动化能力的内核开发高手。

最后于 2天前 被yuzhouheike编辑 ,原因:
收藏
免费 0
打赏
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回