“顶会”看安全(十六):深入分析函数内联及其对基于机器学习的二进制分析的安全影响

这期解读的安全论文来自安全顶级会议之一 ​NDSS 2026 ​,论文题目是 ​A Deep Dive into Function Inlining and its Security Implications for ML-based Binary Analysis ​,中文可以译为 ​深入分析函数内联及其对基于机器学习的二进制分析的安全影响 ​。论文链接为:https://www.ndss-symposium.org/ndss-paper/a-deep-dive-into-function-inlining-and-its-security-implications-for-ml-based-binary-analysis/。

一、论文背景

这篇论文关注的是一个很容易被忽视的问题:编译器优化会不会破坏基于机器学习的二进制安全分析模型? 更具体地说,它研究的是编译器中的一种常见优化------​函数内联(function inlining)------是否会改变二进制程序的静态特征,从而影响甚至规避 ML-based binary analysis 模型。

在二进制安全分析场景中,分析对象通常不是源代码,而是已经编译完成的可执行文件。很多商业软件、固件、恶意样本、闭源组件在实际分析时都只能拿到二进制形式。由于二进制文件中缺少函数名、变量名、类型信息等高级语义,传统逆向分析往往需要从机器指令、控制流图、调用图、字符串、常量、统计特征等低层信息中推断程序行为。论文指出,这些静态特征被广泛用于函数边界识别、二进制代码相似性检测、漏洞检测、恶意软件检测、恶意家族分类、崩溃根因分析等安全任务。

近年来,大量研究开始把机器学习和深度学习引入二进制分析。例如,有的模型根据汇编指令序列判断两个函数是否相似;有的模型根据函数体预测原始函数名;有的模型从控制流图、调用图和统计特征判断样本是否为恶意软件;还有的模型通过二进制相似性搜索来定位固件中的已知漏洞。这些方法的共同前提是:​模型输入的静态特征能够稳定反映程序语义​。换句话说,如果两个函数语义相似,它们的指令、CFG、调用关系、统计特征也应当在一定程度上相似。

但问题在于,编译器并不会为了二进制分析模型保留这些特征。现代编译器的目标是生成更快、更小、更适合目标平台的机器码,而不是保留源代码结构。函数内联就是其中非常典型的一种优化:编译器会把某个函数调用点替换成被调用函数的函数体,从而减少函数调用开销,并为后续优化提供机会。这个过程对性能是有益的,但它会改变函数边界、机器指令分布、控制流图结构和调用图结构。论文把这一点作为核心切入点:如果 ML 二进制分析模型依赖这些静态特征,那么函数内联就可能让模型看到一个"语义相同但形态大变"的程序。

更关键的是,函数内联不是冷门技巧,而是现代编译器中非常普通的优化机制。它可以由优化级别触发,

例如 -O1-O2-O3-Os-Oz;也可以受源代码中的 inlinealways_inlinenoinline 等属性影响;还可以受 LLVM 中大量中端优化参数影响。论文强调,函数内联决策并不是简单地"写了 inline 就内联,不写就不内联",而是由 LLVM 的 CGSCC pass manager、inliner pass 和 cost model 共同决定。这个 cost model 会计算内联成本和阈值,只有当成本低于阈值时才执行内联。

因此,这篇论文的背景不是传统意义上的漏洞利用,而是一个更偏"模型鲁棒性"和"安全检测可信度"的问题:如果攻击者不修改源代码语义、不使用明显的混淆器、不修改编译器本身,只是通过合法编译选项重新编译程序,能不能制造出绕过 ML 安全模型的二进制变体?

这就是论文提出的威胁模型。攻击者不需要发明新的混淆算法,也不需要对编译器做后门改造,只需要利用标准构建流程中的优化策略,尤其是函数内联相关选项,生成在语义上等价、但静态结构上显著不同的二进制文件。论文把这种超出标准优化级别、更激进地提高函数内联比例的策略称为 ​extreme inlining(极端内联)。

从安全意义上看,这一点比较"阴险"。传统混淆往往有比较明显的特征,比如控制流平坦化、垃圾指令、字符串加密、异常跳转等,安全工具可以针对这些混淆特征做检测。但函数内联本身是编译器的正常行为,既合法又常见,很难简单地把"内联比例高"直接判定为恶意。因此,它天然具有更低的检测表面。

