基于NLP技术的工业软件代码克隆检测溯源分析

2022-05-30 08:23付修锋贾张涛杨铁湃安恒金玉川耿宏伟
计算机应用文摘·触控 2022年13期

付修锋 贾张涛 杨铁湃 安恒 金玉川 耿宏伟

关键词 开源软件 程序对比 代码溯源 代碼复用

1引言

在行业规模不断扩大的过程中,软件产品功能必须随着用户需求的增加而不断地做出调整[1] 。而功能的迭代使得软件内部业务逻辑变得越来越复杂。

这就要求在资源有限的情况下,开发人员团队必须快速对这些复杂的需求做出应对,以满足如今充满竞争的软件市场。

起初,由一些IT 行业人员自发地用博客的形式将开发过程中的心得、经验分享和代码工程发布到互联网中,构成了早期的开源社群。如今,这些小型社群吸收了越来越多开源的支持者,他们是多方位、多元化、丰富的开源项目代码的来源,从而集成了不断壮大的开源生态系统。市面上热度最高的开源社区有GitHub,CSDN,StackOverflow,据2021 年GitHub 年度报告显示,2021 年GitHub 工程数目已经达到1 亿7000 万,用户年总量增长30%,其中中国用户755 万,位居全球第二。丰富的、易获取的代码来源和庞大的用户群体,方便了程序员快速、准确地找到已有的代码。通过对目标代码的复用和调试,我们可以在短时内解决项目模块开发“冷启动问题”,也减少了时间上的开销。代码复用即开发者通过复制、粘贴或引用调包等方式使用了已有的开源代码[2~4] 。

开发人员在享受代码复用带来高效率开发快乐的同时,也同样意识到了背后所存在的安全隐患和侵权问题。最著名的就属Oracle 与Google 由于代码复用长达数年的诉讼官司。从此可以看出,开发者必须了解项目工程中的复用代码,哪怕仅仅是几行代码,都可能给公司带来不可估量的损失,也会给后期项目维护带来额外管理支出。

代码溯源就是在复用代码与已知代码库中,从代码的相似性高的角度出发,完成代码追溯工作。对此,本文利用NLP 自然语言技术提出了一种精准快速模块溯源方法。通过统一代码中的特定标识,将完整检测样本切分。在每一块函数代码块映射成唯一的特征向量的基础上,利用新颖的工程相似计算方法得出代码溯源。实验结果表明,该方法能满足开源代码复用中相似度检测的需求,且有着较高的准确率。

2算法描述

由于一般商业软件普遍采用“闭源”方式并不会提供软件源代码,所以我们主要研究二进制代码溯源。在本文中,我们将已知项目工程拆分成文件级和函数级两个维度进行溯源。通过归纳,总结二进制文件的反汇编文件中函数跳转标识,将工程文件中方法函数切分成多个代码块,并储存在多个分词序列中。利用NLP 技术将每一条代码序列映射成统一潜在空间的二进制特征向量。我们也可以获取待测工程代码二级制特征向量。两者之间自然语言相似时,其特征向量之间也应该具有相似性,由此判断两个工程的溯源关系。快速溯源模型共包括3 个阶段,即溯源数据预处理阶段(图1)、溯源函数特征提取阶段以及溯源函数复用比对阶段。

溯源数据预处理阶段是将二进制代码文件用函数跳转标识进行切分,再对每一种函数方法利用统一的变量格式进行标准化,最终获得多个统一格式的函数代码块。

函数溯源函数特征提取阶段是以函数为基本单位,对函数代码进行语法、语义等数据分析,获取函数文本中隐含的函数特征向量。函数特征向量必须满足以下特点:(1)相同的函数指令序列产生的函数特征编码应该是相同的,不同的函数指令序列应产生不同的函数特征编码;(2)汇编代码通过特征编码模型构造出来的与函数代码对应的特征向量是唯一的,即每一个函数特征向量都能很好地表达对应函数块的特征;(3)相似的函数指令序列应产生相似的函数特征编码,以满足相似函数的判定需求。

工程相似度计算阶段是计算两个对比函数特征向量的相似性,以判定溯源等级。

2.1溯源数据预处理

溯源数据预处理的对象是汇编代码,所以要用反编译工具将二进制样本逆向获取汇编代码;在项目工程中,多个相互调用方法的组成特定的功能函数,而多个特定函数代码块集成一个汇编文件[5] 。

用P 表示获取的汇编代码,F 表示功能函数,L 则表示代码块。由此可得,P = {F1,F2,Fn }, F = {L1,L2,L,Lm }。随后,我们对每一个代码块进行了标准化,具体如图1 所示。

在汇编代码中有许多代码函数的起始和终止的标识符,如“near”和“end”,由此可以将汇编文件切分成多个代码块。汇编文件切分的代码如下:

