摘要
恶意软件API调用图是从 API 调用序列中派生出来的,被认为是理解恶意软件行为特征的代表性技术。然而,在实践中为每个恶意软件构建行为图是麻烦的。为了解决这个问题,我们研究了如何生成一个简单的特征行为图来描述恶意软件。在本文中,我们介绍了使用词嵌入来理解恶意软件调用序列中 API 函数之间存在的上下文关系。我们还提出了一种方法,将具有相似上下文特征的单个函数聚类到簇中。我们的实验结果证明了恶意软件和良性软件调用序列之间存在显著区别。基于这种区别,我们引入了一种基于马尔可夫链的检测和预测恶意软件的新方法。通过建模恶意软件和良性软件 API 调用序列的行为,我们生成了一个语义转移矩阵,描述了 API 函数之间的实际关系。我们的模型平均检测精度为 0.990,误报率为 0.010。我们还提出了一种预测方法,可以从初始 API 调用函数预测 API 调用序列是否具有恶意性。我们的模型预测的平均准确率为 0.997。因此,我们提出了一种方法,可以在恶意软件执行后检测到它们之前阻止恶意负载,避免修复损害。
引言
计算机和互联网技术的快速发展也伴随着恶意软件的快速增长。病毒、特洛伊木马和蠕虫等恶意软件迅速变化,成为网络空间最严重的威胁。恶意软件在计算机系统上执行恶意活动。通常,恶意软件通过改变或破坏正常进程执行流程来控制计算机系统。新恶意软件实例的数量急剧增加。根据 AV-TEST,他们每天注册超过350,000个新的恶意软件和潜在不需要的应用程序(PUA)。大量恶意软件发出了如何有效分析和处理如此多样本的问题信号。因此,自动恶意软件检测成为处理新生成恶意软件不断增加的必要手段。许多研究人员专注于产生不同的恶意软件检测技术,以缓解恶意软件快速增长的速率。恶意软件检测方法可以分为静态和动态恶意软件检测
。静态恶意软件检测检查可移植可执行(PE)文件的内容,而无需实际执行恶意软件样本。在静态分析期间,分析器提取特征,包括字符串模式、操作码和字节序列。提取的特征用于决定文件是否为恶意软件
。然而,基于静态的恶意软件检测方法本身是不够的。静态分析方法的主要缺点是它可以被混淆技术轻易绕过
。此外,依赖于模式匹配的静态方法在检测已知恶意软件模式方面是有用的。然而,模式匹配方法在检测零日漏洞或多态恶意软件方面是无效的。因此,静态分析方法往往是不可靠的。为了应对基于静态的恶意软件检测的缺点,动态恶意软件检测方法分析恶意软件在执行期间的行为。动态分析注定要在实时性能中监控恶意软件。在这种情况下,恶意软件在安全的虚拟环境中执行,用于在实时执行期间监控恶意代码行为。
2. 相关工作
2.1 概述
许多研究用于分析恶意软件特征。恶意软件分析方法可以主要分为静态和动态分析。在静态分析中,通过分析可执行二进制文件或代码而不执行恶意软件来检查恶意软件文件。与静态分析相反,动态分析方法监控恶意软件执行过程。在执行期间收集、观察和记录恶意软件行为特征。动态分析方法通常在称为沙箱的安全虚拟环境中执行。常见的动态分析沙箱包括Cuckoo Sandbox和CWSandbox。沙箱的主要目标是在防止恶意软件攻击主机系统的同时,检查恶意软件的恶意行为。静态和动态分析方法各有优缺点。静态分析相对于动态分析的主要优势在于它没有程序执行引起的开销成本。然而,静态分析方法在支持打包和复杂混淆代码方面存在局限性。与静态分析相比,动态分析能够有效地分析打包和混淆的恶意软件。原因是恶意软件在执行时必须解包自己。因此,其原始和恶意代码将被加载到主内存中。然而,动态分析的主要缺点是时间和资源的消耗。恶意软件样本需要单独分析,这导致动态分析在商业应用中的局限性。在本文中,我们将专注于动态分析方法。具体来说,我们将专注于分析在恶意软件样本执行时生成的API调用序列。我们认为动态分析方法在恶意软件分析方面是最有效和准确的。原因是动态恶意软件分析捕获了反映真实恶意软件行为的可行特征。API调用可以通过静态和动态方法提取。在静态方法中,API从可执行文件的可移植可执行(PE)头中提取。然而,在动态方法中,API通过观察正在运行的可执行文件来收集。在我们的方法中,我们使用动态方法收集的API调用序列。
2.2 Windows API
在Windows操作系统中,用户应用程序不能直接访问硬件或系统资源。然而,它们可以依赖于动态链接库提供的接口。这些库,如kernel32.dll、user32.dll、gdi32.dll和advapi32.dll,提供了访问硬件和系统资源的功能。图1展示了Windows中的API调用机制。表1展示了提到的DLL文件及其描述的简要说明。图1中显示的接口称为Win32 API。假设用户程序想要读取文件,它调用Win32 API的文件读取函数。该过程最初调用“ntdll.dll”中索引的NtReadFile 函数。然后NtReadFile 函数调用内核模式中的关联服务例程,也称为 NtReadFile。因此,任何需要特定服务的程序都将调用内核模式中的本地 API。在程序监控的情况下,观察程序的主要方式是通过监控其API调用。API函数本身是标准的;没有所谓的恶意或正常函数类别。因此,恶意软件程序也使用标准API函数来执行其恶意目的。API调用机制不区分恶意和正常程序。恶意的或正常的进程都可以使用相同的API。然而,API调用序列的行为可以导致调用进程的上下文属性。换句话说,通过API调用序列可以判断是恶意的或正常的。然而,API函数的数量本身很大。因此,通过同时跟踪所有 API 来描述运行进程的行为属性变得困难。进程和操作系统之间的API函数调用序列被认为是恶意和正常进程之间的基本行为差异。大多数行为恶意软件分析的研究工作都集中在API调用上。API调用序列可以提供有意义的表达,支持和协助更好地理解恶意软件。API调用编码了关于恶意软件在运行时发生的隐式功能的足够信息。
3. 提出的模型
3.1 概述
如相关工作所述,以往的研究主要集中在从API调用序列中提取模式,如n-gram。这些模式被用作恶意软件检测的特征。然而,没有人尝试检查整个序列中单个API调用之间可能存在的关系。受此启发,我们需要回答以下问题:
- 1.恶意软件序列中的 API 调用之间是否存在某种相关性?
- 2.正常和恶意 API 调用序列之间是否存在可发现的差异?
这些问题在以往的研究中没有得到解决。受此启发,我们试图通过动态分析引入恶意软件检测的新方向。
3.2 挑战
在我们的工作中,我们需要找到正常/恶意调用序列中单个API函数之间可能存在的关系。然而,我们的方法面临的主要挑战是:
- API 函数的数量相当大,这使得分析变得困难。
- 恶意软件通常试图逃避被跟踪或检测。因此,恶意软件作者通常添加大量不必要的 API 调用。主要目的是添加一些看起来正常的事件来隐藏其恶意行为,并使分析过程变得困难。
3.3 系统概述
程序的执行过程可以用几种方式描述。API调用和系统调用通常描述了程序的语义执行。通过这种方式,可以描述程序的行为。在本文中,我们从API调用级别和系统调用级别定义了执行程序的语义。我们提出的系统如图2所示,分为三个阶段,即初始化阶段、学习阶段和测试阶段。在以下小节中,我们将详细描述每个阶段。
3.3.1 初始化阶段
初始化阶段的主要目标是将API函数分组到组或簇中。初始化阶段包含三个主要步骤,即1)词嵌入、2)计算API之间的相似性以及3)聚类相似性矩阵。
3.3.1.1 词嵌入
词嵌入是一种在n维空间中表示单词的形式。词嵌入的主要目标是将人类对语言的理解转移到机器上。Word2Vec 是最常用的词嵌入形式之一。Word2Vec以大量文本语料库作为输入,生成几百维的向量空间。输入语料库中的每个不同单词都被分配了一个相应的空间向量。单词向量在空间中的分布完全取决于输入语料库中的上下文相似性。如果两个单词在上下文中相似,它们将位于邻近空间中;如果两个单词在上下文中不同,它们将位于彼此远离的位置。
在恶意软件分析的背景下,我们认为恶意软件序列中API函数的顺序并非随机存在。实际上,它可能编码了一些执行恶意活动的上下文模式。这些模式在不同的恶意软件序列中以某种方式相似。通过从大量恶意软件序列中提取这些模式,我们能够识别序列中API函数之间发生的上下文关系。
在我们提出的模型中,词嵌入模型的输入语料库是良性软件和恶意软件的API调用序列。在我们的实验中,我们将特征向量的维度设置为250,窗口大小为6,工作线程为6。词嵌入的输出如图2所示,是两个嵌入模型,即良性软件和恶意软件嵌入模型。每个嵌入模型包含其不同的API调用函数以及生成的嵌入模型。
3.3.1.2 计算 API 之间的相似性
良性软件和恶意软件的嵌入模型可以用来计算它们的API函数之间的相似性。为了使用Word2Vec计算两个词之间的相似性,我们使用了model.similarity(argument_1, argument_2)方法,并将两个API函数作为参数传递。考虑以下两个API函数getfileversioninfosize和getfileversioninfo。第一个函数确定操作系统是否可以检索特定文件的版本信息,而第二个函数检索指定文件的版本信息。在调用 getfileversioninfo 之前,必须调用getfileversioninfosize。根据我们的嵌入模型,model.similarity(‘getfileversioninfosize’, ‘getfileversioninfo’) = 0.904。结果表示上述两个 API 函数之间的欧几里得相似性。从上面的例子中,我们可以得出结论,getfileversioninfosize与getfileversioninfo高度相似【注:这里的相似不仅仅体现在字符串的相似,更体现在在调用序列上下文中关联得密切性】。
如图2中的初始化阶段所示,API之间的相似性计算以API函数作为输入,然后使用嵌入模型计算所有API函数之间的相似性。我们使用Word2Vec相似性方法来计算每个API函数与其他API函数之间的相似性。此步骤的输出是两个相似性矩阵,即良性软件相似性矩阵和恶意软件相似性矩阵。每个矩阵代表其类别序列中不同API函数之间的相似性。每个矩阵的维度取决于应用词嵌入于训练数据后得到的API函数数量。
3.3.1.3 聚类相似性矩阵
聚类步骤的目标是将具有相似特征的单个API函数分组到簇中。聚类步骤的输入是一个相似性矩阵,它表示单个API函数与其他剩余函数之间的相似性关系。我们使用了k-means算法来对相似性矩阵进行聚类。
为了确定k-means算法的最佳簇数量,我们依赖于一种最常用的方法,称为肘部法则。该方法涉及多次运行 k-means 算法,范围为不断增加的k值。然后,它计算不同k值的 Within-Cluster Sum of Squared(WSS)错误,如公式 (1) 所描述。
其中X是属于簇Ck的观测值,uk是分配给簇Ck的点的平均值。,最佳簇数量是k的值,在“肘部”处,失真/惯性开始线性减少。在我们的实验中,我们在所有数据集上评估了k的值范围为2到21。聚类步骤的输出以及初始化阶段的输出是有限数量的簇,分别用于良性软件和恶意软件。每个类别都有其包含相关 API 函数的簇。
3.3.2 学习阶段
学习阶段的目标是捕捉恶意软件或良性软件序列中API调用之间存在的关系,然后为恶意软件和良性软件创建行为模型。在学习阶段,我们有两个级别的输出,即:
- 1.良性软件/恶意软件簇转移矩阵
- 2.良性软件/恶意软件转移模型
在学习阶段,我们有三个主要过程,即:1)在簇中找到 API 函数、2)创建序列链转移矩阵以及3)计算最大转移序列概率。
3.3.2.1 在簇中找到API函数
在此步骤中,我们的目标是将原始API调用序列转换为簇序列。此步骤以API调用序列作为输入。序列中的每个API函数都被其包含的簇名称所替代。例如,以下序列是名为Worm.Win32.Zwr.c的恶意软件的API调用序列:
在API调用序列中出现的每个API函数都将被搜索到簇中。找到后,它将被包含它的簇编号所替代。以下表示是上述原始API调用序列的簇序列:
将API函数序列替换为簇序列是最重要的步骤。其重要性来自于API函数数量巨大的事实;因此,跟踪恶意软件调用序列中存在的所有API函数变得不可能。通过词嵌入步骤,我们根据上下文相似性表示API函数。当将上下文相似的API函数分组到一个簇中时,我们可以将API函数替换为其包含的簇名称。有了有限数量的簇,我们就有机会定制恶意软件可能具有的序列组合的可能性。例如,Kietal.(2015)中的恶意软件数据集被分组为10个簇,索引了从恶意软件序列中提取的1165个API函数。API函数数量与簇数量之间的巨大差异大大限制了恶意软件序列可能采取的组合,这简化了分析的可能性。在簇中找到API函数步骤的最终输出是一组簇序列。
3.3.2.2 创建转移矩阵序列
在初始化阶段产生的簇可以被视为有限状态集S,其中 S = {S1, S2, S3, …, Sn}。提出的模型假设过程(无论是恶意的还是非恶意的)始终处于有限数量的状态,称为马尔可夫状态。过程最初从这些状态中的一个开始,并连续移动到另一个状态。每次移动或转换称为一步。当过程从状态Si开始时,它可以以概率 Pij 移动到另一个状态 Sj 作为其下一步。移动的概率不依赖于链在当前状态之前所处的先前状态。过程也可以根据当前序列在相同状态之间移动或循环。初始概率分布定义在 S 上,确定起始状态 S0。通常,指定一个特定状态作为起始状态。在本工作中,由于任何状态都可以是起始状态,我们将S0的概率平均分配给模型中已经存在的状态数量。
3.3.2.3 计算最大转移序列概率
恶意软件和良性软件转移矩阵是我们模型的核心。给定任何簇序列,簇之间的转移值在良性软件和恶意软件转移矩阵中是区分恶意软件和良性软件的特征。然而,如果我们能够将恶意软件和良性软件的簇序列重新表述为更简单的形式,可能会更容易理解。重新表述的原因是我们想揭示给定序列的遍历行为。换句话说,我们想观察恶意软件的恶意可能性行为和良性软件序列的良性可能性行为。
为了进行重新表述,我们使用了恶意软件/良性软件簇序列训练集以及恶意软件/良性软件簇转移矩阵。训练集中的每个序列都与两个恶意软件和良性软件簇转移矩阵进行遍历。我们使用公式 (2) 来比较和重新表述每个转移序列,根据其在两个转移矩阵中的转移概率。
根据公式(2),如果转移是恶意的,则转移序列被替换为 1,如果是良性软件,则被替换为 0。例如,第 3.3.2.2 节中的簇转移序列根据图3a和4a中的恶意软件/良性软件簇转移矩阵被重新表述如下,我们使用重新表述的恶意软件和良性软件序列来创建描述恶意软件和良性软件的一般模型。再次,我们依赖于最大似然估计来创建恶意软件和良性软件转移模型。恶意软件转移模型描述了重新表述的恶意软件聚类训练序列。同样,良性软件转移模型描述了重新表述的良性软件聚类训练序列。:


