基于深度神经网络的Android APP与恶意软件分类

本文介绍2017年IJCNN期刊论文《Classification of Android Apps and Malware Using Deep Neural Networks》,其DOI信息在这里

这篇文章使用目前非常流行的卷积神经网络(Convolutional Neural Network,CNN),通过获取Android APP的系统API调用,对其进行分类。文章同时对普通APP与恶意软件作出分析实验,结果较现有方法更优。

摘要

针对移动设备的恶意软件(Malware)在当今是一个普遍的问题。恶意软件的检测本质上是一个基于程序分析信息的软件分类问题。文章聚焦于使用Android APP的系统API调用序列(system API-call sequences)并调研深度神经网络(Deep Neural Network,DNN)解决该问题的效率。DNN具有学习复杂、多变特征的能力,因此可以快速有效地检测恶意软件。文中设计了一个卷积神经网络来进行序列分类,并作了一系列实验,通过测试恶意软件检测与软件功能分类的效率,将文中设计的模型与和循环神经网络(Recurrent Neural Network,RNN,多数情况下即为LSTM)等其它基于n-gram的方法进行对比。CNN与LSTM的效果均明显优于基于n-gram的方法。尽管LSTM被认为是一种自然的序列数据处理方法,但CNN的效果明显优于LSTM。

简介

移动设备是大多数人日常生活的重要组成部分。如今绝大多数移动设备都使用Android,在过去三年里,Android设备的平均销量占到市场份额的80%。Android操作系统的持续扩展导致针对Android设备的恶意软件数量相应增加。自2009年检测到第一个Android恶意软件应用程序以来,针对Android设备的恶意软件数量惊人地增加。考虑到Android应用程序(通常被称为APP)的数量之多,我们根本不可能用人力去审查哪怕是一小部分的应用程序。截至2016年6月,仅在Google Play商店就有超过220万个不同的应用程序,而且这个数字还在迅速上升。

检测恶意软件实际上是一个软件分类问题。分析、分类软件的过程分为两步:

  1. 通过分析程序,提取出多种多样的程序特征。
  2. 基于以上特征构建分类器,以进行分类。
    由于需要处理的APP数量众多,有必要将分析与分类的过程自动化。目标是每一步使用的人力尽量少或不用。

本文中,作者重点关注基于APP进行的Android系统API调用的软件分类。系统API调用描述了一个APP如何与底层的Android系统进行交互。这些交互对于APP执行其任务是必不可少的,因此它提供了APP行为的基本信息。作者设计并实现了一个伪动态(pseudo-dynamic)程序分析器。分析器的每次运行都会跟踪一个可能的程序执行路径,并沿着路径生成一系列API调用。在多次运行分析器之后,每个APP都转化为这些序列的集合。

传统的序列分类方法通常将序列转换为符号计数或n-grams等特征。例如,我们可以用bag-of-words(BoW)来表示每个API的调用序列。即,可以建立一个使用频率向量,其中每个元素都是序列中相应API调用发生的次数。除了单个API调用的频率之外,还可以计算2-grams或3-grams等的组合。但是,这种方法会丢失序列的顺序信息。而且,n-grams表示虽然维护了本地顺序信息,但缺乏灵活性。当n稍微增大时,大多数n-grams只出现一次、只出现在一个序列中。这大大削弱了使用这种特性的分类器的泛化能力。

文章研究了DNN在基于API调用的软件分类中的有效性。DNN自动学习特性的能力对于软件分类很重要,特别是对于处理恶意软件。需要手工制定特性的方法在本质上是缓慢的,因此不能对恶意软件的流行提供快速响应。DNN也可以处理复杂和灵活的特性,有助于分类中的泛化。

文章设计了一个卷积神经网络(CNN)用于序列分类,它沿着序列进行卷积运算,当卷积窗口沿着序列向下滑动时,学习每个位置的序列模式。文中的CNN架构还使用了多个CNN层,能够从小的、局部的特性构建更高级别的特性。除CNN外,文章也考虑了RNN(具体来说,是基于LSTM的RNN,因为LSTM是处理序列的一种自然的结构)。文章设计实验比较两种DNN与基于n-grams方法的效果。实验包括恶意软件检测、恶意软件家族识别与基于功能的良性软件分类。在所有的实验中,DNN明显优于基于BoW和n-grams的方法。尽管LSTM是处理序列数据的自然选择,但CNN在所有测试的方法中表现显著更佳,是性能最好的方法。

