论文名称 | 发表时间 | 发表期刊 | 期刊等级 | 研究单位 |
Interpretation-Enabled Software Reuse Detection Based on a Multi-level Birthmark Model | 2021年 | ICSE | CCF A | 西安交通大学 |
1. 引言
研究背景:随着开源软件(OSS)的日益普及,软件重用成为一种普遍现象。 然而,现有代码的广泛重用会导致许多许可证违规问题。 例如,Cisco 和 VMWare 由于不遵守 Linux 内核的许可条款而面临重大法律问题。 更重要的是,由于不小心的软件重用可能会引发安全问题。
软件重用检测:软件重用检测的目标是确定候选程序(candidate)是否包含目标程序(target)中已使用的相似代码。 根据分析对象的不同,这些方法可以分为两类:源代码重用检测和二进制代码重用检测。 第一个通过将源代码抽象为一组特征来计算代码相似度,例如字符串、令牌、抽象语法树(AST)以及程序依赖图 (PDG) 等。 由于候选程序的源代码在现实中通常不可用,因此二进制代码重用检测广泛用于软件抄袭检测、恶意软件检测、补丁分析等。然而,现有的二进制代码重用检测方法存在三个限制:
- 缺乏可解释性:现有方法的检测结果缺乏提供详细证据来支持重用检测结果的能力,因为它们通常只以 0 到 1 之间的相似度分数的形式报告结果。全面解释检测结果的能力极其重要,因为它可以提供细节或理由,使检测结果可接受或易于理解。
- 检测精度低:大多数依赖结构和语法信息的方法无法处理编译变化引起的差异。 这是因为源程序的不同编译自然会在其二进制代码中产生不同的结构和语法,形成混淆。
- 检测效率低:一些工作使用暴力的方法来识别重用函数,这种方法的成本过高,因为它测量目标程序和候选程序之间所有函数对的相似性。 如果两个程序中的函数数量都很大,这种方法会导致效率低下。 因此,这些方法在重用部分仅占候选程序的一小部分的情况下既无效,又在需要比较过多的函数对时无效。
研究内容:为了克服上述限制,论文提出了 ISRD,一种基于多级胎记模型的新型支持解释的软件重用检测方法,它具有以下显着优点:
- 可解释的检测结果:ISRD能够在多个粒度(函数、基本块以及指令)捕获程序语义,并通过多级胎记模型来唯一标识程序。 在函数级别,构建函数调用图(FCG)来分析程序行为。 程序的FCG是一个有向图,由一组表示函数的节点和一组表示函数之间的调用者和被调用者关系的边组成。在基本块与指令级别,提取基本块链与规范化指令实现基本块和指令的语义表示。上述三个级别中 target 的胎记与 candidate 的胎记之间的相似部分可以重建重用场景,进而解释和证明检测结果。
- 更高的检测精度:为了提升重用检测的精度,论文在胎记模型的不同级别上进行标准化。 具体来说,在基本块级别,论文首先将每个函数转换为一组最小分支路径(MBP),它们是从初始节点或分支节点开始并以终端节点或相邻分支结束的长度可变的部分执行路径节点。 然后,为了减轻交叉编译造成的指令的巨大差异,我们只考虑关键指令来表示其核心语义。 此外,我们将低级汇编指令提升为高级操作,并从中删除操作数以解决指令的语法差异,从而完成指令规范化的过程。
- 更高的检测效率:为了加速大量函数对之间的相似度计算,论文提出了一种 "基于锚点识别的意图搜索" 的过程。该过程识别锚点函数(简称锚点),并执行源自锚点的意图搜索以发现所有潜在匹配的函数对。 该过程可以大大减少了相似度计算之前的比较次数,同时保证了比较质量。 具体来说,该过程利用严格的指令匹配和相同的库调用调用检查,通过考虑开发人员定义的函数和库函数来搜索匹配的函数对作为锚点。 通过源自锚点的意图搜索,它进一步探索锚点的邻居,以找到具有高相似度分数的新函数对。
此外,由于目前没有可以直接用于部分重用检测测试的数据集,因此论文构建了一个包含 24 个真实世界软件项目的数据集,并手动标记了总共 74 个部分重用。
2. 相关工作
静态分析:静态分析适用于不运行程序的二进制文件。 Bindiff 提出利用CFG的结构相似性来比较二进制代码。Luo 等人提出了CoP,一种面向二进制、抗混淆的代码重用检测方法,它将严格的程序语义与基于最长公共子序列的模糊匹配相结合。David 等人提出了一种计算二进制相似度的新方法Esh,该方法首先将二进制代码分解为小的可比较片段,然后定义片段之间的语义相似度,并进一步使用统计推理将片段相似度提升为程序之间的相似度。Chandramohan 等人提出了 Bingo,它通过内联相关库和开发人员定义的函数来捕获完整的函数语义,并通过使用长度变化的部分跟踪以程序结构不可知的方式对二进制函数进行建模。Feng 等人提出 Genius 通过将属性控制流图(ACFG)转换为高级数字特征向量来搜索大规模物联网生态系统中的漏洞。与 Genius 采用基于码本的方法嵌入 ACFG 不同,Xu 等人建议 Gemini 采用基于神经网络的方法将 ACFG 转换为二元函数的嵌入以进行相似性检测。Liu 等人提出了一种名为 αDiff 的解决方案,它采用三种语义特征:函数内特征、函数间特征和模块间特征,来解决跨版本二进制代码相似性检测挑战。
动态分析:二进制代码重用检测的动态方法是在代码执行期间通过运行程序执行的。 Tian 等人提出 DYKIS 来唯一标识一个程序来检测相似程序。 Tian 等人提出了一个名为 Thread Oblivious 动态胎记 (TOB) 的框架,该框架复兴了检测多线程程序重用的现有技术。 Ming 等人提出了一种基于逻辑的方法 LoPD,通过利用动态符号执行和定理证明技术来捕获两个程序之间的差异,以排除语义上不同的程序。
为了更好地理解上述方法与 ISRD 的差异,论文在上表中从分析类型、应用胎记的粒度、可解释性、抵抗交叉编译引起的混淆的有效性和效率方面对它们进行了比较。 从上表我们观察到,现有的方法虽然可以在一定程度上抵抗交叉编译引起的混淆,但无法解释检测结果。 Genius、Gemini 和 αDiff 使用深度学习进行检测重用并实现高效率,但它们无法解释检测结果。 除ISRD外,现有方法均无法全面克服上述局限性,这显示了ISRD的新颖性和优势。
3. ISRD 设计
ISRD 包括胎记生成和重用检测两个阶段,如下图所示。胎记生成阶段构建 target 和 candidate 的胎记。胎记涉及三个层次,即功能层、基本块层、指令层。在函数层面,构建FCG来描述程序语义。在基本块级别,提取给定函数的 MBP 以分析其行为。在指令级别,执行规范化以捕获指令核心语义。重用检测阶段引入 "基于锚点识别的意图搜索" 实现锚点的识别与基于锚点的意图搜索,进而显著加速函数的匹配。具体来说,在锚点识别中,使用严格的指令匹配和相同的库调用调用检查来查找锚点。然后,在意图搜索中,我们从锚点对出发,根据函数调用关系探索潜在匹配的函数对。
3.1 胎记生成
胎记是从程序中提取的反映其语义行为的一组特征,可用于唯一地标识程序。在文献中,有许多提出的具有不同粒度的胎记来表示程序。 但在实践中,应用粗粒度可能会限制程序相似性捕获的精确性。 例如,程序级胎记无法检测部分重用,因为候选程序通常仅重用目标程序的一部分。 同时,细粒度的胎记相似度不能用来推断粗粒度的相似度。 例如,鉴于指令的相似性,我们不能断定两个程序的功能相似。
为了解决这个问题,论文提出了一种多级胎记模型,通过涉及三级粒度来表征程序,形成从函数层到基本块层,最后到指令层的分层胎记。具体来说,论文首先构建程序的 FCG 以从宏观的角度描述程序语义。之后,论文通过使用 MBP 表示来提取函数语义,将注意力转向程序中的各个函数。 最后,为了进一步捕获指令语义,论文对最细粒度的指令执行规范化。 因此,程序可以通过三级胎记来识别,包括函数胎记、基本块胎记和指令胎记。
3.1.1 FCG 生成
为了在函数级别捕获程序语义,论文为 Target 和 Candidate 构建 FCG。 FCG 从程序的结构信息中从语义上捕获程序的功能和目标,以描述程序行为。
为了构建给定程序的 FCG,论文首先从其汇编代码中识别调用语句(即“call”)以提取调用者和被调用者。 然后,调用者和被调用者作为节点添加到图中。 另外,如果调用者和被调用者之间存在函数调用关系,则在图中在它们之间插入一条边。程序由函数组成,FCG表征函数间关系
3.1.2 MBP 提取
为了表征函数语义,应用了控制流图(CFG),它包含函数中基本块的详细信息。 在 CFG 中,每个节点代表一个基本块,是一段没有任何分支的直线代码,每条边代表块之间的控制流关系。 其定义如下:
当应用不同的编译设置时,在基本块特征很容易受到块分裂或合并的影响。论文基于 TRACY 的核心思想提出 MBP(CFG 中分支节点之间的部分直线执行路径)。为了提取MBP,论文首先通过将基本块分组为许多直线路径来预处理 CFG。 这种分组不影响函数语义,只是为了简化生成的MBP,其定义如下:
与固定长度的tracelet不同,MBP 的长度根据 CFG 的结构是可变的。具体来说,MBP 算法将 CFG 作为输入,遍历 CFG 中的节点以确认初始节点,通过 EXTACT 方法生成初始节点对应的 MBP,最终实现 MBP 集合的生成(为每个初始节点生成一个 MBP )。MBP 算法如下所示:
上图描述了 CFG 及其提取的 MBP 的示例,在图中 CFG 的控制流被分割为一些列由基本块组成的执行流行。从 CFG 中提取的 MBP 是:(BB1,BB2),(BB1,BB3,BB5),(BB2,BB4,BB5).(BB2,BB3,BB5)。
3.1.3 Instruction 标准化
语法是表示指令语义最直接的胎记。 然而,不同的编译过程可能会导致程序集的显着差异。 为了保留指令的核心语义并能够抵抗交叉编译引入的混淆,对指令应用了规范化。
指令提取:理想情况下,关键指令应构成整个执行序列的一小部分,并且必须相对唯一。 通过观察,论文发现几乎所有的 MBP 中都存在大量的数据传输指令,例如 push 和 pop,尤其是mov。这些指令可以被丢弃,因为它们通常有助于计算而不属于 MBP 逻辑的一部分。 此外,与其他指令相比,它们更容易添加和删除。删除所有数据传输指令可能是解决交叉编译问题的最简单方法,但它可能会导致数据传输语义的丢失。 这样,当连续出现多个数据传输指令时,我们只保留第一个数据传输指令。删除基本块中的数据传输指令
操作码优化:相同的操作可以用不同的指令来表达。为了实现指令的语义等效,论文将指令提升为高级操作。例如,两条指令 inc 和 add 可以映射到一个加法运算。操作码优化
操作数优化:指令中的操作数在编译时很容易改变。 即使使用相同的编译设置编译相同的源代码,由于内存布局的差异,操作数也可能不同。 因此,论文从指令中删除操作数。操作数优化
3.2 重用检测
论文提出了一种有效的匹配过程以加速 target 和 candidate 之间的匹配,该过程包括锚点识别(Anchor Recognition)、意图搜索(Intent Search)以及相似检测(Similarity Calculation)。
3.2.1 锚点识别
联合使用两种方法来识别候选程序中大量函数中的锚点,(1)在给定 target 中的函数中搜索 candidate 中满足严格指令匹配(指令的数量和顺序完全相同)的函数;(2)在给定 target 中的函数中搜索 candidate 中具有相同库调用的函数。基于指令和库函数确定锚点
3.2.2 意图搜索
在意图搜索中,论文从匹配的锚点出发,发现新的匹配函数对。 ISRD 循环遍历每个识别的锚点并探索其直接邻居,以找到具有高相似性得分的新匹配函数对,并将新识别的匹配函数对添加到锚点集合。
给定匹配的锚函数对 A 和 a,意图搜索的基本思想是锚点函数 A 与锚点函数 a 通常具有相同的相邻函数。具体来说,论文从锚点对的当前状态和 FCG 中的函数调用关系获得线索,并设置优先级(优先考虑具有最多匹配邻居的函数)来引导搜索。
3.2.3 相似性匹配
在识别出潜在匹配的函数对后,论文根据两个函数的基本块级胎记(MBP 集)计算相似性分数来测量两个函数的相似性。 为了计算每对 MBP 的相似性得分,如下式所示:
论文利用 LCS 动态规划算法计算两个 MBP 的最长公共子序列 (LCS),表示为 lcs(mbp1, mbp2)。 然后,通过将 LCS 的长度除以两个 MBP 的平均长度来计算两个 MBP 之间的相似性得分。 通过计算每对 MBP 的相似度值,论文使用收集到的个体相似度分数计算两个 MBP 集的相似度,如下式所示:
对于 MBP1 中的每个MBP,首先使用等式 (3) 获得 MBP 的最高相似性得分,表示为 MaxScore。 然后,两个 MBP 集合 sim(MBP1,M BP2) 的相似度得分是 MBP1 中每个 MBP 的最高相似度得分乘以 MBP 的长度除以基数的总和。
函数之间的相似性不足以描述目标程序与候选程序之间的相似关系,为了证明 target 和 candidate 之间的相似性,论文提取它们三级胎记之间的相似部分作为可解释的检测结果,其相似性计算公式如下所示:
4. 实验评估
论文首先介绍实验设置,然后提出 6 个研究问题,并对这些问题进行回答。研究问题如下所示:
- RQ 1:ISRD 能否实现部分重用高效、可靠的检测?
- RQ 2:ISRD 的可解释性如何?
- RQ 3:ISRD 的跨编译器版本弹性如何?
- RQ 4:ISRD 的跨优化级别弹性如何?
- RQ 5:ISRD 的跨编译器供应商弹性如何?
- RQ 6:与其他工作相比,ISRD 的性能如何?
4.1 实验设置
数据集:ISRD 将二进制代码程序对(target,candidate)作为输入。 为了进行彻底的评估,论文需要真实数据集,其中的二进制代码确实共享相同的代码。 为此,论文使用了两个数据集,包括论文自己构建的数据集(Dataset_1)和 Bingo 和 αdiff 提供的广泛使用的基准数据集 Dataset_2。
- Dataset_1:论文从开源平台收集了真实世界的数据来构建 Dataset_1。 具体来说,论文首先从开源平台(例如 Github 和 SourceForge)下载了 24 个开源项目,它们属于不同的应用领域。 然后,论文使用手动方法和自动方法选择并标记了总共 74 个真实的部分重用作为真实值。 最后,构建了包含 24 个程序和 74 次部分重用的数据集。 论文使用 gcc 和默认优化 (O2) 将这 24 个程序编译成二进制文件。 编译的程序会生成多个二进制文件,论文仅提供评估中使用的二进制文件的信息。
- Dataset_2:是广泛使用的评估交叉编译稳健性的基准,论文通过该数据集比较 ISRD 与现有方法的性能。 Dataset_2 中的二进制文件是从实验对象 Coreutils 编译而来,它是用 C 编写的 GNU 操作系统的基本文件、shell 和文本操作实用程序。Coreutils 有 107 个组件,因此每次编译生成 107 个二进制文件。论文在实验中使用了三个编译器(gcc v4.6、gcc v4.8和clang v3.0)和四个优化级别(O{0, 1、2 和 3}),生成 107 个二进制文件中的每一个都有 12 个不同的变体。
评价指标:用于衡量 ISRD 性能的指标如下表所示。 误报率(FPR)代表不可重用函数被错误检测为可重用函数的比率。 假阴性率 (FNR) 量化了未检测为可重用函数的可重用函数的比率。 精度、召回率和 F 测量值的计算方法如表中所述。
参数设置:相似阈值在 ISRD 中起着重要作用。如果函数对的相似度得分大于相似度阈值,则确定该函数对匹配。为了确定合适的阈值,论文改变了它的值以在两个数据集上进行测试。根据测试结果,将相似度阈值设置为0.5使得 ISRD 达到最佳性能。
4.2 RQ 1:ISRD 的有效性与可靠性
论文使用 Dataset_1 来测试 ISRD 是否能够有效且高效地检测部分重用。 我们使用数据集 I 作为输入运行 ISRD,并将其结果与真实情况进行比较。
上图展示了评估结果的精确度和召回率的累积分布函数(CDF)。 几乎所有的精度值都高于82%,平均值可达 97.2%。 此外,15 个二进制对的精度等于 1,表明所有重用函数都可以被准确地检测为可重用。 对于召回率指标,其平均值达到 94.8%,表明论文的方法可以有效地找到重用的函数对。可靠性实验;ISRD 的精度优于召回率,不容易出现误报,容易出现漏报
上图展示由 ISRD 计算的减少的函数对数量与原始函数对数量之比的 CDF。通过观察可知,在重用函数检测过程中,ISRD 有效地将函数对的大小平均减少了 97.9%。具体来说,当重用代码仅是目标程序和候选程序的一小部分时,缩减率高达99.9%。例如,bzip2 的重用代码仅占 precomp 的 2% 左右,如果应用一些现有方法(即 BinDiff、TRACY),则需要计算 208824 个函数对,而应用 ISRD 仅需要计算 128 个函数对。因此可以得出结论,ISRD 中使用的函数对的数量远小于目标程序和候选程序之间所有函数对的总数。重用检测的大幅减少使得 ISRD 对于大型实际程序来说非常实用。有效性实验
Answering RQ 1:ISRD有效检测部分重用,平均精度达到97.2%。 ISRD 可以显着减少重用检测所需的函数对数量,平均高达 97.9%。 因此,处理大规模程序是有效的。
4.3 RQ 2:ISRD 的可解释性
为了评估 ISRD 的检测结果是否可解释,论文利用 Dataset_1 中的两个程序 precomp 和 minizip 进行案例研究。该案例研究通过提供重用的图形演示来说明 ISRD 的可解释性。 这里,precomp 是一个命令行预压缩器,minizip 是一个 C 语言的 zip 操作库。请注意,precomp 和 minizip 都重用了两个压缩库 lzma 和 bzip2。
上图展示了 ISRD 的检测结果,precomp 和 minizip 的 FCG 显示在图的左侧,其中红色和蓝色的节点分别代表库 lzma 和 bzip2 中的函数。 在 precomp 和 minizip 上运行 ISRD,得到的程序相似度为 17.9%,这意味着 precomp 的 17.9% 功能已经在 minizip 中使用了。实现函数对的生成后,右图展示了函数 lzma_alone_decoder 及其匹配函数(其相似度得分为 1.0)作为示例,在基本块级别(识别出4个匹配的MBP对)和指令级别(语义等价性)包含该函数的匹配部分。
Answering RQ 2:案例研究表明,通过在函数级、基本块级和指令级详细描述目标程序与候选程序之间的匹配部分,ISRD的检测结果是可解释的。
4.4 RQ 3:ISRD 的跨编译器版本检测
论文使用 gcc v4.6 和 gcc v4.8 编译了具有各种优化级别(O0 - O3)的 Coreutils。 这样的设置导致 Coreutils 中的每个二进制文件都有 8 个不同的版本。 随后,论文通过比较使用具有相同优化级别的不同编译器版本编译的二进制文件来评估 ISRD,如下表所示。编译器版本不同,编译优化级别相同实验
结果表明,平均精度可达96.0%,召回率为99.3%,表明 ISRD 对不同编译器版本引起的混淆具有弹性。 此外,对于没有代码优化的情况(即 O0),精度为 100%,而 FPR 为 0%。 也就是说,即使使用不同版本的编译器,没有代码优化级别的编译也会导致高度相似的二进制代码,而使用高优化级别的编译会产生更多二进制代码之间的差异。
Answering RQ 3:ISRD 对交叉编译器版本具有弹性,平均精度为 96.0%,召回率为 99.3%。
4.5 RQ 4:ISRD 的跨编译优化级别检测
论文使用 clang v3.0 和 gcc v4.8 以及各种优化级别(O0 到 O3)编译了适用于 x86-32 位架构的 Coreutil。 通过此设置,Coreutils 中的每个二进制文件都有 8 个变体。 我们比较了使用具有不同优化级别的相同编译器编译的二进制文件,如下表所示。编译优化级别不同,编译器版本相同
通过上表可知ISRD 在 clang v3.0 中的精度比 gcc v4.8 中的表现要好得多。 具体来说,clang v3.0 的平均精度为 95.7%,而 gcc v4.8 的平均精度仅为 91.1%。clang 编译的重用更容易检测
除此之外,当目标程序和候选程序分别以高度优化 O2 和 O3 编译时,实现最高的 F1-Score。 如果目标程序和候选程序分别在没有代码优化(即 O0)和高代码优化级别(即 O3)的情况下编译,F1-Score 会下降到 72.0%。 这表明,无论编译器供应商如何,使用高优化级别编译的二进制文件都是相似的。 优化是否进入二进制文件的影响非常明显。类似编译优化级别重用更容易检测
Answering RQ 4:ISRD 在某种程度上对交叉优化级别具有弹性。 评估 gcc v4.8 和 clang v3.0 编译的二进制文件,ISRD 的平均精度分别为 91.1% 和 95.7%。
4.6 RQ 5:ISRD 的跨编译器供应商检测
论文使用 clang v3.0 和 gcc v4.8 以及各种优化级别(O0 到 O3)编译了适用于 x86-32 位架构的 Coreutil。 通过此设置,Coreutils 中的每个二进制文件都会生成 8 个不同的变体。 随后,论文在不同供应商的编译器以相同优化级别编译的二进制文件上评估了 ISRD,如下图所示。不同编译器供应商
实验结果表明,在FPR为0.1%的情况下,平均精度达到94.2%。通过上表可知,当候选程序以无优化级别作为目标程序进行编译时,获得了最佳结果。此外,当检测二进制文件以优化级别 O3 编译时,精度达到最低点 91.2%。此外,在各个编译器供应商中,检测精度随着优化级别的升高而下降(除了使用优化级别O1编译的检测程序时,使用优化级别O2编译的检测程序具有更好的精度)。
Answering RQ 5:实验结果证明了 ISRD 跨编译器供应商的弹性。 此外,它的平均准确率达到 94.2%。
4.7 RQ 6:ISRD 与其他工作的对比
为了与现有的方法进行直接比较,论文使用了与他们相同的实验配置和相同的指标。 更具体地说,论文使用 gcc v4.8 和 clang v3.0 编译了具有各种优化级别(从 O0 到 O3)的 Coreutils。 论文评估了六种实验设置。 重用检测的召回值在如下表所示。
通过召回率比较,可以发现 ISRD 平均优于三种基线方法 27.0%,尤其是平均优于 BinGo 36.5%。 我们注意到 ISRD 的平均召回率为76.4%,低于上面的实验结果。 这是因为涉及到跨编译器和交叉优化级别的混淆,使得检测比仅发生一种混淆的情况困难得多。 此外,当目标程序由没有代码优化的 gcc v4.8 编译以及候选程序由具有最高优化级别 O3 的 gcc v4.8 编译时,每种方法获得的结果最差。 另一方面,当目标程序和候选程序分别由具有高优化级别 O2 和 O3 的同一编译器编译时,所有方法都能获得最佳结果。
Answering RQ 6:与基线方法相比,ISRD 在广泛使用的基准数据集上取得了更好的性能。
5. 工作缺陷
函数内联引起的问题:函数内联是 ISRD 的一个主要内部限制。 在 ISRD 中,程序内的函数语义是独立提取的。 具体来说,被调用函数的语义并未集成到调用函数的语义中。 这导致了部分语义问题,因为内联策略是根据编译期间配置的优化级别有选择地应用的。 编译器中使用内联来优化二进制文件,以实现最大速度或最小大小。 其他内联相关库和开发人员定义的函数语义的方法(例如 Bingo 的选择性内联策略)可以合并到 ISRD 中来解决该问题。
库函数引起的问题:在 ISRD 中,利用两种方式来识别锚点。 第二种方法基于检查相同的库调用。 然而,这种方式受到以下两个原因的限制:(1)库函数依赖于操作系统; (2) 它无法识别具有不同名称但具有相似功能的库调用(例如,memcpy 和 memmove)。 为了解决上述问题,受 CLCDSA 的启发,可以借助文档和 Mikolov 的 Word2Vec 模型来学习跨操作系统库调用的相似性。