3.3.3 测试阶段
测试阶段的目标是测量所提出模型在识别和分类未见序列到其类别中的准确性。在此阶段,使用未见的良性软件和恶意软件簇序列对所提出的系统进行测试。簇测试序列作为输入输入到恶意软件和良性软件簇转移矩阵中。每个序列遍历两个转移矩阵,并在从一个状态转移到另一个状态时存储状态转移的概率。当序列遍历结束时,根据公式 (2) 比较恶意软件和良性软件的遍历概率。输出是一个由一和零组成的新序列。将重新表述的序列作为输入输入到恶意软件和良性软件模型中。最终决策是根据从跟踪恶意软件/良性软件转移模型中获得的最大累积似然转移概率来确定的。 让我们用真实的恶意软件和良性软件样本的子序列来测试我们的模型。在示例 1 中,我们的工作针对真实的恶意软件 API 子序列进行测试,而在示例 2 中,我们的工作针对真实的良性软件 API 子序列进行测试。我们使用了图3a 和4a 中的簇转移矩阵以及图5a 和 b 中的恶意软件/良性软件模型.
示例1:给定一个由 Worm.Win32.Zwr.c 产生的API调用子序列:
最初,子序列中存在的每个API函数将在簇中进行搜索。找到后,我们写下包含它的簇。在搜索簇以找到上述子序列中的每个API调用函数后,我们得到了以下簇序列:
我们想知道给定的序列是否具有恶意性。因此,系统将不得不计算以下转移概率,这些概率描述了输入序列:p(1,1), p(1,1), p(1,1), p(1,1), p(1,0), p(0,4), p(4,1), p(1,1), p(1,2), p(2,0), p(0,1)
步骤1:检查每个转移在恶意软件和良性软件转移矩阵中的转移概率。表2概述了上述簇序列的转移概率追踪。
步骤2:使用公式 (2) 重新表述簇序列,结果为新序列:1,1,1,1,1,1,1,1,1,0,1
步骤3:计算新序列相对于恶意软件和良性软件模型的似然性:p(1,1) + p(1,1) + p(1,1) + p(1,1) + p(1,1) + p(1,1) + p(1,1) + p(1,1) + p(1,0) + p(0,1)
- 似然性 (序列, 恶意软件) = 0.948 + 0.948 + 0.948 + 0.948 + 0.948 + 0.948 + 0.948 + 0.948 + 0.052 + 0.824 = 8.46。
- 似然性 (序列, 良性软件) = 0.322 + 0.322 + 0.322 + 0.322 + 0.322 + 0.322 + 0.322 + 0.322 + 0.678 + 0.066 = 3.32
步骤4:计算最大似然累积转移值:Max((序列, 恶意软件), (序列, 良性软件)) = Max(8.46, 3.32) = 8.46。因此,该序列被认为是恶意序列。
示例 2:给定一个由 AriaMaestosaSetup-1.4.13.exe产生的API调用子序列:
以下表示是上述原始 API 调用序列的簇序列:
在搜索簇找到上述子序列中的每个API调用函数后,我们得到以下簇序列:5,5,5,5,5,5,5,9,9,5,9.
现在,我们想知道给定的子序列是否具有恶意性。系统将不得不计算以下转移概率,这些概率描述了输入序列:p(5,5), p(5,5), p(5,5), p(5,5), p(5,5), p(5,5), p(5,9), p(9,9), p(9,5), p(5,9), p(9,5).
步骤1:检查每个转移在恶意软件和良性软件转移矩阵中的转移概率。表3概述了上述簇序列的转移概率追踪。
步骤2:使用公式 (2) 重新表述簇序列,结果为新序列:0,0,0,0,0,0,0,0,0,0,0
步骤3:计算新序列相对于恶意软件和良性软件模型的似然性:输入序列:p(0,0) + p(0,0) + p(0,0) + p(0,0) + p(0,0) + p(0,0) + p(0,0) + p(0,0) + p(0,0) + p(0,0)
- 似然性(序列, 恶意软件) = 0.176 + 0.176 + 0.176 + 0.176 + 0.176 + 0.176 + 0.176 + 0.176 + 0.176 + 0.176 = 1.76
- 似然性 (序列, 良性软件) = 0.935 + 0.935 + 0.935 + 0.935 + 0.935 + 0.935 + 0.935 + 0.935 + 0.935 + 0.935 = 9.35
步骤 4:计算最大累积转移值:Max((序列, 恶意软件), (序列, 良性软件)) = Max(1.76, 9.35) = 9.35
因此,该序列被认为是良性软件序列。
4. 结果与讨论
在本节中,我们展示了使用不同数据集的评估。我们评估了我们的模型正确检测和预测给定 API 调用序列是否具有恶意性的能力。
4.1 数据集
为了验证我们的方法,我们收集了多个API调用序列。我们的实验使用了不同大小的不同数据集。大多数作者没有提供他们使用的数据集的访问权限,或者他们提供的 URL 链接已经不再有效。因此,收集恶意软件和良性软件的数据集并非易事。我们注意到,作者只提供恶意软件 API 调用序列数据集,而忽略了提供良性软件 API 调用序列,如 Ki et al. (2015), Catak and Yazı (2019)。为了比较恶意软件和良性软件的行为执行,有必要拥有良性软件 API 调用序列数据集。因此,我们从开源平台如 Github 提供了一些可用的良性软件 API 调用序列数据集,以进行恶意软件和良性软件行为执行的比较。表 4 列出了数据集的详细信息,包括其大小和描述。
4.2 评估指标
我们依赖于准确率、精确率、召回率和 F1 分数等评估指标来评估我们提出模型的性能。这些评估指标的计算公式如下:
4.3 恶意软件检测评估
在我们的实验中,我们选择了 50% 的数据作为训练集,保留剩余的 50% 用于测试。在训练中,我们依赖于随机子样本(有放回),这类似于 k 折交叉验证策略;然而,在每次迭代中,我们随机选择一些样本用于训练,一些其他样本用于测试。这种方法的优势在于我们可以自由决定迭代次数和每次训练-测试的长度。为了防止训练偏差,实验运行了十次。我们将十次实验返回的结果的平均值作为每个数据集的最终评估指标。
为了验证我们提出的工作,我们使用了两个额外的评估指标。这些指标是受混淆矩阵启发的,混淆矩阵是一个描述分类方法性能的表格。评估指标是假正例率 (FPR) 和假负例率 (FNR)。FPR 是错误预测类别的指示因子,FNR 是错误负向分类类别的指示因子。
实验结果显示,我们的方法返回了高准确率的恶意软件检测率,误报率极低。如表 5 所示,我们的方法返回了平均精确率准确率为 0.990,平均假正例率为 0.010,平均假负例率为 0.010。结果表明,我们的模型在检测和分类未见恶意软件方面具有很高的效率和准确性。
【余下评估内容不作翻译】
拓展
在本文的词嵌入步骤中,所使用的簇似乎过于少,这样就导致样本API的泛化效果不好,例如写入文件(CreateFile+WriteFile),以及读取文件(CreateFile+ReadFile),其中WriteFile和ReadFile不应该归属到一个簇中。