基于机器学习的动静结合的二进制漏洞检测

2021-12-11 06:01蔡昊君牛少彰
新一代信息技术 2021年20期
关键词:二进制漏洞内存

蔡昊君,牛少彰

(北京邮电大学 计算机学院,北京 100876)

0 引言

随着互联网的飞速发展,网络与人们的生活越来越息息相关,各种社交、学习、购物等软件逐渐渗透到我们生活中,给我们的生活带来了很多便利的同时也带来了很多网络攻击。软件漏洞是网络攻击盛行的一个根本原因,在过去的十几年中,软件漏洞数量增长迅速,NVD的统计数据显示,2002年到2019年共有121 279条漏洞出现,其中未知漏洞类型就多达38 868条。因此,及时发现漏洞是我们必须大力研究的内容,漏洞检测可以帮助安全工作人员在软件发放到市场前及时发现并修补漏洞。

本文研究的是与内存有关的漏洞,在 CWE最近两年的最危险软件漏洞榜单中,对内存缓冲区范围内操作不当和“越界写入”引起的漏洞在榜单中排名非常靠前,在官方统计中跟内存相关的缺陷所引发的漏洞威胁程度还是很高的。软件漏洞分析大致分为源代码分析和二进制代码分析,源代码的漏洞分析大部分是基于静态的,Kim等人[1]提出了基于代码相似性的方法,Yamaguchi等人[2-4]提出基于模式的方法,Ramos等人提出[5-6]符号执行的方法,而动态分析技术包括污点分析[7-8]和模糊测试[9-10]。但大部分商业软件的发布并不包含自身的源代码,而是以二进制的形式发布,所以面向二进制程序的漏洞分析具有极其重要的意义。现有的面向二进制程序的漏洞分析方法主要是静态分析方法[11-12]和动态分析方法[13-14],静态分析通过利用软件工具收集程序的语法和语义等信息,从而达到软件分析的目的。动态分析通过实际运行程序,观察程序的状态信息来检测程序中存在的漏洞。

近年来机器学习在漏洞检测领域也有了一定的发展,使用机器学习的二进制代码检测模型有着检测速度快、数据处理量大、检测成本低的优势,但由于二进制程序不能像源程序那样直接的表达程序信息,从中提取有效的特征集非常困难,导致现有的基于机器学习的漏洞检测方法存在主观性强,检测粒度粗,误报率和漏报率高等问题。

对于静态分析技术存在的分析效率低、误报率较高的问题和动态分析技术存在的路径覆盖率低的问题,本文提出了一种基于机器学习的二进制代码漏洞检测方案,引入动静结合的思想,同时提取二进制程序的静态特征和动态特征,构建面向二进制程序的漏洞自动检测模型。

1 相关研究

随着机器学习的迅速发展,成功在图像、文字、语音等领域有所应用,近期机器学习也被用到了漏洞检测中,基于机器学习方法的软件漏洞检测研究也取得了一定的进展,根据研究对象的不同主要分为源程序漏洞检测和二进制程序漏洞检测。

1.1 基于机器学习的二进制漏洞预测

BM 等人[15]对二进制程序进行研究,首先识别潜在的易受攻击的语句构造,根据提出的表征方案为每个构造提取静态特征,捕获代码中采用的缓冲区使用模式和防御机制,对这些收集到的属性使用数据挖掘的方法预测缓冲区溢出。Lee等人[16]提出了一种改进的机器静态二进制分析技术——Instruction2vec,在word2vec的基础上,使用 Instruction2vec对汇编代码进行建模,利用Text-CNN学习并提取软件缺陷代码的特征,文章使用Juliet Test Suite中的CWE-121作为数据集,最后实验证明该方法的准确率可达到 91%。Liu等人[17]为解决二进制软件漏洞自动检测问题提出了一种基于深度学习的方法,分为两个阶段:使用 IDA Pro从二进制代码中提取函数;利用带注意力机制的双向长短时记忆网络构建预测模型。

1.2 基于机器学习的源代码缺陷预测

