αDiff:Cross-Version Binary Code Similarity Detection with DNN阅读笔记

二进制代码相似性检测

Posted by Yunlongs on May 3, 2020

请咨询作者同意后转载。

αDiff: Cross-Version Binary Code Similarity Detection with DNN

github :https://twelveand0.github.io/AlphaDiff-ASE2018-Appendix

期刊/会议: ASE(A类)
发表时间: 2018年11月
发表机构: 中科院

1.前言

该方法主要针对跨version二进制检测问题:在源代码自身随着时间进行更新后,产生了跨版本的二进制,这些二进制仅在语法结构和语义上有轻微的差别。

主要思想:

  1. 使用DNN在函数的Byte层面提取函数内特征
  2. 分析函数调用图,提取函数间特征
  3. 统计导入函数,提取模块间特征
  4. 基于以上三个语义特征计算距离

研究问题: 针对人工定义特征引入的偏差,本文研究的第一个问题是:

  • RQ1:如何从二进制代码中提取尽可能少的人类偏差的特征。

针对BinGo和Esh对CFG片段语义相似性严格的检查和计算代价的昂贵,本文研究的第二个问题是:

  • RQ2:如何有效地利用语义特征来提高BCSD的准确性?

针对BinDiff在跨长版本时准确率的急剧下降,本文研究的第三个问题是:

  • 本文提出的第三个研究问题是:RQ3:如何建立一个适合跨版本的BCSD的解决方案?

2.问题定义

BCSD的一个核心任务就是为每一个函数寻找匹配。如果两个函数编译后的函数名相同且具有相似的上下文,那么可以考虑这两个函数为一个匹配。

2.1 跨版本BCSD问题定义

(1)函数匹配:对于每个在binary $B_1$中的函数$f_{1i}$,如果存在,在binary $B_2$中寻找它的匹配$f_{2j}$

(2)相似性分数:为函数对$f_{1i},f_{2j}$计算0-1之间的相似性分数来表征其相似度

(3)差异识别:如果函数对$f_{1i},f_{2j}$之间的相似性分数小于1,找出其在代码比特层面的不同。

本篇本章主要针对的是前面两个问题,但是在实验中发现,此方法在跨平台二进制相似性检测上也能取得很好的效果。

2.2 评估指标

计算TopK的召回率:Recall@K

3.方法介绍

3.1 概述

如图1所示:

  1. 输入两个二进制的函数,提取函数的Byte流输入到CNN Siamese网络中,输出两个函数的特征向量和距离
  2. 根据两个函数的调用图得出函数间特征向量,并计算其距离
  3. 根据两个函数的导入函数得到模块间特征向量,计算距离
  4. 根据上面计算的三个距离,综合得出两个函数的相似性分数

3.2 函数内语义特征

将函数转化成比特流后,作者尝试了很多网络(Conv1d,LSTM,ConvLSTM..),最后醉着发现CNN最适合来提取函数的语义特征。

为了能够满足相似性准则,我们采用Siamese网络结构,使得相似的函数特征之间的距离小,相似性低的函数特征之间距离大。

CNN网络结构: 8卷积层,8 batch normaliztion层,4 maxpooling 层,2 全连接层,所有的激活函数都是用RELU,参数有160w。

CNN的输入: 提取二进制函数的比特流,若超过1w字节则截断,不足则拿0填充。将这1w的字节流转换成$100\times 100 \times 1$的张量作为CNN的输入。

1w比特已经足够判断两个函数是否相似了。

函数特征距离度量:

Siamese网络的损失函数 其中$m$为提前定义好的超参数,用来决定我们期望不相似的函数之间至少应当有多大的距离。

负采样: Siamese需要大量的正样本和负样本。正样本可以通过对同一项目不同版本的源代码编译得到,对于负样本,需要在训练中每个mini-batch基于正样本来生成。

例如对于一个mini-batch中的每个正样本$(I_q,I_p)$,我们生成两个semi-hard负样本,$(I_q,I_{n1})和(I_p,I_{n2})$。以函数$I_q$为例,我们将随机挑选一个函数$I_{n1}$满足如下约束:

\(0< D1(I_q,I_n) < m\) 但是我们也要跳过那些最难训练的负样本,例如$argmin D1(I_q,I_n)$,因为这些样本会使模型陷入坏的局部极小值。

3.3 函数间语义特征

作者根据 相似的函数具有相似的函数调用图,从而在函数调用图中提取相似性特征。

因为函数相互调用、递归等存在,使得对整个函数调用图处理代价太过于昂贵。所以作者只采集函数的入度节点和出度节点来作为函数间特征。例如,对于函数$I_q$,我们将其函数间特征定义为如下的二维向量:

\[g(I_q) = (in(I_q),out(I_q))\]

其中$in(I_q)$和$out(I_q)$分别为函数$I_q$在调用图中的入度和出度。

3.4 模块间语义特征

作者发现相似的函数会调用相似的导入函数,并在版本的变化当中相对稳定。

为了一致性,我们将模块间特征也转化为向量来用于距离计算,其计算公式如下:

\(h(set,superset) = < x_1,x_2,...,x_N >\) 其中$N$为超集的大小,如果super中的第i个元素在set中,那么$x_i = 1$

对于两个函数$I_q$和$I_t$,假设它们的二进制程序为$B_q$和$B_t$,我们可以得到它们的导入函数集合$imp(B_q)$和$imp(B_t)$。我们使用$imp(B_q) \hat imp(B_t)$作为superset,然后可以得到这两个函数的距离为:

3.5 整个相似度计算

因为跨版本的二进制中,函数调用图可能会发生较大的变化,我们希望函数间特征在最后的相似性计算中起的作用小一些,所以就增加了一个0-1的超参数。