二、工作概述

这篇论文的核心工作可以概括为一句话:系统研究函数内联如何影响基于机器学习的二进制安全分析,并证明攻击者可以通过"极端内联"制造能够规避 ML 模型的二进制变体。

论文的研究对象主要有两条线。

第一条线是​编译器机制线​。作者深入分析 LLVM 中函数内联的完整决策流程,包括外部因素和内部因素。外部因素包括源代码中的内联提示、优化级别、编译器选项;内部因素包括 Clang 给函数打上的属性、LLVM CGSCC pass manager 的调用图优化流程、inliner pass 的 cost model,以及链接阶段的 LTO 和 ThinLTO。论文还使用 LLVM 的内联诊断选项和 DWARF 调试信息来提取内联 ground truth。

第二条线是​安全影响线​。作者不只是定性说"内联会改变二进制",而是选择了五类 ML-assisted binary analysis 安全任务进行实验:二进制代码相似性检测、函数符号名预测、恶意软件检测、恶意软件家族分类、漏洞检测。论文共围绕九个研究问题展开,其中前五个问题研究内联对安全模型的影响,后四个问题研究内联比例、编译选项、LTO 和静态特征变化本身。

论文的实验规模也比较完整。良性软件方面,作者使用 coreutils、binutils、diffutils、findutils、OpenSSL、lvm2、gsl、valgrind、openmpi、putty、nginx、lighttpd、SPEC2006 等程序,构造了 1,524 个程序变体,其中包含 398 个极端内联变体。恶意软件方面,作者关注 IoT malware,使用 Mirai、Gafgyt、Tsunami 等样本,并重新编译开源泄露的 Mirai 和 Gafgyt 以生成极端内联恶意样本;最终恶意样本数据集包含 13,688 个样本。

论文的主要结论非常明确:函数内联虽然是良性优化,但确实会显著影响 ML-based binary analysis 模型的行为,尤其是依赖静态特征的模型。 在二进制代码相似性检测中,普通内联会带来一定性能下降,极端内联会导致更明显下降,尤其是 recall 降低,也就是原本相似的函数更容易被模型判成"不相似"。在函数名预测任务中,普通内联有时甚至能给模型提供更多上下文,但极端内联会明显破坏模型性能,例如 AsmDepictor 和 SymLM 在极端内联下分别出现约 24.0% 和 75.7% 的性能下降。

在恶意软件检测和恶意家族分类中,影响更直观。论文表 VI 显示,恶意软件检测任务中,模型在野外样本上的平均 F1 约为 0.98,但面对极端内联恶意样本时平均 F1 降至约 0.78;恶意软件家族分类任务中,平均 F1 从约 0.87 降至约 0.41。这说明模型学习到的很多特征并不是真正稳定的恶意语义,而是与编译形态、控制流结构、调用图统计等强相关。

漏洞检测任务中,论文使用基于二进制相似性的方式搜索 OpenSSL libcrypto 中的已知漏洞函数。结果显示,函数内联后,MRR@100 平均下降约 69%。这意味着安全工具在查找已知漏洞函数时,可能因为编译优化导致相似性排序显著下降,从而把真正的漏洞函数排到很后面,甚至漏报。

论文提出的 extreme inlining 是这篇工作的关键概念。它不是简单地使用 -O3,而是通过组合优化级别、内联阈值、调用惩罚参数、LTO 等编译器配置,使函数内联比例超过标准编译行为。论文发现,提高 inline-threshold、降低 inline-call-penalty、启用 Full LTO/ThinLTO 等因素都可能提升内联比例。在 coreutils 上,极端内联比例甚至达到 79.64%,超过 Full LTO 的效果。

所以,这篇论文的创新点主要有三个。

第一,它把函数内联从"编译器优化问题"提升到了"安全鲁棒性问题"。过去很多二进制分析研究知道内联存在,但没有系统研究它对 ML 模型的安全影响。

第二,它不是只看单个模型或单个任务,而是覆盖了五类安全任务和多种模型架构,包括判别式模型、生成式模型、传统机器学习模型、深度学习模型、图/序列/统计特征模型等。