J Ren等人[18]针对缓冲区溢出,提出了一个基于软件度量方法—BOVP算法,对程序源代码进行预处理,然后在功能层采用动态数据流分析的方法,通过分析软件代码的特点和不同类型的缓冲区溢出漏洞的特征,建立了基于功能层面的多类型缓冲区溢出漏洞模型的决策树算法,该算法降低了测量的维度,减少了实验的开销,并与SVM、Bayes、Adaboost和随机森林算法进行对比,该文章提出的算法预测结果更准确。Bilgin等人[19]开发了一种源代码中间表示方法,可以对源代码的抽象语法树形式进行智能分析,使用机器学习算法去检测脆弱性代码。文中与code2vec方法进行了对比,code2vec使用了神经网络生成代码片段的向量表示,在 CWE数据集上进行比较,该文章提出的方法效果更好。Li等人[20]提出了一种基于混合神经网络的源代码漏洞自动检测框架,利用低层虚拟机中间表示和逆向程序切片将输入转换为具有显式结构信息的中间表示,通过卷积神经网络学习漏洞的局部特征,循环神经网络学习漏洞的全局特征,局部和全局特征的融合提高了检测的准确性。

从相关学者的研究可以看出,机器学习应用在程序源代码的研究居多,通过抽象语法树、数据流图等提取源代码的结构特征,再利用机器学习模型进行预测,而对二进制程序的研究相对较少,并且基于机器学习的漏洞检测大多是提取程序的静态特征,很少有动态特征和静态特征相结合的。本文提出采用动静结合的方法,提取二进制程序的动态特征和静态特征,利用分片方式精确定位漏洞位置,并结合机器学习的算法去检测二进制程序漏洞,提出一种新的检测方式的同时也丰富了对二进制程序的研究。

2 方法设计

本文用Juliet Test Suite作为研究的数据集,Juliet套件中包含 CWE多种漏洞类型的样例文件,漏洞类型丰富、涵盖范围广,将测试用例编译后即可得到可执行文件,之后的研究将在可执行文件上进行。本文选用 Pin作为提取二进制程序动态特征和静态特征的工具,首先获取程序的特征日志文件,经过预处理,切片生成数据集,通过分词转换成词向量,输入到模型中进行训练。

通过Pin的特定API可以获取动态特征和静态特征,利用 Addr2line判断其与漏洞位置的距离。由于一个函数的指令数量范围不定,直接预测出现漏洞的函数粒度也不定。为了能更加精确的定位到漏洞的位置并且保证预测的准确率,本文对函数进行分片,分片标签是通过判断其代码中是否有和漏洞位置较为接近的指令,然后通过本文提出的向量转换方法进行特征向量化。

各类机器学习算法有不同的优缺点和适应性条件,由于漏洞的多样性,其特征也存在多样性,所以不同漏洞适用的分类算法也不同。本文通过对同一漏洞进行多种分类算法建模,比较各模型的准确率、召回率等评价指标来选择最适合此漏洞类型的算法。具体方案如图1所示。

图1 方案设计Fig.1 schematic design

2.1 特征提取

本文采用 Pin作为提取程序动态特征和静态特征的工具。Pin可以认为是一个JIT编译器,他的输入是可执行文件。本文首先对二进制程序进行函数粒度的插桩分析,通过Pin特定的API获取运行中函数的相关信息,然后对函数中的指令进行插桩。获取运行中指令的行为状态信息,包括指令的特性、内存和寄存器的读写信息等。静态特征指的是可以反应程序语义和语法信息的特征。例如指令的操作码和操作数、是否为转移指令、是否为分支指令等;动态特征为程序运行时的行为状态信息。例如指令操作数大小、寄存器值等。

相关的 API见表 1,内存相关的漏洞可能跟操作数和寄存器的值有关。操作数规定了指令中进行数字运算量,操作数可以有三种方式获得:立即数、寄存器、内存。对于双操作数指令,操作数可以是寄存器操作数、内存操作数和立即数。本文 Pin插桩读取操作数值和寄存器值,解析日志中3个内存操作数和4个寄存器值。

表1 APITab.1 API

插桩后的 log文件中会包含大量的函数和指令。为了提高实验精度和速度,筛选掉与测试样例无关的函数,例如 malloc库函数。通过 RTN所属的 IMAGE是否为测试样例文件来判断该RTN是否需要被过滤。

