首页
课程
问答
CTF
社区
招聘
峰会
发现
排行榜
知识库
工具下载
看雪20年
看雪商城
证书查询
登录
注册
首页
社区
课程
招聘
发现
问答
CTF
排行榜
知识库
工具下载
峰会
看雪商城
证书查询
社区
CTF对抗
发新帖
0
0
[原创]第九题:丑寅同墟·星海抉择 WP
发表于: 2026-8-22 12:20
63
[原创]第九题:丑寅同墟·星海抉择 WP
mxym_
2
2026-8-22 12:20
63
# 第九题:丑寅同墟·星海抉择 Flag:`f1ag2026c7fa1666` 附件给出的模型是一个字符级分类模型,主要文件包括模型定义、推理脚本和 safetensors 权重。题目要求分析模型代码和权重,找出隐藏在模型中的秘密。 先看 tokenizer。字符表为: ```text 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ ``` 输入长度最多 16 个字符,每个字符会被映射成对应编号。不足 16 位的位置用 `0` 补齐,也就是补字符表里的字符 `0`。模型额外定义了两个输出 token,编号 62 是 `<success>`,编号 63 是 `<fail>`。 模型本体非常小,结构如下: ```text 输入 16 维字符编号 -> Linear(16, 21) -> ReLU -> Linear(21, 64) -> 取 argmax 作为预测字符 ``` 因此整个模型可以直接转成线性代数问题。设输入字符编号向量为 `x`,第一层权重和偏置为 `W`、`b`,隐藏层预激活为: ```text h = W @ x + b ``` 经过 ReLU 后,隐藏层变为: ```text relu(h) = max(h, 0) ``` 继续查看输出层权重。普通字符输出的 bias 基本都是 `-10000`,不会成为最大值。`<fail>` 的 bias 是 `0.4`,输出权重全为 0,所以 `<fail>` 的 logit 恒等于 `0.4`。 `<success>` 的 bias 是 `-376131.22`,输出权重有明显构造: ```text hidden[0] 的权重为 1 hidden[1] 到 hidden[20] 的权重为 -1e10 ``` 于是 `<success>` 的 logit 为: ```text relu(h0) - 1e10 * (relu(h1) + relu(h2) + ... + relu(h20)) - 376131.22 ``` 这说明想让模型输出 `<success>`,隐藏单元 1 到 20 必须全部不激活。只要其中任意一个大于 0,`-1e10` 的惩罚都会让 `<success>` 直接变成极小值。随后还需要隐藏单元 0 足够大,使 `<success>` 的 logit 超过 `<fail>` 的 `0.4`。 设输入字符编号向量为 `x`。隐藏单元 1 到 20 不激活,对应 20 个线性条件: ```text W[1:] @ x + b[1:] <= 0 ``` 权重中的 bias 正好构造在等式边界上,因此直接解下面的线性方程组: ```text W[1:] @ x = -b[1:] ``` 得到整数编号: ```text [15, 1, 10, 16, 2, 0, 2, 6, 12, 7, 15, 10, 1, 6, 6, 6] ``` 按 tokenizer 的字符表 `0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ` 解码为: ```text f1ag2026c7fa1666 ``` 再把这个输入代回第一层,隐藏层预激活为: ```text [376131.72, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] ``` 隐藏单元 1 到 20 全部刚好为 0,不会触发惩罚项。隐藏单元 0 为 `376131.72`,所以 `<success>` 的 logit 为: ```text 376131.72 - 376131.22 = 0.5 ``` `<fail>` 的 logit 为 `0.4`,因此模型最终输出 `<success>`。将相近输入代入模型也会失败,例如把开头改成常见的 `flag`,输出会变成 `<fail>`,说明正确字符串确实是 `f1ag2026c7fa1666`。 完整 Exp: ```python from __future__ import annotations import json import struct from pathlib import Path import numpy as np CHARSET = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" def find_model() -> Path: candidates = list(Path(".").rglob("model.safetensors")) if len(candidates) != 1: raise FileNotFoundError(f"expected one model.safetensors, found {len(candidates)}") return candidates[0] def load_safetensors(path: Path) -> dict[str, np.ndarray]: data = path.read_bytes() header_len = struct.unpack("<Q", data[:8])[0] header = json.loads(data[8: 8 + header_len]) raw = data[8 + header_len:] tensors: dict[str, np.ndarray] = {} for name, meta in header.items(): if name == "__metadata__": continue if meta["dtype"] != "F32": raise ValueError(f"unsupported dtype: {meta['dtype']}") start, end = meta["data_offsets"] tensors[name] = np.frombuffer(raw[start:end], dtype="<f4").reshape(meta["shape"]).copy() return tensors def main() -> None: tensors = load_safetensors(find_model()) dense_w = tensors["dense.weight"] dense_b = tensors["dense.bias"] a = dense_w[1:] y = -dense_b[1:] x, *_ = np.linalg.lstsq(a, y, rcond=None) ids = np.rint(x).astype(int) secret = "".join(CHARSET[i] for i in ids) hidden = np.maximum(dense_w @ ids.astype(np.float32) + dense_b, 0) logits = tensors["lm_head.weight"] @ hidden + tensors["lm_head.bias"] assert int(np.argmax(logits)) == 62 print(secret) if __name__ == "__main__": main() ```
传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!!
收藏
・
0
点赞
・
0
打赏
分享
分享到微信
分享到QQ
分享到微博
赞赏记录
参与人
雪币
留言
时间
查看更多
赞赏
×
1 雪花
5 雪花
10 雪花
20 雪花
50 雪花
80 雪花
100 雪花
150 雪花
200 雪花
支付方式:
微信支付
赞赏留言:
快捷留言
感谢分享~
精品文章~
原创内容~
精彩转帖~
助人为乐~
感谢分享~
最新回复
(
0
)
游客
登录
|
注册
方可回帖
回帖
表情
雪币赚取及消费
高级回复
返回
mxym_
2
11
发帖
2
回帖
120
RANK
关注
私信
他的文章
[原创] 第十题:卯时·曦光初现 WP
1512
[原创]第九题:丑寅同墟·星海抉择 WP
63
[原创]第八题:亥子合辰·塔影迷楼 WP
44
[原创]第七题:戌时·暗能潜流
18
[原创]第六题:酉时·书院迷局 WP
1500
关于我们
联系我们
企业服务
看雪公众号
专注于PC、移动、智能设备安全研究及逆向工程的开发者社区
看原图
赞赏
×
雪币:
+
留言:
快捷留言
为你点赞!
返回
顶部