第三,它强调攻击者可以利用标准编译流程制造规避样本。也就是说,二进制安全检测不能只考虑"代码是否被混淆",还要考虑"代码是否通过合法编译优化发生了结构性变形"。

三、工作具体说明

论文首先拆解了 LLVM 的函数

内联决策流程

论文第一个重要工作是把 LLVM 中函数内联的流程拆开。作者认为,要研究内联的安全影响,不能只停留在"函数调用被替换成函数体"这个直观定义上,而必须理解编译器到底如何决定一个函数是否被内联。

在 LLVM 中,函数内联大体经过三个阶段。

第一阶段是​外部因素输入 ​。这些因素包括源代码层面的属性和指令,例如 inlinealways_inlinenoinlineflattennakedoptnone;也包括编译优化级别,例如 -O0-O1-O2-O3-Os-Oz;还包括 Clang/Opt 提供的大量内联相关选项。论文第 4 页图 1 把这些因素分为 external factors 和 internal factors,并展示了它们如何影响最终内联比例。

第二阶段是​Clang 和 LLVM 中端的内部处理 ​。Clang 会根据源代码提示、优化级别和编译器选项,为函数标记内部属性,例如 inlinehintalwaysinlinenoinlinenakedoptnone。之后,LLVM 的 CGSCC pass manager 会在调用图的强连通分量上运行 inliner pass,并结合 SimplifyCFG、SROA、EarlyCSE 等优化 pass 迭代更新调用图。这里的重点是:内联不是一次性全局决定,而是在调用图变化过程中反复发生的局部决策。

第三阶段是​cost model 决策 ​。LLVM inliner 会针对每个 call site,即"调用者---被调用者"组合,计算内联成本 cost 和阈值 threshold。如果最终 cost 小于 threshold,就执行内联。这个 cost model 又会先排除一些永远不应内联的情况,例如 optnonenoinline、间接调用、递归调用、可变参数函数、无法确定目标的间接分支、复杂 intrinsic 等。对于可考虑内联的 call site,模型再根据优化级别、冷热路径、函数属性、指令复杂度、目标架构等因素动态调整 cost 和 threshold。

这个机制说明了一个很重要的事实:函数内联不是一个简单开关,而是一个复杂决策系统。 也正因为它复杂,攻击者才有空间通过多个编译选项组合去"推高"内联比例,而安全模型也很难简单地用一个规则覆盖所有情况。

论文澄清了几个关于函数内联的常见误解

这篇论文很有价值的一点,是它专门整理了函数内联实践中的若干误解。这部分对做二进制分析和漏洞检测的人很重要。

第一个误解是:-O0-fno-inline 就一定不会内联。论文指出,这并不成立。因为 always_inline 这类属性可能绕过普通优化级别限制,即使在 -O0 下也可能发生内联。

第二个误解是:写了 always_inline 就一定会内联。这个也不成立。因为 LLVM 内部还有一系列永不内联的规则,例如递归、间接调用、可变参数等场景可能阻止内联。

第三个误解是:没有写任何 inline 相关指令的函数就不会被内联。实际上,LLVM 会把很多函数都视为潜在内联候选,是否内联取决于函数大小、复杂度、优化级别和 cost model。

第四个误解是:函数被内联后,它原来的函数符号一定消失。论文指出,这也只是部分成立。对于内部链接的 static 函数,如果所有调用点都被内联,函数本体可能被删除;但如果还有调用点需要它,或者它具有外部链接属性,那么即使发生内联,它也可能仍然保留在最终二进制中。

第五个误解是:库函数不会被内联。论文指出,如果库函数定义在头文件中、使用 header-only 形式,或者是 LLVM intrinsic,就可能成为内联候选。

这些澄清服务于论文的核心观点:二进制分析模型如果把函数边界、调用关系、符号存在性当成稳定事实,就会遇到麻烦。因为编译器优化并不保证这些结构稳定。

论文提出并构造了 extreme inlining

在普通认知里,想让编译器更激进地优化,可能会直接想到 -O3。但论文指出,-O3 只是标准优化级别,并不等于内联比例最高。作者提出的 extreme inlining,是通过组合 LLVM 内联相关选项,让函数内联比例进一步超过标准优化行为。