2.2 切片及标签

一个RTN包含多条指令,少则几十条,多则上万条。如果按照RTN的粒度对二进制程序进行漏洞检测,还需要对有漏洞的函数进行排查,极其耗时耗力。如果按照INS的粒度进行检测,检测效果大概率较差,因为一行源码可能会对应多条指令。所以本文采用切片的方法,将长度不等的RTN切分成长度相等的代码片段,代码片段构成了样本集。统计所有RTN包含的指令数量,打破原有的函数框架,根据粒度和精度的原则选择较为合适的分片长度,对每个RTN按照指定数量进行切片。

Pin插桩二进制程序可以得到指令的地址,利用 Addr2line工具从指令地址中获取到指令所对应源码所处的文件名、函数名及代码文件中的行号。数据集中的 manifest.xml文件记录了缺陷样例的文件名、漏洞类型和导致漏洞的行号,以此可以判断该指令是否在漏洞附近,并标注为有/无漏洞(1/0)。

2.3 漏洞检测及定位

本文在漏洞定位的粒度上选择分片粒度,将函数中的指令进行分片。本文针对二进制程序进行漏洞检测及定位,由于二进制程序可读性低,为方便研发人员进行修改,需要定位二进制程序漏洞在源程序的位置。如果进行指令级的漏洞定位,很难在源程序中找到相应的语句,源程序的每条语句对应多条指令,而且很多时候漏洞的出现不仅是单个指令的问题,所以指令级的漏洞定位粒度太小,预测准确率较低。如果进行函数粒度的漏洞定位,输出源程序中可能会出现漏洞的函数名称,若函数过长,研发人员需要花大量的时间核对代码。

针对以上问题,本文的切片、代码段的粒度,既可以在指令层面学习到细粒度的信息,有利于漏洞的定位,也可以在多指令的粗粒度上学习到更丰富的结构信息和漏洞整体特征。如果出现函数的调用关系,漏洞定位可定位到最内层函数,例如函数A调用函数B,若B中存在漏洞,则漏洞定位会直接定位到函数B中,省去了检查函数A中其他语句的成本。

2.4 检测模型

本文利用机器学习的算法,在获取到程序运行的动态特征和静态特征之后,经过特征分析和向量转换,通过模型自动检测程序漏洞。本文采用的算法有 ID3、C4.5、随机森林、改进的随机森林(Extra-trees)和CNN。ID3和C4.5是经典的决策树算法,产生的分类规则易于理解,可解释性强,准确率较高;随机森林算法不容易过拟合,具有较好的抗噪声能力,支持并行化;CNN可以捕捉局部相关性,在文本分类任务中可以提取语句中的关键信息。

传统的静态分析方法和动态分析方法在提取特征后需要大量人工分析,耗时耗力,且很难解释特征之间的内在联系。而机器学习算法具有自动化、精确、迅速、可自定义和规模化等优点,可自动学习新的模式,在新数据输入的情况下可迅速做出反应。对于二进制程序的漏洞检测任务,可以归为二分类问题,即将目标程序分为有漏洞和无漏洞两种,通过机器学习模型学习程序的动态和静态特征向量,判断样本有无漏洞。

3 实验及结果分析

3.1 技术实现

3.1.1 数据集预处理

本文选用 NIST的 Juliet测试套件,其中包含CWE许多条目测试用例。CWE是开发常用的软件安全漏洞列表,是软件安全工具的重要衡量标准。本文研究的漏洞类型有内存泄漏、内存溢出和内存访问越界。具体选择的 CWE条目见表2。

表2 数据集条目Tab.2 data set entry

将数据集中 C/C++源程序编译链接成可执行文件,即为二进制文件,具体实验将在此二进制文件上进行。数据集中有可单独运行的文件,有包含数据流或控制流的文件,漏洞形式丰富。测试用例可能会包含多个测试文件,编译链接得到的可执行文件名是以“a”结尾的。编译后得到各个漏洞类型所对应的可执行文件数量如表 3所示。

表3 二进制程序数量表Tab.3 table of the number of binary programs

