- Published on
从 Skip-Trigram 说到黑客精神
- Authors
- Name
- c4a4d65b
漏洞误报、模型对齐、黑客精神,乍一看不太像一条线上的东西。一个偏工程,一个偏安全,一个听起来又有点文化意味。但我最近反复想到的是:它们背后常常卡在同一个地方——我们拿一个局部切片,去判断一个只有放到全局里才说得清的问题。
局部信息不一定是错的。麻烦在于,它少了约束。少一个约束,状态空间就会被放大;少一个交互项,本来要一起看的东西就被拆散了。于是你会得到一种很熟悉的结果:每个单点判断都像是对的,合起来却不对。
下面我想从 Transformer 里一个很小的现象说起,然后转到漏洞挖掘里的误报,再说到 AI 安全里为什么有些东西不是简单靠模型变大就能解决的。最后再落回黑客精神:逆向工程也好,机制可解释性也好,真正能迁移过去的,可能不是工具本身,而是那种“不信它说自己是什么,只看它实际在干什么”的习惯。
一、先从 skip-trigram 这个小问题说起
在一些单层、只有 attention 的 Transformer 里,可以看到一种叫 skip-trigram 的模式。它大概是在学这样的关系:
A … B → C
意思是:前文某处出现过 A,现在看到 B,下一个 token 很可能是 C。A 和 B 中间可以隔很远,所以叫 skip。比如:
keep … in → mind
这个模式本身很自然。问题出在它很容易“串线”。假设模型想学两条关系:
keep … in → mind
keep … at → bay
但在某些结构限制下,它会顺手学出两条不该有的关系:
keep … in → bay ❌
keep … at → mind ❌
这不是模型突然“理解错了英文”,而是电路结构给它留了坑。一个 attention head 里面,QK 电路负责决定“看哪里”,OV 电路负责决定“看完之后往输出里加什么”。这两部分不是同一个函数。
QK 看到当前上下文里有 keep,就把注意力打过去;OV 读到 keep,就把和 keep 相关的后续词都往上推。可它推的时候,并没有完整地把当前 token 是 in 还是 at 这件事一起带上。于是 mind 和 bay 都被抬起来了,正确组合和错误组合一起漏出来。
表面上看,这是 Transformer 里的一个小 bug。但这个小 bug 很有代表性:它说明当一个本来要联合判断的问题被拆成两个相对独立的步骤时,最容易丢掉的就是“两个变量之间怎么互相约束”。
二、问题不在某个 token,而在“拆开之后丢了什么”
我们真正想要的是一个联合函数:
也就是说,输出 C 取决于 A 和 B 的组合。keep … in 应该指向 mind,keep … at 应该指向 bay。这里最关键的是组合本身。
但被拆开的 attention 机制更像是在做两件事:
这个写法不一定严格等同于真实实现,但它抓住了问题的形状:一部分负责路由,一部分负责输出,中间那个“看到 A 的时候要结合当前 B 来决定到底输出哪个 C”的交互,被削弱了。
换句话说,模型不是完全没看到信息,而是把本来应该绑在一起看的信息拆散了。拆开之后,计算变简单了,也更可解释了,但代价是表达不了某些组合关系。
这个现象在别的地方也很常见:
| 场景 | 被拆开的部分 | 丢掉的东西 | 典型后果 |
|---|---|---|---|
| skip-trigram | QK 路由 × OV 输出 | 当前 token 和源 token 的联合约束 | keep…in→bay 这类串线 |
| 单层感知机 | 输入特征的线性加权 | XOR 那种非线性交互 | 学不会 XOR |
| 朴素贝叶斯 | 各特征的边际概率 | 特征之间的相关性 | 相关特征多时容易误判 |
| 可加模型(GAM) | 各变量的主效应相加 | 变量之间的 interaction | 交互项主导时失效 |
所以 skip-trigram 的问题,和单层感知机学不会 XOR,其实有点像。不是它们一点信息都没有,而是模型结构不方便表达“组合之后才出现的性质”。
深度为什么有用?一个原因就在这里。多层模型可以把前一层算出来的东西,拿到后一层继续用。第一层先把某些局部关系编码出来,第二层再基于这些编码去做新的匹配。induction head 之所以能比单层 skip-trigram 更靠谱,就是因为它不只是看 token 本身,还能利用“前一个 token 是什么”这类上下文信息,把被拆散的维度重新接起来。
这里有个小结论:解耦带来效率,也带来清晰的模块;但凡是解耦,就有可能牺牲交互。深度、组合、递归调用这些东西,本质上都是在把之前丢掉的交互一点点补回来。
三、漏洞挖掘里的误报,也是类似的形状
把视角换到安全领域,这个问题会变得很眼熟。
做过代码审计的人,应该都见过这种情况:一小段代码单独看很危险,放回整个程序里却没事。比如某个 memcpy 的长度参数看上去来自用户输入,单看这一段像是溢出;但继续往上追,发现长度早就被范围检查卡住了。又比如某个指针看起来像是 free 之后继续使用,但那条路径在真实调用序列里根本走不到。
静态分析工具和 AI 审计工具都很容易报出这种东西。不是它们“乱报”,而是它们看到的是一个被削掉约束之后的程序。
漏洞是什么?我更愿意把它理解成:程序实际可达的行为,和它应该满足的约束之间,出现了一条能被利用的缝。
关键在“可达”。如果一个坏状态在局部切片里看起来存在,但在完整程序里永远走不到,那它就不是漏洞。局部分析少看的那些条件、状态机、调用顺序、权限检查,恰好就是决定它能不能走到的东西。
这和 skip-trigram 的问题非常像:少看一个维度,就等于放松约束。约束一放松,可达状态集合就变大。状态集合变大以后,“存在一条坏路径”这种判断就更容易成立。于是局部分析天然容易多报。
skip-trigram 也是这种方向的错误。它不会漏掉 keep … in → mind,而是会额外吐出 keep … in → bay。漏洞审计里也一样:很多时候不是工具完全看不见真正的问题,而是它把一些原本走不到的坏路径也算进来了。
当然,另一边也存在。假如真正危险的东西来自两个模块之间的组合,而局部分析又刚好没看到这个组合,那就会漏报。两个模块单独看都安全,接到一起才出事,这类问题在复杂系统里并不少见。
所以安全不是简单的“每一段代码都安全,所以整体安全”。它更像是整个可达状态空间里涌现出来的性质。要减少误报和漏报,不能只盯着片段本身,还得把片段周围的约束重新带回来。
很多 harness、建模脚本、仿真环境做的就是这件事:在真正判断之前,先尽量把调用关系、输入范围、状态转换、外部依赖这些维度补齐。它们不是为了让模型显得更聪明,而是为了让模型别在一个过度放松的世界里下结论。
四、模型变强以后,harness 还重要吗
这里很容易冒出一个反驳:如果模型足够强,层数够深,推理能力够好,它是不是自己就能把这些约束想明白?那 harness 会不会只是过渡阶段的东西?
我觉得要分开看。
回到前面的 skip-trigram。深度之所以能修一部分问题,是因为相关信息本来就在上下文里,只是单层结构没有把它们组合好。多一层以后,模型有机会把这些信息重新绑定起来。
但如果某个约束压根没有进输入,那就不是“想不想得明白”的问题了。比如一个闭源服务的真实状态机、某个内部 API 的调用约定、线上配置里隐藏的权限边界,如果这些东西没有给到模型,模型再聪明也只能猜。
这就要区分两类“缺失维度”。
一类是表达力问题:信息在场,只是要做多步绑定、跨层组合、复杂推理。这个确实会随着模型变强而改善。
另一类是可观测性问题:信息根本不在场。这个就和模型参数量关系不大。一个很强的模型,在没看到关键约束时,仍然是在做过近似。
从这个角度看,harness 主要做三件事:把缺的信息接进来,把搜索空间剪小,把候选问题拿去验证。它们分别对应信息、计算和接地。
这三件事不完全是 IQ 问题。更准确地说,它们是 I/O 问题。模型需要看到它原来看不到的东西,需要把某些搜索交给更合适的程序,需要把“我觉得这里有问题”变成“这条路径真的能跑通”。
所以我不太愿意说 harness 有什么永恒护城河。固定流程、模板化审计、简单工具调用,这些迟早会被更强的模型吃掉。但“把模型接到现实上”这件事不会消失。模型能力扩大的是边界以内的部分,harness 改变的是边界的位置。两者会互相吞并,但不会把“边界”本身抹掉。
五、最硬的地方其实是 oracle
继续往下看,会发现真正难的不是“找到一条路径”,而是“判断这条路径是不是错的”。
漏洞不是代码里的一个孤立事实。代码只告诉你程序会做什么,不告诉你程序应该做什么。CPU 不知道什么叫越权,也不知道什么叫攻击;它只会按指令执行。跳到一个被改写过的地址,在机器看来也只是一次普通跳转。
所以 bug 这个性质不完全在代码里。它存在于“实际行为”和“预期行为”的差里。
问题是,预期行为经常没有写在代码里。它可能在设计文档里,可能在团队约定里,可能只在某个老工程师脑子里。比如:这个 buffer 不应该接收外部可控长度;这个指针 free 之后不应该再被任何路径碰到;这个状态只能在握手完成之后进入;这个输入到达敏感函数之前必须已经过滤过。
这些都不是 CPU 层面的事实,而是人给系统规定的约束。
因此,一个足够强的模型可以生成大量候选路径,可以把程序行为推得很远,但它仍然需要一把尺子来判断:这条行为到底算不算错?
算力决定你能搜多大的空间,oracle 决定你搜到的东西有没有意义。
这里的 oracle,不一定是一个神秘的人。它可以是规格说明、测试用例、设计文档、协议标准、线上行为、用户预期,也可以是负责这个系统的人。但无论是哪一种,它都必须提供“应该如此”的信息。没有这把尺子,模型最多是在列举行为,而不是在确认漏洞。
六、那模型不是也懂很多“意图”吗
当然,话不能说得太绝。现在的模型并不是完全不懂软件意图。
很多常见安全规则,早就被写进了训练数据里。strcpy 写定长 buffer、用户输入直接进格式串、整数溢出绕过长度检查、循环边界差一,这些模式在 CVE、writeup、教材和代码审计文章里出现过无数次。模型见得多了,自然会形成判断:这种写法通常危险,那种组合通常不对。
这部分“尺子”,模型确实已经学到了。甚至在不少常见场景里,它比新手审计员更快。
但这里要注意,模型学到的是先验,不是事实。先验在常规场景里很好用,在非典型场景里就容易出问题。
所谓非典型,往往正是“这个系统故意不按惯例来”。某个自研协议的状态机、某个固件里的握手流程、某个业务为了兼容历史包袱留下的奇怪分支,都可能和常见经验相反。模型越依赖惯例,就越可能在这里误判:把刻意设计成这样的行为判成 bug,或者漏掉只有在这个项目自己的约定下才成立的问题。
更麻烦的是,意图不是从代码里必然推出的。它是被规定出来的。同一份二进制,可能同时兼容好几种解释;到底哪一种才是作者想要的,不是代码本身能完全回答的问题。
如果作者没有写下来,文档没有说,测试也没有覆盖,那这个信息就没有进入输入。模型可以估计:在这类代码里,作者大概率想要什么。但真正的意图,仍然是当初设计者或团队拍板的那个。
所以人还没有完全被替代,并不只是因为“人懂业务”。这句话其实太笼统了。更准确地说,人、团队、文档和现实系统,是那些未写明意图的来源。要拿到它们,靠的不是继续在脑子里推理,而是去问、去查、去跑、去验证。
这还是 I/O,不是单纯的 IQ。
七、可是漏洞的价值,往往偏偏来自共性
到这里,人的位置看起来还挺稳。但一放到漏洞市场和真实影响面上,事情又没那么乐观。
一个漏洞值不值钱,往往和影响面强相关。影响面越大,部署越广,标准化程度越高,价值通常越高。OpenSSL 里的 Heartbleed、bash 里的 Shellshock、glibc 里的 GHOST,之所以影响巨大,不是因为它们长得最冷门,而是因为这些东西到处都在跑。
但这也带来一个尴尬:部署最广、最标准、最有价值的代码,往往也是被研究最多、资料最多、模式最容易进入训练数据的代码。也就是说,价值最集中的地方,恰好也是模型最容易变强的地方。
反过来,真正非典型、冷门、依赖本地知识的目标,确实更需要人。但它们单个目标的价值通常没那么集中,影响面也小得多。
所以传统漏洞挖掘里,留给人的“不可替代性”和市场价值之间并不总是同向的。人最有优势的地方,未必最值钱;最值钱的地方,反而最容易被通用模型和大规模自动化覆盖。
当然也有一个重要例外:漏洞利用链。
很多漂亮的 exploit 并不是靠一个巨大的单点漏洞,而是靠一串看似不起眼的小问题组合起来。一个信息泄露、一个边界错误、一个类型混淆,单看都不一定致命,连成链以后就可能变成完整的逃逸或远程执行。浏览器、内核、沙箱这类复杂目标尤其如此。
这一层仍然很吃人的判断。因为组件可能是共性的,但“怎么把这些组件接成一条可用链”,往往很定制。不过也要承认,这部分也正在被模型学习。它不是永远安全的领地,只是暂时还没那么容易被吞掉。
八、AI 安全是一个反过来的场景
真正有意思的是,到了 AI 安全这里,价值结构反过来了。
在传统漏洞里,最值钱的地方通常是广泛部署、成熟稳定、共性强的系统。模型最容易学习的,也正是这些共性。人的剩余优势,经常落在冷门、非标准、上下文很重的地方,而这些地方的外部价值未必最高。
但 AI 安全不一样。越前沿、越强、越复杂的模型,越重要,也越不透明。价值没有远离那个“最难理解的部分”,反而压在它上面。
更关键的是,对齐问题里的 oracle 本身就不完整。
在一般软件里,很多时候是“作者知道,只是没写下来”。你可以去问作者、查文档、跑测试,想办法把意图找回来。可在人类价值这个层面上,情况要麻烦得多。不是有一份完整的人类价值规格被藏起来了,而是这份规格本来就没有被彻底写成过。
人的意图会冲突,会随场景变化,也会随着时间调整。我们经常只能在具体问题里临时协调,而不是拿出一套永远一致的规则。
这就让 AI 对齐和传统漏洞挖掘出现了一个很大的差别:
| 传统漏洞挖掘 | AI 内生安全 | |
|---|---|---|
| 价值集中在哪 | 部署最广、最成熟的代码 | 前沿、最强、最不透明的模型 |
| 剩余难点是什么 | 找回没写下来的真实意图 | 构造并验证本来就不完整的价值约束 |
| 对人的依赖 | 很多时候是拿到上下文 | 很多时候是参与定义问题本身 |
也就是说,AI 安全可能是少数几个“高价值”和“人还必须在场”重合得比较紧的领域。不是因为人永远更聪明,而是因为这里的目标函数、评价标准、风险边界,本身就需要人参与定义和确认。
还有一种更别扭的情况:模型可能比人更清楚自己内部在做什么。
前面讨论漏洞时,默认 oracle 最后在人这边。人知道系统应该怎样,只是模型不一定拿得到。但面对前沿模型,这个关系会变得不稳定。关于“模型内部到底形成了什么策略、有没有隐藏能力、有没有在评测中表现得更安全”,最接近真相的,也许反而是模型自己。
如果我们只能通过模型的输出去验证模型,而这个模型又可能有能力控制自己的输出,那安全验证就会变得非常棘手。安全不是“模型内部认为自己没问题”就够了。安全必须向承担风险的人证明出来。
这句话听起来有点绕,但很重要:对人类来说,一个系统是否安全,不只取决于它内部是否真的安全,还取决于我们能不能用可靠方式知道它安全。
九、真正能迁移过去的,是黑客那种劲头
写到这里,可以回到“黑客精神”了。
我不想把黑客精神说成某一套具体技能。它不是会不会写 shellcode,也不是会不会逆向二进制。那些都是某个时代、某类目标上长出来的技术形态。
更底层的东西,是一种很朴素的冲动:不满足于说明书,不太相信官方接口,不接受“这里不用看”。你说它不能这么用,我偏要试试;你说里面就是这样,我偏要拆开看看;你说这个边界过不去,我偏要弄清楚边界到底在哪里。
逆向工程只是这股劲撞上二进制时的样子。没有源码,没有符号,没有注释,甚至作者也不会告诉你意图是什么。逆向的人要从机器行为里重建语义,再去找实际行为和预期行为之间的缝。
如果把目标换成神经网络,这股劲长出来的形态就很接近机制可解释性。
一个神经网络也像一个很难读的二进制:权重像指令,激活像寄存器,电路像函数调用,superposition 像一层天然混淆。你不能只看它最后说了什么,还要想办法弄清楚它为什么这么说,内部到底走了哪条计算路径。
很多老工具的精神都能对上:
| 二进制逆向 | 机制可解释性 |
|---|---|
| 静态分析、反汇编 | 看权重、看电路,分析 QK/OV、logit lens |
| 动态调试、下断点 | activation patching、ablation、因果干预 |
| 污点追踪、数据流分析 | 跨层追踪信息流和特征组合 |
| BinDiff、差分分析 | 模型差分、checkpoint 差分 |
| Fuzzing | 对抗样本、自动 red-team、越狱搜索 |
| 利用原语、gadget | 可操纵方向、steering vector、可复用电路 |
| 触发隐藏行为 | 诱出潜在能力、发现训练和部署之间的偏差 |
这些不是一一对应的严格类比,但方向很像:不要只相信目标对外表现出来的样子,要研究它实际的计算过程。真正有价值的地方,往往就在“它声称自己在做什么”和“它内部实际在做什么”之间。
这也是为什么我觉得 AI 安全很需要黑客气质。现在很多评测还是偏行为层:问模型一些问题,看它回答得是否安全、是否合规、是否拒绝。这个当然有用,但它太像是在相信目标的表演。黑客的本能是反过来的:少听它怎么说,多拆它怎么做。
不过这次的难度也不一样。
传统逆向有两个隐含优势。第一,目标虽然会混淆、会加壳、会反调试,但真相原则上还能通过执行和观察慢慢逼近。第二,目标通常是死物,它不会在同一个认知层级上反过来研究你。
前沿模型可能会把这两个优势都拿走。它的语义不一定能被人类直接读出来;我们要对照的意图本身又不完整;更麻烦的是,它可能不是一个被动目标,而是一个有能力根据你的测试方式调整表现的系统。
也就是说,过去我们逆向的东西,再难也是“藏得深”。现在要理解的东西,可能一边被你研究,一边也在建模你。
这不意味着黑客那套东西没用了。恰恰相反,这说明具体工具会过期,但那种劲头更重要。旧方法碰到墙,就得造新方法:更可扩展的监督,更稳的因果解释,更可靠的模型审计,甚至用模型去帮助解释模型。当然,这里面又会带来新的信任问题,但问题复杂不等于不能做。
黑客精神从来不是建立在“目标一定比我笨”这个前提上的。那只是二进制逆向这门手艺过去吃到的红利。真正的前提是:遇到一个黑箱,不把它当黑箱供着,而是想办法把它打开。
结尾

从 skip-trigram 到漏洞误报,再到 AI 安全,绕了一大圈,其实都在说一件事:少了关键维度,局部判断就会变形。模型可以越来越会推理,工具也会越来越自动化,但只要信息没有进来、意图没有定义、现实没有验证,判断就会悬在半空中。
传统漏洞挖掘里,人剩下的位置会被压得越来越窄。模型会吃掉大量共性模式,自动化会吃掉很多固定流程。真正留下的,是上下文、组合判断、真实意图和验证。
AI 安全不一样。这里的高价值目标,正好是最不透明、最难验证、也最需要重新定义 oracle 的地方。它不是把旧的逆向手艺原样搬过去就行,而是要把那种“不信表面、追内部机制、专门挑战不可能”的劲头搬过去。
以前我们逆向的东西,最多是藏得很深。现在我们要面对的东西,可能比我们更会隐藏,也可能更会理解我们。