论文探索了 12 个与内联相关的 LLVM Opt 选项。实验发现,最关键的两个方向是:提高全局内联阈值,以及降低函数调用惩罚。简单理解,inline-threshold 越高,编译器越愿意接受较大函数的内联;inline-call-penalty 越低,函数调用开销在 cost model 中的惩罚越小,某些 call site 更容易满足内联条件。论文第 10 页图 10 展示了这些选项对内联比例的影响趋势。

此外,LTO 也会显著影响内联。普通编译通常以 translation unit 为单位进行优化,而 LTO 会在链接阶段获得更全局的程序视图,从而允许跨模块内联。论文比较了无 LTO、ThinLTO、Full LTO 和 extreme inlining,发现 LTO 在 -O1 及以上优化级别下明显提高内联比例,但 extreme inlining 在部分程序上仍然可以超过 Full LTO。例如 coreutils 上 extreme inlining 达到 79.64%。

coreutils 的结果非常直观。以 -O0 下的 2,070 个函数作为基线,-O3 下有 1,183 个函数发生内联,其中 997 个被删除;而 extreme inlining 下有 1,744 个函数发生内联,其中 1,504 个被删除。这意味着函数级结构被大幅重塑:很多原本独立的函数不再作为独立实体存在,而是被吸收到调用者中。

论文评估了五类 ML 二进制安全任务

论文最核心的实验,是看函数内联对五类安全模型的影响。

第一类是​**二进制代码相似性检测(BCSD)**​。这类模型用于判断两个二进制函数是否语义相似,常用于补丁比对、漏洞搜索、跨架构函数匹配等。论文评估了 Gemini、Asm2Vec、SAFE、Trex、BinShot、JTrans 等模型。结果显示,普通内联会导致平均 F1 下降约 3.6%、recall 下降约 6.2%;极端内联导致平均 F1 下降约 12.6%、recall 下降约 21.2%。这里最值得注意的是 recall 下降,也就是模型更容易把本来相似的函数判为不相似。

这对漏洞检索尤其危险。因为漏洞检索往往依赖"已知漏洞函数"和"目标固件函数"的相似性。如果内联导致相似函数被判成不相似,安全工具就可能漏掉真实漏洞。

第二类是​函数符号名预测​。这类模型根据汇编代码预测函数原始名称,有助于逆向工程和代码理解。论文选择 AsmDepictor 和 SymLM。结果很有意思:普通内联对 AsmDepictor 甚至可能有帮助,因为内联带来了更多上下文;但在 extreme inlining 下,两个模型都明显受损,AsmDepictor 下降约 24.0%,SymLM 下降约 75.7%。这说明适度上下文扩展可能有利于模型,但过度内联会形成训练集中很少见的结构,使模型难以泛化。

第三类是​恶意软件检测​。论文使用逻辑回归、随机森林、CatBoost、KNN、CNN、DNN 六类模型,并使用 TikNib 提取的 62 个统计语义特征。结果显示,在野外恶意软件样本上,模型平均 F1 约为 0.98;但在极端内联恶意样本上,平均 F1 降至约 0.78。也就是说,仅通过合法编译优化生成的恶意变体,就能让检测性能下降约 20%。

第四类是​恶意软件家族分类​。这个任务比"恶意/良性二分类"更细,因为模型要判断样本属于 Mirai、Gafgyt、Tsunami 等哪一个家族。论文结果更严重:在普通野外样本上,模型平均 F1 约为 0.87;在极端内联样本上,平均 F1 下降到约 0.41。也就是说,模型不但可能识别不出恶意,还可能识别不出它属于哪个家族。

第五类是​漏洞检测​。论文选择 OpenSSL libcrypto 中的 12 个已知漏洞函数,并通过二进制相似性模型在不同编译版本中进行搜索。结果显示,内联后 MRR@100 平均下降约 69%。例如 Gemini 在 TP-Link Deco-M4 场景下从 0.321 降到 0.004,Trex 在 TP-Link Deco-M4 场景下从 0.286 降到 0.048。虽然个别模型在个别场景有例外,但总体趋势是:函数内联显著削弱了通过二进制相似性定位漏洞函数的能力。