3.1.2 特征提取

本文借助动态二进制插桩工具 Pin提取二进制程序的动态特征和静态特征,编写 Pintools对函数和函数中的指令进行插桩分析,获取RTN的信息以及 RTN包含的 INS的信息,具体调用的API见章节2.1。Pin插桩二进制程序得到的特征信息保存在log文件中,如图2所示,第一行为函数名、SECTION名、IMAGE名。IMAGE为函数所在的代码文件。为减少无效数据干扰,本文通过判断 IMAGE字段是否为代码文件名来删除不匹配的样例文件中的函数,并将 SECTION中非.text,即非代码段的函数删除。

图2 插桩日志Fig.2 log of Pin

得到日志后,通过addr2line获得每条指令的地址。addr2line是一个可以将指令的地址和可执行映像转换为文件名、函数名和源代码行数的工具。在 Pin插桩完成之后,插桩日志中保存了每条指令的地址,通过地址找到指令所对应的源码行号,由此生成一个新日志,如图3所示。

图3 解析后的日志Fig.3 parsed logs

3.1.3 标签生成

日志解析完成之后,就提取到了程序中各函数的动态特征和静态特征,向量化之前首先需要进行切片。程序切片是为了更加细粒度的定位到漏洞出现的位置,在保证漏洞检测准确率高的基础上使漏洞范围更加精确,从而减少排查漏洞所耗费的成本。为尽可能的缩小定位范围,本文设置分片长度slice_length=50。slice_length是RTN切分时每个分片所包含的 INS数量,长度不足slice_length的部分补0。

对于本身无漏洞的函数,切片片段标签为“0”;对于存在漏洞的函数,切片后若片段中指令所对应的源码与漏洞位置在5行范围内,则该片段的标签为“1”,否则为“0”。

在特征向量化之前将按此规则得到的数据集进行划分,随机提取数据集中的80%作为训练集,20%作为测试集。由于内存泄漏中有漏洞的样本仅有1 758个,为了保证样本量充足尽可能使得正负样本均衡,内存泄漏中正样本取12 000个,内存溢出和内存访问越界的正负样本数各取 7 500个。训练集和测试集中正负样本的具体数量见表4,Good表示有漏洞,Bad表示无漏洞。

表4 数据集Tab.4 table of the number of binary programs

3.1.4 向量化

由于特征中存在汇编指令等非数值特征无法被模型直接理解,在输入到模型训练之前需要先将特征向量化。首先将特征进行分词,通过word2vec模型,将分词映射成向量。

常见的语料库词维度是100~200维,本文中需要转换成向量的是汇编指令,操作数操作码等,词表大小相比一般的文本分类小,每条指令转换成176维的向量,每个样本的维度是50×176。

向量化结果如图 4所示,行号和指令地址不作为模型训练的特征(前8个非数值特征均转换成20维的向量)。

图4 向量化Fig.4 vectorization

3.1.5 漏洞检测及定位

将向量化后的特征输入模型中进行训练。多次调整学习率、Batch_size、优化器等参数优化模型,保存最优模型的训练结果。

将待检测的二进制程序,选择要检测的漏洞类型及检测模型,加载此漏洞类型最优模型进行预测,输出是否存在漏洞,若有漏洞存在则输出存在漏洞的函数名称,并通过指令的地址范围来确定漏洞位置。

3.2 实验结果分析

3.2.1 评估指标

常用的模型评价指标是准确率,但是单用准确率评判模型效果说服力较低,所以本文选择了更多的指标来共同衡量模型训练的有效性。

TP表示正类中被预测为正类的样本数量,TN表示负类中被预测为负类的样本数量,FP表示负类中被预测为正类的样本数量,FN表示正类中被预测为负类的样本数量。

Accuracy是准确率,是所有实例中,被分类正确的比例,Precision是精确度,衡量的是查准率,也就是模型预测的准不准;Recall是召回率,衡量的是查全率,就是有没有把正例全部找出来,F1 score是精确度和召回率的调和。

本文从以上所列指标对不同漏洞类型的不同模型的预测结果进行比较。

3.2.2 漏洞检测结果

