1. 项目概述当静态分析遇上动态洞察在软件安全领域漏洞检测一直是一场猫鼠游戏。开发者编写代码安全研究员则试图找出其中的缺陷。传统上我们主要依赖两种武器静态代码分析和动态程序分析。静态分析就像一位经验丰富的建筑师审阅蓝图它能快速扫描整个代码库的结构找出所有“理论上”可能出问题的设计比如缓冲区未检查边界、可疑的指针操作。但它的痛点也很明显蓝图是静态的而程序是动态运行的。一个在静态分析中看起来危险的数组访问可能在所有实际执行路径中其索引值都被严格限制在安全范围内这就导致了大量的“误报”False Positive。安全团队不得不花费大量时间手动验证这些警报效率低下。另一方面动态分析尤其是符号执行则像是一位实地测试的工程师。它不只看蓝图而是实际“运行”程序用符号值代替具体输入系统地探索不同的执行路径并记录下程序状态的变化。这种方法能捕捉到精确的运行时语义极大降低误报。但它的代价是高昂的面对复杂的循环和条件分支路径数量会呈指数级爆炸使得对大型现实项目的完全分析在计算上几乎不可行。那么有没有一种方法能像建筑师一样快速通览全局又能像工程师一样对关键部位进行精准的实地勘测呢这正是 Concoction 系统试图回答的问题。Concoction 这个名字本身就有“混合”、“调和”之意其核心思想正是将结构化静态代码信息与动态符号执行轨迹进行深度融合利用深度学习模型来学习一个更强大、更精准的“程序表示”从而实现对软件漏洞更智能的检测。我过去在尝试构建自动化安全审计流水线时就深刻体会过这种两难境地。纯静态工具的报告往往多如牛毛让人无从下手而配置一个符号执行引擎去扫描整个项目又常常在几小时后因为状态爆炸而停滞不前。Concoction 提出的思路——不是对所有路径进行蛮力探索而是利用学习到的知识去“聚焦”于那些最可能蕴含漏洞的关键路径——这听起来正是解决实际工程痛点的方向。它不再将静态和动态视为对立的选择而是让它们协同工作用静态的广度来指导动态的深度用动态的精确来修正静态的模糊。接下来我们就深入拆解这套系统是如何设计并实现这一目标的。2. 核心架构设计静动融合的深度学习框架Concoction 的整体设计目标很明确构建一个端到端的深度学习系统其输入是源代码函数输出是该函数是否存在漏洞的预测。为了实现静动信息的有效融合它的架构可以清晰地分为离线训练和在线部署两个阶段其核心在于几个精心设计的组件。2.1 系统工作流全景在线部署时Concoction 的工作流是一个高效的自动化管道主要包含三个核心环节如下图所示此处为逻辑描述非图表预处理与信息提取系统首先使用基于 LLVM 的编译器插件处理目标项目。关键的一步是“函数内联”它将调用者函数、相关的数据结构和全局变量整合到目标函数上下文中形成一个独立的、上下文完备的分析单元。这解决了过程间分析的部分难题。接着系统并行提取两类信息一是从源代码生成增强的抽象语法树AST和程序数据与控制流依赖图PDCG二是通过符号执行引擎KLEE在被选中的关键执行路径上收集动态符号执行轨迹。联合表示与漏洞预测提取的静态信息AST/PDCG和动态信息符号轨迹被分别送入两个预训练好的 Transformer 编码器生成对应的静态嵌入向量和动态嵌入向量。这两个向量被拼接成一个联合表示向量作为检测模型的输入。这个多层感知机MLP模型最终输出一个概率值表示该函数存在漏洞的可能性。定向模糊测试验证当检测模型预测某个函数存在潜在漏洞但符号执行未能直接触发崩溃或暴露问题时系统会启动一个定向模糊测试引擎如 AFL。这个引擎会被引导至该可疑函数集中生成测试输入试图触发崩溃从而为安全研究员提供一个可复现的测试用例极大简化了漏洞验证和报告流程。离线训练阶段则更为复杂目标是让模型学会如何生成有效的“程序表示”。它采用了一种分阶段、结合无监督与有监督学习的策略。首先在一个包含10万个未标记C函数的大规模数据集上使用对比学习分别预训练静态和动态表示网络。这一步不需要漏洞标签目的是让模型学会理解代码的通用语义和结构。然后在一个包含约1.4万个带有漏洞标签的样本数据集上将预训练好的表示网络与检测网络连接进行端到端的微调。这里的一个关键创新是路径选择组件的训练它同样使用无监督主动学习目的是学会从PDCG的所有可能路径中筛选出那些对表征程序行为最关键的子集以供在线部署时进行高效的符号执行。2.2 静态信息提取超越语法树静态分析是 Concoction 的基石它提供了程序的全局视图。Concoction 没有停留在简单的词法标记Token或原始AST上而是构建了一个增强的抽象语法树Enhanced AST。标准化与解析首先系统使用基于语言服务器协议LSP的解析器对变量名进行重写采用一致的命名方案。这处理了不同编码风格带来的语法变异问题例如将idx,index,i等指向同一语义的变量统一减少噪声。构建增强AST在标准AST包含IfStmt,FunctionDecl等语法节点和标识符、常量等词法标记的基础上Concoction 引入了八种额外的边类型来丰富代码的语义信息。这些边包括Child: 标准的AST父子关系。NextToken: 源代码中下一个词法标记的顺序关系。Data Flow: 数据从定义到使用的依赖关系如a b c则b和c到a有数据流边。Control Flow: 控制流依赖关系如if条件对其内部语句的控制。GuardedBy: 表示一个变量或表达式被某个条件所保护例如数组访问arr[i]位于if (i len)块内。LastUse: 变量在某个作用域内的最后一次使用。LastLexicalUse: 考虑词法作用域的最后一次使用。ComputedFrom: 值计算来源关系。这种增强的AST本质上融合了部分程序依赖图PDG的信息形成了一个丰富的图结构。它不仅能反映代码的语法层次还能刻画数据如何流动、控制如何转移为模型提供了深度的结构化语义信息。在我处理过的许多代码分析任务中单纯基于序列如文本或Token序列的模型很容易被代码格式、变量名变化所迷惑而这种图结构表示能更稳健地捕捉程序的内在逻辑。2.3 动态信息提取符号执行的精妙采样动态信息是 Concoction 提升精度的关键。它使用 KLEE 符号执行引擎来获取程序在符号化输入下的执行轨迹。训练阶段的随机采样在模型训练阶段由于是离线进行对时间限制相对宽松。Concoction 采用非均匀随机搜索启发式方法在限时例如4小时内探索不同的执行路径收集多样化的符号执行轨迹。这些轨迹记录了在符号化输入下程序执行的语句序列以及程序状态变量值、内存状态的变化。这些轨迹被序列化后作为动态表示网络的输入。部署阶段的智能选择这是 Concoction 降低开销的核心。在线检测时如果对每个函数都进行无差别的、长时间的符号执行成本是无法接受的。因此系统启用了训练好的路径选择组件。该组件分析当前函数的静态PDCG从中智能筛选出最重要的前K条执行路径例如选择最能代表程序行为的前30%的路径然后只对这些选中的路径发起符号执行时间限制更短如5分钟。这样就实现了“好钢用在刀刃上”。2.4 模型架构双流Transformer与对比学习Concoction 的深度学习模型采用双流编码器架构。静态表示网络输入是源代码文本和扁平化后的增强AST序列。使用一个双向Transformer编码器进行学习。在预训练阶段它通过预测被掩码的Token类似于BERT的MLM任务或AST中的关系来学习代码的上下文表示。动态表示网络输入是符号执行轨迹序列。同样使用一个双向Transformer编码器来学习程序运行时行为的时序模式和依赖关系。对比学习预训练为了解决漏洞标注数据稀缺的问题Concoction 在预训练阶段大量使用了基于Dropout的对比学习。具体做法是将同一个代码样本两次输入表示网络由于Dropout的随机性会得到两个略有不同的嵌入向量它们构成一个“正样本对”。同时另一个不同的代码样本的嵌入则构成“负样本对”。训练目标是让正样本在嵌入空间中尽可能接近而与负样本尽可能远离。这种方法作为一种高效的数据增强能显著提升模型对代码语义相似性的理解能力增强模型的鲁棒性。联合表示与检测两个编码器输出的嵌入向量各100维被拼接成一个200维的联合向量。这个向量输入到一个简单的多层感知机MLP包含全连接层、Dropout层和Sigmoid输出层中进行最终的漏洞二分类预测。实操心得这种双流架构的设计非常务实。静态和动态信息本质上是互补的不同模态强行用一个编码器去处理混合的原始数据可能效果不佳。分开编码再融合给了模型更大的灵活性去分别学习两种模态的特征。在实际部署中两个编码器可以并行处理也能在一定程度上提升效率。3. 关键技术实现路径选择与模型训练Concoction 系统中最具创新性和工程挑战的部分莫过于如何实现高效的路径选择以及如何组织有效的模型训练流程。这两者直接决定了系统能否在可接受的开销下达到高精度。3.1 无监督主动学习驱动的路径选择路径选择组件的目标是给定一个函数的所有静态执行路径集合 H每条路径已被静态表示模型编码为一个向量选择一个大小为 K 的子集使得用这个子集进行符号执行后得到的动态信息能够最大程度地代表整个路径集合的语义。它的核心是一个编码器-选择块-解码器结构采用无监督方式进行训练编码与表示首先编码器将输入的路径嵌入矩阵 H 映射到一个潜在的语义空间。双分支选择块这是关键部件。选择块包含两个并行的全连接层无偏置和非线性激活。分支一重构分支学习一个系数矩阵 Q目标是使得 H 能够通过 Q 被近似重构。这保证了被选出的路径子集在信息量上能代表整体。分支二聚类分支首先对编码器输出的潜在表示进行 K-means 聚类得到聚类中心矩阵 C。然后学习另一个系数矩阵 P目标是使得 H 能通过 P 近似重构出这些聚类中心 C。这保证了被选出的路径能够保留原始路径集合的聚类结构即覆盖不同的行为模式。训练目标整体的损失函数是三个部分的加权和ℓ最小化原始路径信息与通过 Q 重构的信息之间的差异。ℓ最小化聚类中心与通过 P 重构的信息之间的差异。ℓ标准的自编码器重构损失即编码器-解码器输出的信息应尽可能接近输入。路径排序与选择训练完成后对于新的函数我们得到其路径矩阵 H通过编码器和选择块计算出 Q 和 P。将它们的列进行 L2 归一化并合并得到一个针对每条路径的重要性评分向量。根据这个评分降序排列选择 Top-K 条路径即可。为什么这样做有效传统的符号执行面临路径爆炸是因为它试图覆盖所有可能性。而漏洞往往只隐藏在少数特定的、满足某些条件的路径中例如某个循环的迭代次数恰好溢出某个指针恰好为空。Concoction 的路径选择组件通过无监督学习学会了识别那些在程序语义上“有代表性”或“与众不同”的路径。这些路径更可能包含关键的数据流和控制流约束从而更有可能触发深层的漏洞。这相当于用一个学习到的“注意力机制”来引导昂贵的符号执行避免了盲目搜索。3.2 分阶段模型训练策略Concoction 的训练流程设计得非常精巧以应对标注数据不足的挑战第一阶段无监督对比预训练数据使用从 GitHub 和 SARD 收集的 10 万个未标记的 C 函数。任务分别训练静态表示网络和动态表示网络。使用 Dropout 对比学习作为自监督任务让模型学会理解代码的通用语义和运行时模式而不需要漏洞标签。输出两个训练好的编码器能够将代码的静态和动态信息分别映射到有意义的嵌入空间。第二阶段路径选择组件训练数据同样使用上述未标记数据集。任务使用编码器-选择块-解码器结构以无监督主动学习的方式训练路径选择网络。目标是最小化前述的三部分损失函数。输出一个能够为任意函数筛选重要执行路径的模型。第三阶段有监督端到端微调数据使用从 SARD 和 CVE 数据库获取的约 1.4 万个带有漏洞标签的代码样本包含漏洞版本和修复后的良性版本。任务将预训练好的静态、动态编码器固定或进行轻微微调与一个新的 MLP 检测层连接构成完整的漏洞检测模型。使用交叉熵损失函数在标注数据上进行端到端的训练。输出最终的 Concoction 漏洞检测模型。注意事项这种“预训练 微调”的模式在 NLP 和 CV 领域已是标准做法但在程序分析领域大规模高质量的未标注代码数据相对容易获取如开源代码库而漏洞标注数据则非常稀缺且构建成本高。Concoction 巧妙地利用对比学习从海量无标签代码中学习通用表示再用相对少量的标注数据做针对性微调这是一个非常实用的工程策略。在实际操作中确保预训练数据和微调数据分布的一致性很重要例如都主要来自 C 语言的开源项目。3.3 与模糊测试的协同Concoction 并没有试图用深度学习模型“解决一切”。它清醒地认识到模型的预测可能存在假阳性也可能会遗漏需要复杂条件才能触发的漏洞。因此它集成了定向模糊测试Fuzzing作为验证和增强手段。工作流程当检测模型以高置信度预测一个函数有漏洞但符号执行在限定时间内未能生成导致崩溃的输入时系统会调用 AFL。定向引导Concoction 会告诉 AFL 需要重点测试哪些被预测为可疑的函数。AFL 使用其部分插桩模式专注于对这些目标函数生成和变异测试输入。价值这形成了一个高效的闭环。深度学习模型快速筛选出高风险目标模糊测试则作为一种强大的、基于运行时的验证工具。如果模糊测试触发了崩溃就生成了一个极有价值的、可复现的漏洞利用测试用例PoC极大方便了开发人员定位和修复问题。如果在一定时间如12小时内未触发崩溃则提示需要人工复审该预测结果。实操心得这种“AI筛选 Fuzzing验证”的模式是当前安全自动化研究的一个趋势。它结合了AI的广度快速扫描和Fuzzing的深度随机探索在实践中非常有效。我们在内部项目中尝试过类似思路将静态分析警告作为Fuzzing的种子确实能更快地发现真实漏洞。Concoction 将其流程化了。4. 效果评估与深度解析任何新系统的价值都需要通过严格的实验来验证。Concoction 的论文在多个维度上对其进行了全面评估结果颇具说服力。4.1 在真实开源项目中的实战表现研究团队将 Concoction 应用于 20 个未参与训练的开源 C 项目包括 Linux 内核、ImageMagick、SQLite 等进行了大规模自动化测试。漏洞发现数量在总计约200小时的自动化并发测试中Concoction 在所有被测试的项目中都发现了漏洞。共提交了54份漏洞报告其中53份被开发者确认37个获得了新的CVE编号。这是一个非常扎实的成果证明了其在现实场景中的有效性。漏洞类型分布发现的漏洞中缓冲区溢出相关堆和栈占比最高62.3%其次是段错误11.3%和内存泄漏7.5%。这符合C语言程序常见漏洞的分布。Concoction 能够检测这些漏洞关键在于其结合静态数据流分析和动态符号值范围推理的能力。例如对于缓冲区溢出它能通过静态图分析数组访问模式再结合符号执行验证索引值的实际范围是否可能越界。案例分析堆缓冲区溢出CVE-2022-26181在Lepton项目中一个内存释放函数aligned_dealloc存在缺陷。静态分析可能因为指针运算和条件判断的复杂性而误判或漏报。Concoction 通过将关键变量data符号化并结合静态分析发现data[-1]的访问模式推断出在某些路径下可能发生越界读从而成功识别。内存泄漏CVE-2021-3574在ImageMagick中一个内存分配malloc的大小变量是samples_per_pixel而释放free的大小是MaxPixelChannels。Concoction 通过学习动态执行轨迹中这两个变量的值关系发现可能存在分配多、释放少的情况从而检测出内存泄漏。这种需要关联不同位置变量值的漏洞纯静态分析很难精确判断。4.2 与现有方法的横向对比论文将 Concoction 与16种先前的方法进行了对比包括11种基于学习的模型、2种符号执行引擎、2种静态分析工具和1种模糊测试工具。在标准数据集SARD上的表现Concoction 在准确率、精确率、召回率和 F1 分数上全面领先。尤其值得注意的是它在保持高精确率低误报的同时也实现了高召回率低漏报F1分数显著优于其他方法。这表明静动融合的策略在平衡“查得全”和“查得准”方面取得了良好效果。在更复杂的CVE数据集上的表现该数据集中的函数更大、更复杂。在此挑战下基于纯静态信息的方法如 VulDeePecker, Devign性能下降明显。而 Concoction 依然保持了最佳性能。基于动态信息的 LIGER 方法表现次之但其 F1 分数仍比 Concoction 低 10.4%。这凸显了结合结构化静态信息对于理解复杂程序的重要性。在已知CVE项目上的对比在包含35个已知CVE的3个开源项目测试中Concoction 实现了100%的精确率和89%的召回率发现了31个漏洞。它不仅发现了所有其他方法能找到的漏洞还额外发现了9个被其他方法遗漏的漏洞。纯静态分析工具CodeQL, Infer由于依赖手写规则覆盖能力有限只发现了最多5个漏洞。纯符号执行KLEE和模糊测试AFL由于路径覆盖和时间的限制分别只发现了4个和8个漏洞。这充分说明了 Concoction引导式分析的高效性。4.3 消融实验与组件分析为了理解各个组件的贡献论文进行了详细的消融研究仅静态Static只使用增强AST信息F1分数为68.7%。这代表了当前主流静态深度学习方法的水平。仅动态Dynamic只使用随机采样的符号执行轨迹F1分数为77.2%。这显示了动态信息的强大但成本高且可能因采样偏差遗漏关键路径。无对比学习NonCL移除对比学习预训练模块F1分数下降至82.4%。这证明了在无标签数据上进行对比预训练对于学习高质量程序表示、防止过拟合至关重要。无路径选择NonSel不使用智能路径选择而是随机选择路径进行符号执行F1分数降至77.6%。这直接验证了路径选择组件对于高效获取有价值动态信息的关键作用。随机采样可能错过关键路径。完整系统Conc完整的 Concoction 系统取得了86.1%的最高F1分数。此外实验还测试了系统对训练数据噪声错误标签和动态信息缺失部分符号轨迹无法获取的鲁棒性。虽然性能会下降但在极端情况下完全无动态信息系统仍能退化为一个纯静态分析器其表现仍优于其他纯静态的基线方法这体现了系统的实用性。4.4 开销分析训练开销Concoction 的训练开销主要来自特征提取特别是符号执行和模型迭代。由于使用了路径选择和对比学习其训练时间比一些简单的序列模型如 VulDeePecker长但与使用复杂图神经网络的模型如 Reveal, Devign处于同一量级。考虑到模型训练是一次性的离线过程这个开销是可以接受的。部署开销在线检测时对于单个函数Concoction 能在几分钟内完成预测包括静态分析、路径选择、有限时间的符号执行和模型推理。只有当预测为漏洞时才会触发可能持续数小时的定向模糊测试。论文指出这可以集成到夜间自动化构建系统中对开发流程的干扰很小。深度思考Concoction 的成功并非偶然。它反映了一个清晰的思路演进从纯静态易误报到纯动态开销大再到静动结合。而其结合方式并非简单堆叠而是通过深度学习模型进行“表征融合”并用一个学习到的模块路径选择来优化最耗时的环节符号执行。这为构建下一代智能漏洞检测工具提供了一个非常有力的蓝本。当然它目前主要针对C语言函数级漏洞对于更复杂的跨过程分析、递归、指针别名等挑战以及向其他语言如C、Java、Python的扩展仍是未来需要探索的方向。但无论如何Concoction 已经清晰地展示了这条道路的潜力和可行性。