论文进一步解释了为什么模型会失效

论文并不是只给结果,还分析了静态特征到底发生了什么变化。作者使用 TikNib 提取 62 个统计特征,其中 1--36 是指令级特征,37--56 是控制流图特征,57--62 是调用图特征。也就是说,论文观察的不只是某一类模型,而是二进制静态表示整体如何被内联改变。

在 extreme inlining 下,指令数量、未知指令数量、算术指令比例、控制转移指令、CFG 环结构、CG 调用关系都会发生变化。论文指出,极端内联会导致算术指令相关特征增加,因为被内联的函数体会在多个调用点重复展开;同时,循环数量可能减少,但循环规模变大,说明内联和后续优化可能把多个小循环合并、展开或重构成更大的结构。

这就解释了为什么 ML 模型会受影响。很多模型表面上是在学习"恶意行为"或"函数语义",但实际上可能学到了大量与编译形态相关的统计模式。例如某个恶意家族在训练集中大多使用某种编译方式,模型就可能把这种编译形态当成家族特征。一旦攻击者用极端内联重新编译,语义没有变,但静态特征分布变了,模型就会被拉出训练分布。

论文还用 SHAP 分析了增强训练前后特征重要性的变化。结果显示,极端内联会影响整个特征空间,不仅改变指令频率,也改变 CFG 和 CG 特征;增强训练后,DNN 的特征重要性变化比随机森林更明显,说明神经模型对训练分布调整更敏感,也可能更依赖复杂特征组合。

论文讨论了可能的防御方向

论文最后给出的防御思路主要有三类。

第一是​训练数据增强​。也就是在训练 ML 二进制分析模型时,不只使用标准编译样本,还应加入不同优化级别、不同内联配置、LTO、极端内联等编译变体,让模型见过更多"同语义、不同形态"的样本。论文附录表 XII 也初步显示,加入极端内联变体后,神经网络模型如 CNN、DNN 在恶意检测任务上的表现有所改善,但传统非神经模型收益有限。

第二是​compiler-aware adversarial training​。也就是把编译器变换看成对抗扰动的一部分,在训练阶段主动生成多种编译器优化变体,让模型学习更稳定的语义特征,而不是只记住某一类编译形态。

第三是​inlining-aware preprocessing​。例如在分析前尝试识别内联函数、恢复被内联的结构,或者在相似性比较时显式考虑内联影响。论文提到 Highliner 这类 de-inlining heuristic 的方向,说明未来工具可以尝试在二进制分析前做"反内联"或"内联归一化"。

不过论文也承认,这些防御都不完美。DWARF 中并不总是完整记录所有内联情况,例如函数体被死代码删除后可能没有记录,intrinsic 内联也可能缺失。不同编译器、不同版本的内联行为也不同,论文虽然主要基于 LLVM,但也观察到 GCC 在不同版本间有更大波动。因此,要做真正鲁棒的二进制分析,不能只针对某一个编译器版本做适配。

相关推荐
麻雀飞吧1 小时前
最新量化实现难点,规则和流程要先有形状
人工智能·python
武子康1 小时前
权重已到、Recipe 还在变:Kimi K3 发布后该怎样做工程验收
人工智能·后端·agent
Urbano1 小时前
打破缝制品类偏见:工序模板机跨界生产家居收纳布艺,实现服装与家居制品通用智造
人工智能·自动化
rrrjqy1 小时前
风险与组合管理——Quant-for-Beginners 量化入门Task6
大数据·人工智能
学术小白人2 小时前
医学交叉会议!EI检索!2026年人工智能与健康信息学国际学术会议(AIHI 2026)
大数据·人工智能·搜索引擎·自动化·医学
RD_daoyi2 小时前
Google偷偷给AI引用加了五个新功能:内联引用、悬停预览、品牌展示……但点击率真的回来了吗?
大数据·网络·人工智能·算法·安全·搜索引擎
网易云信2 小时前
AI 员工也要"绩效考核"?企业管理 Agent 的未来图景
人工智能·agent
小e说服饰2 小时前
表格自动化处理工具简要横评
人工智能
俊哥V2 小时前
每日 AI 研究简报 · 2026-07-27
人工智能·ai