本实验中,对二进制程序进行有无漏洞的检测,预测结果为“1”则有漏洞,“0”则无漏洞。找到最适合每一种漏洞类型的检测模型,不同漏洞模型的检测结果如表5所示,表中的精确度、召回率和F1 score都是针对负样本的。

从表 5中可以看出,在内存泄漏和内存访问越界两种漏洞的实验中,改进的随机森林模型检测效果最好,而内存溢出中随机森林模型检测效果最好,预测准确率都高达 97%。对于内存泄漏,准确率和精确度都比较高,都超过了96%,但精确度比准确率稍低一些,说明模型存在少许误判;召回率和精确度相差4%,说明有漏检的情况,一些有漏洞的样本没有检测出来,可能是因为内存泄漏的数据集中负样本的数量少,所以没有提取到丰富的负样本特征。对于内存溢出和内存访问越界,四类指标值都较为均衡,说明模型能很好地检测到有漏洞的样本,误判率和漏检率都比较低。

表5 漏洞检测实验结果Tab.5 experimental results of vulnerability detection

3.2.3 漏洞定位结果

本文是针对二进制程序进行漏洞检测的研究,对于待检测的二进制程序,通过本文提出的特征处理方法和模型,可以输出程序中可能存在漏洞的函数及位置。图5是漏洞检测定位结果,可以直观的看到存在漏洞的函数,源程序可以通过源码中漏洞存在的位置直接定位并检查代码;若只有二进制程序,可以通过二进制程序漏洞存在的地址向后检查,极大的缩小了检查代码的范围,节省了大量的时间和人力成本。由于本实验的数据集只提供了真实漏洞在源码中的位置,所以可以使用预测漏洞在源码的位置来检验本文方法的有效性。

图5 漏洞定位结果Fig.5 vulnerability location results

选择三种漏洞类型中各自表现最好的模型,检测他们在测试集上的定位效果。本文通过两步来检验漏洞定位的准确性:(1)预测的漏洞位置是否包含了真实漏洞位置;(2)预测的漏洞位置范围和真实漏洞位置的距离。

表6统计了三种漏洞类型的测试集中漏洞定位结果不包含真实漏洞位置的比率,可以看出,每种类型都是超过99%的预测结果包含了真实漏洞的位置,程序员检查此范围内的代码即可定位到漏洞。

表6 不包含真实漏洞位置的样本数占比Tab.6 proportion of samples that do not contain the actual vulnerability location

第一步的检验完成,但预测结果包含真实漏洞位置并不代表定位精确,是否能真正地缩小排查范围,还需第二步的检验。

图6表示的是预测的漏洞位置和真实漏洞位置的距离统计,距离以行为单位,可以看到和真实漏洞位置的距离不超过 10行的占整个测试集的90%左右,预测结果比较精确。

图6 预测位置误差统计Fig.6 prediction position error statistics

以上两步的检验证明了本文提出的方法在漏洞定位上有比较好的表现效果。

4 结论

本文提出了一种基于机器学习的二进制漏洞检测方法,利用动态二进制插桩工具 Pin提取程序的静态特征和动态特征,通过本文提出的切片以及向量化方法得到训练模型的数据集,训练漏洞检测模型。实验结果表明,本文提出的方法在漏洞检测和定位上都取得了较好的效果,由于实验中的数据集是基于真实的项目,所以该方法在网络安全实际应用中也适用,并且这一课题的研究为二进制漏洞检测提供了新的思路。实验结果较好,但该实验仍有很多可以继续尝试优化的方向,例如分词方法、标签生成的规则,都会在一定程度上影响模型的检测效果。未来我们也会尝试构建多种漏洞的统一检测模型,提升检测模型的自动化能力和泛化能力,这是在本文中还没有实现的,还有继续探索的空间。

猜你喜欢
二进制漏洞内存
漏洞
用二进制解一道高中数学联赛数论题
有趣的进度
二进制在竞赛题中的应用
“春夏秋冬”的内存
三明:“两票制”堵住加价漏洞
漏洞在哪儿
二进制宽带毫米波合成器设计与分析
高铁急救应补齐三漏洞
内存搭配DDR4、DDR3L还是DDR3?