本片文章主要翻译国外的一篇文章,原文在这里
摘要
Android插件化技术
是一种新型的应用程序级的
的虚拟化框架,它允许移动应用程序在不安装应用程序的情况下动态加载并启动其应用程序。该技术的初衷是为了解决热修复问题和减少APK的安装包大小而诞生的。使用该技术的应用主要是为了满足在同一个设备启上动同一个应用程序的多个实例的需求,比如同时登录两个Twitter账户,其中一个是个人账户,另外一个是企业级的。 Parallel Space是Googl play中使用插件化最为广泛的APP,它在Google Play的安装量已经达到了5000万次。
然而,据我们所知,永远都不要低估黑客对移动趋势的兴趣。在境外就有一款Android的流氓软件——“Dual-instance”,它可以动态的加载并启动官方的Twitter应用的APK文件,并可以劫持用户输入的密码等敏感信息,并用于钓鱼攻击。除此之外,在我们全面分析Android插件化技术的安全风险后,我们发现插件化存储的数据可以被恶意的宿主应用或其他插件所窃取。在Wildfire产品中,我们发现了119,898个样本在使用插件化技术。其中114,630个样子是恶意的或者灰色的。所以说,Android插件化技术正在成为普通Android应用 新的安全威胁。
我们在这里将揭示Android插件化的神秘面纱,并且在这里讲解底层攻击和安全问题。我们也提供了一个轻量级的防御方案,一个已经发布了,名字叫做"Plugin-Killer"的库,它可以防止Android应用被使用Android插件化技术的宿主应用所启动。一旦在你的项目中依赖该库,该库就可以检测到虚拟环境中的潜在威胁,并在被其他使用插件化的软件启动的时候,终止该启动
1介绍
Android插件化技术是应用程序级别的一项创新型技术,它的初衷主要是用于热更新,减少APK安装包的大小,以及解决65535方法数量的限制。从技术层面来说,Android插件化技术与传统意义上的动态加载还不一样,因为它在不需要声明任何特定的接口或组件的情况下,它就在可以加载或者启动整个应用程序(比如apk文件)。Android插件化技术的主要应用场景是,在同一个设备上启动多个应用的实例,也就是我们常说的"双开"。根据我们的观察,诞生Android插件化的的两个主要动机是:1是在社交APP中的多账户需求,2是在应用商店中即时启动应用程序。上面这两种应用场景均来自用户的需求。比如,一个用户既拥有Twitter的个人账户,也有一个拥有Twitter的企业账户,而又不想来回注销切换账户并重复登录,并且不想使用两个手机。Google Play中有一个很受欢迎的APP——"Parallel Space",就是采用的这项技术,它的安装量已经有5000万
次。
然后,正如我们所知,流氓软件的作者一直都在关注新技术,因此,黑客最近已经自行创建基于Android插件化技术的流氓软件。AVAST软件
报道了一个新发现的Android恶意软件——“Dual-instance ”就是基于Android插件化技术。在这个恶意软件中,开发者开发了一个假的Twitter应用程序(宿主应用程序),该应用程序可以动态加载Twitter的APK文件,而无需在设备中安装Twiiter应用程序。由于用户真实的与Twiiter进行交互;所以,从本质上来说,这是一种很好的网络钓鱼攻击。插件化技术允许恶意的宿主应用完全控制并hook插件应用,所以宿主应用就在Twitter的登录界面,劫持对应的"EditText"来实现窃取信息的目的。Android插件化技术一把双刃剑。尽管它为用户和开发人员带来了更多的便利性,开发和维护成本更低,但它也有许多安全问题的隐患,与使用该技术的数百万可疑恶意应用相比,它仅仅是沧海一粟。我们预测,黑客将会利用插件化技术来用为新的攻击媒介。由于攻击者不会损害APK文件的完整性,所以这个新的攻击媒介可以完全绕过现有的流氓查杀检测系统。
最严重的安全问题是,由于插件化技术的出现,Android系统中的信任环境发生变化。之前信任的基础是建立在Android系统没有漏洞这个假设上的。现在这个假设依然存在,但是由于Android应用完全可以在插件环境中运行,而不是在真正的Android系统,因此无法得到对环境的信任。一旦应用在插件环境中加载并启动,它就完全由被宿主应用控制了。在插件环境下运行官方的应用存在潜在风险,主要包括:1)宿主应用可以将官方应用作为插件而运行起来,这样存储在文件系统中的所有数据可能会有被盗取的风险;2)宿主应用可以盗取用户输入的信息,比如登录凭证。因此,官方应用正面临来自Android插件化技术的新的安全威胁。
在本文中,我们将深入研究Android插件化技术,并阐述底层攻击媒介涉及的安全问题。我们提供给了一种轻量级的防御方案,并发布了一个名为"Plugin-Killer"的库,它可以阻止Android应用使用Android插件化技术启动Android应用。一旦正常的Android应用集成这个库,它会自动检测是否运行在插件化环境,并在启动时,自动终止。
2 揭开插件化技术的神秘面纱
实现插件化有多种途径,比如DroidPlugin
,VirtualApp
和DynamicAPK
,他们在架构设计上有很多相似的地方。本地,我们将以DroidPlugin
作为例子来讲解。
下图描述了DroidPlugin内部是如何工作的。
它包含3个主要部分:Android系统框架、依赖DroidPlugin
SDK的宿主应用程序、作为单独的APK的插件。DroidPlugin
库中的基础组件被称之为Proxy Hook
。它位于插件与Android系统框架之间,负责拦截插件应用对Android系统API的调用。同时在发送到Android系统框架之前,拦截的调用将会被DroidPlugin
进行修改,比如修改传递的参数,这就是DroidPlugin
无需安装即可启动APK文件的魅力之所在。
2.1 虚拟环境
插件化技术启动应用的多个实例的原理机制是在Android系统框架的上面建立一个虚拟环境,这个虚拟环境对Android系统框架是透明的。所以插件可以绕过系统的限制。DroidPlugin
的神奇之处在于利用了Proxy Hook
来拦截来自插件应用的Android API调用,并修改他们的参数
通常Hooking系统是一个标准的中间人的角色。当我们谈论如何设计Hooking系统时,我们通常会需要解答两个问题:"如何Hook API"和"要Hook 哪个API"。第一个问题很简单,因为在Java Hook一个API是有标准答案的。Java提供了一个"动态代理"的设计模式,这种设计模式用来创建动态代理的实例。一些API是在Android框架内部定义的,所以我们需要用反射来Hook它们。第二个问题就比较复杂了。涉及到了Android系统框架的工作原理。基本上,DroidPlugin
Hook了API以及以下任务:
- 无需安装即可加载并启动插件APK
- 管理APP组件的生命周期
- 插件之间通信
- 管理插件,主要是下载与更新
这种技术听起来有点和DCL类似,动态代理加载技术允许APP在运行时记载和执行不属于其静态代码库中的代码。在安装包中并没有这些代码,而是在APP在运行时被加载的额外代码。DCL只能允许加载一小部分紧密依赖基本应用程序的代码。但是插件化技术更先进,因为它可以启动一个完整的APK文件,其中包含执行更复杂功能和更多与系统交互的代码。
通过Hook ClassLoader无需安装来启动插件,在Android系统中,APK或者Dex文件由ClassLoader加载。加载的文件和类存储在名为DexElement的ClassLoader对象中定义的列表。当需要加载一个类的时候,ClassLoader会扫描这个列表来找到匹配给定的类名。如果未能在当前的ClassLoader中找到对应的类,它会追溯到父ClassLoader。Android中有几种类型ClassLoader:BootClassLoader
用于加载系统类;PathClassLoader
用于加载应用程序类。但是所有这些都是从其基类BaseDexClassLoader派生出来的。
起初,系统会找到安装包的的路径,通常是data/app文件夹下,用来查找已安装应用的APK文件和相关资源。在启动步骤中,只有主机应用的APK文件解析并保存在DexElement列报表中。由于插件APK文件不在这个特殊的路径下,系统无法自动加载该文件。Android系统依赖ActivityThread这个对象来加载并启动一个新的Activity,代码如下:
java.lang.ClassLoader cl =
r.packageInfo.getClassLoader();
activity = mInstrumentation.newActivity(cl,
component.getClassName(), r.intent);
StrictMode.incrementExpectedActivityCount(activity.getClass());
r.intent.setExtrasClassLoader(cl);
所以,如果系统尝试使用当前ClassLoader加载在插件文件中定义的类,则无法找到任何内容,并且插件无法启动。而DroidPluging
会Hook这个ClassLoader,并将其解析的插件APK插入到它的DexElement
列表中。可以通过插件文件的路径作为参数调用loadDex函数(图3)。如果这个Hook步骤完成,插件中定义的类可以被搜索和启动。这个技巧有点类似Android中使用热修复的套路。
共享UID,Android的包管理在安装应用程序时会创建一个唯一的用户ID(UID)和组(GID),并且这些保留直到应用程序被卸载。对于插件应用来说,虽然它被动态加载并由Hook类加载器启动,但是从系统角度来看,它不被视为新应用程序。因此,所有插件应用程序与宿主应用程序共享相同的UID。不同的PID。由于所有的应用程序都是用相同的UID,所以Android系统自带的权限模型和数据隔离模型无法确保插件应用程序的安全性。
插桩组件 启动没有安装的插件APK仅仅是第一步,Droid
插件还需要维护插件应用中组件的生命周期。应用程序组件Activity
,Service
,Broadcast Receiver
,Content Provider
是构成一个移动应用的基本模块。与UI组件不同的是,应用程序组件的生命周期是有系统来管理的。这意味着使用这些组件,就必须和Android系统框架进行交互。正是由于插件化特殊的启动方式,DroidPlugin
必须使用一些其他的技巧。
我将启动插件应用中的一个新的activity来做为样例来讲解DroidPlugin
。Activities是用户与应用程序交互的入口点,也是用户在应用程序内或应用程序之间跳转的的核心。它经常在应用程序中被使用。图4说明了在Android平台上启动一个新的activity的整个流程。Android应用程序无法自行创建新的活动,他们需要使用系统提供的一个名为ActivityManagerService(或者叫AMS)来实现。AMS负责管理每个Activity的生命周期,例如创建activity和销毁关闭activity。
创建一个activity
是通过显示或者隐士的调用系统的startActivity
这个API。然后AMS将执行一些任务,例如pause Activity
,创建一个新的Activity
,然后将它们维持在一个堆栈中。AMS完成这些任务后,它会把控制权返还给新的Activity
,并通知ActivityThread
加载并执行新的Activity的代码。就像Activity
的里面的onCreate
函数回调一样
对于我们这篇文章,我们只关心3个函数startActivity
,AMS
,handleLauncherActivity
。上面这3个函数是Activity
与AMS
进行交互通信的地方。就像上面图5所示,函数startActivity
向AMS
发送一个Intent
,这个Intent
里面包含将要被用于创建Activity
的Class类。当前Activity
就是这样通知AMS
的。一旦AMS
为这个新建的Activity
创建了Context
,AMS
就会将这个Intent
转发到应用程序中的的ActivityThread
处理,并调用handleLaunchActivity
来处理意图,该函数将从Intent中
提取新的Activity
的类,然后进行加载和执行相应的代码。这就是系统开启一个新的Activity
的流程。
然而,我们如果按照这个套路来启动插件中的Activity,则会在AMS中启动失败。这是因为插件的Activity没有在宿主应用中的AndroidManifest
里面注册Activity。用户可能会启动任何一个插件应用,这样就会导致DroidPlugin
库无法预测它们的名称。因此,在安装之前就不可能将每个插件中的Activity注册到宿主应用中。DroidPlugin
的解决方案是预先在宿主应用里面的AndroidManifest
里面注册几个"桩",这些"桩"可以定义为stubActivity01
之类Actiivty。宿主应用作为一个代理,已经预先注册了AndroidManifest里面的所有组件(例如Service、Activity、Receiver和Content Provider)和权限。通常的做法是,宿主应用预先为每一种组件注册10个"桩"组件,同时也要注册所有的权限。因此,主机宿主应用和插件应用都是用这些预定义的"桩"组件
Hook AMS 来解决没有定义的应用程序组件 在运行时期间,DroidPlugin
将拦截从当前Activity发送给AMS的Intent,并且将其包装成一个新的intent,这个新的intent包含了"桩"Activity(图6所示)。通过修改intent,DroidPlugin可以欺骗AMS来创建"桩"Activity,这样就不会报错了。但是这并没有结束,因为AMS也会将intent转发给新的Activity,ActivityThread根据包装后的新的Intent内容来加载"桩"Activity的类。所以说,DroidPlugin也需要替换Intent,并将原始的Intent返回给ActivityThread。
为了将这个包装的intent发送到AMS,DroidPlugin
需要像我们刚刚提到的那样hookstartActivity
这个函数。这个函数的核心逻辑是获取AMS的Binder代理,然后就可以通过这个代理发送这个intent了。像图7代码所示,Activity由于与AMS通信非常麻烦,所以不可能每次都从系统来获取AMS的代理Binder,因此系统中保留了一个名为gDefault
的本地对象作为缓存。所有的框架都是通过这个对象来获取AMS的Binder代理。一旦DroidPlugin
hook 了这个缓存对象,它就可以提供这个hook过的binder代理实例给"startActivity"这个API进行调用,通过这个套了,它就可以在发送给AMS之前,拦截intent。使用同样的套路也可以hook handleLaunchActivity
以打开转发的intent
如果我们再研究其他组件(Service
,content Provider
,broadcast receiver
)的系统创建过程,我们就会发现他们的创建流程很类似。这是因为,系统需要使用相同的机制来和AMS进行交互。唯一的不同点是由当前的Activity来调用API。图8显示了通过调用调用startService
函数开启一个新的service的过程。DroidPlugin
采用相同的hook机制来拦截intent,并且将intent中的目标service替换为"桩"service
3、利用恶意软件破坏插件技术
插件化技术让Android应用程序用户体验更好,所以说它功能强大,并且非常实用。但是,我们想说的是这门新技术已经被恶意软件滥用了。在下面的文章中,我们将展示一些统计数据和真实的恶意软件样本来证明它被滥用了。
我们统计了我们数据库中的所有应用程序,我们发现大约有12万个应用使用了DroidPlugin库或者其定制版本。其中有有5268是良性的,但是其中有114630个是恶意的。(在我们的数据库中,我们根据动态分析模块会发现应用的可疑行为,并将其标记为恶意)。
而且我们测量了自2015年7月开始,DroidPlugin 支持的恶软件样本趋势。从下图我们发现,截止到2016年1月,恶意样本的数量是非常少的,但是在2016年1季度出现了大幅增长,随后又在第二季度再次增长。在2016年的最后几个月,新样本的数量增加了大约1000到2000
为什么插件技术被滥用? 根据我们分析过的样本,我们认为有三个原因:
- 1、更新恶意软件,是不需要root 手机的,一旦这些攻击者诱导用户去安装他们的恶意软件到手机上,他们就可以利用插件化技术安装新的恶意软件或者更新现有的恶意软件,而且还不需要root手机。这样恶意软件就可以把核心的恶意代码放到单独的插件APK中,当需要更新的时候,只需从远程服务器下载最新的APK文件进行替换即可。通过这样操作,当用户去检测的时候,就大幅降低被检测到的风险,而且整个过程,不需要和用户进行交互。
- 2、方便逃避静态检测。由于恶意软件的核心代码位于远程服务器并且是动态获取的,这就导致了宿主应用不包含任何可疑的API代码,所以它很难被静态检测出来。如果不进行动态检测,是很难从宿主应用中找到恶意行为的证据
- 3、目前最普遍,泛滥的恶意程序是2次打包,但是随着检测技术的发展,这种方式的机会越来越少小了。由于插件化技术可以实现不用打包,所以它可以作为发起网络钓鱼攻击的另一种方式。攻击者可以将经过身份验证的APK作为插件来加载,而且不用进行任何修改,然后再将恶意代码放入另一个插件中,这样既可以窃取到用户输入信息或者其他凭证信息。
恶意软件案例研究:PluginPlathom 我们发现第一个利用DroidPlugin
库作为攻击目标的恶意软件被我们命名为PluginPhathom
。在2016年11月,在alo Alto Networks
研究中心,我们发布了一篇博客,该博客展示了我们对PluginPhathom
的样本分析,并且在媒体,比如SC Media
,SecurityWeek
和BleepingComputer
中被转载了。PluginPhathom
的攻击很简单,主要包括拍照、截图、录制音频,以及截取和发送短信。它的独特之处在于,PluginPhathom
每个恶意功能模块化,并将其放入某个插件中,然后利用宿主应用在运行时来动态加载这些恶意插件并进行攻击
PluginPhathom
家族,自2016年7月被我们首次发现至今一直在不断的更新。攻击者通过重构将每个恶意功能模块化,然后将其放入每个插件APK中,最后他们再利用DroidPlugin
库来集成。它一共包含9个插件。比如,通讯录插件,可以窃取用户的联系人信息;文件插件可以窃取本地的文件。它还有一个叫做update的插件,它用来去远程服务器下载最新的插件APK文件,然后进行更新这个模块。
恶意软件案例研究:双开 插件化技术被滥用的另一个场景是,启动多个正版而且受害的APP的实例(图11)。Avast Threat Intelligence Team
首次发现在使用Twitter的双开恶意软件。其中有这样的一个样本——DualTwitter,当用户想同时在一个设备上登陆多个Twitter账户的时候,用户就可以下载这个APP,但是它的成本是高额的。因为用户一旦使用这个应用登陆Twitter,该恶意软件就记录用户键盘输入并窃取用户的凭证信息。DualTwitter
使用的是VirtualApp
库(另外一种插件化技术),在不需要重新打包的情况下,可以直接加载多个twitter应用程序实例。同样,我们也发现了类似的Instagram的恶意软件。
4、解决方案
滥用插件化技术趋势现在已经愈演愈烈了,为了防止普通应用程序被这类新型的技术攻击,我们提出了一个轻量级的解决方案。我们叫他Plugin-Killer。它是一个Android SDK,它可以帮助普通应用检测其他APK文件是否在插件化技术提供的虚拟环境中运行。同时我们也开源了我们的SDK,可以直接从Github进行下载
4.1、潜在的解决方案
由于我们是第一个发现插件化被滥用的,所以目前还没有一个科学的解决方案来解决问题。我们将分享我们对潜在解决方案的思考,并且阐述我们为什么选择Plugin-Killer作为我们最佳的解决方案
禁止插件化技术。很明显,Android的插件化技术违背了移动安全的原则,并导致了多重的安全隐患。禁止这个功能是一个简单的解决方案,这样我们的生态系统就安全了。由于在日常生活中灵性的使用插件化技术有着大量的需求,所以直接禁止插件化技术是不现实的。而且,由于没有严格的标准,所以全面禁止插件化技术同样也是不现实的
从检测使用插件化技术的恶意软件。一些安全产品(比如防火墙或者杀毒软件)可能会在用户安装此类恶意软件时发出警告
但是我们却很难区分恶意软件和普通软件,这是因为恶意软件在开始使用APP使用的是同一个SDK的实例,而且他们的行为基本一致。这样就会导致检测系统有很高的误报率通过Android 平台来解决插件化的问题。最好的解决方案就是让Android系统支持一种安全的机制来启动一个应用的多个实例,我们坚信这是插件化攻击的最终解决方案。在这个解决方案中,Android系统可以扩展现有的Android安全体制一隔离同一个应用的多个实例,并且设计一套可配置的交互协议,这样当用户想启动实例的时候,就可以直接启动了。如果这样做的话,用户就不需要在第三方APP上进行操作,替代的是直接在Android系统上进行操作了,这样Android 系统就直接扮演了宿主的角色了。这个解决方案看起来来和
AFrame
方案很相近,AFrame
方案就是将主机应用程序与第三方库相隔离。但是这种解决方案需要发布和更新所有的移动射击,所以它肯定需要花费比较长的时间去设计(比如针对不同的Android版本的兼容性问题)。所以一个轻量级的并且更快部署的解决方案是当下最需要的
4.2我们的解决方案:插件杀手
我们遇到一个问题就是:真实的应用程序不知道作为一个插件而被启动。所以我们的解决方案主要就是要解决上面的这个问题。换句话说,像DualTwitter
这样的软件之所以能够钓鱼Twitter
的原因是,Twitter它作为原始的APK文件,它并不知道它作为插件而被启动。所以我们提出一个检测方法,通过这个检测方法,Android应用程序可以检测它是否正在运行在插件化技术创建的虚拟环境中,并且提供一个可以终止运行的选项。基于我们队潜在解决方案的分析,我们的解决方案既不用禁止插件化技术,也不用对Andorid系统进行任何修改。
Plugin-Killer 使用场景
我们的解决方案——Plugin-Killer是一种防御机制,可提供多种方式来检测虚拟环境,用来防止潜在威胁。如果一个应用程序不想成为新型钓鱼的受害者,可以直接将我们的解决方案集成他们APP中,集成方式既可以是以库的形式,也可以是sdk的形式。就像我们刚刚分析的,普通应用被用作插件在第三方程序中启动是非常危险的。我们提供的PluginKille
来帮助普通应用的开发者防止他们的应用被作为插件而启动。
良性应用是我们库的客户,他们希望在被作为插件启动的时候,启动检测潜在威胁。比如,Twtter
的合法应用可以将PluginKiller
库嵌集成到它们的APK中,当DualTwitter
恶意软件将其作为插件启动的时候,Twitter
就可以检测出来这些潜在风险。如果图12所示,Twiiter
应用程序只需要在其代码的初始位置添加3行代码即可。比如在MainActivity
的onCreate
函数里面添加。
这两个红色标记的函数,由我们PluginKiller库来实现:isLoadedAsPlugin函数的目的是返回一个布尔值来告诉应用程序是否运行在虚拟空间;TerminatesApp函数是我们库中的实现的另外一个API,通过它可以直接终止应用程序或者提供用户以传统的方式来启动这个应用程序。
对于熟悉网络安全的人员而言,他们很自然的将点击劫持(clickjacking attack)
攻击与双开攻击进行比较。在点击劫持
攻击中,受害人的网页可能会加载到恶意网页的iframe中。为了对抗攻击,当受害人服务器生成网页时,通常会添加一段JavaScript代码来检测页面是否在主框架或子框架内部加载。这种被称之为FrameBusting
的技术被大多数流行网站所使用。我们认为双开恶意软件与点击劫持
存在很多相同点。如果在宿主应用创建的虚拟环境中,移动应用就崩溃,就可以有效的抵抗这种新型攻击。
Plugin-Killer 的优势
除了我们的解决方案是唯一为用户和应用开发者提供退出选项的解决方案,除此之外,我们的解决方案还有以下优点:
- 目前市面上仅有的解决方案。我们是第一个注意到恶意软件大规模使用插件化技术的公司。PluginKiller是可以帮助良性应用抵抗这种新型攻击的最佳解决方案
- 非常轻,我们的解决方案不需要对移动系统进行任何修改,它可以在任何版本上的Android系统上运行。移动应用程序除了添加上面的3行代码,不需要对其代码进行任何修改
- 良好的兼容性。所有Android版本都支持插件化技术,同样的,我们的解决方案也都支持这些所有的版本。这是因为我们用于检测虚拟环境的方法仅仅依赖Android版本通用的API,所以我们的解决方案与所有Android版本兼容
- 操作简洁。
Plugin-Killer
库很小,因为它只包含很少的函数调用和很少的检测逻辑
4.3、如何检测虚拟环境
为了抵抗被不可信的宿主应用动态加载,我们必须寻找出一个检测方法,这个检测方法可以检测移动应用是否被作为插件来加载。据我们观察,尽管Android插件化技术创建一个虚拟环境来加载和启动插件,但虚拟环境仍然与传统方式所创建的虚拟环境存在很多差异。
在我们的库中,我们系统的列举了所有能区别差异的方法,包括:
4.3.1检测不匹配的AndroidManifest
每一个移动应用的根目录都必须有一个AndroidManifest.xml文件。AndroidManifest.xml文件向Android系统提供有关应用程序的基本信息,系统运行任何应用程序的代码之前,必须提前获取该信息。在安装阶段,系统将解析AndroidManifest.xml文件,并记录其中定义的信息(比如声明的权限,组件包括Activity、Service)。但是由于插件应用从来没有被安装过,并且由主机应用程序动态启动。插件应用和宿主应用的清单会有很多不匹配的地方。我们的库,尝试通过发现插件应用和宿主应用的差异来检测是否是虚拟环境。我们罗列了AndroidManifest中定义的可用于检测差异的项目:
权限 由于用户可能会将任何一个APK作为插件二载入,所以宿主应用通常会声明大部分Android权限(例如DroidPulgin会声明125个Android权限)。但是几乎所有的宿主应用和库都绝对不会遗漏插件中需要的权限的。所以,插件被授予的权限绝对大于他们在AndroidManifest.xml里面声明的权限。我们就可以利用PackageManager从宿主应用程序中获取授予的权限,然后尝试访问哪些没有在插件的AndroidManifest.xml里面声明的权限限制的资源,如果可以访问,说明是虚拟环境,如果不能访问则说明正常。
包名 所有安装到系统中的Android应用程序都有一个独特的包名,用于在这个设备上的唯一标示符,标示一个应用程序。由于插件还没有被安装,所以可以检测其包名是否已经注册到系统中,如果还没有注册说明,在插件环境中。
应用组件名 正如我们所说的,
DroidPlugin
利用"桩"组件来欺骗AMS,这样就可以创建一个未在manifest文件中注册的文件。所以说,AMS记录的是都是"桩"组件的信息,而不是实际调用的"插件组件"的信息。举个列子,如果我们调用ActivityManager
的getRunningServices
这个API来获取正在运行的Service信息,假设正在使用的是DroidPlugin,则存在AMS中的"桩"Service就可能是stub.ServiceStubStubP08P00
- 检测宿主应用的运行时信息 鉴于插件是由宿主应用启动的,我们发现插件的运行时信息和由系统启动的运行时信息略有不同。所以。所以说,我们就可以根据运行时信息来分区是否处于虚拟环境中
进程信息 PID(进程ID),它是一直保持不断变化的。但是与PID的不同的是UID是保持不变的,系统在每个应用安装的时候会给每个应用分配一个UID,只要没有重新安装,这个UID就不变发生改变。所以说,对于每个应用程序来说,它的UID应该是唯一的,除非应用程序显示请求说要与另外一个应用程序共享一个userid。由于 插件 APK并没有安装到系统,所以 插件 程序没有唯一的UID。即便宿主应用可以向DrroidPlugin那样,fork一个新的进程来启动它,这样就会导致 插件 进程和 宿主应用 所在进程共享不同的PID,但是它们的UID却是一样的。如果我们要检索正在运行的应用程序的进程信息,我们需要申请
GET Task
权限(在Lollipop后弃用),我们可以使用ActivityManager
类的getRunningAppProcesses
这个API来获取所有的正在运行的进程信息,它的返回值是一个list。举个例子,在DroidPlugin
库的进程名称类似于{host app pkg name}:PluginP02
。内部存储信息 Andorid文件系统会在APP安装的时候,将文件保存在内部存储的指定目录下,每一个应用程序都有一个指定的文件目录,对应的文件目录就是其包名。动态启动的插件应用的目录不在其包名下,而是在宿主的应用的包名下。举个例子说明弄下,系统通过PackageManager
来分配应用程序包目录的,所以我们可以通过
PackageManager来获取应用程序的
dataDir路径。正常情况下一个应用程序的包路径是
'/data/data /{pkg name}'。由于
DroidPlugin要分离不同的插件保存的数据,所以每个插件的路径是宿主应用的子目录作为
dataDir。例如
DroidPlugin中插件的
dataDir是
/data/data/{host app pkg name}/Plugin/{plugin pkg name}/data/`。
4.3.2检测APP的组件
对于Android应用来说,应用组件是其必不可少的组成部分。一个这些应用组件的独特之处在于,任何应用都可以启动其他应用组件。Android系统的异步消息是通过intent来实现,在运行时intent将各个组件进行相互绑定。对于Android系统而言,动态启动一个插件应用和启动其他普通应用是无差别的。为了同时支持插件之间交互和其他应该交互,宿主应用需要去自定义一套intent通信协议。通过自定义的通信协议,来差异化跳转。
已启动APP中的Activity和Service的数量:
App必须先在AndroidManifest.xml里面注册Activity和Service,只有这样系统才能访问对应的Activity和Service。由于插件应用还没有被安装,所以宿主必须先预注册一定数目的"桩"组件,只有这样才能欺骗AMS。在DroidPlugin中,它先预定义了10个Activity和10个Service。如果这个插件引用需要启动超过10个Service,就会产生冲突。发送静态广播:
Android应用可以向Android系统/其他应用发送braodcast receiver,也可以接收由Android系统/其他应用发出来的braodcast receiver。这种方式类似于发布/订阅的设计模式。应用可以注册去接收特殊的广播。发送广播的手,系统会根据当时已经注册的广播接收者来寻找合适的接收者,然后进行广播转发。一个APP即可以静态注册广播,也可以动态的注册广播。静态广播是在应用安装的时候注册的。在安装应用程序时,系统在解析AndroidManifest.xml的时候,会根据里面"intent- filter"标签来定义一个广播接收者。应用也可以在代码中调用registerReceiver
这个函数来动态注册广播接收者,动态广播接收者是与应用的生命周期息息相关。
由于插件应用程序从来没有被安装过,所以AndroidManifest.xml里面定义的静态广播接收者不会被系统解析。所以DroidPlugin
将解析插件AndroidManifest.xml里面的定义的静态广播接收者,并在运行时在代码中动态去注册它们。由于动态广播接收者和静态广播接收者在拦截广播方面是相同的,所以插件不会特意去区分他们的不同。但是我们在运行时操纵接收者,还是能检测出变化的。
举个例子说明一下,我们在插件的AndroidManifest里面注册一个静态广播接受者,然后我们在运行时注销所有的广播接收器(通过使用unregisterReceiver
这个API)。在真实的环境下,静态接收者还是有效的。但是在DroidPlugin
创建的虚拟环境中,静态和动态的所有广播接收者都不可用了。所以,我们可以发送一个可以被静态广播接收者拦截的广播,我们就可以检测当前环境是否是虚拟环境了在运行时修改应用程序组件属性
我们也可以另一种方式来检测,即在运行时修改应该程序中组件的属性来检测。当应用程序在AndroidManifest里面注册一个组件的时候,可以注册一些属性,就像组件名,默认状态或者标签。其中有一个特殊性,就是enable,系统用这个属性来判断在APP启动的时候是否允许实例化。在正常的启动流程中,应用程序通过调用setComponntEnabledSetting
和应用组件名称
来修改运行时启动组件的enable属性。如果是enabling
,这个组件将会保持可用。
对DroidPlugin
而言,他们有一个硬伤就是——在虚拟环境中,他们无法实例化一个被禁用的组件。如图14所示,我们在插件的清单文件中定义了广播接收器,并将其的启动属性默认设置为false。然后,我们在运行时再调用setComponentEnabledSetting
这个API来启动这个接收器。如果它以插件的形式运行,则我们在运行时的启动行为将不起作用,而且该接收器仍处于禁用状态。所以,我们可以轻易的发送一个匹配这个接收器的广播来检查它是否被弃用,并把它作为这个应用是否被作为插件的证据。
- 检测其他插件应用程序的痕迹:
共享native组件,一些native组件在同一个应用内实例之间共享内部信息。即便是插件还没有运行,宿主应用仍然会保留共享信息,这样就会导致一些数据残留实例。举个例子说一下,WebVeiw,一个用C++实现的web容器,它会记录浏览状态到本地数据库中,而且这些记录还是在同一个应用实例间共享。如果应用被双开的情况下,宿主应用和插件应用会被看成一个应用,因此它们共享一个相同的UID。因此,如果用户在插件中的WebView登录过某个网站,其他插件的用户就可以再次访问这个网站来来检测登录状态,遮掩就可以确认自身是否已经在虚拟环境中了。
4.4 PluginKiller的评估
为了测试PluginKiller
能够检测处各种插件化技术产生的不同的虚拟环境,我们拿了很多主流的开源的宿主应用来进行评估。这并不代表图中所示的流行宿主应用(Parallel Space
,Gemini
等)是流浪软件。我们用他们来进行测试因为当前没有一套插件化技术的标准。攻击者,可以对其代码进行逆向,并使用类似的方法来实现自己的恶意代码,甚至会提出一种更新的方法来实现插件化技术。尽管我们现在只发现一些恶意软件只集成了DroidPlugin
和VirtualCore
这两个插件化库,但是我们仍想展示PluginKiller
是极有可能检测出恶意软件创建的虚拟环境。
评估结果如图15所示。 每一行代表我们前面讨论的测试用例的检测结果,每一列都是不同的虚拟环境。 我们构建一个仅嵌入PluginKiller库的虚拟APK文件,并使用不同类型的宿主应用程序将其作为插件启动以获取检测结果。 评估显示PluginKiller可以检测到所有当前的虚拟环境。