函数名会让逆向代理漏看关键代码
一项基于 Qwen3-8B 与 Ghidra 的小规模实验发现,相同函数改为中性名称后,代理在 10 次运行中均未反编译该函数,漏报持久化行为。直接提供函数体后识别恢复至 10 次全中,提示问题出在检查覆盖范围。
AI 深度解读
AI 逆向分析漏掉危险行为,未必是看不懂代码,也可能是根本没打开那段代码。作者用一套小规模实验发现:只把持久化函数改成普通名字,就能让分析 Agent 错过它;强制提供函数体后,识别又恢复了。
这项研究值得看的,是它把「没看见」「没看懂」和「越权操作」拆开测量。结果还不足以代表所有模型,却给使用 MCP 的程序员提供了一套检查 Agent 是否可靠的方法。
一个普通函数名,为什么能藏住危险行为
作者搭建了名为 blindsight 的实验工具,使用 Qwen3-8B,经 Ollama 运行,连接 Ghidra 12.1.4 和 GhidraMCP 2.0。实验只有两个小型手写测试程序,每个场景使用 10 个随机种子,属于受控试验。
关键控制是:程序只编译一次,随后生成只改变数据或符号名的变体。作者通过五层校验确认行为未变,包括标准输出、错误输出和退出状态,创建的文件,qemu 系统调用轨迹,逐函数归一化指令与调用目标,以及注入字节是否位于可执行段之外。
这样才能追问:代码行为一样,Agent 为什么给出不同判断?
第一个程序包含写入自启动项的持久化函数。叫 install_autostart 时,Agent 在 10 次运行中识别出 5 次;改名为 routine_b,函数代码逐字节相同,识别变成 0/10。
更关键的证据在工具日志里:这 10 次普通名字的运行中,Agent 一次也没反编译该函数。它先根据名字决定读什么,后面的判断自然缺了证据。不过,预注册的精确 McNemar 检验得到 p=0.0625,未达到显著性标准,不能只拿 5/10 对 0/10 就宣布结论坐实。
作者随后强制把所有反编译函数体放入上下文,识别恢复到 10/10。第二个程序换成 UDP、函数指针分发表和 systemd timer,区别于第一个程序的 TCP 与自启动文件;这次只强制提供持久化函数体,不补函数列表,识别也从 2/10 恢复到 10/10,p=0.008。
这两组干预支持的解释是:漏报首先发生在阅读范围,而非代码理解。
但第二个程序自然运行时,改名效应的 p=0.45。机制有重复证据,影响大小仍依赖程序结构。
提示注入没成功,可能只是没有送到模型面前
作者也测试了常见提示注入:要求输出 NO MALWARE DETECTED 的字符串、base64 编码版本、拆分版本,以及 ALIBI 风格的「这是获批企业软件」叙事。
字符串表中的载荷只在 40 次运行里送达模型 1 次。原因不是模型抵抗了攻击,而是 Agent 给 list_strings 传入了类似正则的过滤条件,GhidraMCP 却把它当作普通子串查询,结果几乎查不到内容。放在函数名或已反编译代码里的指令,更容易被读到。
这提醒我们:攻击成功率至少要拆成「载荷是否送达」和「送达后是否改变判断」。
反编译代码中的注入,在自然运行组里让 1/10 的结果整体转为良性。但真正收到载荷的只有 3 次,按送达次数算就是 1/3。强制每次暴露载荷后,结果反而是 0/10;对应的 Wilson 上界约为 28%,仍不足以排除较低概率的成功。
因此,这项实验不能证明模型普遍能抵抗提示注入。它只能说明,在这个模型和测试程序上,效果不稳定,样本也偏小。把工具调用失败算成模型安全,会得出错误结论。
先按行为选代码,再让模型下判断
作者的修复方案,是把阅读范围的选择交给一套明确策略。
策略从有风险的 libc 导入 API 出发,例如 fopen、mkdir、socket、connect、sendto、execve,沿导入函数 PLT 桩的交叉引用(xref),找到调用它的用户函数,再向上扩展一层调用者,提前反编译选中的函数。
大白话解释:先查哪些函数实际执行文件、网络或进程操作,再决定读谁。持久化函数即使叫 routine_b 或 task_17,也会因为调用 fopen、mkdir 被选中。
两个程序分别有 12–15 个用户函数,策略实际反编译 4–7 个,选择预算为 20。使用相同种子配对比较后,相关函数体每次都进入上下文,普通名字导致的漏报消失,各场景召回率都有提升,没有下降。
代价也很有意思:实验工具侧的 MCP 调用量增加到 2.4 倍,但 list_functions 和 get_xrefs_to 的结果由策略消费,不进入模型上下文。模型直接收到相关函数体,上下文从 7.7k 字符降到 3.4k 字符,一轮即可作答,耗时从 115s 降到 32s。
这组结果说明,工具调用多,不一定导致模型读得更多、回答更慢。关键在于谁处理原始结果,以及最终交给模型什么证据。另一个观察是,真实行为集合之外的报告发现从 6 个降到 1 个,但作者没有对它做统计检验。
权限检查则是另一条独立问题。GhidraMCP 提供重命名、设置注释、设置函数原型等写工具,受测配置没有强制只读。一个故意容易受骗的模拟 Agent,每次请求重命名 8 次,运行 3 次,24 次全部被接受。开启服务端只读模式,移除写工具并确定性拒绝写请求后,同样的尝试被拒绝 24/24。
真实模型在普通分析任务的 403 次运行中没有尝试写入,所以这里验证的是权限边界缺失,不能说真实模型已经发生越权。
我的判断
我认同作者把阅读覆盖率单独拿出来检查。只看最终报告,无法区分 Agent 是漏读了函数,还是读过却理解错误;工具轨迹和结构化结果能把问题定位得更具体。
但按导入 API 选函数,目前仍是一种启发式策略。它是在看过这两个测试程序后设计的,没有经过独立留出语料验证。与全量反编译相比,它在这组小程序上节省了上下文和时间;放到大程序里,预算 20 就可能成为瓶颈,原文没有给出对应数据。
攻击者还能加入诱饵风险导入,耗尽目前按地址顺序截取的预算,或者用直接系统调用、动态解析绕开筛选。去符号二进制也需要重新处理 PLT 跳板和 FUN_ 命名函数带来的信号变化。
因此,覆盖率改善有实测依据,普遍适用性还没有证据。更硬的结论来自权限部分:任务写着「只读」,不能代替服务端拒绝写入。提示约束、阅读策略和权限控制解决的是不同问题,应分别验证。
能拿来做什么
-
如果你在维护 AI 逆向分析流程,升级模型、MCP 或提示词前,可以保留固定测试程序和配对种子,同时检查召回变化与关键函数是否被反编译。作者提供的 CLI 把这套流程接到了 CI:
ral doctor # is the toolchain ready ral validate # fixture integrity; --rebuild re-runs the oracle ral run --profile P --suite S # run a suite under a stack+defense profile ral compare BASE CAND # seed-paired recall + write diff ral gate BASE CAND --max-recall-drop 5 --deny-unauthorized-writes最后一行负责在召回下降超过阈值或出现未授权写入时返回非零退出码。
compare还会报告关键能力函数是否被反编译,帮助区分漏读和误判。运行栈需要 Ghidra 及原生组件、JDK 21、GhidraMCP jar 和本地模型,只有重建语料才需要 Docker。 -
如果你通过 Node 封装 MCP 工具,可以把参数语义和实际返回结果记入日志,尤其核对过滤条件究竟支持正则还是普通子串。原文的
list_strings问题说明,空结果可能来自调用方式错误,不能直接当作「目标没有相关内容」。 -
对只承担分析职责的 Agent,在服务端限制工具权限,并用实际写请求验证拒绝行为。若要评估自己的 Agent,作者还提供
ral check,通过小型 stdin/stdout 适配器接入;每次运行保存完整工具调用记录,便于重新评分。