新闻详情

新闻详情

首页 / 资讯中心 / 详情

基于函数语义与程序嵌入哈希的大规模二进制代码相似性分析

发布时间:2026/9/28 13:28:27来源:尧图网络
基于函数语义与程序嵌入哈希的大规模二进制代码相似性分析
把二进制里的一个函数换一种架构重新编译它可能完全变样mov变成str循环被展开成线性指令寄存器被换名常量被重算。你问一位做二进制分析很多年的工程师“这两坨二进制是不是同一个函数”他盯着屏幕看半天也只能说一句“感觉像”。这正是二进制代码相似性分析Binary Code Similarity AnalysisBCSA领域每天要面对的难题。KEENHash 这篇论文想做的事情就是把这句“感觉像”变成可在百万级函数库里自动检索、有依据、可批量完成的“像”。论文标题里的三个关键词——基于函数语义、程序嵌入哈希、大规模二进制代码相似性分析——基本概括了它的技术路线。这篇解读适合正在做二进制漏洞挖掘、恶意代码变种追踪、固件组件识别等方向的同学。我会先用大白话把 BCSA 的问题掰开再顺着论文的核心流程讲清楚它怎么把“函数语义”变成“可检索指纹”最后从工程落地角度聊聊这套思路的适用边界和实战价值。1. 先明确问题二进制相似性分析到底在比什么1.1 为什么“源码比对”的思路搬不到二进制上在源码层面比较两个函数很简单规范化抽象语法树、去掉注释、统一变量名剩下的可以当作文本差来比。但二进制完全不是这样。编译过程丢掉了绝大部分语义信息——变量名、类型、函数名、注释全部消失剩下的只有指令流和跳转边。编译器不同GCC、Clang、MSVC指令选择就不同架构不同x86、ARM、MIPS指令集本身就没有对齐的语法优化级别不同O0、O3函数的控制流图可能结构都变了。所以 BCSA 领域有一个共识二进制相似性不能建立在“长得像”上而要建立在“做同一件事”上。这里的“做同一件事”就是函数语义。一个函数从源码变成二进制无论被哪个编译器、哪种架构、哪级优化处理过它在逻辑上到底完成了什么操作、调用了哪些外部函数、对哪些内存区域做了读写、在什么条件下跳转——这些信息才是跨平台比对时真正稳定的锚点。1.2 传统方法各有什么本钱和短板在 KEENHash 之前业界和学术界已经积累了不少办法但各有各的痛方法类型代表优点主要瓶颈模糊哈希ssdeep、TLSH计算极快、实现简单跨架构/跨优化级别基本失效结构指纹BinDiff对结构化程序接近度判断准图匹配代价高大规模不可行图神经网络嵌入Gemini、AlphaDiff能学习跨架构的结构语义训练成本高冷启动困难符号执行摘要各类 symbolic execution 工具能给出精确路径语义路径爆炸规模化受限语义元组嵌入哈希KEENHash跨架构、可增量索引、支持百万级对语义词表和嵌入质量敏感这张表想说明的核心是没有银弹。选型本质上是在“语义表达力”和“检索性能”之间做权衡。模糊哈希快到没有朋友但语义表达能力约等于零图神经网络语义表达力不错但百万级函数库上做全库图匹配就是灾难。KEENHash 的定位是在语义表达力和检索效率之间找了一个新的平衡点。2. KEENHash 的核心流程从函数语义到可检索指纹2.1 表示层先给函数画结构骨架一个函数不是一串平铺的指令它内部有基本块和分支跳转。KEENHash 的第一步是先把二进制函数恢复成控制流图CFG。CFG 的每个节点是一个基本块——一段顺序执行、中间没有跳转进出的指令序列边则是基本块之间的跳转关系。为什么要用 CFG 而不是把所有指令连成一串因为顺序串会丢失分支结构信息。循环、条件分支、switch 跳转表往往是函数语义最密集的地方这些信息只有在 CFG 这种图结构上才能被有效保留。后面所有语义单元的特征提取都是在 CFG 的节点和边上完成的。这里得提醒一句工程现实从真实二进制里提取 CFG 时很多函数没有符号函数边界可能不准确未对齐代码、不可达代码、异常表中涉及的地址都可能给 CFG 带来结构噪声。论文里的方法对噪声有一定容忍度但如果拿到手的第一步函数边界就是错的后面语义元组分得再好也白搭。实际做的时候最好先用 Ghidra 或 angr 做一轮函数恢复再用序言指令做二次校验。2.2 语义层把基本块里的指令变成“语义元组”论文里最有辨识度的部分是“函数语义”的具象化方式。它没有直接拿原始字节或者助记符来做比较而是把基本块内的指令按语义角色进行抽取和归一化形成一种语义元组semantic tuple。什么叫语义角色就是先把指令抽象成操作类型——访存、算术逻辑、函数调用、跳转、栈操作、返回等等再把寄存器按角色归类通用寄存器、参数寄存器、返回寄存器、栈指针、帧指针。这一步做完跨架构的可比性就出现了。举个例子x86 里的push rbp和 ARM 里的str r11, [sp, #-4]文本上完全不是一个东西但它们在语义上都属于“保存栈帧上下文”的操作。如果直接比助记符它们之间没有任何交集但如果被抽象成语义元组两者就对齐到了同一种语义角色上。从工程角度讲这一步也是最费力气的地方。解析器要处理不同架构的寻址模式、操作数类型、立即数大小、PC 相对寻址、Thumb/ARM 模式切换等一堆细节。论文说自己是“基于函数语义”分量其实就压在这里——语义表达的质量决定了嵌入向量能学到什么。2.3 嵌入层把语义元组投影到向量空间有了离散的语义元组之后KEENHash 采用了类似自然语言处理中词嵌入的思路把这些语义元组当成 token在大量二进制代码语料上训练成稠密向量。为什么要嵌入而不是直接比较字符串因为语法不同但语义等价的元组在离散空间没有任何交集但在向量空间中只要训练语料足够丰富它们会被推到相近的位置。这个逻辑和 NLP 里“国王”和“君主”的词向量靠近是同一个原理。函数在这里被类比成一段话语义元组就是组成这段话的词汇整个 CFG 则是这段话的篇章结构。训练语料怎么来最常见的方法是拿同一批源代码用不同的架构、不同的编译器、不同的优化选项编译出多份二进制函数对以语义等价作为监督信号。这也是 KEENHash 这类方法能跨架构的根基——没有这种带等价标签的大规模语料嵌入模型学不到“不同字节码同一语义”的对应关系。在基本块向量聚合成函数级向量时论文通常会用带注意力的聚合方式而不是简单做均值池化。原因很朴素一个函数里不同基本块的语义权重不一样。循环入口、调用点、比较跳转这些位置携带的信息量往往比纯算术计算块大得多。简单平均会把这些关键信号稀释掉。用注意力机制去学一个权重让模型自己判断哪些基本块对函数语义的贡献更大聚合出来的函数向量信息密度会高很多。2.4 哈希层让百万级检索变得可操作嵌入向量是高维浮点数。如果直接把所有函数的向量存在内存里每次查询都做全库距离计算百万级函数就是百万次浮点运算工程上撑不住。KEENHash 的做法是在最后加一道哈希化利用局部敏感哈希LSH的性质把语义嵌入向量投影到若干个随机超平面或锚点上得到固定长度的二进制指纹。这里的关键点在于它用的不是普通哈希。普通哈希追求的是“输入微小变化输出完全改变”LSH 要的是反过来的性质——原始向量空间中距离越近的点哈希后落在同一个桶里的概率越高。正是因为这种“越像越容易碰撞”的特性检索时不需要全库扫描只需要遍历查询函数落在的桶把桶内候选捞出来再做精确排序。这个设计把“嵌入”和“检索”的职责分得很清楚浮点向量负责表达语义的细微差异二进制指纹负责快速圈定候选范围。这也是标题中“程序嵌入哈希”的含义——嵌入是用来学习的哈希是用来索引的。3. 为什么“嵌入哈希”的组合比单一相似度算法更抗打3.1 向量近邻搜索的瓶颈在于全库扫描很多人看到“嵌入向量可以直接算余弦相似度”这句话会觉得问题已经解决了。但放到大规模场景里直接算距离是 O(N) 的复杂度。N 是百万时一次查询就是百万次向量乘法。如果只是偶尔查一两次还能忍但 BCSA 在真实场景里往往是批量任务——一个固件库里可能有上万个函数需要逐一匹配这就是上亿次计算。所以工程上必须有一个前置阶段先快速圈定“大概率相似”的候选集把百万级收窄到几百个再对这几个候选做精确的向量相似度计算。KEENHash 的哈希索引就是那个前置阶段。3.2 哈希把函数库按语义行为切成薄片二进制库里函数的长度和复杂度分布极不均衡简单函数几千个复杂函数几百个。嵌入哈希带来的一个好处是语义指纹天然把函数按行为切片——即使函数总量达到百万级落入同一个桶的往往只有几十到几百个候选。构建倒排索引之后查询就不再是全库扫描了而是“按桶取交集”。可以把它理解成图书管理员先根据分类号找到对应的书架再在书架上逐本翻而不是把整个图书馆的每一本书都过一遍。桶的数量、哈希串的位数决定了这个“书架”分得多细也直接决定了候选集的大小和召回率。3.3 碰撞容忍与重排序两阶段检索的工程细节LSH 必然存在碰撞问题真相似的函数可能没有落进同一个桶不相似的函数可能挤进同一个桶。工程上通常用多桶策略来缓解——用多组相互独立的 LSH 哈希函数族一个函数同时落入多个桶查询时把所有相关桶的候选汇总、去重最后用嵌入向量的余弦相似度做精排。另一个细节是重排序不能直接看指纹重合的比特数。因为哈希已经丢失了细微差异指纹相等只能说明“语义很可能接近”不能说明“语义几乎一致”。真正要拿到精确匹配分数必须回到浮点向量上重新计算距离。这也是 KEENHash 分层设计的精髓——嵌入负责表达哈希负责检索两阶段各司其职。4. 论文评估里值得关注的三个观察4.1 对比基线的选择比总准确率数字更有信息量做 BCSA 论文一个很容易被质疑的问题是“是不是只在自家数据集上刷分”。靠谱的评估通常要选两条基线路线传统哈希类ssdeep、SimHash、TLSH和深度学习方法类Gemini、AlphaDiff 等。前者用来证明“语义嵌入哈希”比“字节级哈希”表达力强后者用来证明检索效率和冷启动成本有优势。但我觉得最有价值的评估不是总准确率而是分情境的准确率同架构、不同优化级别的同一源码函数能找回多少不同架构之间的跨体系函数能找回多少经过轻微混淆、加壳、指令替换之后的变体还稳不稳这三个问题分别对应三种不同的工程需求。如果论文只给一个总数字、不给分情境的结果等于没有把场景想清楚。KEENHash 的公开工作里比较难得的就是区分了这些条件读者可以按自己的业务场景挑对应指标来参考。4.2 跨编译器和跨优化级别才是真正的试金石“同一段 C 代码分别用 O0 和 O3 编译”之后这两个函数的外表差异非常大。O3 会把函数内联、循环展开、指令重排CFG 结构可能面目全非。如果一个方法在这里表现好说明它不是靠表面结构记忆而是真正抓住了语义骨架。KEENHash 这类“语义元组嵌入”的方法优势在于它比纯图结构更灵活基本块顺序变了、分支结构变了只要关键操作函数调用、访存行为、比较跳转的语义还保留着嵌入哈希的相似度就不会崩塌。从实践结果看这类方法在跨优化级别和跨架构检索上的表现通常要明显优于基于语法 n-gram 或者简单 CFG 结构指纹的方案。4.3 看效率指标时别忽略“百万级”背后的构建成本标题里“大规模”不是装饰词。公开评估一般要覆盖几十万到百万函数量级这时候需要同时关注三个指标索引构建时间从反汇编到哈希入库是完全离线的过程应该在小时级完成。单查询时延正常情况下应落在毫秒到几十毫秒区间。索引体积二进制哈希串比浮点向量省几个数量级这也是哈希化的直接红利。很多方法在小数据集上是英雄一到百万级就拉胯根因就是缺少索引结构。KEENHash 通过“嵌入LSH”把查询复杂度压到亚线性这是它能宣称支持大规模分析的关键。自己做选型对比时一定要拿百万级数据重新测一遍别被小样本上的漂亮数字骗了。5. KEENHash 这条思路给二进制分析工程带来的启发5.1 把“单函数匹配”升级成“全库扫描”以往做漏洞影响面分析常规做法是拿着已知漏洞函数一个固件一个固件地翻效率非常低。KEENHash 的思路反过来先把整个固件库、组件库里的函数全部离线生成语义指纹索引等到新漏洞函数出现时直接查索引几秒钟得到候选再用慢而准的精排器确认。这种“先建库、后查询”的模式让漏洞影响面排查从人工差事变成了半自动化流程。对于做安全运营的团队来说这个模式的吸引力很大补丁发布了、新 CVE 爆出来了能第一时间知道哪些存量固件受影响而不是等到被攻击之后才去倒查。5.2 流水线里的四个组件可以单独复用即使你不打算完整复现 KEENHash把它的模块拆出来单独用也能给现有工具链带来改善反汇编CFG 提取直接承接 IDA、Ghidra、angr 的输出。语义元组生成器跨架构归一化的关键可以独立用于特征工程。预训练嵌入模型token 级语义表示适用于任何需要“代码向量化”的下游任务。LSH 多桶索引任何“向量化表示相似检索”的场景都能套用不只是二进制领域。这四层结构几乎是可插拔的。哪怕只是把 LSH 索引模块接入现有向量检索流程性能提升也是立竿见影的。5.3 冷启动与增量更新分层设计的长线价值有一点很值得注意KEENHash 把嵌入学习和索引构建分离了。嵌入模型一旦训练完成新入库的二进制不需要重新训练只需要做推理哈希入库即可。对于持续更新的威胁情报库、组件版本库这类长期运营的业务这一点非常关键。对比之下GNN 类方法如果来了新架构、新编译器往往要把图模型重新训一遍冷启动成本很高。KEENHash 的分层设计让“更新”这个动作变轻了模型不需要动变动只发生在索引层。长期维护一个持续增长的分析平台时这个差异会随着时间放大。6. 实操视角我要不要用 KEENHash 这套方案6.1 什么场景收益最大什么场景别碰先泼一盆冷水KEENHash 不是万能的选型前一定先判断场景匹配度。收益最大的场景有三类大规模固件库漏洞影响面排查几千上万个固件每次新漏洞都要快速知道影响范围。跨架构恶意代码变种检测同一恶意家族在不同硬件平台上的变种字节码差异极大但行为语义一致。二进制软件成分识别拿不到源码但需要回答“这个库里包含哪些开源组件、什么版本”。不适合的场景也有三类单文件少量函数的精细分析函数数量不超过几千哈希索引的优势无法发挥直接用图编辑距离或向量化线性扫描更简单。强混淆对抗场景语义被刻意破坏时任何嵌入和哈希方法的可靠性都会显著下降这时候得靠符号执行甚至人工逆向。实时在线流量检测如果要求在纳秒级完成匹配哈希指纹仍然不够快需要专门的硬件加速方案。6.2 落地时容易翻车的几个细节结合我自己实际跑 BCSA 项目的经验有几个坑比想象中更容易踩函数边界恢复是地基。很多二进制没有符号表拿不到准确函数边界。语义元组分得不对后面嵌入、哈希全盘出错。建议用 Ghidra 或 angr 做函数恢复再用序言指令匹配做二次校验不要在边界不干净的数据上强行跑。哈希桶参数必须调。桶数和哈希位数需要根据目标库的函数分布来调整。函数库越密集桶数要越多否则候选集过宽重排序压力大。论文里的参数是基于它的实验语料调的直接照搬到自己数据集上大概率不是最优。训练语料的架构覆盖面决定泛化上限。如果嵌入模型只在 x86 和 ARM 上训练过上 MIPS、RISC-V、龙芯等架构会明显掉点。工程上线前至少要验证三个异构架构并且预留扩展新架构时的增量训练通道。阈值校准要看分布不要只盯 Top-1。Top-1 命中率好看不代表阈值好用。把相似度分数画成分布图观察同类函数和异类函数的分界区间再设置阈值做批量扫描否则误报会淹没人最终还得人工一条条核。我自己在做一个固件运营平台的时候最大的体会是BCSA 的工程本质其实是“先召回、后精排”。KEENHash 把召回阶段做得足够快、足够广把精排阶段留给可插拔的精确比较器这个两层设计比任何一个单独的相似度算法都重要。如果你的团队手里也有成千上万个固件要持续盯与其反复调一个“万能匹配模型”不如认真把语义嵌入和哈希索引这条链路建起来。先用语义哈希把候选范围从百万缩到几百再用你信任的细粒度比对器做最终裁决这条路在真实业务里的性价比远比想象中高。
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

嵌入式MCU开发全流程:编译、烧录与仿真实践指南 2026/9/28 19:17:24

嵌入式MCU开发全流程:编译、烧录与仿真实践指南

很多刚接触嵌入式MCU开发的朋友,经常被“编译、烧录、仿真”这三个词绕晕:代码写完了编译一下,报错;好不容易编译通过,烧录时又提示连不上设备;好不容易烧进去,程序却不按预期跑。我在带新人和做…

阅读更多 →
python执行脚本快捷方式 2026/9/28 19:17:11

python执行脚本快捷方式

1.复制代码echo off chcp 936 >nul cd /d "%~dp0" python 截图.py %* echo. echo finished echo (book counts are in the last lines above, or in logs folder) echo pause >nul2.将文件名字改名 截图.py改成你想要运行的项目名称.py3.将文件保存成bat后…

阅读更多 →
超薄电池产品开关机方案选型:DFN负载开关的工程实践 2026/9/28 19:17:11

超薄电池产品开关机方案选型:DFN负载开关的工程实践

做超薄电池产品这两年,我在硬件选型上最有发言权的就是开关机方案。TWS充电仓、智能工牌、电子价签、智能戒指这种贴着电池做的产品,PCB厚度被卡得死死的,以前常用的滑动开关、轻触开关根本塞不进去,而软件软开关那套自锁电路也经…

阅读更多 →
超薄电池产品开关机方案如何选?DFN单键开关机芯片专治量产通病 2026/9/28 19:17:11

超薄电池产品开关机方案如何选?DFN单键开关机芯片专治量产通病

前段时间朋友那边一款超薄磁吸移动电源(总厚度不到9毫米那种)量产遇到了糟心事:几十台机器在老化房里关机再开机,怎么都点不亮。我过去一看,问题不在电池,也不在MCU,而是卡在开关机方案上——他…

阅读更多 →
PS5 PKG游戏安装全攻略:从环境准备到后台安装与排障 2026/9/28 19:17:11

PS5 PKG游戏安装全攻略:从环境准备到后台安装与排障

1. 先搞清楚一件事:PKG安装到底在解决什么问题这两年PS5的玩家社区里,"PKG"这个词出现的频率越来越高,很多刚接触的新手一脸懵:这不是PS3、PS4时代的东西吗?怎么PS5又开始流行了?其实逻辑完全一样…

阅读更多 →
AI绘画批量交付:ComfyUI与PS协作全流程解析 2026/9/28 19:17:11

AI绘画批量交付:ComfyUI与PS协作全流程解析

最近有一个需求特别能说明问题:甲方要在七天内交付三十张电商场景图,产品是同一款保温杯,却要出现在办公桌、露营营地、厨房台面等七八种不同场景里。一开始我天真地以为,只要找到一个大模型,输入几段提示词就能批量出…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