Android APP的API调用分析

Android APP简介

Android APP的所有必要文件都被打包进一个APK压缩文件中。APK文件通常含有下面几项:

  • res文件夹。存放可编译的资源文件,如布局文件等。
  • asset文件夹。存放一般资源文件,如图片等。
  • classes.dex文件。包含应用程序的字节码(bytecode),是可执行代码的主要来源,尽管有一些方法可以加载外部代码或从APK的本地库包(native library package)中加载代码。
  • AndroidManifest.xml文件。
  • lib文件夹。存放本地库文件。
  • 其它文件。

本文只关注classes.dex文件中包含的代码,而不研究本地库中的代码或asset文件的内容。迄今为止,大多数Android恶意软件都使用了dex代码,尽管最近使用本地库代码的恶意软件有所增加。文章使用开源工具Androguard获得classes.dex的smali代码表示,以便进行分析。

伪动态代码分析

为了进行软件分类,程序分析试图从可执行文件中获得对程序功能的良好描述。然后从描述中提取一组特征,通常使用某种形式的机器学习算法对程序进行分类。所面临的挑战是,选择具有足够特异性的特性,以便在保持足够通用性的同时进行准确的分类。

程序分析大体可以分为两类:静态分析与动态分析。静态分析只检测代码本身,不执行;动态分析通过执行程序来获取特征。为了追求更好的分类效果,本文的目标是对于程序来说更加基本的描述和特征,它们更加难以混淆。为了实现这一目标,作者设计了一个分析器来执行所谓的伪动态程序流分析(pseudo-dynamic program flow analysis)。

在标准的动态分析中,分析者执行软件程序,通常是在独立的仿真环境(如虚拟机)中执行,并在执行过程中收集信息。在执行过程中,可以手动或通过使用自动化工具提供程序交互。一组程序活动(如API调用、内存访问或网络通信)被记录下来,并用作程序的描述。

本文聚焦于记录、追踪Android系统API调用,作为特征呈递给分类器。文章的分析器进行代码追踪,而不是在模拟器或沙箱中运行通过反编译得到的代码。在解析过程中,分析器以一种反映程序可能执行情况的方式执行程序指令,但不计算程序的任何状态信息(内部函数调用的返回点除外,下文将详细介绍)。

研究人员已经发现,恶意软件在开始执行恶意代码之前,可以通过分析诸如传感器测试和延迟计时器之类(有时持续数周)来规避沙箱检测等技术。通过静态分析得到程序流分析,可以达到更加全面的代码覆盖,避免可能发生的动态分析混淆。

假设在代码分析的过程中,分析器遇到一条指令$I$。如果$I$是一个不影响程序流的常规指令,分析程序只需遍历它,除了使用这条指令的长度更新程序计数器外,不会改变状态。如果$I$进行了Android系统API调用,分析器会将API调用的名称记录到外部文件中,以进行分析。

为了恰当地表示程序流,分析器需要特别注意两种情况:

  1. $I$是一个产生分叉路径的指令。
  2. $I$调用了dex文件代码的函数(不是系统API调用,或对另外一个库的函数调用)。
    对于第一种情况,分析器随机选择一条路径运行。对于第二种情况,分析器跳入所调用的函数,跟踪该函数直到其返回(return)。因为分析器有可能递归地跳入多个函数,有必要记录每个函数的返回点,使得分析器能够在函数返回后回到正确的位置。当分析器来到初始函数的返回语句时,分析结束。

分析器向下遍历的路径是代码的一种可能执行的路径。因为分析器在跟踪代码的同时记录系统API调用,所以每次分析器的运行都会生成一个API调用序列。下图显示了来自我们数据集中APK的单个反编译方法的代码。即使在这个简单的示例中,代码也是高度非线性的,因为在执行代码时,可能会沿着许多不同的路径执行。

Android APP方法代码示例

由于使用随机选择来确定分支方向,因此任何一次执行都可能跳过APK文件中较大的部分代码。为了增加代码覆盖率(即,跟踪代码的不同可能的执行路径),给定一个单独的APK文件,在APK上反复运行分析器,每次运行时在分支点都有不同的随机选择。这将为每个APK文件生成一个API调用序列集合。使用这样的集合作为APK的描述,并建立基于序列集合的深度神经网络模型进行分类。

