应用虚拟化场景中的 Android 恶意软件防护
本文介绍2020年 CCS 会议的一篇文章,题目是《VAHunt: Warding Off New Repackaged Android Malware in App-Virtualization’s Clothing》,DOI信息在这里。
针对目前普遍使用的“应用双开”等基于应用虚拟化的技术,文章指出其中存在的安全隐患,并基于状态检测模型与数据流分析提出被称为 VAHunt
的检测方法。
概览
将流行的良性应用与恶意有效载荷一起重新打包 (repackage) ,曾经是传播 Android 恶意软件的最常见方式。尽管如此,自 2016 年以来,文章作者观察到了一个令人震惊的 Android 生态系统新趋势:越来越多的 Android 恶意软件样本通过滥用近期的应用虚拟化 (app-virtualization) 技术作为新的分发渠道。应用虚拟化使用户能够在同一设备上运行同一应用的多个副本,数千万用户在使用此技术。但是,网络犯罪者将各种恶意 APK 文件重新包装成插件,放置于应用虚拟化平台中,这样能够灵活启动任意插件、无需安装。这种新型的重打包方法通过将恶意负载隐藏在插件中,能够逃避反恶意软件的检测,它也违背了现有重打包应用检测方案的基本前提。
由于基于应用虚拟化技术的应用不都为恶意软件,在文章中,作者致力于在运行前判断其是否具有恶意行为。通过深入研究,文章得出以下两个重要结论:
- 在 Android 框架与插件应用之间的代理层,是应用虚拟化机制的核心,它展现出有限状态转换的特点。
- 恶意软件通常会隐蔽地加载插件并隐藏恶意行为。
以上结论启发作者开发一个称为 VAHunt
的双层检测方法。首先,设计一个状态检测模型 (stateful detection model)识别 APK 文件中是否存在应用虚拟化引擎。之后,进行数据流分析,提取指纹特征以区分恶意和良性加载策略。自 2019 年 10 月以来,作者在Antiy AVL Mobile Security中测试了 VAHunt
,检测了超过 139K 个基于应用虚拟化的样本。与真实值 (ground truth) 相比, VAHunt
达到 0.7% 假阴性和零假阳性。此自动化检测方法使安全分析师免于逆向工程的负担。
简介
为了引诱移动用户在其设备下载恶意应用,恶意软件开发者始终设法规避安全措施,潜入移动应用市场。过去,分发 Android 恶意软件的一种常见方式是对良性应用进行重打包。一般地,攻击者通过反编译原始 APK ,并在分发前添加恶意负载,以劫持良性应用的逻辑;用户因为相信自己运行的是原始应用,而被欺骗安装重打包的恶意软件。但是,从 2016 年开始,作者观察到恶意软件的一种新型的分发趋势:恶意软件利用最新的应用虚拟化技术作为新的重打包方式,这使得恶意软件的分发方式更加简单和隐蔽。
在应用虚拟化方案中,宿主应用通过创建系统服务代理,在 Android 框架之上提供一个虚拟环境。由此,宿主应用能够运行任意 APK 文件(称为访客应用或插件),而无需安装。与重打包类似,应用虚拟化同样以插件的形式加入了额外的代码,从而影响宿主应用的运行逻辑。该技术已经被大量应用使用,获得了数千万用户。应用虚拟化的一大优势是能够运行同一应用的多个副本,例如在同一设备运行两个 WhatsApp 账户。此外,开发者使用应用虚拟化还可以有利于模块化编程、减少APK文件的大小、绕过 DEX 对于方法数量的限制。例如, 360 安全应用包含多个功能模块(如内存清理、智能加速与反病毒扫描),作为插件以供加载和使用,减少了模块间的耦合。
但与此同时,网络罪犯也注意到了最新的技术发展趋势,他们利用应用虚拟化进行重打包、加载恶意负载。例如,安全专家已经发现,木马病毒 PluginPhantom
已经从传统的重打包方式进化到使用应用虚拟化:其将每个恶意功能(如拦截来电、上传窃取的数据)以插件的方式实现,利用应用虚拟化平台安排控制这些插件。通过此种方法, PluginPhantom
能够更加灵活地升级插件而无需重新安装。此外,其所有的恶意负载插件都被进一步加密,以阻挠静态分析,只有虚拟化框架的可执行代码是静态可见的,这对目前业界的反病毒引擎提出了巨大挑战。作者的恶意软件追踪研究也证实,自 2017 年第二季度以来,基于应用虚拟化的恶意软件的增量已经超过了重打包的恶意软件,且差距保持增加。这种新型重打包方式的快速传播,说明应用虚拟化将成为下一代 Android 恶意软件的传播渠道。
遗憾的是,面对新型应用虚拟化重打包的恶意软件,现有的防御技术是不够的。要了解和战胜这种新出现的威胁,还需要做出大量的努力。最近的工作主要集中在应用虚拟化造成的安全威胁上,但是,这些动态检测启发式方法主要用于保护被宿主应用作为插件加载时的良性 app。基于应用重打包的恶意软件,如 PluginPhantom
,是自成一体的系统,不需要依赖第三方插件。此外,所有恶意负载插件均被加密或需要从网络下载,因此试图检查恶意负载插件的静态检测分析同样徒劳无功。
为了帮助安全分析师和应用市场维护者战胜由应用虚拟化武装起来的恶意软件威胁,在本文中,作者探讨了在运行前对基于应用虚拟化的应用做出判断的机制。作者的深入研究得出了两个关键的观察结果。首先,插件应用与 Android 框架之间的代理层(即虚拟化引擎)是应用虚拟化机制的核心,它创建系统服务代理,用预先定义的桩 (stub) 组件包装插件组件,以维护插件的生命周期。尽管有各种不同的实现方式,但在插件和系统服务之间的进程间通信 (Inter-Process Communication,IPC) 中,代理层通过 Intent 封装/解封装来传输数据,这种常见的行为展现了有限状态转换的特点。其次,为了掩盖恶意活动,恶意软件应用通常会在没有任何用户交互的情况下隐蔽地加载和执行插件,这就是所谓的“自我隐藏行为”。
以上的观察结果启发作者开发一种双层的检测方法。第一,作者设计了一个状态检测模型,识别 APK 文件中是否存在应用虚拟化引擎。具体地,作者将“把插件应用的 Intent 包装为预定义的桩组件”这一行为抽象为有限状态机 (finite state machine,FSM) 模型,之后提取所有可达的 Intent 相关 FSM 与它们关联的桩组件,检查它们是否与参考模式匹配。第二,进行数据流分析,以提取隐形加载和应用隐藏策略,区分恶意软件和良性软件。基于 FlowDroid
,作者分析了与路径 API 和文件对象相关的数据流和调用图。作者总结了四个加载策略特征来区分恶意与良性虚拟化应用。如果一个应用同时暴露出应用虚拟化引擎和隐形加载特征,则将其作为应用虚拟化的恶意应用样本。
设计的新颖之处在于,从一个新的角度来检测基于应用虚拟化的恶意应用:虚拟化引擎加载插件的方式是一个突出的恶意软件特征,即使插件的 APK 文件是加密的,或者在运行时从网络下载。文章将这个双层检测模型开发为一个名为 VAHunt
的开源工具。从 2019 年 10 月, VAHunt
部署于 Antiy AVL Mobile Security 以评估其恶意软件检测准确率。使用 139K 个基于应用虚拟化的应用测试发现, VAHunt
达到非常高的准确率, 0.7% 假阴性和零假阳性。少数假阴性来自于几个游戏欺诈应用,这些应用有一个接口,可以让游戏欺诈者修改游戏属性。
总体上,文章主要具有以下三个贡献。
- 虽然通过应用虚拟化重打包的 Android 恶意软件已经兴起一段时间,但研究具体对策的工作有限。作者希望其深入研究能够为安全社区描绘出对这一迫在眉睫的威胁的警示。
- 我们放大了这种新威胁的内在机制,并提出了一个双层检测模型,该模型可以检测应用虚拟化引擎的存在和恶意软件的自我隐藏加载策略。作者的工作为避免应用虚拟化重打包恶意软件潜入 Android 应用商店提供了可行的解决方案。
- 文章在反病毒生产环境检测了原型的
VAHunt
,并将其开源以启示其它研究者探索更加综合的防御方法。
应用虚拟化的技术基础
在计算中,虚拟化一般是指在多功能平台上运行虚拟机的技术。应用虚拟化用于将单个应用与底层 Android 操作系统和其他应用隔离开来。 2015 年以来,应用虚拟化作为一种新的技术出现,它可以在不安装、不修改的情况下加载任意第三方 APK 。宿主应用通过动态代理为插件应用创建虚拟环境,依靠 API 拦截 (hook)和 binder 代理绕过系统服务限制。宿主应用拦截插件应用的 API 调用,这样 Android 系统就会认为所有的 API 请求和组件都来自宿主应用。同时,宿主应用预先为插件应用定义了桩组件和权限,它在运行时将插件组件封装在桩组件中。这样一来,同一应用的多个实例就能够绕过 UID 限制,同时运行。
动态代码加载 (dynamic code loading,DCL)与应用虚拟化一样可用于加载额外的代码。但是, DCL 主要是调用 APK 文件中的方法,不能支持组件的生命周期,而应用虚拟化代表了一种更先进的技术,可以加载整个 APK 文件。
VirtualApp
与 DroidPlugin
是最受欢迎的两个应用虚拟化引擎。它们均支持在同一设备上运行同一 app 的多个副本。尽管实现细节上有些许差异,它们的核心设计思想是非常相近的。下图展示了一个典型的应用虚拟化结构图。宿主应用为每个插件应用提供独立的运行空间,它加载的多个插件应用有着不同的进程 ID ,但是它们与宿主应用共享同一个 UID 。应用虚拟化的核心是代理层(即虚拟化引擎),它在插件应用与 Android 框架之间。代理层大量依靠拦截 (hook) 机制来欺骗 Android 系统服务和插件。例如,它拦截 ClassLoader 来加载插件的 DEX 文件,拦截 IPC 来维护插件应用组件的生命周期。具体来说,一个应用虚拟化的框架,会发现有以下几种常见的机制。
- 共享 UID 。通常情况下,安装在 Android 系统上的每一个应用都会根据其包名分配一个唯一的 UID ,由于 UID 的限制,用户不能在同一设备上安装两个相同的 APK 文件。由上图所示,虽然宿主应用与插件应用运行在不同的进行下,但是它们共享同一个 UID 。因此,不同的插件与宿主应用的权限列表也是一样的。
- 过度授权。宿主应用为满足插件的权限需求,需要申请尽可能多的权限。例如,
VirtualApp
默认申请 186 个权限,基于应用虚拟化的 app 平均申请 129 个权限,但是良性 app 平均申请 5 个权限。明显地,过度授权违反了最低限度授权的原则。插件可以自由地使用宿主应用的所有权限,即使它没有声明需要权限。 - 预定义的桩组件。插件应用的组件没有在宿主应用的清单文件中注册,因为宿主应用不能预测插件应用的组件名称。宿主应用通过预先在清单文件中定义一组桩组件 (stub component) 解决此问题。例如,
VirtualApp
默认为 Android 四大组件创建各 100 个桩组件。 - 组件生命周期管理。宿主应用必须与 Android 系统交互,以管理插件应用组件的生命周期。下图展示了宿主应用如何通过预定义的桩组件与拦截 Android 系统服务 API,对插件的组件生命周期进行管理。以启动名为 “TargetActivity” 的 Activity 为例。当插件启动一个 Activity 时,宿主应用首先拦截 “TargetActivity” 并将其修改为 “StubActivity” 。 “StubActivity” 预先在宿主应用的清单文件中定义。通过这种方法,宿主应用可以欺骗 Activity 管理服务 (Activity Manager Service,AMS) 创建一个新的 Activity 以供 “StubActivity” 使用。之后,宿主应用通过拦截 ApplicationThread 类的 “handleLaunchActivity” 的 API 与 ActivityThread 类的回调,来恢复 “TargetActivity” 的真实 Intent。最后,插件应用的 Activity 成功运行。其它组件, Service 、 Content Provider 与 Broadcast Receiver 的过程类似。在
VAHunt
的检测方法中,将代理层的 Intent 封装行为看作一个有限状态机模型。
基于应用虚拟化的新型 Android 重打包恶意软件
在本节中,作者介绍了三年来跟踪基于应用虚拟化的恶意软件发展的深入研究。其研究结果揭示了这种新一代恶意软件的特点,并强调了安全界迫切需要设计具体的应对措施。
应用虚拟化技术的局限性,如缺乏权限分离和数据隔离,对恶意软件的开发影响有限。相反,网络犯罪分子将应用虚拟化作为一种新的恶意软件重打包方式。因此,恶意软件的开发和分发变得比以往更容易、更隐蔽。特别是,应用虚拟化重新包装的恶意软件显示出以下优势:
- 越过反病毒检测。应用虚拟化使恶意功能(作为不同的插件)与宿主应用脱钩,并且每个插件以加密形式存储在 “Asset” 目录下或动态下载。 只要宿主应用不涉及任何恶意行为,反病毒扫描程序就很难检测到它。
- 低开发成本。传统的重打包方法仍然依赖于大量逆向工程,攻击者需要修改原 app 的代码,修改包名,将 app 重打包为一个新的 app。相对地,应用虚拟化使得重打包简单许多:原 app 可以直接作为插件加载,不需要作任何修改。
- 灵活更新模块。通过应用虚拟化,恶意软件开发者能够方便地更新它们的模块而无需重新安装 app 。除了更新恶意代码外,许多恶意软件样本还开始添加更多有利可图的功能,例如广告促销和色情传播。
- 易于传播。移动开发者与用户对应用虚拟化有巨大需求,这一事实为基于应用虚拟化的恶意软件的传播提供了天然的覆盖面。例如,它们可以将自己伪装为一个双开应用,用户下载后,宿主应用动态地下载恶意插件,并在用户不知情的情况下执行。
下图展示了由 Antiy AVL 统计的基于应用虚拟化的良性与恶意 app 数量分布与新恶意样本出现数量分布。 VirtualApp
与 DroidPlugin
作为最受欢迎的两个开源应用虚拟化引擎,具有 92.4% 的占有率。基于 VirtualApp
的恶意软件数量大约是 基于 DroidPlugin
的 3 倍。从 2017 年第三季度,基于应用虚拟化的恶意软件数量有大幅增长。特别地,很多新的恶意软件已经从之前的重打包方式转为应用虚拟化。
VAHunt 概述
前文所述的研究揭示了两个关键结论。第一,在 Android 框架与插件应用之间的代理层,是应用虚拟化机制的核心,它的 Intent 封装行为展现出有限状态转换的特点。第二,为了掩盖恶意活动,恶意软件应用程序通常会在没有任何用户交互的情况下隐蔽地加载插件。这些见解促使作者抓住一个突出的恶意软件特征:虚拟化引擎加载插件的方式。即使插件的 APK 文件不是静态可见的,这个检测特征也是有效的。
文章提出一个名为 VAHunt
的双层的检测方法。首先,设计一个状态检测模型,以识别 APK 文件中是否含有应用虚拟化引擎(即代理层)。第二,进行数据流分析以提取出隐藏的、能够区分良性与恶意 app 的加载策略。下图示意了 VAHunt
的工作流程。在从字节码和清单文件中提取必要的信息后, VAHunt
识别出意图包装行为,并搜索桩组件以检测应用虚拟化引擎。之后, VAHunt
会追踪文件对象和路径 API ,以找到隐蔽插件安装的特征。
应用虚拟化引擎的检测
Activity 管理服务 (Activity Manager Service,AMS)管理每个 app 中的组件调度。如果一个 app 开启了一个 Activity 或一个 Service ,它首先报告给 AMS ,由 AMS 决定是否启动这个 Activity 或 Service 。由于 app 与 AMS 运行在不同的进程中,它们的进行间通信依赖于 Intent 。应用虚拟化的一个技术挑战是,如何维护插件 app 组件的生命周期,因为插件没有安装在 Android 系统中,而是在虚拟环境中运行。前文已经展示了宿主应用克服这个障碍的方法:它将插件 Intent 与宿主应用的预定义桩组件进行包装,欺骗 AMS 创建一个新的 Activity ;然后它拦截 Android 系统服务 API ,将插件 Intent 从桩组件中解开,最后启动插件的组件。
尽管有不同类型的应用虚拟化引擎在使用,如 VirtualApp
、 DroidPlugin
和自定义引擎,但是,用预定义的桩组件包装插件应用的 Intent 这一方法是通用的。 VAHunt
通过检测这一内在机制,确定虚拟化引擎的存在。
预处理
对于给定的 APK 文件,首先提取接下来的分析所必要的信息。文章使用 AAPT
提取清单文件,获取 app 的组件信息;使用 dexdump
从 DEX 文件中获取 Smali 代码;使用 FlowDroid
为每个 APK 文件建立控制流图。
Intent 状态机
文章将涉及到通过 Intent 进行数据传输的插件 Intent 包装行为看作是一个有限状态机 (finite state machine,FSM) 模型。 FSM 的状态代表 Intent 的状态,状态的转移由相关 API 导致。检查从每个 app 中提取的 Intent 状态机是否与参考 FSM 模式相匹配。
Intent 操作提取
为生成 Intent 状态机,需要提取所有 Intent 对象与操作。文章分析 Android app 的 Smali 代码,定位所有 “android/content/Intent” 类别的对象,并提取所有操作这些对象的 API 。具体而言,对 Intent 的操作包括创建、设置属性与转移。文章还记录了与 Intent 对象相关的组件和其他 Intent 。如果一个 Intent 的组件被设置为来自另一个 Intent 的组件名,这两个 Intent 就有了关联关系。
包装插件的 Intent
如前所述,宿主应用通过将 TargetActivity Intent 包装在预定义的 StubActivity Intent 中,将修改后的意图传输给 AMS 。当 AMS 创建 Activity 时,宿主应用会恢复插件的真实 Intent 。
下图给出了包装插件 Intent 的核心代码。第 3 行的 intent 变量是通过从方法 startActivityProcess()
的参数中得到的 targetIntent ,创建一个新的实例。 stubIntent 变量由第 4 行创建,由第 5 行赋予 StubActivity 类。 第 6 、 7 两行实现了对 targetIntent 的包装: targetIntent 的组件被添加至 stubIntent 。之后,被包装过的 stubIntent 通过 startActivity
传递至 AMS 。通过这种方法, AMS 认为启动 Activity 的请求由宿主应用(而不是插件)提出。
Intent 状态转移
默认情况下,一个 Intent 是以初始状态创建的。当添加属性时(例如 “setClass” 和 “addFlags”),它的状态会发生变化。对于与包装 Intent 相关的转换,targetIntent 和 stubIntent 的状态会随着其实例、类名、组件名和类型属性的变化而变化。生成 Intent 有限状态机的方法如下所述。如果一个语句创建了一个新的 Intent ,那么 FSM 的入口就是新 Intent 对象,在 Smali 代码中表示为一个寄存器。新 Intent 对象的状态被初始化为其对应的 Intent 状态机的初始状态。如果一条语句通过调用相关的 API 修改了 Intent 的属性,那么 Intent 状态就会改变。读取 Intent 的属性不会改变其状态。此外, VAHunt
还记录了相关信息,如它的类名、函数名、 Intent 操作等。当 Intent 对象通过调用 “startActivity” 或 “startService” 被发送到 AMS 时, Intent 状态机就结束了。
参考 FSM 模式匹配
在为每个 Intent 生成状态机后,检查它们是否与代表应用虚拟化的参考 FSM 模式匹配。如下图所示,这个参考 FSM 模式用以下三种方式抽象了包装插件 Intent 的行为。
- 从两个作为源头的 Intent 创建开始(如例中的 targetIntent 与 stubIntent ) ,并且 stubIntent 的最终状态启动了 Activity ,如下图 1 。
- stubIntent 的类型来自于 targetIntent ,如下图 2 。
- stubIntent 将其 Class 属性设置为一个新的 Class 名称,如下图 3 。
一旦找到了参考 FSM 模式,文章将从 stubIntent 的新 Class 名称开始(如下图 4 ),进行后向分片,以确认 stubIntent 来自一个桩组件,该组件已在宿主应用的清单文件中注册(如下图 5 )。通过这种方法,在成功匹配 Intent 状态机模型并定位预定义的桩组件后,就会报告应用虚拟化引擎的检测。
定位桩组件
组件切片
在获得最终的 Intent 状态机信息后,文章追溯对 stubIntent 的操作,找到真正的组件名。一般来说,组件名以 ComponentName 类型、 ClassName 类型或 String 类型存在。特定的 API 可以转换类型,比如使用 flattenToString
将 ComponentName 类型转换为 String 类型。开发者可以通过硬编码字符串的形式或调用 API 来设置 Intent 的组件属性,setComponent
和 setClassName
是两个最常用的 API ,用于设置组件名称或组件的类名。
因此,文章使用如下的步骤,寻找最终的组件名。首先,从有限状态机的信息中,定位 setComponent
或 setClassName
的参数。如果参数的值没有直接被硬编码,而是来自其它函数,则进行数据流分析,以找出真实的组件名。例如, setClassName
的参数可能来自于其它函数的返回值,这里则沿着调用图进行反向切片,寻找所有用来生成组件名称的 API 。对于存在 StringBuilder 或 StringBuffer 的字符串初始化和连接,计算并记录每次转换时产生的所有组件名称值。
桩组件匹配
最后,将通过切片提取的组件名称与预先定义的桩组件名称进行匹配。由于应用虚拟化引擎要获取桩组件来分配桩 Intent ,所以它必须管理当前可用的桩组件。在清单文件中,典型的桩组件名称由统一的 String 和不同的数字组成。例如下图,桩 Activity 的名称由 “com.lody.virtual.StubActivity$C” 与数字 0 组成。由于组件运行在不同的进程中,它们在清单文件中的 “android:process” 属性值不同,但其它属性值都是相同的。所有应用虚拟化平台均按照相似的方式命名其桩组件。因此,如果收集到的组件名称与清单文件匹配,则 VAHunt
可以确定检测到应用虚拟化引擎。
加载策略检测
对于基于应用虚拟化的恶意 app 检测,另一个问题是如何进一步区分恶意和良性 app 。传统的重打包恶意软件可以通过测量代码/界面布局的相似性来检测。与其不同,大多数基于应用虚拟化的恶意软件样本都会对其恶意插件进行加密以阻止静态分析。为了克服这一挑战,文章研究了恶意软件宿主应用如何隐蔽地加载插件,并在安装后隐藏它们。这就是所谓的“自我隐藏行为”,它已经被研究者们当作一种恶意指标。文章的目标是找到运行恶意插件而不引起怀疑的自我隐藏功能。
隐蔽加载策略
下图为应用虚拟化的插件加载流程。对于良性的应用,在定位到插件 APK 的路径后,插件会在用户同意的情况下,以正常的用户界面执行。然而,大多数基于应用虚拟化的恶意软件都是在没有任何提示框或用户点击的情况下隐蔽地加载插件。一些恶意软件变种甚至会在安装后立即隐藏插件。基于这些见解,我们在表格中总结了四个隐蔽加载特征。前两个特征描述了在哪里加载插件 APK ;接下来的两个特征总结了隐蔽加载和执行插件的行为。文章通过进行数据流分析寻找这四个特征。
特征 | 起始点 | 结束点 |
---|---|---|
从自定义路径加载插件 | 敏感路径 API | Intent 包装函数 |
从系统路径加载插件 | getInstalledPackages() Runtime().exec(“pm list packages”) |
onCreateView()/onViewCreated()/ inflate()/onCreateViewHolder()/setContentView() |
用户的同意 | installApp()/installPackage() | OnClick()/setOnItemClickListener() 清单文件中定义的 Launcher (“android.intent.category.LAUNCHER”) 应用生命周期 (onCreate(), onStart(), onReceive()) |
应用隐藏 | setComponentEnabled(comp, 2, 1) Window.addFlag(FLAG_NOT_TOUCH_MODAL) Window.setFlag(FLAG_NOT_TOUCH_MODAL) Window.requestFeature(FLAG_NOT_TOUCH_MODAL) |
宿主 APP 的组件 |
从自定义路径加载插件
在虚拟环境中加载插件的前提是要知道插件 APK 的位置。大多数基于应用虚拟化的恶意软件样本,比如自包含 (self-contained) 系统,都会提前了解自己的插件,并直接从自定义的位置加载插件,这其中包括所有 app 可以访问的公共存储(如 SDCard )和宿主应用的私有数据区域(如 Assets 子目录)。即使在运行时从网络下载插件,它们仍然存储在这些位置。宿主应用通过调用常见的 Android 路径 API 或从硬编码的 APK 路径中获取插件的 APK 路径。
文章分析与这些路径 API 相关的调用图来搜索对应的文件对象。得到文件对象后,进一步判断它们是否被虚拟化安装。一般来说,应用虚拟化为宿主应用提供了安装接口,以供安装插件。开发者只需调用常规的函数(如 installApp()/installPackage()
) 或重写应用虚拟化的 startActivity()
方法。但是,安装界面的函数名可以自定义为各种名称,所以不能通过安装界面的固定字符串来检测插件被应用虚拟化加载。文章使用的方法是,对文件对象进行数据流和调用图分析,直到追溯到 Intent 包装函数为止。
从系统路径加载插件
另一类攻击良性插件的恶意应用会诱导用户加载已经安装在系统上的良性应用。与自定义路径相比,系统路径是宿主应用没有写入权限的隔离存储空间。在获得 app 列表后,宿主应用会显示一个用户界面,供用户选择加载哪个 app 。之然后,宿主应用将插件 APK 从原应用的数据空间复制到自己的空间,并安装插件。一般情况下,开发者会使用 PackageManager
的 getInstalledPackages()
来获取已安装的应用。因此,文章将调用图中的 getInstalledPackages()
定位为起点,向前跟踪查找安装的包信息是否显示在 ListView
中。
用户的同意
通常情况下,良性的应用虚拟化应用需要用户在插件安装时点击安装按钮或弹出对话框来获取用户的同意。然而,大多数基于应用虚拟化的恶意软件样本从一开始就不需要任何用户交互就能加载其恶意插件。为了检测用户点击的不存在,文章首先定位插件安装界面,然后从界面向后追溯到应用的启动。 App 的启动界面通常为在清单文件中定义了 “android.intent.category.LAUNCHER” 属性的组件。 如果发现插件安装的开始是在组件生命周期中(如 Activity 的 onCreate()
) ,则不再跟踪 API 调用链。当用户同意加载插件时,他们经常会点击屏幕上的按钮或项目。所以,文章从插件安装中收集调用链,看它是否包含 onClick()
或 onItemClick()
。 如果调用链到达了应用的入口,但没有显示用户交互,那就说明宿主应用在隐蔽加载插件。
应用隐藏
安装后,良性应用会将其图标添加到主屏幕,以方便用户下次使用。但是,恶意应用往往会把自己隐藏起来,在后台运行,不显示任何图标或者 Activity 。因此,用户无法在屏幕上看到任何 GUI 。恶意软件可以通过修改清单文件,从启动器删除 app, 或调用 setComponentEnabledSetting()
以在运行时取消图标。恶意软件有两种方法隐藏 Activity :第一,在后台运行一个 Service;第二,将 Activity 的主界面设置为透明,并移除操作栏和窗口标题。文章对这些特殊 API 的参数进行数据流逆向分析,检测宿主应用是否对 API 中涉及的组件进行配置,以隐藏插件和自身。
检测规则
文章将隐蔽插件加载和应用隐藏行为视为恶意行为,因为它们违反了谷歌开发者内容政策。如前图,恶意宿主应用可能从自定义路径加载插件(①)或从系统路径加载良性应用并进行攻击(②)。考虑到 app 可能包含应用虚拟化引擎但不使用它,特征①与②仅用于确认宿主确实使用应用虚拟化引擎加载插件。基于此,多数恶意宿主应用静默加载恶意插件而不引起用户的注意(③)。文章提出一个名为 $slientInstall$ 的检测规则,以检测恶意加载策略。 $slientInstall$ 的表达式是: $(① OR ②) AND ③$ 。只要一个应用具有应用虚拟化引擎并符合 silentInstall , VAHunt
就会将其标注为基于应用虚拟化的恶意软件。
此外,作者还对恶意软件通过隐藏图标与 Activity 采取更隐蔽的行动感兴趣,如前图④。因此,作者又提出了 $slientInstall+$ ,表达式是: $slientInstall\ AND\ ④$ 。如果一个 app 隐藏了应用,但没有隐蔽安装插件,则会将其作为可疑样本。