Automated Hybrid Analysis of Android Malware Through Augmenting Fuzzing With Forced Execution(TMC’18)
问题
应对安卓机中的逃逸技术,不仅是依赖于Event Input
的逃逸技术,还包括Environment Setting
的逃逸。而且不存在扩展性问题。
贡献
- 我们提出了一种新的混合方法来对抗Android恶意软件中使用的各种规避技术。通过增加对新实现的按需强制执行的模糊测试(动态修改某些条件检查的分支结果),我们的方法可以触发更多隐藏的恶意行为。
- 我们建议通过模糊测试和必要的插桩来避免崩溃。 结果表明,崩溃可以在很大程度上处理。
- 我们实施了我们的方法原型,并在大量现实世界的恶意软件样本上对其进行评估,以证明其有效性和效率。
原理
Proper Input Event Generation.
Specific Environment Setting.
动态执行器。给定目标位置,动态执行程序在监视相关触发器的同时重复执行应用程序,直到达到目标位置。在此过程中,将fuzzing和按需强制执行结合起来,将应用程序引导到每个目标位置。首先,fuzzing负责通过拦截应用程序对模糊API的调用,并用从值提供者中选择的值替换其返回值来提供必要的输入。
换句话说,fuzzing仅旨在允许执行正常运行并且可能不满足某些已识别的触发器。我们重用FuzzDroid [8]中的一些技术来实现模糊引擎和值提供程序,如第5.5和5.6节所述。其次,通过监控触发器的分支结果,我们可以确定朝向目标位置的执行是否存在偏差。如果是,则执行按需强制执行以强制设置相关触发器的分支结果。另外,为了确保代码正常运行,实现了崩溃检测以提供模糊反馈,以迭代地改进值的选择。
设计与实现
为了到达给定的目标位置,DirectDroid重复执行该应用程序,它由四个关键模块支持,即崩溃检测,模糊引擎,按需强制执行和值提供程序。本节首先介绍动态执行器的整个过程,然后描述每个模块的细节,最后介绍我们解决的几个挑战。
为清楚起见,我们首先介绍FuzzInput的定义。为确保应用程序正常运行,需要提供一些必要的程序输入。 Android应用程序通常通过API调用处理程序输入,例如getDeviceId
和getMessageBody
。我们通过修复此类API调用返回的值来优化提供给应用程序的程序输入。
整体过程
算法1总结了动态执行器的完整过程。 请注意,在动态执行之前,首先会根据第4.3节对应用程序进行检测(第2行)。 主循环从第6行开始。执行程序重复执行应用程序,直到达到目标位置或可配置的最大执行次数。 在每次执行之前,函数InitializeFuzzInput(第8行)创建一个fuzzinput。 (详见第5.5节)。 在执行期间,执行程序为模糊API提供必要的返回值,并执行按需强制执行。 检测器收集每个执行的一些运行时信息,并将它们汇总到记录池中,以便为后续执行提供反馈。 每次执行后,执行程序检查是否已到达目标位置(第15行)。 如果是,则报告结果。 否则,它会优化模糊测试,根据先前执行的反馈执行按需强制执行,并再次执行应用程序。
重复执行
我们的执行程序的核心在于函数execute
,在算法1的第10行。
1)触发事件和服务:如我们所知,Android OS是基于事件驱动的环境,该环境响应各种事件并执行已注册的事件处理程序。简单地启动应用程序通常不足以触发执行目标位置。只有当应用对特定事件做出反应时,才会触及许多目标位置,例如传入的短信或点击按钮。为了使方法能够到达这样的目标位置,DirectDroid重用IntelliDroid [11]中的技术来提取事件入口点并以编程方式触发相关的事件处理程序。为此,我们的系统计算应用程序的静态调用图,并提取通向目标位置的调用路径。找到调用路径后,DirectDroid会检查相应回调的事件,然后以编程方式触发事件。我们直接调用相应的事件处理程序方法,而无需为整个应用程序及其UI建模。
2)执行:基于创建的FuzzInput和指定的force,应用程序将被执行。在每次执行之后,执行器记录执行的运行时信息,例如跟踪和历史。
定义5.2。 (跟踪)跟踪:T =(ID,Ω,L,cf)是执行的摘要。 ID是每次执行的唯一标识符,例如,4表示第四次执行。列表L = [l1,l2,..,ln]是记录的执行路径,其中li是执行语句的位置。 Ω是此执行中使用的模糊输入。布尔值cf表示在执行期间是否发生了任何崩溃。
定义5.3。 (历史)历史H =(p,b)总结已执行触发器的分支结果,其中p表示执行的触发器,b表示运行时的相应分支结果。
在每次执行结束时,执行程序将跟踪添加到跟踪池。这些先前执行的痕迹将用于创建初始模糊输入,如第5.5节所述。另一方面,根据记录的历史,还在算法1中的第18-20行(下面详细描述)对某些触发(即,力)执行强制执行。
强制执行
给定目标位置,静态分析器已经提取了控制执行它的触发器及其所需的分支结果。 为了以后使用,我们为每个识别的触发器分配特定的标识符P ID。 由于模糊测试引擎旨在确保执行正常运行,因此可能无法满足某些已识别的触发器。
因此,动态执行程序执行按需强制执行以强制设置某些触发器的分支结果。 具体来说,强制执行只强制设置触发器的分支结果,这些结果不能通过模糊测试来满足,并且它允许其他谓词像往常一样运行。 接下来,我们首先描述如何确定执行强制执行的触发器,然后详细介绍如何实现强制执行。
Determination of forced execution
算法1中的第18-20行总结了确定某些触发器上的强制执行的步骤。在这里,我们使用一个简化的例子来说明这个过程。图3显示了一个规避代码,用于确定执行是否发生在模拟器上(第5行),否则开始执行恶意操作。如果我们将abortBroadcast(第10行)标记为目标位置,则静态分析器会将第5行标识为触发器,并且其所需的分支结果为false。要监视此触发器的分支结果,将通过检测插入两个语句(第6行和第9行)。假设在第一次执行时,模糊测试引擎为模糊API getDeviceId提供值,并且此触发器的结果为true。通过比较所需结果和此触发器的执行结果(算法1中的第19行),我们可以确定此触发器导致执行偏离目标位置。因此,该触发器将在下一次执行中强制执行,即将其添加到forces
。
Implementation of forced execution
为了强制执行某些触发器,我们建议通过插桩动态修改其控制流程。 再次考虑图3中所示的示例,强制执行表明我们需要强制设置触发器的分支结果(第5行)为false。 为此,我们通过引入布尔变量(例如,力)和使用逻辑AND操作(第4行)来修改其条件。 由于这个布尔变量是从’server’(我们的计算机)返回的,所以我们可以动态地修改分支结果。 对于此示例,如果返回值为true,则触发器将照常运行; 否则,触发器将强制执行到假分支。 请注意,当触发器的所需分支结果为真时,强制执行会略有不同。 在这种情况下,我们通过使用逻辑OR操作来修改其条件,而其他操作则相同。
崩溃检测和处理
为了在执行期间检测应用程序崩溃,我们在系统报告之前覆盖默认异常处理程序并拦截崩溃。 在上述代码跟踪的帮助下,我们首先确定发生崩溃的位置,即代码位置和相应的语句。 第二,可以从截获的崩溃子句中提取特定类型的抛出异常。 抛出的异常类型通常会给出错误提示; 因此,我们建议根据提取的信息处理发生的崩溃。 特别是,如果崩溃是由NullPointerException引起的,我们通过初始化相关对象来处理它,如第5.7节所述。 对于其他类型的崩溃,我们尝试通过改进由模糊引擎创建的模糊输入来处理它们,如下所述。
Fuzzing
如算法1所示,每次执行都以一个初始模糊输入Ω开始,该模型将执行期间可能发生的所有可能模糊API调用的子集映射到某些返回值。 在执行期间,只要执行到达模糊API,应用程序就会查询fuzzinputΩ。 如果初始fuzzinputΩ包含此API,则它将返回相应的值。 否则,执行程序查询模糊测试引擎,从值提供程序中选择值(下面讨论)并将其添加到当前模糊输入Ω。 该过程将在5.6节中详细描述。 接下来,我们将首先展示如何创建初始fuzzinput。
Create an initial fuzzinput
算法2总结了关于如何创建初始模糊输入以确保朝向给定目标位置的正常执行(即,没有崩溃)的主要步骤。首先,算法从跟踪池中提取带有崩溃的跟踪(如果有的话)(第2行),这将用于以下崩溃处理。在使用崩溃排除这些跟踪后,如果跟踪池中没有更多跟踪,算法将返回空的fuzzinput(第5行)。空fuzzinput强制模糊引擎在运行时查询value provider
的值,从而产生额外的traces
以供学习。如果有一些没有崩溃的traces
,算法将从这些轨迹中选择一个使用过的fuzzinput。为了找出最有可能导致正常执行的目标位置,我们借用适应性概念[8]并从之前的轨迹中选择最佳的一个,如下所示:
定义5.4。 (模糊输入的适应性)给定迹线T和目标位置ltarget,来自T的模糊输入Ω的适应度是ltarget和T中L的任何位置l之间的最小距离。
DirectDroid计算代码位置之间的距离,作为程序间控制流程图中位置之间的最小边数。基于模糊输入的适应性,算法2通过它们的适应度分数对这些痕迹进行分类并选择最佳的(第6行)。这背后的直觉是,最接近目标位置的轨迹最有可能在其模糊输入中具有适当的值,这将使应用程序能够正常朝目标位置运行。在简单的情况下,当先前执行中没有崩溃时,算法将使用最佳轨迹的模糊输入τ作为下一次执行的初始模糊输入(第6-10行)。
Refine initial fuzzinput for handling crashes
当先前执行中发生崩溃时,创建初始fuzzinput需要额外的处理,如算法2(第11-16行)中所述。这里的直觉包括两个方面。
1)。如果发生崩溃,我们可以回到之前没有崩溃的痕迹并创建一个新的初始fuzzinput。如上所述,在算法2的第8行,通过适应度测量提取最佳的先前轨迹及其使用的模糊输入τ。然后,基于来自具有崩溃的轨迹的反馈,先前的最佳模糊输入τ被精确地细化以创建下次执行的初始fuzzinput。
2)。如果先前使用某个fuzzinput执行崩溃,则需要对此fuzzinput中的某些模糊API及其分配值进行变更,以避免在以下执行中发生崩溃。 mutate函数是通过要求值提供者为每个模糊API提供不同的值来实现的。具体来说,它首先收集崩溃的跟踪并提取他们使用的模糊输入,如算法2中的第11-12行所示。让τ1成为先前跟踪崩溃时的模糊输入。当τ1包含新的模糊API时,调用mutate函数,该API未出现在先前提取的τ中(第14行)。然后通过合并新的模糊API和它们的变异值来细化τ(第16行)。最后,精炼的τ作为下一次执行的初始fuzzinput返回。