用于APP分类的DNN

预处理与CNN的输入

设分析器为每个APP生成了包含$h$个元素的API调用序列集合。用${ S^{(1)}, S^{(2)}, \ldots, S^{(h)}}$代表这个集合,其中$S^{(i)} = a_1, a_2, \ldots$,其中$a_i$是一个API调用。因为路径选择是随机的,序列(即$S^{(i)}$)的长度可能不同,即使它们是由同一个APK生成的。作者通过实验得出,由APK生成的序列长度从低于100到超过10000不等,其中大多数在数千左右。

对API调用序列的观察发现,某些API常常被直接连续调用多次。例如,Java的string builder API经常在一个序列中连续被调用5次以上。这种重复调用不能提供有意义的新信息,而且噪声实际上会严重影响分类。因此,文章将重复的调用减少到一个API调用。实验表明,通过这样的降低,可以明显提高准确度。

首先研究用于软件分类的CNN模型。CNN要求输入的大小是固定的。可以对较小的序列进行padding,从而使所有序列长度相同(这个长度即为序列的最大长度)。但是,最长和最短的序列长度差异非常大,所以采用了另外的一种方法。文章选择了一个长度$n$,将序列$S^{(i)}$分为多个段(segment)${ S^{(i)}_1, S^{(i)}_2, \ldots }$。注意,$S^{(i)}$的长度不一定是$n$的倍数,通过重复它的内容达到$n$。实验确定$n$的理想值是100-200个API调用。

使用one-hot编码每个API调用。one-hot编码后,每个段$S^{(i)}_j$被编码为一个$n\times m$大小的矩阵,作为CNN的输入。

CNN与分类器结构

给定一组段$S^{(i)}_j$,表示一个APK文件,任务是构造一个分类器来为这个APK分类。首先训练CNN对每个段进行分类。然后,对来自同一APK的每个段进行分类,以选择该APK的标签。这里的分类是软分类(soft classification),软分类明确地估计类的条件概率,然后根据估计概率进行分类。相比之下,硬分类直接瞄准分类决策边界,而不产生概率估计。

文中的CNN被设计为,使用段作为输入,输出这个段属于哪一类的软分类结果。正式地说,令$c$代表分类任务中类别的数量,则CNN相当于一个函数$R^{n\times m} \mapsto R^c$。为解决此问题,文章将APK的标签赋予它对应的段。即,若$l$是一个APK的标签,${ S^{i}_j }$是从这个APK中获得的段的集合,则所有$S^{i}_j$拥有同样的标签$l$。

CNN的结构如下图。

CNN结构图

LSTM分类

本文的软件分类基于API的调用序列。LSTM是一种为处理序列数据提出的自然的神经网络结构。因此,文章也对数据集使用了一个两层、每层256个隐藏单元的LSTM。LSTM层处理后,输出传入全连接层,之后再传入softmax神经元以进行软分类。每个APK文件提供一系列的API调用序列,LSTM对每个序列输出软分类结果。APK文凭的标签由软分类结果求和确定。

实验

文章针对恶意样本与良性样本分别进行实验。对于恶意样本,测试分类器的检测和家族分类能力;对于良性样本,则测试分类器能否准确按功能分类。选用的分类方法有:CNN、LSTM、n-gram SVM与朴素贝叶斯。效果展示在下面两张图中。

恶意样本家族分类效果图

良性样本功能分类效果图

为了理解CNN为什么适合这项任务,有必要考虑CNN的基本性质,并将其与CNNs被广泛研究的图像处理领域进行类比。在分析程序代码时,API调用的特征局部性是非常重要的。看起来彼此接近的API调用之间的关系要比相隔数百行的API调用紧密得多。CNN通过使用小的本地过滤器在网络的每一层寻找特性,这些特性连续向上传递,从而在网络中创建更多的抽象特性。这类似于人类专家如何进行恶意软件分析,通过查看一小部分代码来确定它们的功能,并逐步考虑不同代码之间的交互。

结语

本篇文章通过对比多种分类方法,最终发现CNN更加适合基于API调用的分类。作者自己实现的静态代码分析器
对API调用分段、对不同长度的API调用段进行padding的方法值得关注学习。