机器学习与恶意代码分析
业界普遍认为,简单的字节签名已不足以可靠地检测恶意软件。相反,现代反病毒产品严重依赖静态和动态分析的某种组合来将特征输入预测模型,从而确定特定文件是否为恶意文件。 直到最近,我们一直完全专注于动态或行为分析,而不是静态或基于文件的分析,因为我们认为动态具有最大的长期潜力,并且更擅长检测新的和未知的威胁。前段时间,我们开始研究和开发基于静态的防病毒功能,现在我们开始向客户推出。
在这篇文章中,我将讨论静态分析的优缺点,并概述我们如何使用机器学习和静态特征提取来确定文件是否是恶意的。
静态检测的优缺点是什么? 一般来说,动态分析是观察程序在执行时做了什么,而静态分析是检查文件不运行时的样子。使用动态分析检测恶意软件涉及大量检测操作系统并在程序运行时观察程序是否存在可疑或恶意行为并停止它们(即运行它并查看它的作用)。另一方面,静态分析只是查看文件本身并尝试提取有关文件中结构和数据的信息。
不幸的是,存在许多免费工具,恶意软件作者使用它们来打包、加密和混淆文件的数据和代码。但是,不可能完全加密可执行文件的结构,因为它必须由操作系统解释。这种结构相当容易解析,并且包含有关程序是如何创建的以及它的行为方式的信息。例如,您可能能够确定程序的编译时间、使用的编译器以及它可能进行的 API 调用。从下图的结构可以看出,微软的 Portable Executable 格式包含了大量的信息:
基于静态的检测的主要好处之一是它可以在文件执行(或预执行)之前执行。这显然很有用,因为如果永远不允许执行恶意软件,修复恶意软件会容易得多。一盎司的预防胜过一磅的治疗。这种好处的一个必然结果是,即使是无法执行的损坏和格式错误的可执行文件仍然可以静态检测到。当然,任何主要基于行为分析的检测都将无法检测到这些相同的样本,因为它们不会产生任何行为。这些类型的文件是否应该被视为恶意文件是值得怀疑的。即便如此,检测和删除实际上不会伤害您的恶意软件可能还是有一些价值,因为它可以让您安心并适合现有的政策和程序。
即使恶意软件可以执行,它通常也是无能为力的,因为重要的命令和控制 (C&C) 服务器在最初发现后很快就会被关闭。许多恶意软件家族依赖来自 C&C 服务器的指令或额外的有效载荷来操作,因此实际上是恶意行为。与无法执行的损坏文件一样,可以说这些文件只是名义上的恶意,但删除它们仍然是可取的,因为它消除了标准操作环境的潜在不确定性来源。
至于静态检测的缺点,更难检测完全不同于您以前见过的任何文件的全新和新颖的威胁。造成这种情况的一个原因是,操纵可执行文件的结构比改变行为要容易得多。考虑勒索软件的行为。合法的应用程序可以通过某种方式进行修改,使其看起来不是恶意的,但它的行为就像勒索软件一样。如果您只是查看文件结构,您将知道它导入了哪些函数,但您不知道它们是否或何时被调用或以什么顺序调用。由于大多数应用程序的代码和结构都是合法的,因此根据静态分析的复杂程度,可能很难检测到特定文件。然而,即使是简单的动态分析,您也会看到一个程序打开许多文件、调用加密函数、写入新文件以及在启动后不久删除现有文件。这种行为看起来非常可疑,并且主要是勒索软件的特有行为。 综上所述,静态检测的优势在于:
1 |
|
虽然主要缺点是:
1 |
|
机器学习概述
机器学习是一个广阔且不断变化的领域,我将在这里描述的内容通常涉及机器学习如何应用于反病毒行业,特别是我们如何使用它来预测文件是恶意文件还是良性文件。
创建预测模型首先要收集大量各种恶意和良性文件。然后,从每个文件中提取特征以及文件的标签(例如恶意或良性)。最后,通过向模型提供所有这些特征并允许它处理数字并在数据中找到模式和集群来训练模型。根据您的硬件有多好,这可能需要数小时或数天。这样,当将带有未知标签的文件的特征呈现给模型时,它可以返回这些特征与恶意和良性集的特征相似程度的置信度分数。
创建模型后,我们可以将其部署给我们的客户,我们的代理可以使用它来扫描它看到的任何新文件:
由于我将在下面描述的各种原因,我们决定使用随机森林作为我们的模型。随机森林几乎是非常有效的,并且在没有太多调整的情况下开箱即用地工作得很好,即使有非常多的特征。
为了帮助说明随机森林是如何工作的,我设计了一个涉及猫科动物和犬科动物饲养的荒谬示例,我希望通过幽默和图片来弥补它缺乏严谨的数学。
假设您拥有一个充满猫狗的广阔牧场,并且您已经建造了一个机器人来帮助您照顾所有猫狗。机器人需要能够确定动物是猫还是狗,以便知道在治疗动物时使用哪种协议。当然,任何拥有或经营过猫的人都知道它们是特殊的雪花,处理方式与狗略有不同。
现在,让我们规定机器人可以使用各种战术和视觉传感器从动物身上提取以下特征:
1 |
|
你会注意到特征 #1 和 #2 对所有动物都是正确的,假设它们是健康和正常的。这些功能不会添加任何有用的信息,因此随机森林会忽略它们。 要训练你的随机森林,你首先需要为你奇异的牧场上的每只动物提取特征,并将它们记录在电子表格之类的东西中:
# | number of legs | covered in fur | sometimes goes outdoors | loves you | … |
---|---|---|---|---|---|
1 | 4 | 1 | 1 | 1 | … |
2 | 4 | 1 | 0 | 0 | … |
3 | 4 | 1 | 1 | 0 | … |
4 | 4 | 1 | 0 | 1 | … |
5 | 4 | 1 | 1 | 1 | … |
对于每一行特征,另一个电子表格包含该动物的标签(猫或狗):
# | label |
---|---|
1 | dog |
2 | cat |
3 | cat |
4 | cat |
5 | dog |
训练随机森林的过程实际上是创建许多决策树的过程,其中树中的每个节点代表某个特征值的 if-else。 在随机森林中,每个决策树的特征都是随机确定的,但在这个例子中,每个特征都是为了简单起见。 随着决策树的训练,每一行特征都通过遵循所有条件来遍历决策树。 树的叶子包含特定标签到达该特定叶子的概率。 这是上述功能的示例决策树。 叶子包含两个概率,一个是 C(猫),一个是 D(狗)。
计算总分的一种方法是通过森林中的每棵决策树运行特征并将分数平均在一起。
为了完成现实生活和这个动物牧场之间的类比,您总是可以投入更多时间来开发提取更有意义的特征的方法,以提高模型的准确性。 例如,您可能会直觉以下功能很有用:
1 |
|
不幸的是,这些特征要复杂得多,需要付出很多额外的努力才能提取出来。 因此,您保留一份您想要拥有的所有花哨功能的列表,并将它们保存到 2.0 版。
提取和选择特征
我们想检测 Windows 可执行恶意软件,因此我们首先尝试使用 pefile,它是一个用于解析可移植可执行文件的库。 它给了我们很多功能。 例如,这是分析 kernel32.dll 的输出。
我们扫描每个文件以生成大量原始特征。 一些特性的值是字符串,例如部分名称(.text、CODE、.bss 等),而其他特性要么是浮点数(熵),要么是二进制(0 或 1)。
许多学习算法仅适用于具有数值的特征。 如果要使用字符串,则必须对输入数据进行矢量化。 矢量化将您的原始特征(可能包括字符串)转换为漂亮、干净、易于机器读取的位向量。 例如,这是一个原始特征列表,其中包括熵、文件大小和 PE 部分名称的字符串:
sha256 | features |
---|---|
34973274ccef6ab4dfaaf86599792fa9c3fe4689 | sect0: ‘.text’, sect1: ‘.data’, entropy: 3.1415926, size: 1234 |
aa6c73ce643102b45ed60d462cfc8d9eb771677a | sect0: ‘CODE’, sect1: ‘.data’, entropy: 2.71828, size: 256 |
c8fed00eb2e87f1cee8e90ebbe870c190ac3848c | sect0: ‘.bss’, sect1: ‘.text’, entropy: 1.6180339, |
处理后,这是位向量或特征矩阵:
sha256 | sect0_.text | sect0_CODE | sect0_.bss | sect1_.data | sect1_.text | entropy | size |
---|---|---|---|---|---|---|---|
34973274ccef6ab4dfaaf86599792fa9c3fe4689 | 1 | 0 | 0 | 1 | 0 | 3.1415926 | 1234 |
aa6c73ce643102b45ed60d462cfc8d9eb771677a | 0 | 1 | 0 | 1 | 0 | 2.71828 | 256 |
c8fed00eb2e87f1cee8e90ebbe870c190ac3848c | 0 | 0 | 1 | 0 |
每列代表一个特征或维度。 如您所见,原始特征(不包括 sha256)的数量只有 sect0、sect1、熵和大小,但特征矩阵中的维数是 7。在我们的例子中,我们从大约 60,000 个不同的原始特征开始,但 当矢量化时,维度的数量增长到超过 1,000,000。
许多机器学习和聚类算法通过确定数据点之间的空间关系来工作。 拥有多个维度会增加数据集的噪声和复杂性(您尝试将 1,000,000 个维度可视化!),使用主成分分析 (PCA)、特征聚合和特征选择来减少维度数量通常是件好事。 这些步骤可以提高训练时间和预测准确性,因为训练数据中的噪音更少。
PCA 的工作原理是将特征的高维图投影到低维空间,以保留给定维数的尽可能多的信息。 要获得更好和更详细的解释,请查看通过 RBF 内核 PCA 的内核技巧和非线性降维。 特征聚集通过将高度相关的特征聚集在一起来工作。 特征选择通常是通过训练具有所有特征的模型并对每个特征的有用性进行评分来完成的。 在随后的培训中,您可以删除被认为不够有用的功能。 所有这些都需要大量的实验和调整才能正确。
我们尝试了 PCA 和特征聚集,但我们只使用特征选择获得了最好的测试结果。 在执行特征选择之前,我们删除了所有不变的特征或每个样本都相同的特征。 然后,我们计算每个特征的 ANOVA F 值,并将所有特征降至某个阈值以下。 我们通过检查 F 值并发现相当数量的特征高于某个水平并且大多数特征远低于该水平来确定最佳阈值。 最后,我们得到了一个包含大约 31,000 个特征的特征矩阵。
随着我们继续开发我们的静态检测引擎,我们将不得不继续收集新的恶意和良性样本。 随着我们的样本集增长,训练时间也会增长。 许多文件高度相似,因此对我们来说,尝试不同的特征选择方法最终将变得很重要,这些方法可用于快速准确的样本聚类。 通过使用聚类,我们将能够限制训练集的大小,而不会对样本多样性产生负面影响。 我们想要尽可能多样化的集合,以便我们的模型即使在我们以前从未见过的文件上使用时也能保持准确。
选择和调整模型
我们为最初的概念证明尝试了许多不同的模型。 由于维数过多,或者可能是库的限制,我们无法理解的其他一些神秘原因,通过确定向量空间上的决策边界来工作的模型,例如线性 SVM 和多层感知器 (MLP) 神经网络 表现不佳。 最终,我们选择了决策树,尽管我非常努力地避免使用决策树,因为我认为它们很无聊而且不酷,而且我想使用一些时髦而华丽的东西,当我和我的朋友谈论它时听起来很聪明。 但尽管如此,我们还是选择了基于决策树的模型,最终选择了随机森林,它只是决策树的集合。
在随机森林取得一些初步有希望的结果之后,我们需要确定用于训练的最佳超参数。 我们最关注的参数是:
1 |
|
不可能先验地知道调整学习模型的最佳方法。 唯一确定的方法是尝试某些参数的每个排列。 这种技术称为网格搜索。 在我们的例子中,我们只是为上述每个参数提供了一个范围来构建随机森林,并运行了几天的网格搜索。 网格搜索的每次迭代都使用来自我们样本语料库的所有输入数据训练一个随机森林,然后进行快速的 3 折交叉验证并记录准确度。 然后,我们绘制结果以查看特征在不同值下的表现如何,并预测如果我们继续将其调整到超出我们最初设置为网格搜索范围的范围,一个特征可能会变得更好。 以下是其中一张图表的样子:
y 轴是模型精度,x 轴是估计器的数量。 如果你的视力非常好或者你放大图像,你可以看到绿色圆圈线表现最好。 这条线表示 max_features 为 0.3,min_sample_split 为 1,oob_score 为 True。 在大约 255 个估计器中,它表现最好,但随着估计器数量的增加,其他参数最终超过并最终超过了它。 我们最终选择了橙色八边形(max_features=0.3,min_samples_split=4,oob_score=False)。
如果你好奇,这里是生成上图的代码:graph_gridsearch.py。 已经有大量关于机器学习用于反病毒行业的研究。 回顾现有文献有助于我了解哪些功能和模型最有可能运作良好,最重要的是,建立了一些关于它们为什么会运作的直觉。
但是,在我看来,在阅读学术研究时要牢记两个主要警告:样本选择和测试分数与现实世界的分数。关于样本选择,许多研究使用的样本少于 100,000 个,并且通常没有讨论样本集的多样性或新鲜度。我不认为这是由于无知或懒惰造成的,但这是很多额外的工作,并且一个小的、陈旧的样本集的后果是微妙的,并且在你为模型配备删除文件并将其部署的能力之前不会强烈感觉到现实世界中的数千台机器。同样,许多论文引用了模型准确度超过 99.9% 的测试结果——这意味着假阴性和假阳性加起来不到 0.1%。在使用标准的 10 折交叉验证评估我们自己的模型时,我们也有类似的分数。但是,如果我们下载数千个未经训练的新恶意和良性文件,我们的模型准确率将下降 0.5-5%。这意味着您不应该对交叉验证分数印象深刻,并且您还必须针对不在训练集中的各种来源的样本进行测试。我们通过简单地增加我们的训练集大小和多样性来解决这个问题。
结论
我希望您在阅读本文后学到了一些东西,并且您对机器学习的总体情况以及它如何应用于反病毒行业有更好的理解。 我相信这个新的基于静态的检测功能很好地补充了我们的动态引擎,我们的客户将得到更好的保护。