在函数loc_4023E7 中,根据跳转指令“jnz”与跳转地址“loc_4023E7”将其划分为2 个代码块,对第一个代码块,依据函数位置命名,如表1 所列。

2.2使用NLP 技术进行代码特征的提取

通过跳转符拆分代码块序列,利用NLP 算法为开源工程中的每一个标准化工程文件序列、函数块序列、代码块序列分别进行编码,获取对应的特征向量Embding。在函数特征构造算法中,每一步的输出均为下一步的输入,具体实现步骤可分为5 个部分。

(1)将汇编文本中的代码块序列映射到潜在特征空间中:使用传统哈希算法将每个特征分词映射成一组长度为64 位的二进制数列。

(2)统计分词权重:在一段代码分词序列中,某一个分词出现的次数越多,可能其对整个代码特征提取的影响越大。通过统计出分词例如“sub,mov,js”出现的次数作为该分词的权重值。

(3)获取每一个分词的加权向量:本文使用的是将“统计分词权重”中获取的分词权重值与分词哈希值相乘获取分词的加权向量。例如,通过哈希函数得到“mov”的6 位二进制哈希向量(1,1,0,0,1,0),序列中“mov”出现的次数是6,其中比特值为0 的位替换成-1,则“mov”的加权向量是(6,6,-6,-6,6,-6)。

(4)合并:将上述加权向量的对应位相加合并,压缩数据,此部分的输出是一个含权值向量。

(5)降维:将合并后的向量降维,若该位的数值大于0,则出1,否则该位出0,最终计算出特征向量值。

2.3工程相似度计算

常见用来衡量代码相似度的指标主要有以下几个:欧氏距离、余弦相似度等。由于本文提出了一种基于函数代码块的特征提取方法。因此,将两个函数块分别得到对应的函数特征向量,其计算原始文本相似程度则可以通过计算它们特征向量之间的距离来获取。该距离是指函数指纹对应比特取值不同的比特数(函数指纹长度需相同),具体计算公式如下:

公式中X 和Y 表示两个长度为n 位的函数指纹,M 表示函数指纹X 与Y 的异或结果,m 表示异或结果M 的第i 位的值。首先对两个函数指纹X,Y 做按位异或操作,得到异或码M。此步骤中,如果两个函数指纹对应位相同,则出“0”,若不同则出“1”。然后对M 的每一位求和,即可获取两函数指纹间的距离。我们通过大量的参考文献的统计,同时考虑方法的精确度和运行效率的情况下,将评估两个特征向量之间的距离阀值设定为3,即当两个代码特征向量通过距离运算得到的结果小于等于3 时,我们认为这两个函数相似。

输入:两个工程的源代码

输出:两个工程的代码相似度S

3實验结果与分析

本文从工程相似度角度出发进行程序对比分析。为了证明方法的函数溯源能力,我们采用评估函数复用的准确率作为模型的评估指标。

二进制代码溯源验证主要通过针对同一软件不同编译版本和已知存在复用事实的不同软件开展二进制代码溯源。具体验证过程如下:(1)将Chrome 浏览器(版本号49.0.2623.112) 入库;(2) 上传Chrome浏览器(版本号44.0.2403.155)开展二进制代码溯源;(3)上传Redcore 浏览器(版本号49.1.2623.213)开展二进制代码溯源。

通过前期数据处理分析,Chrome 浏览器44.0、Redcore 浏览器49.1 文件级和函数级个数如表2 所列。我们将Chrome 49.0 中的函数作为基准代码,Chrome 44.0,Redcore 49.1 中的每个函数作为溯源的对比函数。利用特定的对比方法判定两个特征向量的相似性,对比结果如表3 所列。

利用代码克隆检测溯源方法对两个工程中每一个函数进行编码得到函数特征向量,发现Chrome44.0,Redcore 49.1 与Chrome 49.0 文件级别高相似个数都是7 个,两者函数级别高相似函数个数分别为14个和13 个。同时,由手动查看三者之间相似文件和函数统计得出,该方法溯源准确率为95.77%,因此本文的溯源方法具有很高的准确率。

在待检测的两个工程中,存在大量的相似函数调用关系,例如chrome.dll,chrome_elf.dll 就被调用。导致它们虽然从界面上和操作流程上不大相同,检测结果却非常相似。因此,通过函数块相似度计算判定溯源,比手工判定更加准确和高效。

4结论

在工业软件项目的开发过程中,由于复用代码的同源判定通常依赖于人工经验识别,导致同源判定工作效率较低。针对该问题,本文提出了一个能够快速准确提取二进制工程文件特征的模型,并在此基础上设计了一种代码相似度对比方法,完成待检测代码的溯源。在已知代码数据集的实验表明,该方法通过提取代码特征,有助于减少出现复用代码溯源误报、漏报的情况。