亚历山大-巴特尔(Alexandre Bartel)
摘要
近年来,移动设备,如智能手机,已经以指数级的速度发展。在这些设备上运行的最常用的系统是安卓软件栈,几乎占了全世界智能手机市场份额的80%。该系统运行用户从应用市场下载的安卓应用程序。该系统被称为基于权限的系统,因为它通过检查应用程序是否有必要的权限来限制对受保护资源的访问。用户使用他们设备上的应用程序存储和操作个人信息,如联系人名单或图片,并相信他们的数据是安全的。分析应用程序和它们所运行的系统是评估数据是否得到良好保护的客观方法。
在这篇论文中,我们旨在从安全的角度分析Android应用程序,并回答以下具有挑战性的问题: 如何分析安卓应用? 安卓应用的权限是否得到很好的定义?应用程序能否泄露受保护的数据?动态分析如何能补充静态分析?为了回答这些问题,我们的论文围绕四个目标展开。
第一个目标是用静态分析工具来分析Android应用程序。我们面临的挑战是,Android应用程序是用Dalvik字节码打包的,在许多方面与Java字节码不同。我们开发了Dexpler,这是一个将Dalvik字节码转化为Jimple的工具,Jimple是Soot的一种可理解的格式,是最常用的基于Java程序的静态分析框架之一。有了Dexpler,我们现在可以分析Android应用程序了。
第二个目标是检查开发者是否给他们开发的安卓应用提供了太多的权限。 减少权限的数量可以减少恶意用户利用应用程序的攻击面。 我们分析应用程序的代码,以检查它们真正需要哪些权限。 这需要深入分析安卓框架,提取API方法(安卓应用程序调用)和所需权限之间的映射。我们提出了一种类似于安徒生的领域敏感方法,使用新的领域特定的优化来从安卓框架中提取映射。
权限能保护敏感数据。然而,拥有正确权限访问数据的应用程序可能会泄露数据。例如,恶意软件或与积极的广告库打包的应用程序就是这种情况。第三个目标是静态分析Android应用程序,以检测这种泄漏。Android应用程序与传统的Java应用程序不同。最重要的区别之一是,Android应用程序是由组件组成的。分析Android应用程序以发现泄漏,需要将一起通信的组件联系起来,并对每个组件进行建模。我们开发了IccTA来检测隐私泄漏。它在代码层面连接组件,以执行组件间和应用间的数据流分析。
静态分析Android应用程序可以发现安全问题,如GPS坐标从设备中泄露出来。然而,静态分析并不直接在用户的设备上运行,因此没有考虑到设备的上下文。本论文的最后一个目标是深入了解动态方法如何补充静态分析。我们首次提出了一个工具链,用于动态检测体内的Android应用,即直接在设备上检测。我们提出了两个对应用程序进行检测的用例,以表明动态方法是可行的,它们可以利用静态分析的结果,并且从安全和隐私的角度来看,它们对用户是有益的。其中一个用例是一个内粒度的权限系统原型,使用户能够随意禁用或启用应用程序的权限。
这四个贡献已经通过严格的实验得到了尽可能完整的验证。
通过这篇论文,我们提供了使用静态分析来分析安卓应用的解决方案,检查应用的权限集,在安卓应用中ind私有数据泄漏,以及分析基于权限的框架。通过分析出错的地方,我们可以改善移动应用的安全和隐私。
鸣谢
展开全文
首先,我要感谢我的导师,Yves Le Traon。能够成为他的SERVAL团队中的第一个博士生,我感到非常荣幸。我很感谢他花时间与我讨论新的想法和贡献。当遇到技术问题时,他坚持让我退一步,看一看全局,这确实有帮助。Yves告诉我好的软件工程研究是如何完成的。
然后,我想感谢我的日常顾问,雅克-克莱因。是他启动了第一批安卓研究项目,他把安卓设备带到我的办公桌上,引导我找到了正确的研究问题。他总是心情很好,和他一起工作真的很愉快。他教会了我如何集中思想,如何在抽象的层面上进行推理,以清楚地提出新的想法。
Martin Monperrus是我在里尔大学的远程顾问。我想感谢他对我工作的建设性和有洞察力的反馈,以及关于论文写作和时间管理的伟大建议。他总是激励着我努力工作,以达到顶级会议和期刊的水平。
在博士期间,我与卢森堡大学的许多人进行了非常有趣的技术讨论,特别是与凯文-阿利克斯(Kevin Allix)、特加文德-弗朗索瓦-迪塞斯-比桑德(Tegawendé François D'Assise Bissyande)和李力(Li Li),我特别要感谢他在IccTA方面的出色工作。
我有机会与美国宾夕法尼亚大学和德国达姆施塔特技术大学的研究人员合作并访问他们。 我想感谢宾夕法尼亚大学的Damien Octeau和Patrick McDaniel,他们发起并领导了我们在EPICC上的成功合作。我还要感谢达姆施塔特技术大学的Eric Bodden、Steven Arzt和Siegfried Rasthofer,感谢他们在FlowDroid上的出色工作以及我们在污点分析上的富有成效的合作。
我想感谢Lionel Briand,他让我有幸成为论文答辩的主席。我感谢我的口头论文答辩委员会成员,Andreas Zeller,Benjamin Livshits和Eric Bodden,感谢他们的时间和有见地的问题。对于这篇论文,我还要感谢我的读者Jacques Klein, Yves Le Traon, Martin Monperrus, Li Li和Panuwat Trairatphisan,感谢他们的时间、兴趣和有益的评论。
我要感谢我的博士生同事和朋友,他们让我在卢森堡大学度过了愉快的时光。我感谢Panuwat Trairatphisan、Wei Dou、Lamia Bekkour、Li Li、Phu-Hong Nguyen和Rustam Mazitov的难忘的羽毛球和网球比赛。我感谢凯文-阿利克斯(Kevin Allix),感谢我们在非常广泛的主题上进行的有趣讨论。更广泛地说,我感谢所有我有机会见到的SERVAL团队和SnT的人,特别是Christopher Henard, Donia El Kateb, Anestis Tsakmalis, Iram Rubab, Jabier Martinez, Thomas Hartmann, Jorge Meira, and Grégory Nain。
我感谢卢森堡国家研究基金(FNR),它是我的资金来源,使我的博士工作在三年半的时间里成为可能。
最后,我想感谢我的家人,感谢他们的鼓励和爱。 感谢我的父母,他们在我学习期间一直支持我。最重要的是感谢我的妻子克莱尔,她在博士期间对我的支持,我非常感激。谢谢你。
亚历山大-巴特尔
卢森堡大学
2014年9月
第一章
引言
近年来,移动设备,如智能手机,已经以指数级的速度发展。在这些设备上运行的最常用的系统是安卓软件堆栈,几乎占了全世界智能手机市场份额的80%,然而,随着普及,为安卓系统定制的攻击也越来越多。
在这篇论文中,我们声称静态分析可以帮助防止这种攻击。我们使用静态分析从一个基于权限的框架1中提取权限。这使得我们能够检查在基于权限的框架之上运行的应用程序是否遵守了最小权限原则,并且没有声明太多的权限。此外,为了评估资源和用户数据是否得到了很好的保护,我们对应用程序进行静态分析,以发现可能表明恶意行为的可疑泄漏。最后,我们表明,从框架的静态分析中提取的信息可以在运行时利用,以从隐私的角度加固应用程序。例如,我们实施了一个用户驱动的动态政策执行,使用户能够启用或禁用任何应用程序的权限。
为了更好地理解Android权限系统,让我们回到访问控制的前提下。事实上,自从多个用户可以访问同一台计算机以来,人们就需要使用访问控制来保护他们的数据不受其他用户的影响,不管他们是恶意的还是笨拙的。在这一章中,我们介绍了访问控制的概念,并激励我们选择对一个名为Android的基于权限的系统进行分析。第1.1节和第1.2节介绍了访问控制的概念,从第一台引入密码的计算机到最新的基于权限的系统Android。
第1.3节说明了基于权限的系统的缺点。第1.4节解释了我们在研究和分析基于权限的系统时所面临的挑战。最后,第1.5节列出了这项工作的贡献。
1.1 访问控制的简史
最早允许多个用户同时工作的计算机之一是1963年的兼容时间共享系统(CTSS)[32]。 每个用户都可以访问计算机,并可以通过一个终端发送命令给它,还可以得到一个个人目录来存储文件。只要有一个以上的用户在同一台机器上存储信息,就会出现关于隐私、安全和保障的问题。任何人都应该能够看到谁创建了一个ILE吗?如果有人错误地删除了另一个用户的ile怎么办?是否应该允许任何人看到任何用户的任何ile?
在20世纪60年代,CTSS的用户担心其他用户会修改他们的档案。
为了解决这个问题,CTSS可能是第一个引入这个想法的操作系统,密码被用来验证用户。一旦用户被系统认证,她将只被授权访问他/她的文件,而不是任何其他用户的文件[32, 112, 133]。该系统确保iles只被授权用户访问。换句话说,它保护了系统的资源,防止恶意或笨拙的用户访问或篡改它们。 一个用户可以与其他用户共享ile,允许他们在自己的目录中放置一个称为link的特殊ile,引用共享ile。这是访问控制的第一种方法。 图1.1表示了一个访问控制的通用模型。 每当一个主体(如用户)试图访问一个对象(如链接)时,系统就会检查访问控制策略以允许或拒绝该访问。
1965年推出的CTSS的后继者,称为MULTICS(多路信息和计算服务)[33, 112],从一开始就以信息保护为目的。与CTSS一样,每个用户都有一个标识符,并使用密码对系统进行验证。 然而,MULTICS引入了用户组的概念和访问控制列表(ACL)的概念。ACL是一个可调整的用户列表,这些用户被允许读取、写入、执行或追加一个对象(例如,一个程序)。对象被组织在一个由目录组成的单一层次结构树中。 如果一个用户有修改目录的权限,她可以修改该目录下所有对象的ACL。 由于用户可以改变属于他们的对象的访问权限,所以访问控制被称为自由决定的访问控制(DAC)。
受MULTICS的启发,UNIX [108]在20世纪60年代末70年代初出现。在UNIX中,ILE-系统是一棵由ILE和目录组成的树。每个ILE和目录都属于一个用户和一个组。此外,每个ILE和目录都有权限位,允许所有者为拥有该ILE的用户、该ILE所属的组或其他用户激活或取消读、写或执行的权限。
在接下来的几年里,计算机已经在能够负担得起的公司和军队中普及和使用。这引起了人们对计算机安全,特别是访问控制的兴趣。人们努力将访问控制正式化为数学模型。Bell-LaPadula访问控制模型[16]是在1973年设计的,重点是保密性,这在军事应用中特别有用。在这个模型中,主体(如用户)可以观察、改变或修改对象(如档案)。为主体和客体分配一对,决定类别和分类,称为安全级别(例如,类别 "密码学 "和分类 "未分类",类别 "核 "和分类 "秘密",类别 "化学 "和分类 "最高机密",......)。 实施Bell-LaPadula模型的系统必须确保一些属性是成立的(例如,一个分类为 "秘密 "的用户不能阅读 "最高机密 "的文件)。这些访问规则是由管理员在一个集中的政策中确定的,用户不能修改或绕过。这种访问控制被称为强制性访问控制(MAC)。
Bell-LaPadula侧重于保密性,因此只限制对数据的访问,但对保护数据的损坏没有任何作用。简而言之,它不能确保数据的完整性。1977年,另一个模型被设计来解决这个问题:比巴模型[21]。这个模型确保一个主体不能破坏数据,其安全级别高于主体的级别。
在20世纪70年代末、80年代初,最初的UNIX系统诞生了许多基于相同理念的版本。最著名的系统可能是GNU/Linux、基于BSD和MAC X的操作系统。它们的基本访问控制类型是DAC,但也存在MAC的实现,例如SELinux[121]是Linux内核的MAC扩展。
1.2 大众化的访问控制
确定一个系统的安全策略(即所有访问控制规则的集合)是一项复杂的任务,需要对系统的访问控制模型有深入的了解。 今天,计算机无处不在,新手和专家都在使用。确定访问控制策略不是一件容易的事,甚至可能被那些只想让系统工作而不关心安全问题的新手用户所忽视。
在欧洲,手机的人口覆盖率几乎达到100%[73]。 它们已经从运行一个主要目的是打电话和接电话的系统的设备发展到了完全边缘化的计算机。这些设备能够连接到互联网,观看高分辨率的电影和玩最新的3D游戏,并与电话应用捆绑在一起。这些电脑,或称智能手机,运行用户从互联网上的应用市场下载的应用程序。对于智能手机来说,应用程序在对系统资源进行操作时(例如,GPS、互联网访问......),通常受到开发商为其规定的一系列权限的限制。
当下载一个应用程序时,用户可以看到该应用程序所需的权限列表,并可以决定允许该应用程序被授予列表中的所有权限并安装该应用程序,或者选择根本不安装该应用程序。这种访问控制的权限模型使用户处于管理员的地位:他/她必须在每次安装新的应用程序时更新设备的访问控制策略。 我们称这种系统为基于权限的系统。我们在这项工作中研究的基于权限的系统是Android。
1.3 基于权限的系统分析的动机
下面三节(1.3.1, 1.3.2 和 1.3.3)介绍了一些例子,激励我们分析运行在基于权限的系统之上的应用程序。这些例子代表了基于权限的系统设计中的一个环节,因此与正在研究的基于权限的系统无关。 此外,第1.3.4节说明了这样一个事实:由于文档不完整,开发者可能会无意中在他们开发的应用程序中产生漏洞。这促使我们对基于权限的系统本身进行分析,以改进文档和/或应用程序的开发过程。最后,第1.3.5节强调了安卓系统在权限方面给予用户的自由的局限性,并提出了一个替代方案,该方案依赖于安卓基于权限的系统的分析结果。
1.3.1 混乱的副手
应用程序可以一起交流。 如果一个应用程序被赋予一个权限,另一个应用程序可以利用这个应用程序来滥用其权限。 这种攻击被称为混乱的副手攻击,如图1.2所示。这种攻击经常发生,因为混乱的副手应用程序错误地认为只有一个有限的和受信任的应用程序可以访问它的接口。由于这个原因,它的接口没有得到很好的保护,可以被意识到这个漏洞的恶意应用程序所滥用。在图1.2的例子中,混乱的副程序拥有网络权限,因此能够启用或禁用网络。 它错误地认为只有受信任的应用程序才能访问其接口,因此没有正确地保护它。攻击者利用这个不受保护的接口,让混乱的副手使用其权限代表攻击者禁用网络。
1.3.2 应用程序串通
应用程序被授予权限,可以一起通信。因此,没有什么能阻止一个应用程序从受权限保护的资源中获取数据(例如GPS坐标),并与另一个可能没有权限访问受保护资源的应用程序分享这些数据。当多个应用程序为了一个共同的恶意目标而合作时,它们就会串通起来。
图1.3说明了应用程序串通的情况。 攻击者必须在目标设备上安装两个(或更多)应用程序。 一旦应用程序被安装,它们就会一起交流,分享它们的权限。在安装单个应用程序时,用户只能看到每个应用程序的有限权限。在图1.3的例子中,第一个应用只有GPS的权限,第二个应用只声明了互联网的权限。
然而,应用1可以与应用2分享它通过GPS权限获得的数据。应用程序2可以访问互联网,它可以将GPS坐标发送到互联网上的一个远程主机。互联网上的一个远程主机。
1.3.3 数据泄漏
图1.3的例子说明了两个应用程序共享GPS坐标并将其发送到互联网上。我们说,GPS数据从App1中检索GPS坐标的语句(源)泄漏到App2中发送坐标到互联网的语句(汇)。这种特殊的数据泄露发生在两个应用程序之间,这在恶意软件应用程序中并不常见。为了更有效,大多数恶意软件在一个单一的应用程序中泄漏数据。
1.3.4 不完整的文件
一个Android应用程序包含一个权限列表,描述了该应用程序可以访问哪些资源(例如,访问GPS)。安卓应用的开发者负责编写这个权限列表。为了实现这一目标,他们依靠文档,但遗憾的是,文档并不完整[53]。此外,他们还依赖在论坛或网站上找到的带有权限列表的代码片段[53]。然而,权限表可能包含了超过必要的权限。 因此,开发人员可能会编写一个包含比应用程序所需更多权限的权限列表,增加应用程序的攻击面(即攻击者可以进入系统并可能造成损害的所有方式[85])。事实上,如果一个攻击者破坏了应用程序,她就可以访问更多的资源,而不是在减少权限列表的情况下访问。
在本论文中,权限缺口被定义为一个应用程序声明但不使用的一组权限。为了检测权限差距,我们首先要计算出所有API方法的权限集合。然后,通过查看应用程序调用的API方法,可以从应用程序代码中自动计算出一个权限列表。映射到这些API方法的权限构成了应用程序需要的权限列表,以便正常工作。这个自动计算的权限列表和应用程序的开发者编写的权限列表之间的差异被称为权限差距。
1.3.5 对用户数据的细粒度保护
用户往往自愿或不自愿地在他们的设备上存储大量他们认为是隐私的信息,如图片、联系信息、GPS坐标、电子邮件或日历信息。一方面,他们认为这些信息是私有的,受到设备的保护。 另一方面,他们希望从互联网上的远程存储库(可信和不可信)安装应用程序,并精确控制这些应用程序的权限列表。然而,他们没有可能配置一个细化的权限政策。
在这项工作中,我们探讨了在用户设备上直接实现这样一个系统的挑战。新的软件修改了安卓应用程序的字节码,并在代码中编织了访问控制策略。该策略可以在运行时由用户决定,例如,禁用所有安装的应用程序的权限。
总而言之,我们在本节中看到:(1)混乱的副手、应用程序串通和数据泄漏的例子促使在应用程序中进行泄漏检测;(2)不完整的文件表明应该对框架本身进行分析;(3)为了改善数据保护,应该让用户对设备的安全策略有更多控制。
在这篇论文中,我们旨在从安全的角度分析Android应用程序和Android框架。从激励性的例子中,我们将回答以下具有挑战性的问题:如何分析Android应用?每项任务都是为安卓应用设计的吗? 应用程序会泄露受保护的数据吗? 动态分析如何补充静态分析? 下一节将描述我们在回答这些问题时面临的技术挑战。
1.4 基于权限的系统分析所面临的挑战
在这一节中,我们将介绍在分析Android框架和Android应用程序以回答第1.3节所列研究问题时面临的技术挑战。
第1.4.1节解释了Android应用程序使用一种特殊的字节码,需要将其转换为可分析的表示。第1.4.2和1.4.3节分别描述了分析Android框架和Android应用程序的困难。最后,第1.4.4节强调了直接在设备上运行分析Android应用程序的挑战。
1.4.1 Dalvik字节码
Android应用程序是用Java编写的,然后编译成Java字节码,最后编译成Dalvik字节码。现有的静态分析工具可以在应用程序的源代码或Java字节码可用时分析Android应用程序。然而,这种情况并不常见,因为大多数应用程序是通过市场分发的,而市场只提供最初的Dalvik字节码。 例如,在官方的Google Play市场上有超过一百万的应用程序2,而在F-Droid(免费和开源的安卓应用程序)网站上只有大约一千个应用程序3。这就促使我们使用一个软件模块将Dalvik字节码转换为可分析的表示。
在本论文开始的时候,也就是2010年,还没有可用的工具来对Dalvik字节码进行复杂的静态分析。为了能够分析Android应用程序,我们开发了一个名为Dexpler的模块,将Dalvik字节码转换成可分析的表示。 我们利用了一个现有的名为Soot的工具,其内部的代码表示被称为Jimple。如图1.4所示,Soot能够分析Java源代码和Java字节码,将它们转换为Jimple表示法。 Dexpler将Dalvik字节码转换为Jimple,以便Soot能够分析Dalvik字节码。
当把Java字节码转换为Dalvik字节码时,一些关于变量类型的信息会丢失。对于字节码中每个有这种信息丢失的方法,Dexpler通过分析该方法的代码将这些信息找回来。
1.4.2 框架的分析
静态分析最初是用在程序或应用程序上,而不是API上。对于一个程序,通常有一个单一的入口点,分析从这里开始。然而,对于一个框架或API来说,没有入口点:我们必须为所有的入口点构建包装代码。封装代码的作用是构建入口点方法被调用的对象,以及被调用的入口点方法的参数。 此外,构建封装代码并不是一件容易的事,因为我们必须考虑到如何调用API的方法以及如何初始化其参数。
对一个框架的静态分析从构建封装代码的调用图开始。在Android的情况下,API代码是系统运行的其他程序或应用程序的接口。仅仅从封装代码中构建调用图是不行的,因为系统程序(负责检查权限)没有被正确初始化。由于系统程序是在安卓设备启动时启动和初始化的,所以初始化代码是无法从入口点方法中到达的。 我们采用的解决方案是将所有的系统程序独立出来,从入口点调用图中单独初始化它们,并且每当在入口点调用图中遇到它们时,就引用初始化的程序。
图1.5展示了一个有三个入口点(如ep1、ep2和ep3)的框架。分析这个框架需要对这些入口点进行包装。这是通过处理入口点初始化和参数实例化的 "入口点包装器 "节点实现的。在构建调用图时,ep3调用了 "srv1 "服务的代码。 这个服务已经被单独初始化了(图1.5,右),调用图的构建参考了初始化的服务。如果没有服务的初始化,调用图是不完整的,因为该服务应该是不存在的。
1.4.3 应用程序的分析
分析Android应用程序与分析Java应用程序不同,后者有一个单一的入口点(即主方法)。Android应用程序的特点是,每个组件都有一个由Android系统管理的生命周期。图1.6a代表了一个叫做activity的Android组件的生命周期的简化视图。这个生命周期有四个状态,s1到s4,在事件产生后从一个状态移动到另一个状态(e1到e7)。事件触发了组件的指定方法(m1到m7)。组件的生命周期在Android应用程序中是不存在的。Android系统处理Android应用程序的组件的生命周期。由于为每个应用程序分析Android系统以考虑到组件的生命周期,成本太高,所以我们使用生命周期的模型来代替。
此外,任何组件都可以被同一应用程序或另一应用程序的另一个Android组件调用。一个安卓应用没有单一的入口点,但至少有和组件一样多的入口点。一个应用程序的组件被列在应用程序的清单中。在图1.6b中,没有源组件的黑色箭头代表应用程序一的组件C1和C3以及应用程序二的组件C5和C6的入口点。
有些组件可以在应用程序的组件代码中定义,而不是在清单中定义。因此,仅仅分析清单是不够的,还必须检查组件的代码以确定动态注册的组件。此外,组件之间可以相互通信。组件是松散耦合的,因为它们之间的连接是在运行时完成的,它们之间的通信依赖于被称为Intents的抽象消息。在图1.6b中,应用一的组件C1和C2之间以及C2和C4之间有应用内通信,应用一的C3和应用二的C5之间有应用间通信。
简而言之,当使用静态分析来分析一个Android应用程序时,我们必须对组件的生命周期进行建模,并计算组件之间的通信链接。
1.4.4 直接对设备进行分析
虽然在设备外对应用程序进行分析和转换很有趣,但直接在设备上进行分析会更有趣。例如,用户将直接受益于允许他们拥有细化权限政策的软件(即允许对权限进行细化调整的政策。例如,INTERNET权限不允许完全的网络访问,而是可以将网络访问限制在用户自定义的URL列表中)。主要的挑战是能够在对可用内存和处理能力有限制的设备上进行一些分析,以及对一个进程的可用堆有硬编码限制。 在桌面上需要100MB堆的分析不能在标准的安卓设备上运行,因为安卓设备的堆限制是80MB或更少。
1.5 贡献
在本节中,我们提出了我们的贡献,以回答第1.3节中提出的研究问题,并解决第1.4节中提出的相关挑战。 在这项工作中,有七个主要的贡献,推动了Android和基于权限的系统研究的最先进水平:
- 一种转换Dalvik字节码Jimple的算法。 由于Jimple是静态分析框架Soot的内部代表,Dexpler使Soot能够静态地分析Android应用程序。我们在一组2.5万个应用程序上评估了Dexpler。Dexpler正确地转换了99.9%的应用程序的方法。
- 一种将API方法映射到权限的算法。我们提出了一种算法,首先从API方法中生成入口点,然后从基于权限的框架的API方法中建立一个调用图,最后使用深度优先搜索来进行权限检查并提取权限名称。
- 对Android基于权限的系统进行实证分析。我们已经实现了我们的算法,将API方法映射到权限,并对其进行了定制,以分析Android框架。 安卓特有的修改包括服务重定向、服务身份反转、系统服务和管理者初始化。
- 对权限差距的实证分析。 该分析是在两组Android应用程序上进行的。在第一组中,有18%的应用程序来自官方的安卓市场,第二组来自另一个市场,有12%的应用程序存在权限差距。
- 一个检测Android应用程序内部和之间泄漏的工具。我们的工具叫IccTA,在Jimple级别上链接Android组件。这允许检测组件间和应用间的数据泄露。
- 对IccTA进行实证评估,以检测组件间的泄漏。我们在DroidBench上评估了IccTA,这是一套专门为测试组件内和组件间泄漏的工具而设计的Android应用程序。 我们的算法优于现有的工具,精确度为95%,召回率为82%。 我们还在一组3000个安卓应用中评估了我们的算法。它在其中450个应用程序中检测到了泄漏。
- 第一个关于体内仪器化的实证分析。这项研究显示了直接在设备上对安卓应用进行检测的可行性。我们还提出了两个用例:第一个用例是将广告从应用程序中移除,第二个用例是允许用户制定一个细化的权限策略,这在本地安卓系统中是不可能的。 这些案例表明,该方法是可行的,进行高级分析的主要限制是系统施加的堆大小。
1.6 本论文的路线图
本论文的组织结构如下。在第二章中,我们介绍了静态分析的基本原理、调用图的构建、Android应用程序和基于权限的Android系统。在第三章中,我们讨论了Dexpler,一个将Dalvik字节码转换为Jimple的软件,以便分析Android应用程序和Android系统。在第四章中,我们分析了Android框架,将权限映射到入口点方法。我们利用这些知识来识别那些声明了太多权限的应用程序,这增加了终端用户的攻击面。在第五章中,我们描述了一种在安卓应用中识别私人数据泄露的技术。接下来,在第6章中,我们介绍了关于体内安卓应用工具和分析的第一个结果。 最后,在第八章中,我们总结了论文,并讨论了未来的工作和开放的研究问题。
第二章
技术背景
本章介绍了理解本博士论文所需的主要技术背景。本章分为三个部分。第2.1节介绍了安卓系统:包括安卓应用程序及其组件、系统服务和许可访问控制。 然后,第2.2节向读者介绍了静态分析,特别是基于Java程序的调用图构造和程序间数据流分析。
2.1 安卓堆栈简介
Android是一个为智能手机、平板设备以及更广泛的任何种类的个人设备开发的软件系统。它最初是由安卓公司在2000年初开发的[78]。谷歌在2005年收购了该公司以进一步开发该系统。第一个公开可用的设备出现在2008年[79],运行的是安卓1.0。从那时起,大约每三个月就有一个新的版本发布。在写这篇文章的时候(2014年)
最新的安卓版本是4.4。
在本节中,我们首先在第2.1.1节中对安卓系统进行了概述。然后,我们在第2.1.2节中介绍了Android应用程序的结构。最后,在第2.1.3节中,我们详细介绍了系统服务,这是Android系统的一个主要部分。
2.1.1 整体结构
Android是一个软件栈,意味着它有四个主要的软件层,如图2.1所示(从上到下):应用层、框架层、运行时和本地库层以及内核层。
顶层的特点是Android应用程序。典型的安卓应用有:主页应用,它是第一个运行的应用,显示启动其他应用的图标;联系人应用,管理联系人列表;电话应用,拨打电话;浏览器应用,访问网络资源。运行安卓系统的设备的用户可以在他们的设备上安装更多的应用程序,通常是通过从FDroid1或名为Play Store2的谷歌官方市场上下载。 应用程序主要用Java编程语言编写,但也可以包含本地代码。 应用程序依靠框架层与系统进行通信。
框架层是应用程序和系统其他部分之间的一个用Java编写的接口。它提供了从系统资源中检索信息的设施(例如,应用程序可以通过LocationManager检索GPS坐标)或要求系统在有新事件时回调它们(例如,要求TelephonyManager在有电话时通知应用程序)。
第三层有两个不同的实体:Android运行时间和本地库。
- Android运行时由Dalvik虚拟机和Android核心库组成,前者执行Android应用程序的Dalvik字节码3,后者基本上是应用程序可以利用的Java类(例如,应用程序可以使用本地库,这取决于环境的配置。
- 本机库4提供了可以被应用程序、框架层或核心库使用的基本构建块。应用程序可以有直接使用本地OpenGL库进行快速图形处理的本地代码。框架层可以使用本地SQLite库来存储数据。
最低的一层是Linux内核。从上层的软件层来看,它可以被看作是硬件(CPU、内存......)的一个界面。事实上,它负责在CPU上运行程序5,它有许多驱动来处理不同的硬件,如显示器、网络和管理网络通信的驱动。它还具有一个特殊的驱动,用于有效的进程间通信,称为Binder驱动[116]。
正如我们所看到的,这些层并没有明确分开。 一个Android应用程序可以使用框架层、核心和本地库中的元素,也可以直接与内核通信。安卓系统实现了安全功能,以防止应用程序访问系统的每个部分。简而言之,开发者给他们编写的每个应用程序提供了一个权限列表。这个列表规定了应用程序被允许在系统上做什么,并且必须在安装时由用户进行验证。当一个应用程序被安装时,它被赋予一个用户ID(UID)。每个安卓应用都可以被看作是一个Linux用户。此外,安卓系统有一个列表,将每个权限映射到一个组ID(GID)。对于应用程序声明的每一个权限,系统都会将该应用程序(或更确切地说,相应的Linux用户)添加到相应的GID中。因此,如果一个应用程序没有GPS权限,并想通过LocationManager或GPS的Linux驱动来检索GPS坐标,Android系统会检测到该应用程序不在GPS组中,并阻止它访问GPS数据。
2.1.2 Android应用程序的结构
一个安卓应用程序是一个用开发者的私钥K签名的压缩zip文件。它包含应用程序的Dalvik字节码(由Java源代码编译而成)、应用程序需要的数据(图片、声音......)以及描述应用程序结构和应用程序所需权限的清单文件。简而言之、
Application = Sign(Zip(DalvikBytecode, Manifest, Data), K)。
安卓应用是用开发者的私钥签名的,这确保了应用只能由同一个开发者签名的代码来更新,而且用同一个密钥签名的应用有可能共享权限和UID。然而,它并不保证应用程序作者的真实性,因为证书可以是自签的(例如,任何人都可以声称是无名氏)。
组件
Android应用程序是由组件组成的。有四种组件:活动、服务、内容提供者和广播接收器。活动组件用于图形用户界面(GUI)。它们显示图形元素,如按钮、列表或图片。服务组件用于计算密集型任务或需要长时间的任务,如播放音频文件。 内容提供者被用来在应用程序之间共享数据。例如,联系人列表被实现为一个内容提供者,这样任何应用程序都可以访问它(如果它有适当的权限)。最后,广播接收器组件接收来自系统或其他应用程序的消息(例如,系统已经收到了一条短信)。具体来说,每个组件都是一个Java类,它继承自一个特定的超类,如活动、服务等。图2.2表示一个由三个活动和一个广播接收器组成的Android应用程序。
用意图和URI进行通信
Android应用程序的组件通常使用特殊的系统方法进行通信,这些方法称为组件间通信(ICC)方法。 大约有40个ICC方法,一个组件可以用来与另一个组件通信。 最常用的ICC方法是startActivity(Intent)。 这个方法用来告诉系统启动一个由该方法的参数描述的新活动组件。
意图。 组件可以使用一个叫做Intent的抽象对象相互通信。
通信可以发生在单个应用程序的组件之间或多个应用程序的组件之间。当组件A想与组件B通信时,它初始化一个Intent并将组件B设置为目的地。这种通信被认为是显式的,因为目标组件是明确规定的。通信也可以是隐式的,在这种情况下,源组件用它想执行的动作来初始化Intent(例如,查看pdf文档)。当组件发送Intent时,系统会检查在其意图中是否有该动作的组件。目标组件的选择可以由系统自动完成,如果有多个组件可以处理该动作,则可能需要用户干预。例如,如果图2.2中的Activity3发送了一个带有 "查看txt "动作的Intent,系统就会启动Activity2,因为它是唯一具有 "查看txt "意图的组件。Intent可以将数据以键/值对的形式封装在称为Bundles的对象中。 Intents被用于活动、服务和广播接收者之间的通信。
URI。 URI,即统一资源识别器,用于识别抽象或物理资源[19]。简而言之,URI被用来与内容提供者通信。它们也可用于初始化针对特定资源的意图。以下面的URI为例:.content://comandroid calendar/events。它可以被切割成三个部分。第一部分,内容,称为方案,确定了如何访问资源。读者可能已经知道通过这样的路径来识别网页资源。在我们的例子中,这就是内容提供者的数据库表事件。
清单(The Manifest
清单以组件的形式描述应用程序的结构。 一个组件可以被导出,以便其他应用程序可以使用它。它还可以声明意图过滤器,以便向系统指定它所处理的行动或数据的种类。 清单还列出了应用程序要求的所有权限(如INTERNET、GPS)。图2.3展示了一个清单的例子。它声明了一个有一个内容提供者、一个服务、一个活动和一个广播接收器的应用程序。 该服务只接受带有动作SyncAdapter的意图,活动意图带有动作MAIN和类别UNIT_TEST,广播接收器意图带有动作BOOT_COMPLETED。该清单为应用程序声明了一个权限:INTERNET权限。
2.1.3 安卓系统的结构
启动过程
安卓系统的启动过程如图2.4所示。当安卓设备被打开时,CPU首先执行启动加载程序,初始化RAM(随机存取存储器)和硬件,然后加载内核并跳转到它。内核初始化驱动程序,挂载根系统并启动第一个进程:init。init进程挂载所有ile-系统,设置ile-系统的权限,并启动本地守护程序。 本地守护程序是在后台运行的程序。这些程序包括管理所有蓝牙设备的bluetooth d(蓝牙守护进程)和Rild(无线电接口层守护进程),它是无线电设备(例如全球移动通信系统的无线电设备)的接口。 一个本地守护进程,app_process,启动Dalvik虚拟机的一个实例,并初始化Zygote,一个用于分叉新的Android应用程序的程序。当Zygote启动时,它启动了系统服务器,这是一个初始化所有系统服务的进程6(例如GPS的系统服务),并启动活动管理器。最后,活动管理器启动主页应用程序。
在这一点上,用户面对的是主应用程序的图形界面。 这个图形界面显示设备上安装的Android应用程序的图标。当用户点击一个图标时,主应用程序调用startActivity方法。 活动管理器处理这个方法调用,并要求Zygote分叉自己,以创建和启动与用户点击的图标相对应的新的Android应用程序。
启动后的进程。
当启动顺序完成后,Android系统已经被初始化,其内核正在运行图2.5中所示的进程。 左边是一个进程的堆栈,上面是bluetoothd进程。在bluetoothd进程的右边是服务管理器进程,它被提前使用,因为它被系统服务器用来注册系统服务。这些都是由init进程启动的本地守护进程。在中间的是Zygote,用于启动新的Android应用程序的进程。Zygote启动的第一个进程是系统服务器,它初始化、向服务管理器注册,并运行所有的系统服务。最后,在图的右边是一个代表Android应用程序的堆栈。第一个运行的Android应用程序是Home应用程序。通过Home应用的界面,用户可以启动其他的Android应用。
Zygote、系统服务器和Android应用程序堆栈正在运行一个带有Dalvik虚拟机的进程,执行Dalvik字节码。 该虚拟机也可以通过Java本地接口(JNI)执行本地代码或共享库。另一方面,本地服务不运行一个Dalvik虚拟机。
本地服务和Zygote以根用户的身份运行。系统服务器以系统用户的身份运行,以遵守最小特权原则[113](例如,作为一个普通用户,系统用户不能访问其他用户的数据)。正如在第2.1.2节中已经提到的,Android应用程序作为普通用户运行,每个应用程序分配一个用户ID。
安卓访问控制
Android系统运行Android应用程序。这些应用程序可以访问系统资源,如GPS、联系人列表或摄像头。安卓系统用权限来保护系统资源。如果应用程序没有适当的权限,系统就会阻止它们访问某个资源。
图2.6说明了一个安卓应用如何访问GPS资源。 GPS资源需要特定的硬件,因此需要一个内核驱动来向硬件发送命令。一个Android应用程序在Dalvik虚拟机中运行,可以通过JNI执行本地代码。理论上,应用程序可以使用本地库,通过适当的内核驱动程序与设备直接通信。 图2.6中的虚线表示从应用程序到设备驱动的这种通信。然而,这种直接通信是Android系统所不允许的,因为应用程序是作为普通用户运行的,而驱动只能由系统用户读写。
相反,应用程序必须通过绑定器驱动程序与系统服务器进行通信(关于应用程序访问绑定器的技术细节被Manager类所隐藏)。 由于绑定器是作为内核模块运行的,它可以向系统服务器证明调用应用程序的用户ID(也就是说,应用程序不能伪造它的ID)。然后,系统服务器检查调用程序是否有权访问该资源(即有正确的Android权限)。如果没有,那么系统服务器就会抛出一个异常,通信结束。如果它有,系统服务器就会与驱动程序对话,获取信息并将其送回给调用者应用程序。由于系统服务器是以系统用户身份运行的,所以允许对驱动程序进行访问。
安卓权限
权限被分为四类或权限级别:正常、危险、签名和签名或系统。正常权限保护对用户来说风险较低的资源(例如,读取电池状态的权限)。危险权限保护的是那些一旦被窃取就会对用户造成伤害的资源(如联系人列表)或使其损失的资源(如发送短信)。安卓系统只授予应用程序一个签名权限,如果它的签名与声明该权限的应用程序所使用的签名具有相同的认证。安卓系统只授予应用程序签名或系统权限,前提是它与声明权限的应用程序的签名证书相同,或者该应用程序在安卓系统镜像中。
Android 4.2定义了200个权限。其中,29个权限级别为正常,47个权限级别为危险,63个权限级别为签名,61个权限级别为标志或系统。在实践中,大多数时候,开发者只处理正常和危险权限(29+47=76个权限)。
2.2 静态分析简介
流行的编程语言,如Java,使用类和方法来表示现实世界的概念,并分别操作这些概念(即改变其状态)。对使用类和方法构建的程序进行静态分析,可以使用程序内方法(即逐个方法)或程序间方法(即把方法连接在一起,把程序作为一个整体进行分析)。后面的方法需要连接方法。这可以通过计算程序的调用图来实现。
在本节中,我们将介绍调用图的构造,并着重介绍面向对象的语言,特别是Java语言,因为我们在第四章和第五章中分析了这种语言的代码。
2.2.1 调用图
在面向对象编程中,程序是由类组成的。每个类代表一个概念(如汽车),有一组表示其他对象(如车轮)的单元和一组包含操作对象的代码的方法(如驱动汽车前进)。 代码可以调用其他方法来创建对象的实例或操纵现有对象。
一个程序通常以一个叫做主方法的单一入口点方法开始。 通过分析主方法的代码,我们可以找出它在调用哪些方法。然后,通过分析被调用方法的代码,我们可以找出它/它们在调用什么方法。只要有方法调用其他方法,这个过程就可以重复进行。
其结果是一个有向图,称为调用图,它将方法联系在一起。 该图从代表主方法的节点开始,通过被调用的方法向下扩展。
图2.7说明了一个Java程序的调用图生成过程(a)。 有两个Java类,MyObject和MyOtherObject。程序的起点是MyObject中的main方法。这个主方法也是调用图的起点(b)。主方法首先通过调用MyOtherObject的构造方法(Java中的<init>方法)创建一个实例。 然后它在新创建的对象上调用方法method1和method2。方法1不调用其他方法,只调用自己。方法2只调用大小。方法3不能从主方法中到达,因此没有出现在调用图中。
精确性
继承和多态 面向对象的语言,如Java[61],使用了诸如继承和多态的概念。图2.8说明了这两个概念。继承是对一个抽象概念(这里是抽象的动物类)进行建模,然后将这个抽象概念扩展到这个抽象概念的具体元素(这里是人类和猫类)的能力。这个抽象概念通过行走方法定义了一个行为。人类和猫咪都在行走,但它们以不同的方式在自己的行走方法中描述。
假设我们用Java语言建立一个由猫和人组成的世界。我们将把对猫和人的每一个引用存储在一个动物的容器中。 动物通过迭代容器中的元素并对其调用行走方法来进行行走。当执行这段代码时,如果动物是人类,对动物的行走方法调用将被重定向到Human .walk,如果动物是猫,则重定向到Cat .walk。为不同种类的实体提供一个单一的接口被称为多态性。在分析一个Java程序时,处理多态性的方式对调用图的精度有直接影响。
流程敏感度 流程敏感度分析考虑到了语句的顺序。以图2.9a的代码片断为例。在第二行,一个新的Human实例被创建,并被动物引用a引用。在第三行,方法walk被调用到a上。这意味着在执行时只有方法Human .walk在第三行被调用。 在第四行,a现在引用了一个新的猫的实例。在第三行,一个ow-sensitive分析给出了a只指向一个Human对象,而不是一个Cat对象。 因此,ow-sensitive调用图包含一条通往Human .walk的边。另一方面,一个不敏感的分析给出了a可能指向Human或Cat对象,因为它不考虑语句的顺序。对于一个不敏感的分析,在第二行和第三行之间有第四行的程序会得到同样的结果。因此,ow-insensitive调用图包含两条边:一条指向Human .walk,另一条指向Cat .walk。
路径敏感性 路径敏感性分析将执行路径考虑在内。 以图2.9b的代码片断为例。 在第二行,动物引用a指向没有对象。如果第三行的条件为真,a指向一个新的人类对象(第四行)。如果第三行的条件为假,a指向一个新的猫对象(第六行)。对这个例子进行路径敏感的分析会产生两条路径:路径1 , l2 -l3 -l4 -l8,和路径2 , l2 -l3 -l6 -l8。在第八行,path1有一个指向Human对象的指针,因此方法Human .walk在调用图中。路径2有一个指向猫对象的指针,因此方法Cat .walk在调用图中。对路径不敏感的方法不考虑路径,在第八行会有一个指向Human对象和Cat对象的指针。因此,对路径不敏感的调用图将包含Human .walk方法和Cat .walk方法。 一个对路径敏感的方法可以为每一个可能的路径产生一个图。路径的数量会呈指数级增长,这种方法也就不具有可扩展性。
场敏感度 场敏感度方法对每个对象的每个场进行建模。以图2.9c的代码片段为例。 在第二行,c1指向一个新的C对象。 这个对象包含两个动物字段。在第三行,第一个字段,f1,指向一个新的人类对象。在第四行,第二个字段,f2,指向一个新的猫对象。 一个对字段敏感的分析为每个对象的每个字段建模。因此,在第i行,f1的模型只能指向一个Human对象,并且只有Human .walk方法在ield敏感的调用图中。基于字段的方法只对每一类对象的每个字段进行建模。 这意味着在这个例子中,ield c1 .f1和c2 .f1有相同的模型。因此,在第ive行,f1指向一个Human对象和一个Cat对象,Human .walk和Cat .walk这两个方法都在ield-不敏感的调用图中。 最后,请注意,一个不敏感的方法只对 "对象 "进行建模。所有的元素都被聚合到它们相应的对象上。不敏感的方法不太可能用于Java这样的语言,而只能用于具有类型不安全的指针操作的语言,如C。
上下文敏感度 上下文敏感度方法对方法被调用的每个上下文进行建模。 对上下文进行建模的主要方式有两种:对调用地点进行建模,这在本段中有所描述;对方法调用的分配地点进行建模,也称为对象敏感,在下一段中有所描述。以图2.9d的代码片段为例。在第二行,一个人类对象被实例化了。 变量h指的是这个对象。 在第三行,一个猫对象被实例化。变量c指的是这个对象。在第四行,用c调用方法,该方法返回存储在a中的动物引用。在第五行,用h调用方法,该方法返回存储在a中的动物引用。也就是说,对于第一个方法的调用,参数模型指向c,返回值模型指向c;对于第二个方法的调用,参数模型指向h,返回值模型指向h。另一方面,对于一个给定的方法,上下文不敏感的方法只有一个参数模型和一个返回值模型。在上下文不敏感的方法中,参数模型指向c和h,返回值指向c和h。因此,上下文不敏感的方法在调用图中既有Human .walk方法,也有Cat .walk方法。
对象敏感度 对象敏感度方法是一种上下文敏感度方法,它区分了对不同对象进行的方法调用。以图2.9e的代码片断为例。在第二行和第三行,两个Contains对象被实例化了。变量c1和c2指的是这些对象。Contains类有一个动物类型的实例ield animal和一个实例方法setAnimal,用于将一个值与ield animal联系起来。在第四行,方法setAnimal被调用到c1,参数为Human对象。在第5行,方法setAnimal在c2上被调用,参数是猫对象。最后,在第六行,方法walk被调用到对象c1的animal ield。在第四行和第五行,一个对对象不敏感的方法会认为c1和c2是同一个接收器。其结果是,第四行和第六行的方法调用不能区分接收器,并将c1和c2作为一个独特的含有类型的对象cu的模型。因此,在第六行对对象cu调用的方法walk在调用图中由两个方法表示: Human .walk和Cat .walk。另一方面,一个对对象敏感的方法将为setAnimal的每次调用分别建立c1和c2的模型。因此,第六行的调用在调用图中只由方法Human .walk表示。
不可知性 使用静态分析来确定哪些指针在运行时可以指代哪些对象的问题是不可知的[77]。这意味着对于所有的程序来说,都无法计算出精确的解决方案(即,既合理又完整)。然而,存在许多保守的解决方案。这些解决方案是合理的,因为它们计算的是 "真实 "结果的超近似值。正如我们在上面的段落中所看到的,它们在精度上是不同的(也就是说,有些方法比其他方法产生更多的假阳性结果)。例如,一般来说,上下文敏感的方法比上下文不敏感的方法更精确。
计算Java调用图的算法
Java是一种面向对象的语言,支持多态性。因此,在进行程序静态分析时,方法被调用的对象的确切类型(这种对象也被称为接收器)可能并不为人所知。在下面的段落中,我们将介绍一些静态地构建Java程序调用图的算法。
CHA 第一种方法叫做类层次分析(CHA),由Dean等人提出[39]。该方法是非常保守的,它假定对于一个给定的接收器,接收器的声明类型T以及T的所有子类型(例如所有子类)在运行时都是可能的类型。例如,当在一个程序上运行CHA时,该程序有一个对动物类型的接收器r的方法调用walk(),CHA假定动物、人类和猫在运行时是r的可能类型。即使在分析的程序中没有实例化的猫对象,情况也是如此。
RTA 后来,Bacon等人提出了一种叫做快速类型分析(RTA)的方法[10]。这种方法与CHA类似,但是只有在被分析的程序中创建了T类型的对象时,才会将T类型视为可能的类型。例如,当对一个有方法调用walk()的程序运行RTA时,RTA假设动物、人类和猫在运行时是r的可能类型,当且仅当人类和猫的对象在分析的程序中的某个地方被创建。
VTA Sundaresan等人[126]后来提出了另一种方法,称为可变类型分析(VTA)。这种方法比CHA或RAT更精确,因为它只考虑能到达接收器的类型作为可能的类型。
Andersen Andersen算法[2]的Java扩展[83, 110]进行了对字段敏感的基于子集的点到分析。点到分析描述了一个指针表达式可能指向哪些内存位置(即局部变量、全局变量和动态分配的内存)。一个调用图几乎可以直接从点到分析的结果中计算出来。如上所述,一个对字段敏感的方法对每个创建的对象的每个字段进行单独建模。给定一个语句,如a = b,一个子集方法增加了以下约束:a ⊇ b。这意味着b的点到集是b的点到集的一个子集。该算法收集了所有这样的约束,并使用一个工作表来解决这些约束并为所有指针构建点到集。在现实的Java程序上,类似Andersen的指针分析的最坏情况下的复杂度是四次方的[122]。
Steensguard 与Andersen的算法相反,Steensguard的算法[124]是基于平等的。这意味着,它使用的不是子集约束,而是平等约束。 这个约束意味着a的点到集与b的点到集相同。这个方法不如Andersen的精确,但更具有可扩展性(接近线性时间复杂度[124])。
其他方法 其他点对方法已经被开发出来[135, 119, 134, 64, 65],以提高Andersen引入的基于子集的点对分析的效率,或者提高Steensguard引入的基于平等的点对分析的精度。有兴趣的读者可以参考[80],了解这些方法的概况。
2.2.2 数据流分析
程序内分析
数据流分析[1]是一种在程序的每一个点上计算一组可能的值的技术。 这组值取决于使用数据流分析所要解决的问题的种类。 例如,在达到定义问题中,人们想知道在每个程序点上可达到的定义(例如,int x = 3;这样的语句)的集合。 在这个特定的问题中,程序点P的可能值集合是到达P的脱式集合(即变量在到达P之前没有被赎回)。以图2.10为例。第二行的定义到达第三和第四行,但没有到达ive,因为变量x在第四行被重新定义。第三行的删除到达第四行和ive。第4行的删除到达第ive行。
数据流分析使用一个方程系统来计算每个程序点或语句的信息。 每个语句都有一组可能的值,称为in,它代表了语句之前的有效信息。每条语句都有一组可能的值被叫出来,这代表了语句之后的有效信息。每个语句都有一个方程,描述语句对in集的影响。语句Stmt可以创建由Stmt.gen表示的新的可能值,并杀死现有的值,由Stmt.kill表示。
图2.11表示图2.10第4行的语句。这个语句S有一个in集,包含两个deinitions x = 1; 和y = 2; 这是到达第四行的语句的deinitions。请注意,对于一个有多个前置语句的语句,in集被定义为所有前置语句的out集的联合: S.in = ∪p 2 predecessorsp.out。语句S产生了一个新的定义,x = 3;,并杀死了in集合中的变量x的定义。 语句S的这种行为用等式S.out = S.gen ∪ (S.in / S.kill)表示。函数f S.out = f(S.in)被称为转移函数(也可以称为ow函数或数据-ow函数)。
前向和后向 达成定义的问题是用前向分析来解决的。这意味着分析从程序的第一条语句开始,一直向前走,直到到达程序的终点。其他问题则采用后向分析法解决,即从最后一条语句开始分析,并向后追溯到第一条语句。
程序间分析
到目前为止,我们一直在研究程序内(即在单个方法内)的数据运算分析。程序间分析则是对相连的程序(或Java中的方法)进行分析。正如我们在第2.2.1节中所看到的,计算一个调用图可以得到关于方法是如何连接的信息。
为了说明程序间分析,我们依靠程序间有限分布子集(IFDS)框架[106]。IFDS框架通过将问题转化为图-可达性问题,在多项式时间内解决问题。用于解决该问题的算法被称为 "制表 "算法。它比以前的方法,如 "迭代 "或 "调用字符串 "算法[87]有所改进,后者在最坏的情况下可能需要指数时间。
IFDS 以图2.12为例。 这个例子来自[106],说明了程序间可能未初始化的变量的数据流问题。 在这个例子中,我们假设方法read(a)是一个系统方法,读取一个整数并将其存储在局部变量a中,方法print(a,b)是一个系统方法,在屏幕上打印出整数a和b的值。图2.12b分别表示方法main和p的控制ow图。
图2.12代码的调用图显示,方法main在第6行调用方法p,方法p在第13行调用自己。每个方法的调用由两个节点表示:调用节点c和返回点节点r。通过添加三条边来连接这些方法:一条从c到r的程序内边,一条从c到被调用方法开始节点的程序间边,一条从被调用方法退出节点到r的程序间边。图2.12中的例子的超图表示在图2.13中。
可能未初始化的变量的数据流事实集是每个方法中可用的局部和全局变量集。方法main有局部变量x和全局变量g。方法p有局部变量a和全局变量g。每个语句对数据流事实集的影响由图中每条边的函数表示。例如,read(x)的效果是初始化变量x,该函数用λS表示。(S-{x})。这意味着数据流事实的输出集是输入集S,其中元素x被移除。事实上,由于x现在被当前语句初始化了,它可以从潜在的未初始化的变量集合中删除。类似地,语句a = a - g由函数λS表示。如果((a∈S)或(g∈S)),那么(S∪a),否则(S -{a})。这意味着,如果a或g在输入集中,输出集就是输入集加上元素a(即a也是无因的)。否则,输出集是输入集,其中的元素a被移除(即a和g都没有被剔除,所以a的新值不能被剔除)。
IFDS框架将每个ow函数f表示为一个包含以下边集的图: {(0, 0)} ∪ {(0, y) | y∈f(∅)} ∪ {(x, y) | y∈f(x) and y f(∅)}。 上一段描述的两个函数在表2.1中被表示为图形。一旦所有的函数都被表示成紧凑的图,它们就可以被组合起来形成爆炸的超图。图2.14说明了爆炸的超级图。该图将IFDS问题转换为图-可及性问题。 简而言之,如果爆炸超图的一个节点可以从主方法的入口节点到达,这意味着与之相关的数据流事实成立。例如,方法main中语句p(x)的变量g的节点可以从main方法的enter节点的0处到达。这意味着在main方法中的语句p(x),变量g有可能是未初始化的。
IDE IFDS框架处理的问题是:数据流事实集D是有限的,数据流函数在2D→2D中。这个框架足以解决诸如 "达到定义"、"可用表达式 "或 "可能未初始化的变量 "等问题。然而,有些问题如 "线性常数传播 "问题不能用IFDS框架来编码,因为数据流事实的集合是无限的。
程序间分布式环境(IDE)框架[111]解决了程序点上的数据流信息由 "环境 "表示的问题。换句话说,数据流事实是从一个有限的符号集D到一组值L的映射。这种映射被称为环境,并被称为Env(D, L)。数据流函数被称为 "环境转化器",其形式为Env(D, L) Env(D, L)。
IDE框架是IFDS框架的概括:所有IFDS问题都可以表示为IDE问题,但并非所有IDE问题都可以表示为IFDS问题。一个IDE问题可以由一个超图G(对于一个给定的程序,IDE超图与IFDS超图相同)、一组程序符号D、一个半晶格L和一个环境变换器对G的边的分配来表示:M:E→(Env(D, L) Env(D, L))。
2.3 结论
在本章中,我们首先介绍了Android系统和Android应用的结构。这些领域的知识对于充分理解第4、5和6章是必要的,这些章节的方法总是应用于Android框架或Android应用。然后,我们介绍了静态分析的概念,如调用图构建或程序间分析。调用图构建是第四章中介绍的Android框架分析的基础,而第五章则主要依赖于程序间分析。
第三章
Dexpler:将Dalvik字节码转换为Jimple以实现Android应用程序的静态分析
本章的目标是使Android应用程序的静态分析成为可能。 本章介绍了Dexpler这个软件模块,它将Dalvik字节码(即Android应用程序的代码)转换为Jimple。Jimple是Soot的内部代码表示,是最流行的基于Java程序的静态分析工具之一。将Dalvik字节码转换为Jimple能够对Android应用程序进行静态分析和转换。
本章是基于已经发表在以下论文中的工作:
- Alexandre Bartel, Jacques Klein, Yves Le Traon, and Martin Monperrus. Dexpler:将安卓dalvik字节码转换为jimple,用于soot的静态分析。 在ACM SIGPLAN的Java程序分析技术状态国际研讨会(SOAP@PLDI)上,2012年。
3.1 简介
Android应用程序是用Java编写的。然而,它们不是以Java字节码的形式发布的,而是以Dalvik字节码的形式发布的。分析Android应用程序的一种可能性是使用Dalvik反汇编程序,如Smali [62] 或Androguard [41]。然而,它们并不是为执行高级静态分析而设计的,比如数据流分析,而且它们通常使用自己的字节码表示,这使得它们无法使用现有的工具来执行分析。 此外,用现有的Java静态分析工具来分析Android应用程序意味着必须有Android应用程序的Java源代码或Java字节码。
大多数时候,安卓应用的开发者并不分发他们应用的源代码,使得现有的Java程序分析工具无法对安卓应用进行分析。这对恶意软件的应用来说尤其如此,因为它们的源代码几乎是不可用的。
分析Android应用程序的另一种可能性是首先将Dalvik字节码转换为Java字节码,使用Ded[47]、Dex2jar[99]或undx[115],然后使用Java定制的静态分析工具,如Soot[131]、BCEL[35]或WALA[70]。产生Java字节码的工具可以利用现有的Java字节码分析器。然而,从Dalvik到Java字节码的转换需要时间,可以通过直接将Dalvik字节码转换为工具的内部表示来避免。
为了克服这些限制,我们引入了Dexpler1,这是Soot的一个模块,它直接读取Dalvik字节码,将其转换为Jimple,即Soot的内部代码表示法,并对Jimple表示法的局部变量进行完全类型化。然后,任何静态分析和/或转换都可以应用于Jimple表示。 使用这种方法消除了从Dalvik到Java字节码的中间转换步骤,可以使用更快更简单的工具链进行静态分析。
本章的贡献如下:
- 我们描述了一个叫做Dexpler的Dalvik到Jimple的转换工具
- 我们描述了一种对Dalvik字节码进行分类的算法
- 我们在超过2.5万个Android应用程序上评估了Dexpler。
本章的提醒内容组织如下。第3.2节是对Dalvik字节码的概述。在第3.3节中,我们描述了Dexpler,这个软件使Soot能够分析Dalvik字节码。在第3.4节中,我们在超过2.5万个Android应用程序上评估了Dexpler,介绍并讨论了结果。第3.5节解释了我们工具目前的局限性。最后,我们在第3.6节中总结了本章并讨论了开放的研究挑战。
3.2 Dalvik字节码和它的特殊性
一个Android应用程序是一个包含应用程序字节码的压缩包,描述应用程序结构的An- droid清单,它需要的组件和许可,以及数据文件(例如,图片,声音)。 在本节中,我们关注的是包含应用程序的Dalvik字节码的ILE,参照ILE名称的扩展,也称为dexILE。即使原始的Android应用程序是用Java编写的,在Android应用程序中也找不到Java字节码。Java代码首先被编译成Java字节码,然后由dx工具2转化成Dalvik字节码。使用Dalvik字节码背后的原因是,它是基于寄存器的,并为运行在内存和处理能力不足的设备上进行了优化。 dex ile的结构在第3.2.1节中描述。第3.2.2节介绍了Dalvik指令。然后,在第3.2.3节中解释了Dalvik字节码的规格。
3.2.1 整体结构
在本节中,我们首先描述了Java类的结构。然后,我们描述从Java类中生成包含所有Dalvik类的dex ile的过程。
Java类 如图3.1a所示,每个Java类只有一个储存常量值的地方(常量池)。在Java中,常量池是异质的,因为不同类型的对象是混合的(例如,类,对方法的引用,整数,字符串)。 每个Java类都包含一个常量池。
Dalvik类 一个Dalvik可执行文件是由dx编译器处理的N个Java字节码类生成的。产生的Dalvik字节码被存储在一个.dex ile中,如图3.1b所示。dex ile包含对Dalvik类的描述(名称、元素、方法...)和Dalvik字节码(代表具体方法代码的结构)。此外,dex ile包含四个同质常量池:字符串、类、字段和方法。所有的Dalvik类都共享这四个常量池。此外,一个.dex ile包含多个Class Dehni- tions,每个都包含一个或多个Method dehnition。每个Method dehnition都与存在于数据部分的Dalvik字节码指令相联系。
3.2.2 Dalvik指令
Java虚拟机是基于堆栈的。这意味着操作数是根据指令的语义从堆栈中推送和弹出的。另一方面,Dalvik虚拟机是基于寄存器的。这意味着大多数指令指定了它们所操作的寄存器的名称。这使得Dalvik字节码在语法上接近于Jimple代码,因为Jimple也使用基于寄存器的代码表示。图3.2.a表示Java字节码,其中值被推到堆栈,而图3.2.b表示Dalvik字节码,其中值被分配到寄存器v0和v1。
在Dalvik操作码常量列表中,有237个操作码。然而,12条odex(opti- mized dex)指令不能在Android应用程序的Dalvik字节码中找到,因为它们是在Android系统中生成的不安全指令,用于优化Dalvik字节码。 此外,还有8条指令从未在应用程序代码中被发现[94]。根据这些数字,在实际应用中很可能只有217条指令。
这组指令可以分为提供其操作的寄存器类型的指令(例如,sub -long v1, v2, v3增加两个长寄存器并将结果存储到一个长寄存器)和不提供类型的指令(例如,const v0, 0xBEEF在寄存器v0中存储一个未定义类型的值)。此外,null和0之间没有区别,它们都被表示为0值。
3.2.3 基元和空值
在这一节中,我们强调了Dalvik字节码的特征,这些特征对字节码寄存器的类型解析有影响。请注意,当我们提到Dalvik字节码变量时,我们使用术语寄存器,当提到Jimple变量时,我们使用术语局部或变量。
基元
在Java字节码中,基元变量是由一条指令初始化的,该指令规定了它的类型(例如,int, oat, long, double)。但在Dalvik中却不是这样,常量的初始化没有类型信息。图3.3中的代码片段突出了Java和Dalvik字节码之间的差异。在Java中,类型可以在每条指令中确定:整数常量是通过整数的特殊指令初始化的,而燕麦常量是从常量池中加载的,它们被标记为适当的类型(例子中的燕麦)。然而,在Dalvik中,类型信息无法在常量初始化指令中确定。然而,对于算术操作,Dalvik使用指令来指定操作数的类型。寄存器的类型也可以在它作为方法参数时被确定,因为每个被调用的方法的符号都会显示其参数的类型。简而言之,当分析Dalvik字节码时,只有当寄存器被使用时才能确定由常量初始化的寄存器的类型。
此外,在Dalvik中,Oat和整数常量都是以32位编码的。如图3.4所示,如果一个寄存器被32位常数初始化,那么该寄存器的使用仍然需要被分析以确定寄存器的类型。同样的,long和double常量都是以64位编码的。因此,一个64位的常数分配给一个寄存器并不能直接给出寄存器的类型。然而,分析寄存器的使用方式将给出寄存器的类型。
空值
Null被分配给一个对象的引用,表示它没有引用。在Java字节码中,Null是通过一个特殊的加载常量指令(见图3.2.b)和两条if指令来处理的,以检查一个对象引用是否为null或非null。在Dalvik字节码中,没有这样的指令:null被表示为整数值0。检查一个对象引用是否为空或者非空包括检查对象引用是否是一个等于零或者不同于零的整数。图3.2表明,在Java源代码(a)和字节码(b)中,0和null之间有明显的区别,而在Dalvik字节码(c)中,根本没有区别。正如我们将在第3.3节中看到的,在将Dalvik字节码翻译成Jimple时,缺乏类型和null的表示方法就成了问题。
3.2.4 异常
在Dalvik和Java字节码中,字节码包含异常处理程序。处理程序是方法的字节码中的一组特殊指令,当代码的某个部分抛出异常时,虚拟机会调用它。不处理Dalvik异常的特殊性就会导致代码无法打码。 这就是为什么我们在类型化过程中使用Dalvik的特殊异常模型,而不是原始的Java异常模型。
在构建方法的CFG时,我们必须从可能抛出异常的指令中添加边,以获得正确的异常处理程序的第一条指令。在Dalvik中处理异常的方式几乎与Java相同,但也有一些区别。在Java中,从一个方法返回的指令可以抛出一个异常,但在Dalvik中却不能。存储一个类常数和一个字符串常数的指令在Java中可以抛出异常,但在Dalvik中不能。关于数组,Dalvik只在索引超出范围或在数组引用上有一个空指针的情况下抛出数组指令的异常。
异常处理可以被认为是一个技术细节。然而,如果处理不当,就会破坏代码中变量的类型。 考虑一下图3.5中的代码。如果在label1和label2之间的指令可以抛出一个异常,就会调用label handler的处理指令。throw v4指令就是这种情况。如果Dalvik的返回指令被当作Java字节码的返回指令来处理,在CFG中就会有一条从返回指令之前的指令(即v1=<Object getObject>)到异常处理程序的边缘。这个边在图3.5中以虚线箭头表示。在这种情况下,当寄存器v1在处理程序中被使用时,其类型可能是int(来自getInt方法)和Object(来自getObject方法)。我们不可能获得这段代码的类型。
3.3 从Dalvik到类型化的Jimple代码
本节介绍Dexpler,Dalvik到Jimple的转换工具。 它利用Smali反汇编程序中的dexlib2库[62]来解析Dalvik字节码,并利用Soot快速打字,这是一个实现类型推理算法的Jimple组件[17],来给局部变量打字。然而,类型推理算法在从Dalvik字节码自然生成的Jimple代码上不起作用。我们在第3.3.1节中描述了这个类型化问题。然后,我们在第3.3.2节中描述Dexpler如何解决这个问题。
3.3.1 翻译的要求
图3.6表示Dalvik字节码的类型网格。 请注意,由于Dalvik字节码中的常量类型不为人所知,所以存在32位和64位的ab-stractions: 32位常量可以是Oat、char、short、byte、boolean或int类型(回顾图3.3,初始化指令不提供类型信息),64位常量是double或long类型。图3.7表示Java字节码的类型网格。在Java字节码中,oat、double、long和int的常量是通过指令指定其类型来初始化的。此外,在只能分配给对象的null和可以分配给int-like类型的值int 0之间有明显的区别。
我们的目标是将Dalvik字节码转换为Jimple,然后消除类型的模糊性,以便现有的类型化算法能够完全对代码进行类型化。 为了实现这一目标,我们考虑了图3.8所示的Dalvik类型网格的简化版本。 现有的类型化算法,如[18],可以对对象和int的子类型进行类型化,所以我们没有在简化的网格中完全表示它们。 我们想从图3.8中的格子(代表Dalvik字节码中的类型)到图3.9中的格子,在那里类型之间不能有歧义。
更确切地说,我们要区分(1)作为null的0和作为整数值0的0,(2)作为整数的32位常量和作为燕麦的32位常量(3)作为双数的64位常量和作为长数的64位常量。
一旦变量的类型与图3.9所示的格子相匹配,类型的局限性就被消除了,完全的类型化算法就可以用来对变量进行完全类型化。
图3.10说明了将Dalvik字节码转换为Jimple代码并进行类型化的过程。 首先,原始的Dalvik字节码(1)被dexlib23库解析,每个指令都被反汇编(2)。 从这个中间表示法中,生成未定型的Jimple语句,并将其连接起来,形成控制流图(CFG)(3)。下一步(4)是解决模糊的类型。 最后,在步骤(5)中,使用Bellamy等人[18]提出的有效的局部类型推理算法对所有Jimple局部进行类型化。步骤(5)被用来验证我们的方法。下一节将详细描述步骤(4)。
3.3.2 含糊的类型解析
我们在第3.2.3节中看到,以下指令缺乏类型信息:零值常量初始化指令(是零还是空?)和常量初始化指令(32位:整数还是燕麦?,64位:长还是双?) 下面通过看这些指令在代码中的使用情况来说明我们如何对这些指令的寄存器进行打字。
空值初始化 图3.12用图3.11的Java代码生成的字节码片段说明了这个问题。 寄存器v0在第01行被初始化为0。 在这一点上,我们不知道v0是一个整数、一个燕麦或一个对象的引用。在第02行,我们仍然没有得到答案。我们必须等到第04行的指令才知道v0的类型是Coordinate。 在这一点上,为01产生的Jimple指令必须被更新为一个空常数,而不是默认的数值为0的整数常数。 如果这一点没有得到正确处理,打字组件就会失败。 事实上,寄存器v0不可能既是一个整数又是一个坐标类型的对象。
数字常量的初始化 同样地,Oat常量的初始化不能与int常量的初始化区分开来,双倍常量的初始化不能与long常量的初始化区分开来。因此,我们通过Jimple语句的图形来了解常量的使用情况,并在需要时纠正Jimple语句的初始化。例如,如果一个oat/int常量(在Jimple语句中默认初始化为int)后来被用于一个oat加法,常量的初始化就会从int常量变为oat常量。
算法
我们的算法可以分为三个步骤,依次执行:
1. 阵列类型传播步骤
2. 空与零的区分步骤
3.整数版本的微分和长数与双数的步骤
第1步:数组类型传播 对于每一个未定型的常数,我们要看的是用一个常数值初始化的局部变量的使用。局部可以存储在一个字段中,作为一个方法参数使用,并存储在一个数组中。 对于字段和方法,常量的类型是已知的,因为字段签名和方法签名提供了足够的类型信息。另一方面,对于一个数组来说,类型信息并不总是已知的。一个典型的例子是当数组被别名时(即分配给另一个局部)。
为了传播类型信息,我们从数组初始化语句开始。 对于每一个这样的语句,我们都要确定数组的使用位置。如果数组被别名,我们将类型信息从数组转移到新的局部。 当到达一个ix点时,所有本地引用的数组都被类型化了。
第二步:null和Zero 对于这一步,我们设计了第50页的算法1。该算法从方法nullOrZero(第1行)开始。 该算法首先收集语句,用一个值为0的整数来初始化局部变量(第3行)。 然后,对于局部变量的每一次使用,方法forEveryUse为局部变量的类型集(第5-6行)增加一个类型,要么是 "作为对象使用",要么是 "作为整数使用"。如果一个局部变量的所有类型不一致(即它们不都是一样的),那么在该方法的字节码中就会出现类型不一致的情况,使其无法对字节码进行打字。在这种情况下,算法以错误代码结束(第8行),执行算法的代码会用一个默认的代码块来替换方法代码,这将抛出一个运行时异常。 另一方面,如果所有的类型都是一致的,那么局部变量的定义也会相应地被更新:如果常量被用作对象,那么零值会被替换为null(第10行)。
更确切地说,forEveryUse方法首先收集了本地变量定义d的所有使用(第16行)。对于每一次使用,它检查使用类型是否是一个对象或不是一个对象。如果可以确定使用类型,该类型将被添加到之前定义的类型集合中(第19行)。 如果不能确定使用类型,有三种可能性。 第一种可能性是,正在考虑的语句是一个别名语句。在这种情况下,别名局部变量被获取,forEveryUse方法被再次调用。第二种可能性是所考虑的语句是一个if语句。如果if语句的条件包含另一个局部,方法collectDefinitionsWithAliases就会被调用,同时调用另一个局部。顾名思义,这个方法收集了作为参数的局部的所有定义。它检查所有定义的类型,也检查这些定义的所有用途。最后一种可能是,算法没有为语句找到任何有效的类型。在这种情况下,类型被设置为未知类型。在使用语句的循环结束之前,会调用收集方法--DefinitionsWithAliases。 这在没有足够信息对当前分析的局部变量进行类型的情况下很有用。 事实上,算法可以通过查看局部变量的所有定义,以及查看这些其他定义是如何被使用的,来获得更多关于其类型的信息。
方法collectDefinitionsWithAliases是以一个局部变量为唯一参数调用的。它首先收集本地变量的所有定义(第38行)。对于每个定义,它检查局部是否被定义为一个对象(第40行)。如果找到了一个类型,就把它添加到类型集合中(第42行)。否则,它将检查该定义是否是一个别名声明。如果是,它检索别名并递归调用带有别名的collectDefinitionsWithAliases(第44-45行)。否则,如果该语句是一个零常数定义,则不添加类型,因为此时零常数的类型是未知的(第47行)。在所有其他情况下,未知类型被添加到类型集合中。最后,该方法调用forEveryUse来检查所有定义的所有用途的类型(第51行)。
第3步:Float vs. Int和Long vs. Double 在第1步和第2步之后,如果零整数常量被用作对象,它们已经被转换为空。在这一点上,我们仍然要正确输入32位常量(Oat和int)和64位常量(long和double)。这个方法与第2步非常相似。算法1可以在稍作改动后重复使用。方法getZeroDefs被方法getNumDefs取代,后者返回局部变量被分配了一个数字常数的定义(第3行)。方法isObjectOrNot_Use和isObjectOrNot_Def分别被方法checkNumType_Use和checkNumType_Def取代(第19行和第40行)。这两个新方法分别返回局部变量的use类型和def类型。方法areTypesConsistent检查所有类型是否相同,要么是全燕麦、全int、全long或全double(第7行)。最后,方法correctDefs根据正确的使用类型来更新定义。
第4步:完全类型解析 一旦常量定义之间的歧义得到解决,就可以运行Java代码的传统类型化算法。 我们使用Bellamy等人[17]所介绍的算法。在这个过程结束时,一个方法的代码就完全类型化了。
3.4 评估
我们在27,846个从Google Play下载的Android应用程序上评估Dexpler。这些应用程序总共有135,289,314个方法。其中,37,720,245(28%)个方法有一个数字零常数的定义语句。图3.13表示每个应用程序中含有数字零常数的方法的比例。总共有27,682个(99.41%)应用程序包含数字常数。这一结果表明,在分析Android应用程序时,处理数字常数是必须的。
这27,846个应用程序分布在卢森堡大学高性能计算(ULHPC)的100个节点上[132]。处理时间加上元数据(如每个方法的大小,每个方法的处理时间)在数据库中的存储需要36小时。
Dexpler为135,288,415(99.99%)个方法正确输入了变量。由于Soot产生的异常,Dexpler未能对56个(0.20%)应用程序和它们的所有方法进行分析。对于135个方法,步骤(5)没有成功运行。
3.4.1 关于失败应用的讨论
在27,682个应用程序中,56个(0.20%)没有被Dexpler正确处理。13个(23%)是由我们的应用程序爬虫中的错误或由无效的魔法数字产生的非有效的Android应用程序。五个(9%)是因为内存不足的异常。6个(11%)是因为无效的ield引用,32个(57%)是因为字节码包含无效的指令(例如,virtualinvoke而不是interface-invoke或interface-invoke而不是virtual-invoke 4)。
没有通过步骤(5)的135个方法有一个类似的特点:它们都有在多个分支中使用不同类型的变量。例如,一个初始化为0的变量可以在第一个分支中作为一个整数使用,而在第二个分支中作为一个空引用使用。解决这个问题的方法之一是沿分支传播变量的定义,并删除原始定义。这将产生两个变量,每个变量都有自己的类型。
3.5 局限性
尽管我们的算法在我们分析的2.5万个应用程序中99%以上都能工作,但是它还不能处理一些情况(除了第3.4.1节中的情况)。
3.5.1 无效字节码从未被执行,也从未被虚拟机检查过
Dalvik虚拟机(DVM)只在运行时需要时检查类的有效性。因此,攻击者可以创建一个包含无效字节码方法的假类。这个类不会被应用程序本身使用,所以它不会被DVM检查,应用程序可以成功安装在设备上。然而,分析应用程序字节码的工具,如Dexpler,将分析无效的字节码,因为它不知道这个类在运行时是否被使用。如果Dexpler不ind一个方法的有效类型,它就会用指令替换代码,抛出一个异常。
3.5.2 绕过虚拟机验证的无效的Dalvik字节码
Bremer[24]已经证明,有可能编写不被DVM检查的Dalvik字节码。这个概念证明利用了DVM中的一个错误,该错误不检查字节码,如果该类有一个特定的ag开启。这种字节码对于通常由DVM执行的类型约束来说可能是无效的。这个错误影响到所有4.3以下的Android版本。其后果是,可能无法正确输入这种字节码。在我们的实验中,我们检查了该bug中使用的特定类ag,没有发现任何使用该bug的应用程序。
3.5.3 隐藏的字节码
在字节码之外。 一个Dalvik程序可以在运行时动态地加载Dalvik字节码。然后,攻击者可以将字节码隐藏在一个ile(如图片)中,并在运行时加载它。 由于这个字节码不存在于dex ile inding中,所以在哪里转换它。Dexpler只转换负责加载隐藏字节码的那部分代码。静态地确定隐藏字节码的位置已经超出了范围。
在字节码内。 Schulz [117]提出了一种将Dalvik字节码隐藏在Dalvik字节码阵列中的技术。 这可能会混淆线性字节码分析器,它们可能不会将数组内容正确解释为有效的Dalvik字节码。 我们为每条分支指令检查目标是否是有效的指令,而不是数组中的数据。我们从来没有发现一个分支指令指向数组中的数据的情况:在用于评估Dexpler的任何应用程序中,都没有使用这种混淆的方法。目前Dexpler并不处理这种混淆的字节码。
3.6 结论
Dalvik字节码不能被现有的Java工具所分析。在这一章中,我们介绍了Dexpler 5,这是一个Soot修改,可以将Dalvik字节码转换为Jimple,并允许静态分析Android应用程序。Dexpler利用Dalvik异常的模型来构建方法代码的精确控制ow图,并利用一种算法来解决类型不明确的变量。 该算法从识别具有模糊类型的变量开始,然后通过查看代码中这些变量的使用情况来推断其实际类型。Dexpler已经对来自27,846个Android应用程序的135,289,314个方法进行了评估,并对99.9%的分析方法的变量进行了完全类型化。
第四章
寻找和消除权限差距,减少Android应用的攻击面
本章的目的是检查开发者是否给他们开发的安卓应用提供了太多的权限。减少权限的数量可以减少恶意用户利用应用程序的攻击面。我们利用Dexpler来分析应用程序的代码,以检查他们真正需要的权限。这需要深入分析安卓框架,提取API方法(安卓应用程序调用)和所需权限之间的映射。我们提出了一种类似于安徒生的、对领域敏感的方法,使用新的领域特定的优化来从Android框架中提取映射。
本章基于已经发表在以下论文中的工作:
- Alexandre Bartel, Jacques Klein, Martin Monperrus, and Yves Le Traon. 通过减少攻击面自动保护基于权限的软件: 对An- droid的应用。在第27届IEEE/ACM自动软件工程国际会议(ASE)论文集,2012。短文。
- Alexandre Bartel, Jacques Klein, Martin Monperrus, and Yves Le Traon. 静态分析提取大规模框架的许可检查: 分析Android的挑战和解决方案. 在IEEE Transactions on Software Engineering (TSE), 2014.
4.1 简介
移动操作系统Android和黑莓的安全架构以及其他系统,如谷歌Chrome浏览器扩展系统,都使用类似的安全模型,称为基于权限的安全模型[11]。基于权限的安全模型可以松散地定义为这样一种模型:1)每个应用程序都与一组允许访问某些资源的权限相关联1;2)用户在安装过程中明确接受权限;3)当资源被请求时,在运行时间检查权限。
在Android中,权限模型被嵌入到 "Android框架 "中。该框架公开了一个应用编程接口(API),其中包含了供开发者与系统资源互动的类和方法。例如,API包含一个getGPSLocation2方法,如果有的话,它可以提供智能手机的当前GPS位置。这个API方法,以及其他许多方法,在安全或隐私方面是敏感的。因此,在响应对getGPSLocation的调用时,该框架会检查调用者是否已被明确授予GPS权限。
这种权限模型对应用程序的开发过程有影响。为了编写一个应用程序,开发人员必须为他们使用的每个API方法确定必须声明的权限,以便应用程序能够正确工作。他们需要一个API方法和所需权限之间的映射。
在安卓系统中,这种映射由官方文档提供。然而,这些文档并不总是最新的或清晰的,因此,问答网站上充满了关于使用权限的问题3。这导致开发者经常低估或高估所需的权限。缺少一个权限会导致应用程序崩溃。添加太多的权限则不安全。在后一种情况下,被注入的恶意软件可以利用这些已声明但未使用的权限来达到恶意的目的。我们称这些未使用的权限为 "权限缺口"。任何权限缺口都会导致不安全的、可疑的或不可靠的应用程序。
总而言之,在Android这样一个基于权限的系统中,有一个清晰而精确的映射,将API方法和所需的权限联系起来,是非常有价值的。它使开发者能够轻松地声明他们实际需要的权限:不多也不少。
为了提取这个地图,我们在本章中探讨了使用静态分析来提取每项任务的检查。在Android这种规模和复杂性的框架中,使用现成的静态分析的天真方法会惨遭失败。本章讨论了为提取API方法和权限之间有价值的映射而必须组合在一起的构件,有两种分析方法:基于类层次结构(CHA)和类似于安徒生[2]的场敏感的分析方法,即Spark[81]。 在技术上,我们描述了在Android中提取每项任务检查所需的五个组件。第一个是通用的字符串分析,但对于权限不是静态常量而是动态字符串的安卓系统是必不可少的。其余的是专门针对Android的。在这四个组件中,最后两个组件是专门针对Spark的。服务重定向(Service Redirec- tion)将对服务的调用重定向到一个正确初始化的服务(Android专用)。服务身份反转(Service Identity Inversion)避免分析对服务的不相关的系统调用(Android-speciic)。服务初始化正确地初始化服务以克服空值(Spark speciic)。 入口点初始化初始化所有入口点方法及其参数(Spark speciic)。这项研究的主要困难在于,由于安卓系统的规模和复杂性,没有任何一个构件能单独产生可接受的结果。最终,我们表明Spark可以产生一个很好的API方法到权限的映射,并与相关工作[53, 6]进行了比较。
总而言之,本章的贡献是:
- 经验证明,现成的静态分析并不能解决像Android这样的框架的权限检查的额外问题;
- 三个静态分析组件(通用和Android专用)被组合在一起,以便在Android上使用类层次分析(CHA);
- 两个静态分析组件,允许人们使用场敏感静态分析(Spark [81])来分析Android的权限;
- 将我们的结果与PScout[6]进行比较,PScout是一个与我们平行设计的静态分析,与Felt等人的基于动态分析的结果进行比较[53];
- 在两组1421个真实的Android应用程序上应用提取的映射,显示129个(9%)应用程序存在权限缺口,即它们拥有超过必要的权限。
本章的提醒内容组织如下。在第4.2节中,我们解释了为什么减少攻击面很重要,并提出了一个支持我们直觉的简短研究。在第4.3节中,我们提出了一个基于许可的软件的形式化。在第4.4节中,我们描述了An- droid系统和它的访问控制机制。然后,在第4.5节中,我们使用静态分析从Android系统中提取权限图。在第4.6节中,我们进行了实验,并对结果进行了讨论。在第4.7节中,我们提出了一个通用的方法来推导出正确的应用程序权限集。最后,我们在第4.8节中总结了本章,并讨论了开放的研究挑战。
4.2 权限差距问题
现在让我们详细介绍一下第1节中介绍的权限差距问题。我们还提出了简短的经验事实,表明这个问题在实践中确实存在。
权限差距的可能后果 让我们考虑一下appwrong,一个Android应用,它能够与外部服务器通信,因为它被授予了INTERNET权限。 此外,appwrong还声明了CAMERA权限,但它并没有使用任何与相机有关的代码。 CAMERA权限允许应用程序在没有用户干预的情况下进行拍照,也就是说,权限差距由一个权限组成: CAMERA。不幸的是,appwrong使用了一个本地库,最近发现了一个缓冲区溢出的漏洞。
因此,攻击者可以通过利用缓冲区溢出漏洞,在appwrong的过程中执行其选择的代码。攻击者在appwrong中执行的代码被授予appwrong、INTERNET但也包括CAMERA中定义的所有权限。这有效地增加了攻击者的权限。在这个特殊的例子中,攻击者将能够(1)编写代码来使用相机,拍摄照片并将照片发送到互联网上的远程主机,(2)通过利用缓冲区溢出漏洞在目标应用程序中执行这些代码。Davi等人[36]对这种攻击进行了详细描述。
相反,如果appwrong不声明CAMERA,这种攻击就不可能发生,而缓冲区溢出漏洞的后果也会得到缓解。正如Manadhata[85]所指出的,减少攻击面并不意味着没有风险,而是减少风险。为了说明这个错误配置的应用程序的例子不是假的,我们现在讨论一个简短的实证研究,关于1000多个Android应用程序的两个权限的声明。
权限 "相机 "和 "录音 "的声明和使用 我们对1000多个Android应用程序进行了一个简短的实证研究。
我们对从Freewarelovers应用市场下载的1000多个Android应用进行了简短的实证研究4。对于权限CAMERA和RECORD_AUDIO,我们搜索了An- droid框架的源代码,以接近需要其中之一的方法集。这两组方法被称为MCAM和MREC_AUDIO。然后,我们计算了所有声明CAMERA或RECORD_AUDIO的应用程序的列表A。接下来,我们把每个应用app∈A单独拿出来,通过分析应用的字节码,检查该应用是否至少使用了MCAM和MREC_AUDIO的一个方法。如果没有,这意味着该应用程序没有使用相应的权限。当这种情况发生时,我们修改了声明该权限的应用程序清单,并再次运行该应用程序,以确保我们的搜索近似值没有产生误报。
有7/82的应用程序声明了CAMERA,但没有使用它。 同样地,有3/35的应用程序声明但没有使用RECORD_AUDIO。这些结果证实了我们的直觉:声明的权限列表并不总是必需的,而且权限缺口确实存在。开发者将受益于一个能自动推断出所需权限集和近似权限缺口的工具。
4.3 定义
基于权限的软件在概念上分为三层:1)核心平台(运行系统),它能够访问所有的系统资源(例如,改变网络策略);2)中间件,负责为操作系统资源提供一个干净的应用编程接口(API),并在应用程序要访问它们时检查它们是否有正确的权限;3)建立在中间件之上的应用程序。他们必须明确地声明他们所需要的权限。第2层和第3层是 "基于权限的软件 "这一通用标签的动机。由于中间件也隐藏了操作系统的复杂性并提供了一个API,它有时被称为 "框架",如Android的情况。现在让我们来定义一下这些术语。
框架 框架F是一个层,使应用程序能够访问平台上的可用资源。我们将其建模为一个双向图,其中API方法节点集合中的每个节点都连接着资源节点集合中的一个节点(该集合还包含一个 "无资源 "节点)。例子: 在图4.1中,该框架由九个方法组成(其中四个是公共的)。应用程序通过四个API方法访问该框架。在Android的例子中,F是由4,071个类和126,660个方法组成的Android 4.0.1 Java框架。要访问一个资源,Android应用程序必须通过F进行方法调用。
权限 权限是一个应用程序访问特定资源所需的令牌。例子: 在图4.1中,应用程序声明了两个权限。框架定义了三个权限,但只检查两个。我们对权限不做任何假设,我们认为它们是独立的(既不是分组的,也不是分层的)。
基于权限的系统 一个基于权限的系统至少由一个框架、一个权限列表和一个受保护资源列表组成。每个受保护的资源都与一个固定的权限列表有关。
入口点 一个框架的入口点是一个应用程序可以使用的方法(例如,公共的或记录的)。构造者也被认为是入口点。我们把EntryF表示为F的所有入口点的集合。
例子: 在图4.1中,有四个入口点(e1到e4)。 一个应用程序可以调用框架的任何公共方法。一些访问系统资源的方法(如账户)受到一个或多个权限的保护。在安卓4.0.1的情况下,有50029个入口点。
声明的权限 一个应用程序的声明的权限是一个在
在应用程序的权限列表中. 一个应用程序的所有声明权限的集合被称为Pd(app)。
例子: 在图4.1中, 应用程序声明了p1和p2. 在安卓系统中, 一个应用程序的权限被声明在一个叫做清单的文件中.
所需权限 一个应用程序的所需权限是与该应用程序至少使用一次的资源相关的权限。 一个应用程序的所有必要权限的集合被称为Preq(app)。
推断权限 一个应用程序的推断权限是一个分析技术发现的应用程序需要的权限。
根据所使用的分析技术,推断出的权限列表可能是对所需权限列表的过度或不足的近似。当开发者编写清单时,他们会根据文档和试错来猜测Preq(app),从而编写Pd(app)。在第4.7节中,我们建议自动推断出权限列表Pifrd(app),以避免这种人工和容易出错的活动。
4.4 Android概述
本节概述了Android的架构。它首先提醒读者注意第2.1节,然后重点介绍与权限有关的部分。
4.4.1 软件栈
Android是一个具有不同层次的系统。它由一个修改过的Linux内核、C/C++库、一个叫做Dalvik的虚拟机、一个编译成Dalvik字节码的Java框架和一套应用程序组成。Android的应用程序是用Java编写的,并被编译成Dalvik字节码。Dalvik字节码经过优化,可以在内存和处理能力不足的设备上运行。一个安卓应用被打包成一个安卓包,其中包含Dalvik字节码、数据(图片、声音......)和一个叫做 "清单 "的元数据。
4.4.2 Android权限
应用程序供应商为每个应用程序制定了一套权限。 为了安装一个应用程序,用户必须整体上批准应用程序开发者在应用程序清单中声明的所有权限。如果所有的权限都被批准,应用程序就会被安装并获得组成员资格。组成员资格被用来在运行时检查权限。例如,一个应用程序Foo在安装时被赋予两个组成员资格net_bt和inet,分别具有BLUETOOTH和INTERNET的权限。换句话说,标准的Unix ACL被用来作为检查权限的实现手段。
安卓2.2版在android .Manifest$permission系统中定义了134个权限,而安卓4.0.1版定义了166个权限。这给了我们一个在Android框架中可以检查的权限数量的上限。
Android有两种权限:高层和低层权限。 高级权限只在框架层面上检查(也就是在Android SDK的Java代码中)。Android 2.2声明了八个低级权限,这些权限要么在C/C++本地服务中检查(例如RECORD AUDIO),要么在内核中检查(例如在创建套接字时)。
在本章中,我们重点讨论只在Android Java框架中检查的高级权限。
4.4.3 服务和权限
一个Android应用程序是由以下组件组成的:一个作为用户界面的活动;一个在后台运行的服务;一个监听 "意图"(一种用于进程间通信的消息)的BroadcastReceiver(或Receiver);一个用于存储和共享数据的数据库ContentProvider。大多数权限是在服务层面检查的。
Android应用程序使用一种特殊的服务与操作系统进行通信,这种服务称为系统服务。系统服务是在一个特定范围(称为 "系统服务器")内运行的特殊服务,允许应用程序访问系统资源(例如:GPS坐标)。这些资源可能受到安卓权限的保护,以防止未经授权的应用程序访问。与服务相关的权限检查大部分是用Java实现的。因此,本章的范围包括分析在Java框架的服务中执行的Android权限。这一重点的影响将在第4.6节讨论。
了解系统服务的内部工作对设计良好的静态分析非常重要(这将在第4.5.2节中介绍)。我们现在描述应用程序如何与系统服务进行通信。应用程序通过一个叫做Binder的机制与系统服务进行同步通信,如图4.2所示。与远程服务通信的第一步是通过调用Con - text .getSystemService()动态地获得该服务的引用(接口)(图4.2中的步骤1)。下一步是调用对象引用r上的接口的方法(图4.2中AccountManager服务的getPassword方法)(图4.2中的第2步)。一个被称为 "绑定器 "的特殊组件负责拦截并将该服务调用重定向到执行实际操作的远程服务(图4.2中的步骤3)。 系统服务负责执行权限检查(图4.2的第4步)。为了检查调用者的应用程序是否在其清单中声明了权限(第4.4.1节),该服务调用附录(表4.1)中列出的方法之一,并把要检查的权限作为参数(图中未显示)。程序中的这个特定点被称为权限执行点或PEP。在图4.2中,如果应用程序有正确的权限,密码将被返回给调用的应用程序(步骤5)。
4.4.4 Android启动过程
在用Spark进行精确的静态分析时,知道如何初始化系统服务是很重要的(4.5.3节)。 如果服务没有被正确初始化,分析可能是不完整的。
让我们回顾一下2.1.3节,并进一步详细说明Android的启动过程。 在设备上运行的第一个程序是引导程序,它为加载、恢复或更新系统镜像提供支持。用于加载Linux内核的早期启动代码非常依赖于硬件:它首先初始化环境,然后才通过跳转到start_ kernel()函数来启动独立于架构的Linux内核C代码。 然后,高层的内核子系统被初始化(调度器、系统调用、进程和线程操作......),根文件系统被挂载,init进程被启动。
init进程创建挂载点和挂载iles系统,设置iles系统的权限并启动daemon,如网络daemon、zygote或服务管理器。zygote是一个核心进程,新的Android进程从它那里分叉出来。 zygote的初始化会启动系统服务器,而系统服务器又会初始化系统服务和管理器。系统服务包括输入管理器服务和wii服务。管理器包括处理用户界面(活动)的活动管理器。
Android的启动过程表明,系统服务和管理器在启动时被实例化和初始化了。
4.4.5 安卓通信
正如第2.1.2节详细介绍的那样,组件通过绑定器、Android特有的进程间通信(IPC)机制和远程方法调用(RMI)系统相互通信。组件并不直接与绑定器进行通信,而是依赖于三种高级别的通信抽象,即意图、查询和代理。图4.3着重介绍了Android框架中Java层面的通信。它显示了一个应用程序通过代理和存根(在绑定器之上的抽象)与系统服务器(以及系统服务)进行通信。
Intent Intents描述了要执行的操作。它们被用来启动一个新的用户界面屏幕(Activity),触发一个监听意图的组件(BroadcastReceiver)或与服务进行交流。
查询/Uri 查询用于与内容提供者组件(例如,通过数据库共享数据)进行通信。查询使用统一资源标识(URI)来指示必须在其上执行查询的目标提供者组件。
代理/存根 系统服务扩展了存根类,描述了它们必须实现的方法。 系统服务主要被应用程序用来访问系统资源。 它们被其他组件通过它们的公共接口(称为代理)访问。系统服务在系统服务器中运行,并被注册到服务管理器中。一个应用程序可以通过服务管理器获得对已注册服务的引用,然后可以通过其代理(使用绑定器)与该服务进行通信。
4.5 Android框架的静态分析
我们的目标是设计静态分析来提取权限检查。从本质上讲,每个分析都从字节码中构建一个调用图,输入权限检查方法并提取权限名称。
获得一个有意义的调用图是一个挑战。 我们运行了默认的Soot的CHA-Naive
(Class Hierarchy Analysis)对Android 4.0.1的50; 029个入口点方法。它花了一个多星期的时间,输出了31;458(64%)个没有权限的方法,一个有单一权限的方法5和18;381(36%)个入口点(方法),每个都需要超过100个高级权限。这并没有什么意义。原因是安卓系统是用面向对象的范式实现的,有许多核心类的子类(例如,服务6、活动7等)。 根据构造,CHA的输出是这些类的所有客户端都调用它们的所有子类。 这就导致了调用图中边的爆炸,从而导致了所需权限的爆炸。 设计静态分析以提取权限检查的主要挑战是如何获得一个精确的调用图。
我们的目标仍然是使用CHA,但我们需要为Android定制它。我们还打算使用Soot的Spark[81],一个类似Andersen的点对点分析。我们运行CHA的动机如下。 首先,它使我们能够识别关键的安卓特有的分析组件。 这些组件可以在更复杂的分析中被重用,如Spark。第二,它为我们提供了一个评估Spark所带来的改进的基线。第三,它给出了一个没有权限的API方法列表,这些方法不需要被Spark分析。最终,Android特有的分析组件和Spark的最佳组合使我们能够获得一个精确的权限图。
图4.4代表了操作框架字节码的安卓专用组件,并生成和初始化入口点。 CHA-Android,为Android定制的CHA版本,需要生成入口点,在第11节中介绍,服务重定向,在第11节中描述,以及服务身份反转,在第11节中详述。 除了这些组件,Spark-Android,Android定制版的Spark,需要正确的入口点初始化以及服务和管理器初始化。这些组件将在
4.5.3节中描述。
在我们的实验中,调用图是由Android API 4.0.1版本中的50,029个入口点产生的。 所有的分析都使用Soot[76],一个广泛使用的Java程序静态分析框架。 实验在Intel(R) Xeon(R) CPU E5620 @ 2.40GHz上运行,运行GNU/Linux Debian 3.11;Java虚拟机1.7.0被赋予4Gb的堆内存。实验中使用的Android版本是4.0.1,除非另有说明。
第4.5.1节介绍了修改字节码和从调用图中提取每个任务的不同组件。 第4.5.2节描述了CHA-Android分析,第4.5.3节描述了Spark-Android分析。
4.5.1 CHA和Spark的通用组件
在这一节中,我们介绍了CHA和Spark都需要的三种技术。 字符串分析被用来从调用图中提取权限名称。服务重定向使调用图构建算法能够绕过ICC胶水代码,将服务调用者与服务本身联系起来。最后,服务身份反转从调用图中删除了作为系统服务本身执行的代码,因此从入口点调用者的角度来看是不相关的。
从许可执行点提取许可的字符串分析
基本的调用图只能给出权限检查的数量,而不能给出被检查权限的实际名称,因为缺乏从字节码中提取权限名称的字符串分析。正如第4.4.3节所解释的,权限执行点(PEPs)是对类Context和ContextWrapper的6个方法的方法调用(PEPs的列表见附录中的表4.1)。这些方法调用可以被静态地解决。然而,被检查的实际权限是由一个字符串参数或有时是一个字符串数组动态设置的。因此,当在调用图中发现一个检查权限的方法时,基本分析只能知道发生了一个权限检查,但不知道哪个确切的权限被检查,因为调用图本身并不处理字面和变量的解析。
为了克服这个问题,我们将字符串分析作为一个Soot插件来实现,其伪代码显示在算法2中。 一旦发现PEPs,它就会提取相应的权限(第5行)。这个插件执行方法内分析并管理以下情况:(1)权限直接作为字面参数给出,或者(2)权限值被初始化在一个变量中并作为参数给出,或者(3)一个数组被初始化为几个权限并作为参数给出。 在每一种情况下,我们都会使用Soot的单元图对方法的字节码进行逆向分析,该单元图描述了一个方法的语句之间的关系。 在只有单一权限的情况下,单元图中包含对有效的Android权限字符串的引用的语句被提取出来,并将该权限添加到分析中的方法所需的权限列表中。在数组的情况下,所有对Android权限字符串的引用的权限都被添加到列表中。
可能发生的情况是,在当前方法Mi的主体中找不到权限字符串。这种情况发生在用当前方法的一个参数P初始化的局部变量引用权限字符串时。在这一点上,分析通过Mi 1的语句来寻找对M的调用。当找到一个调用时,参数P被提取出来,字符串分析从这里开始。
服务重定向: 处理基于粘合剂的通信
权限大小爆炸 对服务方法的调用通常要经过一个管理器,该管理器得到对一个叫做代理的系统服务的引用。 它总是对代理的方法进行调用,导致数据从代理通过绑定器转移到存根,存根上有真正的系统服务方法。 代理和存根之间的所有数据传输都通过transact()方法进行,该方法调用onTransact()方法。这个方法根据一个整数值来调用系统服务端的正确方法。这个整数值在做静态分析时是不确定的。因此,如图4.5所示,系统服务的所有方法都被添加为调用图中的边。此外,由于所有的系统服务都实现了一个存根,当使用CHA构建调用图时,所有的系统服务存根的onTransact()方法都是来自代理对象上的每个方法调用的潜在方法调用,因此被添加到图中。这样做的后果是我们在运行CHA时观察到权限集大小的爆炸。简而言之,当从服务的角度做一个天真的分析时,任何系统服务的方法调用确实有边到每个系统服务的所有方法。
服务重定向 图4.2说明了一个应用程序和一个服务之间的通信。该通信是通过绑定器完成的。正如上一段所解释的,问题在于分析基于绑定器的通信会导致权限数量的爆炸。图4.5所示的解决方案是绕过绑定器(代理/存根)机制,将对服务方法的调用直接连接到远程服务的相应方法。在图4.2中,从方法r .getPassword()到绑定器以及从绑定器到服务方法getPassword()的边被移除。只有从调用方法到被调用方法的直接边(图中未显示)被保留。如图4.4所示,这是在Android框架的字节码上做的第一次转换。
服务身份反转
在Android中,服务可以用初始调用者的身份(通过de-fault)或者用服务本身的身份调用其他服务。在后一种情况下,远程调用是在clearI - dentity()和restoreIdentity()方法调用范围内。当使用服务自己的身份时,权限检查不是针对调用者的声明权限,而是针对服务的声明权限。由于我们的目标是计算一个应用程序(而不是系统服务)的权限差距,我们可以安全地放弃所有在调用clearIdentity()和restoreIdentity()之间发生的权限检查。
例如,让我们假设服务S需要并声明了应用程序A没有声明的权限。如果A调用S,S的代码就会以A本身的身份执行,这就需要A声明. 为了避免这种情况,需要的那部分代码是以S的身份执行的。当我们遇到对clearIdentity()或restoreIdentity()的调用时,我们使用程序内ow-sensitive分析来丢弃这些调用之间发生的权限检查。
图4.4显示,服务身份反转步骤是在服务重定向转换之后完成的。
CHA的入口点处理
在API的情况下(如Android API),问题是没有 "main",而是有N个类共M个入口点方法。我们的解决方案是通过为框架的每个公共类(对于Android,android .*和com .android .*)创建一个假的方法mclassi(i∈(1,. ., N))来为Android API的每个公共方法建立一个调用图。方法mclassi的作用是创建一个classi的实例o,并在o上调用classi的所有方法。我们还建立了一个独特的artiicial main来调用所有mclassi方法。这个main方法是分析的唯一起始点。如图4.4所示,入口点是用Android API的方法构建的。
安卓API中的方法来构建入口点。
第4.5.2节介绍了CHA-Android,它利用了服务重定向、服务标识反转和入口点构建组件。
4.5.2 CHA-Android
我们用CHA进行地图构建,因为它使我们能够识别关键的Android特定的分析组件,这些组件可以在更复杂的分析中重复使用,如Spark,它为我们提供了一个评估Spark改进的基线,更重要的是,它提供了一个超过30K的API方法的列表,这些方法没有权限,不需要被Spark分析。
CHA-Android是一个基于CHA的静态分析,用于提取An- droid框架的权限检查。它使用了第4.5.1节中提出的字符串分析,第11节中的服务重定向(Binder),以及第11节中解释的服务身份反转。我们用一个我们现在描述的优化来丰富它。
呼叫图谱搜索优化
第4.5.1节描述了如何提取权限名称。 本节解释了许可名称是如何通过图从PEPs中传播的。算法3在图中传播权限集。 它分三步进行。 第一步(第2行)使用深度优先搜索遍历图,并跟踪已访问的方法。 在遍历过程中,它发现了检查权限的地方并提取了权限名称(见上面的字符串分析)。这第一步使分析比天真的方法快得多,因为没有方法被分析过一次。第二和第三步确保已经分析过的方法的权限在图中得到传播。在第二步(第3-4行),我们使用Tarjan的算法[tarjan:connectedComponents],用一个节点替换图中的强连接组件(SCC)。这实质上是消除了图中的循环,简化了权限名称的表述。在这一步骤中,我们必须注意不要删除图中的重要部分,如检查权限的方法,因为权限在这一阶段没有被传播。具体来说,如果一个检查权限的方法是SCC的一部分,它就不能被删除,否则映射到这个方法的权限就不会被传播,从而丢失。第三步也是最后一步(第5行)将权限传播到整个图中。
这个算法在节点和边的数量上有一个线性的复杂性。在第一步中,图是使用深度优先搜索的,方法不会被分析两次:这一步与边和节点的数量呈线性关系。Tarjan的算法在节点和边的数量上是线性的。最后一步是通过对图的深度优先搜索来传播权限,其中SCC被替换。
经验结果
权限字符串解析 现在让我们分析一下字符串分析的有效性。表4.2列出了字符串分析的结果分布。我们观察到91.89%的权限字符串分析只检查一个权限,83.25%的分析中权限字符串可以直接确定为一个字面参数。因此,在Android的Java代码库中,常见的做法是:(1)保护一个只有一个或两个权限的方法;(2)在同一个方法体中参考权限字符串并调用检查权限方法。这些结果显示,对于99.08%的权限检查,使用字符串分析可以找到权限字符串。
有时(0.92%),不可能解决权限字符串:在12种情况下,权限与URI有关;在两种情况下,权限是从Binder(包裹)中读取的。
执行时间 在Android上,CHA-Android在4分钟的用户时间或10分钟的实时时间内分析了50029个入口点。这表明CHA-Android能够在大规模的真实世界框架上进行扩展。
入口点权限集 运行CHA-Android得到的表4.3显示了入口点的每个任务集大小。由于CHA-Android正确地模拟了系统服务通信,不需要权限的入口点的数量从64%增加到65.1%(31,458到32,429)(一些服务方法不受权限保护),具有一个和两个权限的入口点的数量分别从不到0.01%增加到0.08%(1到39)和从0%增加到0.12%(0到55)(服务方法重定向避免了调用图中边数的爆炸,从而避免了权限的数量)。
然而,34%(17,011)的入口点仍然有一个过度近似的权限集。这是由CHA的点对点集合的不精确性造成的。这导致了权限数量的爆炸。 一个改进是开发其他领域特有的优化:处理其他Android特有的点(如内容提供者、处理程序和消息)与处理服务通信类似,不会对本章的贡献产生影响。
下面的4.5.3节介绍了基于Spark的分析。该分析解决了Spark特有的问题,如入口点初始化或Android特有的问题,如服务初始化。
4.5.3 Spark-Android
我们在上下文不敏感,路径不敏感,ow不敏感,ield不敏感的模式下运行Spark来生成调用图。回顾第2.2节,在上下文不敏感模式下,对同一方法的每个调用都被合并为一条边,与上下文(接收方和参数值)无关。对路径不敏感的分析忽略了条件性分支,因此考虑到了方法体的所有路径。调用图的构造是不敏感的,因为它不考虑指令的执行顺序。它也是领域敏感的,因为它使用和传播初始化数据(例如构造函数调用)来减少边的数量。
我们首先在第4.5.3节中运行一个天真的Spark-Android版本,以说明需要对调用API方法的对象以及方法的参数进行正确的初始化。
第4.5.3节描述了我们如何初始化入口点。它还解释了另一个Spark的微妙之处:为什么以及如何对系统服务进行初始化。
Spark的天真用法
至于CHA,我们 "天真地 "运行现成的Spark来初步了解分析Android API时出现的主要问题。这给了我们一个关键的启示,Spark抛弃了96%的要分析的API方法。 原因是Spark是,ield-sensitive,它只处理静态方法,不处理在未初始化的引用上调用的方法(例如,默认以null初始化)。这意味着,如果不正确地初始化入口点,就不可能运行基于Spark的分析。 即使是对CHA进行关键的Android特有的静态分析,对Spark的天真使用也完全失败。因此,我们需要Spark特定的分析组件。
Spark的特殊分析组件
处理时间 我们的第一次实验表明,Spark的规模与An- droid框架的规模不相称。由于我们体验到Spark在处理一些入口点时很耗时,我们清空了某些类的speciic方法,以便能够在现实的时间内(即少于一天)计算出权限集。
分析耗时的入口点总是导致窗口系统类。窗口系统是Android组件的核心,如活动。 它负责GUI(图形用户界面)的管理,并与众多的GUI抽象有关系,如按钮或文本栏和启动Android组件的方法,如其他活动。当调用图碰到窗口系统的一个组件时,由于点到集的不精确性,它可能会以如此巨大的比例增长,以至于其中的搜索会触发超时。
我们假设,负责GUI渲染和窗口系统管理的类不与任何权限检查相联系。因此,我们删除了他们方法的代码,并再次启动实验。删除这些代码意味着(1)Spark不会为这些代码构建调用图,因此(2)调用图的遍历会快很多。通过这些修改,权限图的计算时间要快得多,在不到11个小时的时间内终止,并且不会触发任何超时。
Spark的入口点处理 Spark-Android利用了为CHA生成的人工方法(见第11节)。然而,它必须初始化Android API的50,029个入口点方法的参数。每个调用Android API方法的接收对象o以及每个方法参数p都分别通过调用generateo()和generatep()进行初始化。 这个量身定做的方法产生了所有可能的P类型的实例(即过度近似)。参数初始化是必要的,因为人们并不预先知道参数对权限检查的影响。由于Spark对字段敏感,非初始化的参数会导致调用图中的边丢失。
服务初始化对Spark的重要性 基于Spark的方法确实需要对Android框架的分析模块进行适当的初始化。原因是,如图4.6和4.7所示,跳过初始化阶段可能会导致重要的边,例如,包含对系统服务的引用,只能指向null。Spark不会为那些只能指向null的引用的方法调用生成边沿。
图4.6是一个代码片段,它检索了一个AccountManager对象,并对其调用了getPassword()方法。 在这一点上,AccountManager的服务引用mServ只能指向null。 因此,mServ .getPassword()不能被执行,也不会被表示在一个对字段敏感的调用图中。换句话说,Spark为AccountManager对象生成了一条边,但没有为其中的服务方法调用生成边,因为服务引用(mServ)指向null。
这个AccountManager对象是由Context类创建的,如图4.7所述。为了简化,只有AccountManager对象在方法getSystemService()中被创建。要创建一个AccountManager对象,需要对AccountManagerService的引用。这个引用是通过调用getService()来获取的。然而,由于ServiceManager还没有被初始化,ServiceManager的sCache map是空的。 因此,getService()总是返回null。
静态分析的服务初始化 正如附录中所详述的,系统服务在SystemServer类中被初始化。这个类的方法不存在于Android API入口点产生的调用图中,因为它们只在系统启动时被调用。
为了模拟系统服务的初始化,我们为每个具体的系统服务创建一个静态对象和一个初始化方法。这些对象是通过向调用图中的服务初始化方法添加边来初始化的。此外,原始字节码被修改为用对新创建的静态对象的引用来代替对getService的调用。
静态分析的管理器初始化 Android应用程序有两种可能性与系统服务进行通信
- 第一种可能性是通过服务管理程序直接获得对服务的引用8,然后调用服务的远程程序。
- 另一种可能性是使用另一个叫做Manager的接口。 管理器是由系统的Context类创建的,它本身有一个对服务的引用,可以直接与之通信,并作为应用程序的代理(如图4.6所示)。
管理器是简化与系统服务通信的包装器。我们将对getSystemService(String s)的调用重定向到我们自己的方法。为了做到这一点,我们用字符串分析法计算了给getSystemService的字符串和初始化相应管理器的代码之间的映射。对getSystemService的每次调用都进行分析,以提取字符串参数,从而知道必须将其重定向到哪个方法。 每个字符串对应一个管理器,因此有一个方法的作用是初始化管理器。
我们还提供了我们自己的getService()方法,如第4.5.3节所述,返回正确初始化的服务。所有对原始getService()的调用都被重定向到我们自己的方法。 方法getSystemService返回一个管理器,而方法getService()返回一个服务的接口。
Android框架的原始字节码被修改以反映服务和管理器的初始化。 由此产生的字节码可以由任何静态分析工具进行分析,而不是专门针对Soot的。
实证结果
Spark-Android的运行时间为11小时。 表4.4中描述了运行Spark-Android时入口点的权限集大小。具有单一权限的入口点的数量是471个。此外,48个入口点的权限集为2,10个为3,3个有3个以上的权限。由于抽象类不能用Spark初始化,所以入口点的总数比CHA的少。在入口点方法集中没有与这些类相关的方法被代表。
4.5.4 总结
我们已经介绍了我们在实现我们的方法时遇到的核心技术问题。我们认为这些问题可能会出现在安卓以外的其他基于权限的平台上,确定这些问题和它们的解决方案对未来的工作有很大帮助。最后但并非最不重要的是,这些问题对于复制我们的结果至关重要。
第4.6节评估了基于CHA和Spark的分析。
4.6 讨论
与其他分析相比,第4.5节中介绍的6个分析的表现如何? 它们的局限性是什么?本节回答这些问题。
4.6.1 CHA与Spark的关系
图4.8是一个入口点方法的数量与权限集大小的累积图。我们所说的累积是指在每一个权限集的大小上,方法的数量被添加到前一个权限集大小的方法的数量上。它首先表明,分析越精确,零权限的入口点集就越大。这个结果反映了这样一个事实:随着精度的提高,"假阳性 "的边缘会从图中移除。然后,图表(Spark-Android)强调,当只处理系统服务通信时,Spark产生了最好的结果,因为它比其他所有的分析输入了更多具有1、2或3权限集的方法。此外,Spark从未以比CHA更多的权限插入一个入口点。它为91个入口点输入了与CHA相同的权限集(有一个或多个权限)。Spark为428个入口点输入了一个较小的权限集。
4.6.2 与PScout的比较
PScout [6] 依靠基于CHA的方法,为Android框架中的类生成一个权限列表。我们只考虑Android 4.0.1 API的类。在PSCout的结果中,有593个方法有一个以上的权限,有468个方法同时出现在PSCout和Spark中。在这468个方法中,289个(61.75%)在PSCout和Spark中具有相同的权限大小,176个(37.60%)在我们的方法中具有较小的权限集大小。
例如,对于KeyguardManager .exitKeyguardSecurely()方法,PScout有多个权限,而Spark只有一个,DISABLE_KEYGUARD。正式的文档表明只需要一个权限,而Felt [53] 的运行时数据也是如此。Spark也漏掉了AudioManager .setMicrophoneMute(boolean)方法的一个权限。这是因为我们没有处理C/C++原生代码的权限检查。 表4.5总结了这种比较的结果。我们的分析产生了比纯粹的基于CHA的方法更精确的结果。
有趣的是,我们还发现了三个方法(0.64%),我们的Spark方法比PSCout的方法发现了更多的方法。我们手动检查了Vibrator类,其中涉及的方法被定义,并且有一个方法检查权限WAKE_LOCK的路径。PScout可能没有正确地将这些特定的入口点方法链接到它们可以到达的所有方法,因此错过了WAKE_LOCK权限。
4.6.3 与Felt等人的比较
现在让我们把我们通过静态分析得到的结果[15]与Felt等人通过测试得到的结果[53]进行比较。两者都为Android 2.2框架的每个方法提取了一个所需的权限列表。安卓2.2具有134个权限,其中8个是我们没有分析的低级权限。Felt等人的结果包含了673个映射到高层次权限的方法。我们只分析了671个方法,因为有2个方法与Felt的方法中提供的应用程序特定对象有关,而我们的静态分析方法中没有。
对于一个给定的方法,我们要么采用相同的权限集,要么采用更大的权限集。 我们的方法从未错过Felt等人所描述的权限,这是我们方法合理性的一个证据。
更确切地说,我们为552个方法(占常用分析方法的82.3%)的每个方法签名推断了相同的权限集。119种方法有一个额外的权限(118种方法有一个额外的权限,1种方法有两个)。没有任何方法我们错过了一个权限,表4.6总结了这些结果。 现在让我们来讨论一下我们的结果之间的差异。
额外的权限是由于分析了不相关的代码或者Felt等人的方法中缺少输入数据。例如,MOUNT_UNMOUNT_FILESYSTEMS只在MountService .shut - down()方法中被检查,如果媒体(存储设备)是 "目前没有安装并通过USB大容量存储共享"(来自API文档)。另一个权限,READ_PHONE_STATE,只有在参数中传递的电话号码是语音邮件号码时,才需要在CallerInfo .getCallerId()方法中检查。这些测试用例不是由Felt的测试方法生成的。在实际应用中,测试生成技术不能保证对输入空间的全面探索。
对我们来说,在比较静态分析方法和测试方法时,这些迹象是典型的:静态分析有时会受到分析所有代码的影响(包括调试和死代码,或在特定运行环境中运行的代码),但在抽象输入数据方面很强。另一方面,测试必须尽可能地模拟生产环境,但被诅咒总是错过非常特殊的使用场景。
这些结果强调了静态分析和测试在权限推理方面的互补性。 我们认为,静态分析方法是对测试方法的补充。事实上,测试方法产生的近似值不足,遗漏了一些权限检查,而静态分析方法产生的近似值过高,这些遗漏的权限检查被发现。将这两种方法结合起来使用,可以使开发者获得权限差距的下限和上限。特别是,对于一个给定的Android应用程序,如果测试和静态分析方法都产生相同的权限列表,这强烈地表明这个列表是所需权限的 "正确 "列表。由于测试可能会遗漏权限,而静态分析可能不会模拟所有的安卓系统特性,这并不是一个强有力的说法。
4.6.4 健全性
我们在本章中已经表明,Android框架有许多可能威胁到静态分析的健全性的特点。 在这种情况下,合理性是指没有错误的否定(没有遗漏的权限检查)。 此外,健全性的概念指的是一个特定的范围:在我们的案例中,对Android服务内部的高级权限的检查。
对于基于CHA和Spark的分析,如PScout、CHA-Android或Spark-Android,基于领域特定的知识(如字节码重定向和窗口系统方法清空)对调用图的操作,当且仅当所有的情况都被包围时,才是健全的。鉴于像Android这样的框架的复杂性和规模,这种完整性是很难证明的。
对于基于Spark的分析,当且仅当对象和静态域被正确初始化时,分析是合理的。因此,分析可能对某些入口点是正确的,而对其他入口点则是不正确的。对于像Android这样的框架,没有神谕来正式回答这些问题。 然而,对于那些基于CHA的结果和基于Spark的结果相同的入口点,这是一个强有力的合理性证据。对于其他的,需要与文档或运行时数据进行比较。
最后,只要静态分析的任何部分(如入口点初始化和字节码重定向)以及我们编写的胶水和测量代码的实现没有严重的错误,我们的结果就能成立。
4.6.5 服务身份反转的影响
一个合理的问题是,服务身份反转是否会对产生的权限集产生影响。为了回答这个问题,我们在运行Spark-Android的时候,有的时候没有进行服务身份反转。在没有超时的入口点中,当服务身份反转被关闭时,有两个入口点有更大的权限集。例如,方法an - droid.net.ConnectivityManager boolean requestRouteToHost (int,int) 在服务反转被禁用时有一个更多的权限CONNECTIVITY_INTERNAL。根据官方文档9,这个权限对入口点来说是不需要的,它验证了服务身份反转构建块的有用性。
服务反转可能只影响几个入口点,但不考虑它就会导致错误的权限设置。
4.6.6 限制
本地代码
安卓框架是一个现实世界的大规模框架,具有用不同语言编写的异质层。对于Android 2.2来说,大多数Android权限(126/134)只在Android Java框架中检查。我们的方法对这126个权限是完整的,但对在本地C/C++代码中检查的8个权限则不完整。这八个权限是
BLUETOOTH_ADMIN, BLUETOOTH, INTERNET, CAMERA, READ_LOGS, WRITE_EX-TERNAL_STORAGE, ACCESS_CACHE_FILESYSTEM 和 DIAGNOSTIC.
框架中的反侦查
如果框架使用reection,那么调用图的构造就不完整。幸运的是,Android框架只在7个类中使用了reection。 我们手动分析了它们的源代码。这些类中有五个是调试类。视图类使用reection来处理动画。最后,VCardComposer在一个只为测试目的而执行的分支中使用reection。 在所有情况下,这些代码都与系统资源无关,因此
没有做任何权限检查。这并不影响Android框架的静态分析。
动态类加载
Java语言有可能动态地加载类。静态分析不能处理这个问题,因为加载的类只有在运行时才知道。 我们发现Android系统中有8个类在使用loadClass方法。 经过人工检查,其中6个是系统管理类,要么与权限检查无关(例如:对应用程序进行检测),要么必须通过服务访问。有两个是与webkit包有关的。它们被用于LoadFile和PluginManager类中。在这两种情况下,都是在加载类之前检查权限,而不是在加载的类中检查权限。 因此,也不存在遗漏的权限执行点。
火花
我们的安卓框架模型专注于服务,而错过了其他的初始化
Android组件(例如,内容提供者)的初始化。换句话说,Spark的声音与我们的Android组件模型有关。
4.7 计算权限差距
我们现在有静态分析来计算安卓API方法和它们所需的权限之间的映射。本节首先介绍了一种有效计算所需权限集和相应的权限缺口(已声明但未使用的权限)的方法,如果有的话。然后,我们提出了一个实证研究的结果,表明在野外存在着权限差距。
4.7.1 权限分析的微积分
本节将权限差距推理描述为布尔矩阵代数之上的一个微积分。 权限推理本质上是一种可及性分析(应用程序是否达到权限检查?
让app是一个应用程序。应用程序的访问向量是一个布尔向量AVapp,代表研究中的框架的入口点。因此,向量AV的长度是框架F的入口点的数量。如果应用程序调用了相应的入口点,向量的一个元素被设置为真。否则,它被设置为假。让我们考虑一个有四个入口点(e1 , e2 , e3 , e4)的框架,以及一个到达e1、e2和e3但没有到达e4的应用程序foo。AVapp读取:
AVfoo = (1, 1, 1, 0)
我们把权限访问矩阵M定义为一个布尔矩阵,表示框架的入口点和权限之间的关系。行代表框架的入口点,列代表权限。如果一个单元格Mi,j被设置为真,则相应的入口点(在第i行)访问由第j列所代表的权限保护的资源。否则它就被设置为假。对于一个有四个入口点(e1,e2,e3和e4)和三个权限(p1,p2和p3)的框架,权限访问矩阵如下:
. .意味着e1和e2需要权限p1,e3不需要权限,e4需要权限p2。
让app和F分别为一个应用程序和一个框架。推断出的权限向量IPapp是一个布尔向量,代表应用程序app的推断出的权限集合。 通过使用布尔运算符AND和OR,而不是矩阵计算中的算术乘法和加法,我们有:
单元IPapp(k)等于true意味着索引k处的权限是app所需要的。使用前面例子中的AVapp和M,推断出的应用程序的权限向量是:
. ......意思是说,应用程序应该声明并且只声明权限p1.
4.7.2 M和AV的提取
权限访问矩阵M是基于对框架F的静态分析. 如第4.5节所示, 我们首先计算框架的每个入口点的调用图, 然后探测
然后检测权限检查是否存在于调用图中。一个许可执行点
(PEP)是一个调用图的顶点,其签名对应于一个检查权限的系统方法。
权限(s)。每个PEP都与一个所需的权限列表permsP EP相关。矩阵
M的构造如下:它是一个大小为(|入口点|×|高层权限|)的矩阵;M的所有元素初始化为假。
M的所有元素都被初始化为false;对于每一个达到一个或多个PEP的Ei
达到一个或多个PEP,以及permsP EP中的每个
permsP EP中的权限j,M(i, j) = true。换句话说,M是一个浓缩版本的
可达性信息的浓缩版,它潜藏在调用图中。
让我们以第4.3节中的图4.1为例。它显示了一个有四个入口的框架
点(e1, e2, e3, e4),以及三个权限(p1, p2, p3)。对于每一个入口点,都构建了一个调用
图被构建。其中三个调用图有一个PEP节点:e1和e2有PEP ck1,它检查权限p1,e4有PEP ck1。
检查权限p1,e4有PEP ck2,检查权限p2。在图中,一个虚线
箭头将每个PEP与它所检查的权限连接起来。然后,框架矩阵是上面的矩阵
M(见4.7.1节)。
提取AV仅仅意味着列出一个由应用程序调用的框架F的入口点列表。
应用程序调用的框架F的入口点列表。图4.1中的应用实例使用一个入口点,AVex =
(1, 1, 1, 0).
4.7.3 计算权限差距
权限差距是指从IPapp提取的权限和声明的权限Pd(app)之间的差异。
宣布的权限Pd(app)之间的差异。在图4.1中,使用矩阵Mex和向量AVex的例子
框架和应用程序,我们得到一个只包含p1的推断权限列表。如果
应用程序声明了p1和p2,那么权限差距就是{p2}。
我们在两个安卓应用的数据集上运行我们的工具。第一个数据集来自于另一个
安卓市场10,包含1329个安卓应用。对于第二个数据集,我们考虑的是
对于第二个数据集,我们考虑了官方安卓市场所有34个顶级类别中的前50个下载应用,以及所有应用中的前500个。
以及所有应用的前500名和新应用的前500名(在2月23日
2012). 在去除重复的应用(出现在多个排名中的应用)后,第二个
数据集包含2057个应用。
替代的安卓市场: 我们放弃了587个使用反射和/或类
装载。在剩下的742个应用程序中,有94个声明了一个或多个权限,但它们并没有使用。
不使用的权限。因此,我们确定了94个安卓应用的权限缺口。我们定义
攻击面的面积 "与权限差距有关,是指不必要的
权限。总的来说,在存在权限差距的应用程序中,76.6%的应用程序的攻击面为1个权限。
攻击面为1个权限,19.2%的攻击面为2个权限,2.1%为3个权限
和2.1%的4个权限。
官方安卓市场: 我们放弃了1378个使用反射和/或类
负载。在剩下的679个应用程序中,有124个声明了一个或多个权限,但它们并没有使用。 总的来说,在存在权限差距的应用中,64.5%的应用有1个权限的攻击面,23.4%的应用有2个权限的攻击面,12.1%的应用有3个或以上的权限。
总而言之,这些结果表明,权限差距是存在的,我们的方法允许开发者在声明的权限列表中加入权限,以减少基于权限的软件的攻击面。
4.8 结论
在这一章中,我们使用静态分析来提取Android框架中的权限。为了使用类层次分析(CHA)和字段敏感静态分析(Spark)来分析Android的权限,至少有三个静态分析组件必须放在一起。它们是:(1)字符串分析;(2)服务身份反转;(3)入口点和服务
初始化。
该方法已在安卓系统中全面实施,安卓系统是一个基于权限的移动设备平台。我们的原型实现能够自动识别9562个检查权限的Android框架工作入口点。同时进行的工作,如PScout[6]和Felt[53]证实了我们的结果。
该方法已经完全实现了Android,一个基于权限的移动设备平台。对于终端用户的应用,我们的评估显示,从安卓应用商店抓取的94/742和35/679个应用确实存在着权限漏洞。
一般来说,基于权限的软件,特别是安卓系统的安全架构是复杂的。在本章中,我们抽象了平台的几个特征,如低级权限。
第五章
Android应用程序中的数据泄露
我们在上一章中看到,权限可以保护敏感数据。然而,拥有正确权限访问数据的应用程序可能会泄露数据。例如,恶意软件或带有攻击性广告库的应用程序就属于这种情况。本章的目标是静态分析Android应用程序,以检测这种泄漏。Android应用程序与传统的Java应用程序不同。最重要的区别之一是,Android应用程序是由组件组成的。分析Android应用程序以发现泄漏,需要将一起通信的组件联系起来,并对每个组件进行建模。我们开发了IccTA来检测隐私泄漏。它在代码层面连接组件,以执行组件间和应用间的数据流分析。
本章是基于以下技术报告中发表的工作:
- Li Li, Alexandre Bartel, Jacques Klein, Yves Le Traon, Steven Arzt, Siegfried Rasthofer, Eric Bodden, Damien Octeau and Patrick McDaniel: I know what leaked in your pocket: uncovering privacy leaks on Android Apps with Static Taint Analysis, ISBN 978-2-87971-129-4, 2014.
5.1 引言
随着安卓系统的日益普及,在安卓官方市场(Google Play)以及一些替代市场上,每天都有数以千计的应用程序(也叫应用)涌现。截至2013年5月,Google Play商店已经安装了480亿个应用程序,而截至2013年9月3日,已有10亿台安卓设备被激活[3]。研究人员表明,安卓应用经常在未经用户事先同意的情况下将用户的私人数据发送到设备之外[141]。 这些应用程序被说成是泄漏私人数据。 Android应用程序是由不同的组件组成的;大多数隐私泄漏是简单的,并在单一组件中操作。最近,跨组件和跨应用程序的隐私泄漏已被报道[136]。单独分析组件并不足以检测此类泄漏。因此,有必要对应用程序进行组件间分析。 安卓应用分析人员可以利用这样的工具来识别泄露隐私数据的恶意应用。为了使该工具发挥作用,它必须高度精确,并在报告泄露私人数据的应用程序时尽量减少假阳性率。
隐私泄露。在本章中,我们使用静态污点分析技术来识别隐私泄露,即从敏感数据(称为源)到向应用程序或设备外发送数据的语句(称为汇)的路径。一个路径可能是在一个单一的组件内,或跨越多个组件和/或应用程序。
依靠静态分析来检测安卓应用的隐私泄露的最先进的方法主要集中在检测组件内的敏感数据泄露。例如,CHEX[84]通过跟踪敏感源和汇之间的污点,使用静态分析来检测组件劫持漏洞。 DroidChecker[29]使用程序间控制流图(CFG)搜索和静态污点检查来检测Android应用程序中可利用的数据路径。FlowDroid[5]也在Android应用程序的单个组件中进行污点分析,但精度更高。 在本章中,我们不仅关注组件内的泄漏,而且还考虑基于组件间通信(ICC)的隐私泄漏,包括应用间通信(IAC)的泄漏。
其他方法使用动态跟踪来识别隐私泄漏。例如,TaintDroid[45]利用Android的虚拟化执行环境,在运行时监控Android应用程序,它跟踪应用程序如何泄漏私人信息。CopperDroid[105]动态地观察Android组件和底层Linux系统之间的互动,以重构更高级别的行为。
动态方法必须在运行时向应用程序发送输入数据以触发代码执行。输入的数据可能是不完整的,因此不能执行代码的所有部分。此外,一些代码可能只有在运行时满足精确的条件时才会被执行,如数据。在本章中,我们将重点放在静态分析上以避免这些缺点。与静态分析相对应的是,它可能会产生一个过度的近似值,因为它分析了所有的代码,即使是那些永远不会被执行的代码。
Android的静态污点分析是很困难的。 尽管Android应用程序主要是用Java编程的,但现成的Java静态污点分析工具并不能用于Android应用程序。这些工具需要进行调整,主要有三个原因。第一个原因是,如前所述,Android应用程序是由组件组成的。组件之间的通信涉及两个主要的工件: Intent Filter和Intent。 一个Intent Filter被附加到一个组件上,并 "过滤 "可以到达该组件的Intent。一个意图被用来启动一个新的组件,首先动态地创建一个意图实例,然后通过调用一个特定的方法(例如startActivity,startService),并将之前创建的意图作为参数。该意图可以通过指定要调用的新组件明确地使用,也可以通过隐含地使用,例如只指定要执行的动作1。 组件的启动是由Android系统执行的,它在运行时 "解决 "意图和意图过滤器之间的匹配问题。这种由安卓系统完成的动态解析在安卓应用程序的控制流中引起了不连续。 这种特殊性使得静态污点分析具有挑战性,因为它需要对代码进行预处理以解决组件之间的联系。
第二个原因是与安卓应用以用户为中心的性质有关,在这种情况下,用户可以通过触摸屏进行大量的互动。对用户输入的管理主要是通过处理特定的回调方法来完成的,如当用户点击按钮时调用的onClick方法。静态分析需要一个精确的模型来刺激用户的行为。
第三个也是最后一个原因是与组件的生命周期管理有关。在传统的Java程序中没有主方法。相反,Android系统通过调用回调方法,如onStart、onResume或onCreate,在组件的生命周期中切换状态。然而,这些生命周期的方法在代码中没有直接的联系。对Android系统进行建模可以将回调方法与代码的其他部分联系起来。
我们的建议。 上述挑战将不可避免地导致控制-流量图中的一些不连续现象。为了克服这些问题,我们提出了一个名为IccTA2的组件间通信污点分析工具。IccTA允许对ICC和IAC链接进行健全和精确的检测。这种方法是通用的,可以用于任何数据流分析。在这一章中,我们重点讨论使用IccTA来检测隐私泄露。
IccTA是基于三个软件工件: Epicc-IccTA、FlowDroid-IccTA和ApkCom-biner。
Epicc-IccTA扩展了Epicc[92],它计算了Android组件之间的ICC链接。Epicc-IccTA利用Epicc将计算出的ICC链接增量存储到数据库中,以方便分析大量的应用集。FlowDroid-IccTA扩展了FlowDroid[5]。FlowDroid只发现Android应用程序单个组件内的隐私泄露,而不是组件之间的隐私泄露。
FlowDroid-IccTA使用由Epicc计算的ICC链接来改进FlowDroid。基于这些计算出的链接,FlowDroid-IccTA修改Android应用程序的代码,直接连接组件,以实现组件之间的数据流分析。通过这样做,我们建立了整个安卓应用的完整控制流图。这允许在Android组件之间传播上下文,并产生一个高度精确的数据流分析。据我们所知,这是第一个为数据流分析精确连接组件的方法。
最后,ApkCombiner帮助分析多个安卓应用,当这些应用之间存在数据流时,将多个应用合并为一个。这将导致拥有合并后的应用程序的完整控制-流量图。这不仅允许在单个应用程序的组件之间传播上下文,也允许在不同应用程序的组件之间传播。
为了验证我们的方法,我们在3000个真实世界的安卓应用和26个包含我们开发的基于ICC的隐私泄露的应用上运行IccTA。我们将这26个应用添加到DroidBench[43],这是一个开放的测试套件,用于评估污点分析工具的有效性和准确性。
该测试套件用于评估专门针对Android应用程序的污点分析工具的有效性和准确性。这26个应用程序涵盖了表5.1中显示的前8个使用的ICC方法。
贡献。总结一下,我们在本章中提出了以下原创性贡献:
- 一种新的方法,通过在代码层面上直接连接Android应用程序的不连续性,来解决ICC问题。
- IccTA,一个用于组件间数据流分析的工具。
- DroidBench的改进版,有26个新的应用程序,用于评估检测基于ICC的隐私泄漏的工具。
- 一项实证研究,在DroidBench测试套件的增强版(可在线获得3)和3000个真实世界的Android应用程序上评估IccTA。
本章的其余部分组织如下。第5.2节解释了Android安全的必要背景。第5.3节给出了一个激励性的例子,第5.4节介绍了一些基本定义。在第5.5节中,本文讨论了Ic- cTA的实现细节,而第5.6节则评估了IccTA。第5.7节描述了IccTA的局限性。最后,第5.8节对本章进行了总结。
5.2 背景
5.2.1 安卓ICC方法
正如第2.1节所解释的,一个Android应用程序是由基本单元组成的,称为组件,在一个特殊的ile中描述,称为Manifest,存储在应用程序中。 有四种类型的组件:a)代表用户界面的活动,是Android应用程序的可见部分;b)在后台执行任务的服务;c)重新接收来自其他组件或系统的消息的广播接收器,如来电或文本消息;以及d)作为标准接口的内容提供者,在应用程序之间共享结构化数据。
一些特殊的Android系统方法被用来触发组件间的通信。我们称它们为组件间通信(ICC)方法。这些方法以一种特殊的对象为参数,称为 "意图",它指明了目标组件。我们进行了一项简短的研究来计算ICC方法的使用率。我们分析了从Google Play和其他第三方市场随机选择的3000个Android应用程序。表5.1显示了前8种最常用的ICC方法。第三列表示至少使用过一次相应ICC方法的应用程序的数量。最常用的ICC方法是startActivity,用于启动一个新的活动组件,占检测到的ICC方法总数的59.2%。
所有的ICC方法4的参数中至少有一个Intent来指定目标组件。 有两种方法来指定ICC方法的目标组件。 第一种是通过Intent设置目标组件的名称来明确地指定它们。第二种是通过设置Intent的action、category和data ields来隐式地指定它们。为了接收隐式Intent,目标组件需要在其应用程序的清单中指定一个Intent Filter。请注意,Intent可以在组件之间传输数据。
同样,我们对3000个应用程序进行了简短的研究,以计算startActivity ICC方法的显式和隐式Intent之间的比例。在55802个startActivity方法调用中,有27978个使用显式Intents,27824个使用隐式Intents。
图5.1代表了三个由活动组件组成的Android应用程序。在应用1中,有一个从Activity1到Activity2的显式ICC。在应用1中有两个从Activity2到Activity3的隐式ICC,在应用1和应用2之间有从Activity2到Activity4的隐式ICC。请注意,隐式ICC的目标组件,Ac - tivity3和Activity4,有一个Intent Filter,其动作和类别值与Activity2中使用的Intent相同。 每次有ICC时,组件之间可能会有数据的欠款,并可能有隐私泄漏。
5.2.2 FlowDroid
FlowDroid[5]是一个用于Android应用程序的上下文,ow-,ield-,对象敏感和生命周期感知的静态污点分析工具。 FlowDroid的上下文、ow-、ield-、object-sensitives由Soot[75]的精确调用图和Heros[22]的基于IFDS[106]的数据流分析保证。FlowDroid使用的源和汇是由SuSi[4]提供的,它也是一个开源的工具,用于完全自动地对Android源和汇进行分类和归类。
生命周期的精确建模
Android应用程序的组件可以独立启动并并行运行。FlowDroid对路径不敏感(见第2.2.1节),并假设应用程序中的组件可以按任何顺序运行。 一个特殊的主方法,考虑到Android组件的生命周期(例如,当相应的Activity组件暂停时执行的方法onPause())、回调(例如,当用户点击按钮时执行的方法onClick())和入口点(例如,当Activity组件启动时执行的方法onCreate())的所有组合,被生成为应用中的数据流模型。
IFDS问题
FlowDroid进行污点分析并使用IFDS框架。分析开始于将源方法(例如,getDeviceId())的结果分配到一个变量中的语句。这个变量是有污点的,因为它包含来自源方法的数据。然后分析使用Andomeda[129]介绍的一个想法,向后分析被污染变量的别名。在前向分析过程中,如果别名达到了源方法的原始赋值,它们也会被玷污。最后,如果一个被污染的变量到达一个汇入方法,那么就会检测到一个泄漏。正如第2.2.2节所述,该分析依赖于应用于语句的ow函数来计算数据流的事实。在这个分析中,数据流事实是每个程序语句中被污染的变量集合。例如,应用于语句x=y的正常ow函数,如果x被污损,y没有被污损,则杀死x;如果y在语句之前被污损,则将y的污损递增到x。
实验结果
在DroidBench上检测数据泄露时,FlowDroid实现了93%的召回率和86%的精度。FlowDroid主要用于分析单个组件中的数据泄露。然而,只要稍加修改,FlowDroid也可用于涉及多个组件的情况,即用于ICC分析。事实上,可以用FlowDroid来计算所有单个组件的路径,然后将所有这些路径结合在一起,无论这些组件之间是否存在真正的联系。这种方法的一个主要缺点是,它产生了许多假阳性结果。下一节将介绍Epicc,一个静态解决ICC链接的工具。
5.2.3 Epicc
Epicc[92]是一个静态分析工具,也是基于Soot和Heros的,它计算ICC(组件间通信)链接。 换句话说,它将ICC方法的链接输入到其目标组件。
集成开发环境问题
ICC方法需要一个叫做Intent的对象作为参数。这个Intent对象描述了设计国的组件。Epicc对应用程序的代码进行静态分析,在每个调用ICC方法的语句中重新构建这些对象。Epicc将Android中ICC的发现简化为程序间分布环境(IDE)问题的一个实例[111]。一个IDE问题会传播环境。在Epicc的例子中,环境可以被看作是代表正在重建的对象的一个变量与该重建对象的当前值之间的映射。例如,当一个新的Intent对象被创建时,指代这个新对象的变量将被映射到一个空的Intent。 在分析过程中,环境转化器(在超图的每条边上)将更新环境。例如,语句i .setAction("A1")的环境转换器通过将字符串 "A1 "作为相应的重构对象的动作值来改变变量i的环境。在Intent对象中使用的其他对象,如ComponentName或Bundle对象,也是通过同样的过程来重构的。
实验结果
实验表明,当应用于一组1200个Android应用程序时,Epicc识别了93%的所有ICC链接,并发现了ICC漏洞,其误报率远远低于次好的工具。在本章中,我们使用Epicc生成的链接来提高FlowDroid ICC分析的精度。下面一节介绍了我们对ICC泄漏的处理方法。
5.3 激励性的例子
本节激励我们的方法,并通过一个具体的例子说明我们解决的问题。这个例子详见图5.2,它展示了图5.1中介绍的应用程序1的代码。该应用程序有三个活动组件,分别由Activity1、Activ - ity2和Activ 3类代表。它还具有ButtonOnClickListener,一个用于处理按钮点击事件的监听器类。 Activity1为to2按钮注册了一个按钮监听器(第6-11行),Activity2为to3按钮注册了一个(第15行)。
当按钮to2和to3被点击时,onClick方法被执行,用户界面将分别改变为Activity2和Activity3。在这两种情况下,包含设备ID的Intent(第7行和第32行)被认为是敏感数据,通过首先用putExtra方法(第9行和第35行)将数据附加到Intent上,然后通过调用startActivity或startActivityForResult(第10行和第36行)在两个组件之间发送。请注意,图5.2是使用显式和隐式意图的例子。 在第 8 行,通过明确指定目标类(Activity2 )来创建意图。在第34行,只规定了意图的动作,而没有明确地提到目标。
在这个例子中,当Activity2或Activity3被加载时,sendTextMessage被直接执行,因为onCreate是一个Activity生命周期中的第一个方法。它将从Intent中获取的数据作为SMS发送到指定的电话号码。
在这段代码中,发生了两个隐私泄露:一个是当按钮to2被点击时,另一个是按钮to3被点击时。当to2被点击时,设备ID从Activity1传输到Activity2(第10行),然后Activity2将其发送到应用程序之外(第18行)。
当按钮to3被点击时,设备ID被从Activity2转移到Activity35(第36行)。实际上,设备ID(源)是在Activity2实例化的ButtonOnClickListener类中获取的。 最后,Activity3将设备ID发送到应用程序之外(第27行)。
上面描述的敏感数据泄漏跨越了两个组件:它们不能直接被检测到,因为在startActivity和onCreate(第10行和第13行)或startActivityForResult和onCreate(第36行和第24行)之间没有真正的代码连接。第5.5节描述了我们连接组件的方法,以分析组件之间甚至是应用程序之间的路径。
5.4 分词
为了更好地描述我们的方法,需要对一些Android和污点分析的相关概念进行定义。
控制流图(CFG) 我们通过分析安卓应用程序的控制流图来检测数据泄露。一个应用程序的CFG由一个方法CFG的集合组成,根据它们相互调用的方式连接在一起。
源方法。一个源方法从用户的角度返回被认为是私有的数据到应用程序代码中。例如,方法getDeviceId(第76行)是一个返回设备ID的源方法。
水槽方法。一个水槽方法将数据从应用程序中发送出去。例如,方法send - TextMessage(第27行)是一个sink方法,使用SMS向另一个手机发送数据。我们使用SuSi工具[4]为Android计算的源和汇。
ICC方法。一个ICC方法被用来触发两个组件之间的通信。例如,方法startActivity(第10行)是一个ICC方法,它触发了从Activity1到Activity2的组件通信。
受污染的语句(Tainted Stmt)。一个有污点的语句至少包含一个有污点的数据。例如,i .putExtra("sensitive",id)(第9行)是一个包含污点数据id的语句。
受污染的语句序列。受污染的语句序列是一个受污染的语句的ow-sensitive序列。例如,第9行和第10行的语句构成一个污点语句序列。
污染的路径。 污点路径是一个污点语句序列,其中1)在污点路径中存在一个以上的语句;2)第一个语句包含一个源方法;3)最后一个语句包含一个汇方法。图5.3说明了污点特写、污点特写序列和污点路径。
在Android中,有三种类型的污点声明路径:基于组件内通信(Intra-Component Communi- cation)、组件间通信(ICC)和应用间通信(IAC)的污点路径。
组件内污点路径。 组件内的污点路径是一个组件内的污点路径。 在我们的激励性例子中,没有组件内的污点路径。 但是如果startActivity的调用被替换为sendTextMessage的调用,将设备ID从应用程序中发送出去,就会有一个组件内的污点路径(第7-10行)。
基于ICC的污点路径。 基于ICC的污点路径是两个或多个组件之间的污点路径,也就是说,路径中至少有一个ICC方法。在我们的例子中,有一条基于ICC的污点路径,从Activity1的源方法getDeviceId到Activity2的汇方法sendTextMessage,通过startActivity ICC方法(第10行)。
基于IAC的污点路径。基于IAC的污点路径是两个或多个应用程序之间的污点路径,也就是说,它在不同应用程序的两个组件之间至少有一个ICC方法。在我们的激励性例子中没有基于IAC的污点路径。但是,如果图5.1中的Activity4将从Activity2转来的设备ID送出应用,那么就有一个基于IAC的污点路径从应用1到应用2。
隐私泄漏。如果检测到一个有污点的路径,就意味着发现了一个隐私泄露。换句话说,从源方法获得的一些隐私数据可以通过污点路径ow到汇方法。
5.5 IccTA
在这一节中,我们描述了IccTA,我们用于检测Android应用程序中的隐私泄露的工具。它使用静态污点分析来检测隐私泄露。 这方面的主要挑战是解决Android系统引入的不连续问题。
我们在图5.4中展示了IccTA的架构,其中新的或修改的组件被虚线包围。IccTA是Epicc-IccTA和FlowDroid-IccTA的组合。Epicc-IccTA依靠Epicc来增量计算Android应用程序的ICC链接。 FlowDroid和Epicc都是基于Soot[75]和Heros[22]。Soot是一个分析基于Java的应用程序的框架。它使用Dexpler[14]插件将Android的Dalvik字节码转换为Soot的内部表示,称为Jimple,并依靠Spark[82]来建立精确的调用图。Heros是IFDS[107]和IDE[111]的可扩展实现,这两个框架用于执行数据流分析。 分析多个应用程序是使用ApkCombiner进行的。 它将多个应用程序合并为一个,以简化IccTA的分析。
图5.5是IccTA和FlowDroid的比较。FlowDroid(5.5起)首先在步骤(1.1)中将Android字节码转换为Jimple。然后,在步骤(1.2)中,它分析Jimple代码以检测单个Android组件中的污点路径。
IccTA(5.5下)可以分析一个或多个Android应用程序。如果分析了一个以上的应用程序,它将使用ApkCombiner在步骤(2.1)中把Android应用程序合并为一个应用程序。然后,在步骤(2.2)中,Android应用程序的字节码被转换为Jimple。
同时,Epicc-IccTA在步骤(2.3)中分析所有输入的应用程序(图中的Apk1和Apk2)以生成ICC链接,并在步骤(2.4)中将结果存储到数据库。IccTA在步骤(2.5)中使用由Epicc-IccTA生成的ICC链接来连接Jimple代码中的Android组件。 步骤(2.2)和(2.6)对应于FlowDroid的步骤(1.1)和(1.2):Jimple代码被更新以考虑到组件的生命周期和回调,污点分析被启动以生成污点路径的列表。
5.5.1 FlowDroid-IccTA:将ICC问题减少为组件内问题
由于两个Android组件之间没有直接的代码连接,FlowDroid不能精确地检测基于ICC的隐私泄漏。 在本节中,我们描述了FlowDroid-IccTA如何将ICC问题减少到一个组件内问题,FlowDroid可以对其进行高度精确的数据流分析。我们的方法是利用Android应用程序的Jimple代码,在代码中直接连接组件。
正如介绍中提到的,Android中有三种类型的不连续:(1)ICC方法,(2)生命周期方法和(3)回调方法。我们首先在第5.5.1节描述了FlowDroid- IccTA如何处理ICC方法。然后,我们详细说明FlowDroid-IccTA如何解决
在第5.5.1节中,我们详细介绍了FlowDroid-IccTA如何解决生命周期和回调方法。最后,使用我们在List- ing 5.2中的激励性例子,我们在第5.5.1节中说明了代码工具化过程。
如图5.5所示,ICC问题在步骤2.5得到解决。这时,Jimple代码被FlowDroid-IccTA更新为连接组件。 所有的ICC方法都需要进行这种代码修改(在表5.1中列出)。我们为两个最常用的ICC方法详细说明这些修改:startActivity和startActivityForResult。 我们以类似的方式处理服务和广播接收机的ICC方法。
StartActivity。图5.6显示了FlowDroid-IccTA对我们激励性例子中Activity1和Activity2之间的ICC链接所做的代码转换。FlowDroid-IccTA首先创建了一个名为IpcSC(图5.6中的B)的辅助类,作为连接源和目的组件的桥梁。然后,startActivity ICC方法被移除,并由调用生成的辅助方法(redirect0)的语句取代(A)。
在(C)中,FlowDroid-IccTA生成了一个以Intent为参数的构造方法,一个假Main方法来调用组件的所有相关方法(即生命周期和回调方法),并重写了getIntent方法。一个Intent被Android系统从调用者组件转移到被调用者组件。我们对Android系统的行为进行建模,通过使用自定义的con- structor方法Activity2 (Intent i)明确地将Intent转移到目标组件,该方法将Intent作为其参数,并将Intent存储到新生成的ield int_for_ipc。原来的getIntent方法要求Android系统提供传入的Intent对象。新的getIntent方法通过返回作为参数给新的构造方法的Intent对象来模拟安卓系统的行为。
辅助方法redirect0构造了一个Activity2类型的对象(目标组件),并用作为参数给辅助方法的Intent来初始化新对象。然后,它调用Activity2的dummyMain方法。
为了解决目标组件,即自动推断在方法redirect0中必须使用的类型(在我们的例子中,推断Activity2),Flowdroid-IccTA使用由Epicc-IccTA计算的ICC链接。Epicc-IccTA不仅为显式意图解析目标组件,也为隐式意图解析目标组件。因此,Flowdroid-IccTA在处理基于显式或隐式意图的ICC方面没有区别。
StartActivityForResult。 在Android中有一些特殊的ICC方法,比如star - tActivityForResult。一个组件C1可以使用这个方法来启动一个组件C2。一旦C2结束运行,C1就会带着从C2返回的一些结果数据再次运行。startActivityForResult的控制-ow机制如图5.7所示。有两个不连续:一个是从(1)到(2),类似于startActivity方法的不连续,另一个是从(3)到(4)。
与只触发组件间单向通信的普通ICC方法(如startActivity)相比,startActivityForResult ICC方法的语义更为复杂。 图5.8显示了在我们的激励例子中,代码是如何处理startAc - tivityForResult方法的。为了与常见的ICC方法保持一致,我们没有用Activity3的finish方法来调用onActivityResult方法。相反,我们生成一个intent_for_ar字段来存储Intent,它将被转发回Activity2。 将被转回的Intent是由setResult方法设置的。我们重写setResult方法,将Intent的值存储到intent_for_ar。IpcSC .redirect0的辅助方法做了两个修改来直接连接这两个组件。 首先,它调用目标组件的dummyMain方法。 然后,它调用源组件的onActivityResult方法。
生命周期和回调方法
在分析Android应用程序时,一个挑战是要解决组件的回调方法和生命周期方法。在应用程序的代码中,这些方法之间没有直接调用,因为Android系统处理生命周期和回调。对于回调方法,我们不仅需要照顾到由用户界面(UI)事件触发的方法(如onClick),还需要照顾到由Java或Android系统触发的回调(如onCreate方法)。在Android中,每个组件都有自己的生命周期方法。为了解决这个问题,IccTA为每个组件生成了一个dummyMain方法,在这个方法中,我们对上面提到的所有方法进行建模,以便我们基于CFG的方法能够意识到它们。请注意,FlowDroid也生成了一个dummyMain方法,但它是为整个应用生成的,而不是像我们这样为每个组件生成。
工具化的激励性例子的CFG
图5.9表示清单5.2中介绍的工具化激励实例的CFG。在CFG中,getDeviceId是匿名的OnClickListener类中的一个源方法(第6行),由Activity1调用。sendTextMessage方法是Activity2的一个汇。从源方法到汇方法有一条组件内的污点语句路径(用边表示
1到12)。
图5.9还显示,IccTA建立了一个精确的跨组件控制--流图。由于我们使用了一种对代码进行检测的技术来构建CFG,所以静态分析的上下文在组件之间被保留下来。这使得IccTA能够分析组件之间的数据流,从而使IccTA比现有的方法有更好的精度。
5.5.2 ApkCombiner: 将IAC问题简化为ICC问题
在Android中,应用间通信(IAC)类似于组件间通信(ICC)。事实上,IAC也依赖于组件通信,只是源组件和目的组件属于不同的应用程序。如果我们能够连接应用程序,IAC问题就会变成标准的ICC问题。
分析多个应用程序。 如图5.5所示,FlowDroid一次只能分析一个应用程序。因此,我们开发了一个工具,ApkCombiner,将多个应用程序合并为一个。 ApkCombiner结合了Android应用程序的所有部分,包括字节码、资产、清单和所有资源。然后,我们使用IccTA来分析合并后的应用程序,以计算基于IAC的隐私泄漏。 由于FlowDroid-IccTA将合并的应用程序作为一个单一的应用程序来处理,它只检测基于ICC的隐私泄漏。为了区分ICC泄漏和IAC泄漏,IccTA会检查所有被污染的路径的语句是否属于同一个应用程序。
减少要分析的组合应用程序的数量。 在实践中,当增加要分析的应用程序的数量时,如果所有这些应用程序都用ApkCombiner组合,FlowDroid-IccTA的处理时间和内存需求也会增加。 为了解决这个问题,我们需要减少需要组合的安卓应用的数量。我们的解决方案是建立一个IAC图,其中一个节点是一个应用程序,一个边是一个链接,以表示应用程序之间的依赖关系。其背后的想法是,如果两个应用程序之间没有联系,就没有必要将它们合并。
IAC图是由小型独立IAC(sIAC)图(连接组件)组成的。给定一个sIAC图,ApkCombiner将其中的所有节点(应用程序)合并为一个应用程序,然后IccTA从产生的应用程序中提取泄漏。然而,在某些情况下,如果一个SIAC图仍然包含大量的节点。这也会限制我们的方法的可扩展性。我们的解决方案是限制一个IAC泄漏的长度(涉及多少个应用程序)7。例如,如果一个SIAC图包含10个节点(其中Ai与Ai+1相连,i∈{1,9}),长度限制被设置为ve。那么,该sIAC图被分割成ive个sIAC(例如,一个sIAC是从A2到A6),IccTA可以分析。这种权衡限制的长度使我们的方法变得可扩展。
构建IAC图的另一个好处是,新的应用可以以迭代和递增的方式添加到图中。当涉及到新的应用程序时,我们只需针对Epicc-IccTA运行它们并将它们添加到现有的IAC图中。在向IAC图添加新的应用程序时,我们不需要再次运行之前计算过的应用程序。
简而言之,通过建立IAC图,原始的安卓应用集被分割成多个小集,IccTA可以分析。
5.6 评价
我们的评估涉及以下研究问题:
RQ1 IccTA在精度和召回率方面与Android和FlowDroid的商业污点分析工具相比如何?
RQ2 IccTA能否在真实世界的应用中识别泄漏,它的速度如何?
RQ3 IccTA与其他学术界的ICC泄漏检测方法相比如何?
5.6.1 RQ1:IccTA与FlowDroid和商业工具比较
我们在DroidBench上对IccTA与FlowDroid和IBM AppScan Source 9.0进行评估和比较,以测试ICC和IAC的泄漏。不幸的是,我们无法将IccTA与其他静态分析工具进行比较,因为它们的作者没有提供这些工具。
DroidBench。 DroidBench[43]是一组手工制作的Android应用程序,所有的泄漏都是事先知道的。 知道应用程序中所有泄漏的事实被称为地面真相,用于评估静态和动态安全工具在数据泄漏方面的表现。DroidBench 1.2版包含64个不同的测试案例,有不同的隐私泄露。然而,DroidBench中的所有泄漏都是组件内的隐私泄漏。 因此,我们开发了26个应用程序和23个测试案例,以扩展DroidBench的ICC和IAC泄漏。一个测试案例应用于一个应用程序以测试ICC,并应用于两个应用程序以测试IAC。总共有18个应用程序包含组件间的隐私泄漏,6个应用程序包含应用程序间的隐私泄漏。新的测试案例集涵盖了表5.1中的前8种ICC方法的每一种。此外,在26个新的应用程序中,有两个不包含任何隐私泄漏。如果一个工具在这两个应用程序上检测到隐私泄漏,那么检测到的泄漏是错误的警报。最后,对于每个测试案例的应用程序,我们添加一个包含水槽的不可达组件。这些无法到达的组件用于ag娱乐平台那些不能正确构建组件间链接的工具。
这23个测试案例列在表5.2的第一栏。
IccTA。我们在所有的23个测试案例上运行IccTA。结果显示在表5.2中。IccTA
成功通过了18个测试案例,其中17个测试案例包含19个隐私泄露,一个测试案例(startActivity5)没有泄露。
在检测到的隐私泄露中,有三个是基于IAC的隐私泄露,其余的是基于ICC的隐私泄露。在startActivity5测试案例中,源组件使用数据类型为text/plain的隐含意图来启动另一个活动。然而,该测试案例中没有其他活动声明它可以接收数据类型为 text/plain 的意图。这意味着startActivity5测试用例中的组件之间没有联系。由于 IccTA 考虑到了意图的数据类型,它没有为该测试用例报告任何隐私泄漏。
startActivity4测试用例不包含任何泄漏。然而,IccTA确实报告了一个错误的警告。原因是源组件使用带有URI的隐式意图来启动另一个活动。由于IccTA依赖于Epicc,而Epicc确实过度估计了URI的链接,所以它报告了一个错误的泄漏。
目前的版本没有考虑到内容提供者。这就是为什么IccTA漏掉了insert1、delete1、update1和query1测试案例的泄漏。 所有这四个测试案例都与内容提供者有关。
FlowDroid。 在[5]中,FlowDroid已经在DroidBench的第一个版本上进行了评估。 在表5.2中,我们介绍了FlowDroid在新的23个测试案例中的结果。如前所述,FlowDroid最初被提议用于检测单个Android组件的泄漏。然而,我们可以用FlowDroid的方式来计算所有单个组件的路径,然后将所有这些路径结合在一起(无论是否有真正的联系)。因此,我们预计FlowDroid可以检测到大部分的泄漏,但会产生几个假阳性。表5.2的结果证实了这种预期: FlowDroid具有较高的召回率(69.6%)和较低的精度(23.9%)。在bindService{2,3,4}中,FlowDroid比IccTA多漏掉了三个泄漏。经过调查,我们发现FlowDroid没有考虑服务组件的一些回调方法。
AppScan。 AppScan Source 9.0需要大量的手动初始化工作,因为它没有默认的源/汇配置,也无法在不指定每个组件的入口点的情况下分析Android应用程序。 我们定义了getDeviceId和log方法,这些方法在DroidBench中一直用于ICC和IAC泄漏,作为源和汇,重新定义。我们还将所有组件的入口点方法(如活动的onCreate)添加为回调方法,以便AppScan知道从哪里开始分析。 AppScan本身不能检测组件间的数据流,只能检测组件内的数据流。AppScan有和FlowDroid一样的缺点,在Droid- Bench上应该有较高的召回率和较低的精度。 我们使用一个额外的脚本来结合组件之间的漏洞。 正如预期的那样,AppScan的召回率很高(56.5%),精度很低(21.0%)。与FlowDroid相比,App- Scan的表现更糟。事实上,AppScan没有正确处理startActivityForResult,因此错过了通过接收start-ForResult{2,3,4}中被调用活动结果的方法的泄漏。
结论。 IccTA在精确度和召回率方面都优于商业污点分析工具AppScan 9.0和FlowDroid。
5.6.2 RQ2:IccTA和真实世界的应用程序
我们在Core i7 CPU上运行具有8Gb堆的Java VM进行实验。 为了评估我们的方法,我们用IccTA分析了从Google Play市场以及一些第三方市场(如wandoujia)下载的3000个Android应用。IccTA在大约100小时内处理了3000个应用程序。IccTA没有检测到2575个(85.83%)应用程序的任何泄漏。IccTA报告了425个包含隐私泄露的应用程序。 在这425个应用程序中,411个应用程序只包含组件内泄漏,14个应用程序包含至少一个ICC泄漏。 在这14个应用程序中,13个同时包含组件内泄漏和ICC泄漏。IccTA检测到6989个IAC链接。其中IccTA检测到一个IAC泄漏。这一结果表明,组件确实在进行通信和共享数据,但发生应用间泄漏的情况很少。
对于应用内的泄漏,IccTA在425个应用中检测到5986个泄漏。在检测到的泄漏中,147个(2.5%)是ICC隐私泄漏。我们手动检查了这147个报告的ICC泄漏,发现17个(11.6%)是假阳性。 换句话说,IccTA在实词应用上达到了88.4%的精度。误报来自于Epicc,它对组件之间的链接产生了误报。
我们在表5.3中总结了425个至少有一个漏洞的应用程序中经常使用的源方法和汇类型(Java类)。 请注意,我们只计算在检测到的泄漏中出现的这些源方法和汇方法。使用最多的源方法是openConnection,它在169个应用程序中被使用了601次。使用最多的汇类型是Log,它在261个应用程序中被使用了2755次。我们研究水槽类型而不是水槽方法的原因是,在同一个水槽类型中存在大量的水槽方法。以Log汇类型为例,有八个汇方法将私有数据记录到磁盘上。
让我们详细描述三种泄漏,每种类型的泄漏都有一个。
组件内泄漏:BZ .Prana .myphonelocator . IccTA检测到从类.SMSReceiver$MyLocationListener8的方法onLocationChanged中的getLongitude源方法开始的组件内隐私泄漏。位置是通过类.SMSReceiver的smsReply方法中的sendTextMessage sink方法从应用程序中发送出来的。该应用程序被设计为通过SMS将位置发送到设备之外。然而,区分检测到的隐私泄漏的意图不在本章的范围内。我们将其作为我们进一步的工作。
ICC泄漏:com .dikkar .ifind . IccTA在这个应用程序上检测到一个基于ICC的隐私泄漏。 在类.iFindPlaces的onLocationChanged方法中,getLongitude源方法被调用并返回Android手机的位置。 然后,位置被转移到另一个组件,.PlaceDetail,其中j类的方法b被调用。 在方法b中,一个水槽方法Log .d用ServiceHandler标签名将位置记录到磁盘中。为了验证检测到的泄漏,我们开发了一个名为LogParser的Android应用程序。通过给予android .permission .READ_LOGS 9的权限,LogParser报告了所有由Find Places记录的位置。
IAC泄漏:com .bi .mutabaah .id到jp .benishouga .clipstore 。 IccTA报告了应用程序com .bi .mutabaah .id和应用程序jp .benishou - ga .clipstore之间的IAC泄漏。在组件com .bi .mutaba - ah.id.activity.Statistic中调用了源方法findById,获得了一个TextView的数据。然后,数据被存储到一个带有两个额外的命名为extra .SUBJECT和extra .TEXT的Intent中。之后,调用startActivity ICC方法将数据发送到jp .benishouga .clipstore应用程序,它从具有相同额外名称的意图中提取数据并将所有数据写入一个ile。
结论。IccTA在现实世界的应用程序中,在合理的时间内发生了泄漏。尽管如此,IccTA只检测到了一个IAC泄漏。这表明应用程序之间的泄漏是罕见的。
5.6.3 问题3:与其他学术工具比较
我们确定了两个能够处理ICC泄漏的学术工具: SCanDroid [56]和SEFA [136]。
然而,ScanDroid未能报告任何泄漏,SEFA也不可用。因此,我们无法在DroidBench上评估它们。
为了回答这个研究问题,我们关注并讨论各种方法的一些关键方面。SCanDroid和SEFA都使用了路径匹配的方法,它为所有单独的组件计算路径,然后将一些路径组合在一起,是否组合两个路径的决定由匹配算法给出。路径匹配方法至少有两个主要缺点。
首先,即使对每个组件进行了污点分析,当SCanDroid和SEFA结合污点路径时,分析的背景也会丢失,因为分析是在结合路径之前进行的。IccTA不存在这个问题,因为它在代码级别连接组件,然后进行分析。因此,它保留了两个组件之间的数据流。丢失上下文会降低工具的精度。事实上,一个意图可以携带数据,也就是说,它可能包含很多额外的键/值对,但其中只有一部分是敏感的。一个精确的工具需要区分它们,以避免假阳性。对于路径匹配方法来说,区分它们并不容易,因为它们在匹配两个可用路径时不保留意图的状态。
其次,一些特殊的ICC方法,如startActivityForResult,用匹配算法处理起来很困难。 当特殊的ICC方法存在于一个被多个组件调用的类中时,情况会变得更加糟糕。 假设一个组件Activ - ity4也使用清单5.2中的ButtonOnClickListener类来与其他组件通信。我们在图5.10中展示了这种情况。路径匹配的方法首先会找出一条从startActivityForResult ICC方法到Activity3的路径。在Activity3的finish方法被调用后,源组件的onActivityResult方法被Android系统调用。 问题是很难知道哪个组件(Activity2或Activity4)是源,因为它们都使用了创建Intent的同一个ButtonOnClickListener类。事实上,静态地解决这个问题是非常困难的,因为它是由Android(或Java)的动态绑定机制造成的。 在我们的方法中,IccTA通过明确调用源组件(Activity2或Activity4)的适当的on-ActivityResult方法(见图5.7和5.8)来解决这个问题,这要感谢辅助类IpcSC。
结论。即使我们无法评估最先进的检测ICC泄露的工具(SCanDroid和SEFA),IccTA似乎更精确,主要是因为它保留了组件之间的上下文,不像路径匹配方法。
5.7 局限性
在本节中,我们将讨论IccTA的局限性。
FlowDroid。IccTA基于FlowDroid进行静态污点分析,因此与FlowDroid的局限性相同。IccTA只在参数是字符串常数的情况下才会解决检测性调用。它对多线程也是无视的。我们经历过FlowDroid无法正确分析一些应用程序(内存消耗太大或者挂起)。我们从分析5000个应用程序开始,只保留3000个能与FlowDroid一起工作的应用程序。在一个大的服务器上运行IccTA可能会大大减少下降的分析数量。此外,我们非常有信心,FlowDroid的下一个版本将解决这个问题。
Epicc。IccTA依靠Epicc来计算组件之间的链接。由于Epicc不处理URI,它不能为ContentProvider计算ICC链接,而且当其他三种类型的组件使用URI进行通信时,会产生误报。在实践中,由于假阳性的存在,链接的数量是巨大的。我们检查这些链接(意图和意图过滤器),只保留不使用URI的链接。
IccTA。目前,IccTA并不处理一些很少使用的ICC方法,如sendActivities或sendOrderedBroadcastAsUser。组件之间发送的数据有一个意图,被表示为键/值对。当一个有污点的数据被放在意图中时,IccTA会污点所有的键/值对。这可能会导致假阳性,如果一个有污点的数据被放在一个意图中,而在接收组件中,一个没有污点的数据被从意图中检索出来,并被发送到水槽中。
本地代码。 一些安卓应用被打包成了本地代码。IccTA只分析包含Dalvik字节码的dex ile。
5.8 结论
本章讨论了在多个组件或多个应用程序中进行数据流分析的主要挑战。我们介绍了IccTA10,一个基于ICC的污点分析工具,能够进行这样的分析。 特别是,我们证明了IccTA可以通过对应用程序的代码进行分析,提供一个高度精确的控制流图来检测基于ICC的隐私泄漏。 与之前的方法不同,IccTA能够对两个组件之间的数据流进行分析,并对生命周期和回调方法进行充分建模,以检测基于ICC的隐私泄露。当在DroidBench上运行IccTA时,其精度达到了95.0%。当在从Google Play商店以及其他第三方市场随机选择的三千个应用程序上运行IccTA时,它在12个应用程序中检测到130个基于组件间的隐私泄漏。
第六章
在生活中:安全和隐私的动态方法
对Android应用程序进行静态分析,可以发现安全问题,如GPS坐标从设备中泄露出来。然而,静态分析并不直接在用户的设备上运行,因此没有考虑到设备的上下文。本章的目的是让大家了解动态方法如何补充静态分析。我们是第一个提出在体内,即直接在设备上动态检测Android应用的工具链的人。我们提出了两个对应用程序进行检测的用例,以表明动态方法是可行的,它们可以利用静态分析的结果,并且从安全和隐私的角度来看,它们对用户是有益的。 其中一个用例是一个内粒度的权限系统原型,使用户能够随意禁用或启用应用程序的权限。
本章是基于以下技术报告中发表的工作:
- Alexandre Bartel, Jacques Klein, Martin Monperrus, Kevin Allix and Yves Le Traon: Im- proving Privacy on Android Smartphones Through In-Vivo Bytecode Instrumentation, ISBN 978-2-87971-111-9, 2012.
6.1 简介
在谷歌的官方市场(Google Play,以前的AndroidMarket)上,每月有超过10000个新的应用程序。2 对于最终用户来说,在她的智能手机上下载一个应用程序类似于在苹果树上选择一个苹果:她只看到表面,没有证据表明其中没有虫子。不幸的是,有许多不同种类的蠕虫等待着感染智能手机,如泄露私人数据的恶意软件和呼叫高价号码的广告软件。
在这一章中,我们声称改善安卓应用隐私的有效和可应用的手段是通过在智能手机上直接对应用的字节码进行仪器化来执行运行时监控和拦截应用与安卓堆栈的互动(体内)。在进一步介绍我们的贡献之前,让我们捍卫我们的关键主张。
为什么要进行运行时监控和拦截? 我们想在运行时允许或不允许一个应用程序的行为。 我们使用运行时监控,因为它包括观察一个应用程序在执行过程中的行为。它收集某些指标或拦截应用程序和系统其他部分之间接口的所有变化。在本章中,我们将讨论两个涉及运行时监控和拦截的案例研究,包括在Android软件堆栈之上实现一个内粒度的权限模型,如[123]所提议的。
为什么要进行字节码工具化?至少有两种方法可以进行运行时监控和拦截:修改Android软件栈或字节码工具化。 软件执行堆栈的修改包括改变操作系统或核心库以拦截所需信息。在安卓系统中,这意味着改变底层内核、Dalvik虚拟机或安卓框架。除非说服安卓联盟,否则这在部署上相当有限,因为正常的最终用户既没有权利(被监禁的手机)也没有能力这样做。 此外,这个解决方案需要用户改变他们的固件,这是一个非同小可的任务,而所谓的安卓系统的碎片化问题又使其更加复杂,因为没有一个单一的安卓系统,而是许多不同的安卓系统,每个系统都是为运行在特定的设备(平板电脑、智能手机......)而定制的。如果操作系统被修改,就需要为每个可能的Android版本创建一个自定义的工具化版本,这在实践中是不容易做到的。然而,字节码工具化是在不能修改的执行平台上执行运行时监控的最简单方法之一。在为改善隐私而进行的粒度政策执行的背景下,我们能够--由于字节码工具化--对已经部署在安卓智能手机上的应用程序执行粒度许可模型,而无需对安卓软件栈进行任何修改。
为什么要在智能手机上直接进行体内工具化?Bytecode工具化可以在设备之外进行,例如使用互联网上的远程服务。然而,许多国家禁止向第三方服务分发二进制文件(例如法国)。 此外,一些市场的服务条款(如Android的Google Play)也不允许这样做。在设备上直接对应用程序进行仪表化,可以使应用程序保持在设备内。
总而言之,我们认为在移动设备上确保安全和隐私的最有效和最实用的方法是直接在智能手机上对应用程序的字节码进行检测(体内),该检测是为特定的安全或隐私问题而定制的。我们的主要贡献是::
- 我们建立了一个工具链,可以直接在Android设备上自动重新打包Android应用程序;
- 我们已经建立了一个工具链,可以直接在Android设备上自动分析Android应用;
- 该工具链已经通过实现两个原型进行了测试,这两个原型增加了终端用户的隐私。一个是删除广告,另一个是让用户完全控制应用程序的运行权限。
- 这样一个工具链的可行性已经得到了评估。限制和挑战已经被指出。
据我们所知,我们是第一个提出在设备上直接自动转换安卓应用的工具链的3。
本章的组织结构如下: 第6.2节为读者提供了两个场景,说明了Android应用字节码工具化的必要性。 第6.3节描述了直接在Android设备(智能手机、平板电脑......)上对Android应用进行编程的工具链。第6.4节介绍了有价值的字节码工具的设计和实现,以保证智能手机的安全和隐私。第6.5节展示了在合理时间内运行整个工具链的可行性。最后,第4.8节总结了本章。
6.2 字节码工具化的动因
在不同的场景下,直接在智能手机设备上操作和分析Android应用程序的字节码是有益的(体内)。在本节中,我们将介绍两个有价值的用例: AdRemover和BetterPermissions。
这两个案例都改善了用户的隐私。AdRemover阻碍了广告库的工作,因此,在同一时间,防止他们发送与定位(GPS坐标,...)或设备本身的私人信息,如IMEI(国际移动设备身份)。BetterPermissions使用户有能力启用或禁用应用程序的权限。在一个极端的情况下,用户希望没有任何应用程序能够访问她的联系人列表,她将从手机上的所有应用程序中删除联系人权限。其结果是为用户提供了更好的隐私。
6.2.1 去除广告
近一半的安卓应用嵌入了第三方代码来处理应用内广告[101]。有相当大比例的广告支持的应用程序包括至少两个广告库[120]。
此外,安卓应用作为自给自足的软件包分发,捆绑了专门开发的代码和他们可能需要的第三方库,如仅有的二进制广告模块。
安卓系统执行的是基于每个应用政策的安全模型:要么一个应用的所有部分都受益于一个给定的权限,要么其任何部分都不受益。这意味着,当用户对一个应用程序授予权限时,她实际上是对可能由不同实体编写的组件授予权限,包括广告库。
例如,一个报纸应用程序可能被允许将其位置发回给应用程序的发布者,这样她就能看到当地的新闻。 然而,从隐私的角度来看,嵌入式广告库不应该被允许向广告公司发送位置数据。目前,用户面临一个两难境地:她要么降低她的隐私水平期望,要么不使用一个有价值的应用程序。
对平台的这一限制的解决方法是禁止在体内使用广告库。
这可能有积极的副作用,因为广告库对电池的使用也有很大的影响。根据最近的一项研究[100],第三方广告模块可以对免费应用程序中所花费的能量的65%-75%负责。
6.2.2 细粒度的许可政策
安卓框架依赖于一个基于权限的模型,并遵循一个 "要么全部,要么全部 "的政策。在安装时,用户必须接受或拒绝应用程序所要求的所有权限。只有当所有要求的权限被接受时,应用程序才会被安装。没有办法只接受某些权限(如访问本地化数据)而不接受其他权限(如连接到互联网)。用户注定要完全相信编写权限列表的应用程序开发者。Enck等人[48]已经指出,一个拥有多个敏感权限的应用程序是一个真正的安全威胁。例如,如果一个应用程序要求有发送短信的权限和读取联系人名单的权限,那么联系人名单就有可能通过短信发送至远程电话。
一个细化的权限模型包括让用户能够根据自己的使用情况,为应用程序指定自己的权限集。 设备上所有应用程序的所有权限集构成了安全策略。然后,基于权限的底层系统将强制执行这个用户自定的政策。
在未经修改的安卓平台上,运行这样的用户级安全策略是不可能的,因为未经修改的应用代码。然而,正如我们在后面所展示的,通过在安装时操纵应用程序的字节码,它确实是可能的,在体内。
6.3 体内字节码仪器化的工具链
本节介绍了我们在体内对Android应用进行字节码工具化的建议,即直接在智能手机上进行。读者可以参考第2.1节,了解关于Android堆栈和Android应用程序的更多细节。
6.3.1 要求
对一个完全可运行的Android应用程序进行仪器化和重新包装并不简单。它包括从应用代码中提取可执行代码,分析和记录它,重建一个新的可运行的Android应用,并再次签署它,因为操作系统要求应用被签署。
我们的工具链有以下要求:
1. 安卓操作系统必须是未修改的(为了6.1节中提出的广泛适用性);
2. 运行Android应用程序的Dalvik虚拟机必须是未修改的,特别是在配置值方面,如最大堆大小(为了广泛适用,见第6.1节);
3. 用于检测字节码的硬件必须是市场上常见的智能手机的代表。
6.3.2 工具链
字节码仪器化过程有以下几个步骤: 1) 从安卓应用apk文件中提取代码;2) 用字节码操作工具修改提取的代码;3) 重新构建一个包含修改内容的新安卓应用
3)重新构建一个包含修改过的代码的新的Android应用程序。
这三个步骤可以分解为五个基本步骤,如图6.1所示:i)提取并将Dalvik字节码转换为Java字节码(步骤a-b),ii)操纵字节码(步骤b-c),iii)将这种表示方法翻译回Dalvik字节码(步骤c-d),iv)重建一个新的apk文件(步骤d-e),v)最后用一个新的私钥签署所有文件(步骤e-f)。现在让我们讨论一下每个步骤中使用的工具。
i) 提取Dalvik字节码 第一步,如图6.1.(a-b)所示,是从apk ile中提取.dex ile类,并将其转换为可以用标准的未修改的Java字节码分析工具包分析的Java字节码类。 对于这一步,我们使用工具dex2jar4。
ii) 仪表字节码 在这一步,我们用两个不同的工具进行实验,它们可以操作字节码。回顾一下,字节码操作是图6.1中(b)到(c)的那一步。使用不同的工具使我们有机会测量它们之间在执行时间和内存消耗方面的差异,并决定哪一个更适合于在内存受限的系统中操作字节码。
ii.a) Soot。用Soot分析工具箱将类转换为Jimple。Soot[75]是一个用于Java程序的开源分析工具箱。它可以对Java源代码或字节码进行操作。它允许开发者对程序进行分析和转换。例如,程序内的ow分析可以确定一个变量在代码中的某个点是否可以为空。Soot还可以进行不同的调用图分析,这对特定的字节码分析非常有用。Soot中的大多数分析和转换都使用一种叫做Jimple的内部表示。Jimple是Java字节码的一种简单的无堆栈表示。我们把Soot移植到Android系统上,把它的Java字节码转换为Dalvik,并创建一个封装的Android应用程序。据我们所知,以前没有任何工作将安卓字节码作为一种抽象,可以直接在智能手机上进行静态分析。
ii.b) ASM。我们体会到,Soot有时很慢,需要大量的资源(特别是内存)。因此,我们也运行ASM来进行字节码分析。ASM[25]是一个Java字节码工程库。它的特点之一是它是轻量级的,因此更适合在内存或处理资源有限的系统上运行。它主要被设计用来操作和转换字节码,尽管它也可以用来进行一些程序分析。它有一个核心API来执行简单的转换,还有一个树形API来执行更复杂的字节码转换(这需要更多的CPU处理和内存空间)。
iii) 将修改后的字节码转换回Dalvik字节码 一旦类被分析工具箱分析和修改后,就用dx5将其转换回Dalvik字节码,dx5会从Java类文件中生成类.dex ile。这个步骤在图6.1中的c-d边显示出来。
iv) 重建应用程序 如图6.1(d-e)所示,在第四步之后,一个新的Andoid应用程序被建立。 新生成的类.dex、数据和原应用程序的安卓清单都被插入到一个新的zip6 ile中。
v) 签署修改后的应用程序 Android要求应用程序进行加密签名。 因此,生成的压缩包的所有文件都使用新创建的一对公钥/私钥(在图中没有表示)进行签名,新的公钥被添加到压缩包中(在图中没有表示)。我们使用keytool和jarsigner Java程序来签署应用程序(图6.1.(e-f))。
用新钥匙签署应用程序可能会导致应用程序之间的兼容性问题。例如,两个或更多用同一密钥签名的应用程序可以共享同一个进程。为了使这个功能继续工作,需要在旧钥匙和新钥匙之间保持一对一的映射,以便用同一个新生成的钥匙签署两个转换的应用程序(最初用
相同的密钥)用相同的新生成的密钥签名。 维护这种映射和处理应用程序之间的这种兼容性不属于本章的范围。
我们在Android上设计了一个使用标准工具的字节码操作过程。 下面介绍两个具体的字节码工具化原型的设计和实现。
6.4 用例设计和实现
任何利用第6.3节中介绍的工具链的用例都会分析或修改一个应用程序的字节码。分析或修改字节码由图6.1中的步骤(b-c)表示。现在我们介绍一下我们是如何实现和评估第6.2节的两个用例的。它们都是修改应用程序的字节码。 AdRemover修改字节码以删除广告。 BetterPermissions修改了字节码,以便为用户提供一个细化的权限策略系统。
6.4.1 AdRemover的实现
我们专注于两个广泛使用的Android广告模块: AdMob和AdSense。广告不是安卓系统的一部分,而是存在于应用程序的字节码中。因此,应用程序不共享广告库代码。然而,它们各自都有一份库代码的副本。禁用广告需要对每个包含广告库的应用程序进行检测。
广告需要I/O操作来获取广告数据。一个使用广告库的安卓应用开发者不希望她的应用因为广告库而崩溃。这就是为什么广告库的开发者在设计广告库时要特别注意异常情况的原因。他们预计I/O操作会经常失败,这取决于不可预测的环境。例如,如果设备不再有网络覆盖,就会抛出一个异常。
基于这一观察,我们假设I/O代码已经被广告开发者放置在Try/Catch块中,以恢复I/O失败引起的异常。 我们的工具利用这一假设,抑制应用程序的广告包的每个Try/Catch部分。对于它遇到的每一个Try/Catch块,我们的工具都会提取被处理的I/O异常的类型,创建这样一个异常对象,并在Try块的最开始插入一条抛出这个异常的指令。
为此,我们收集了这些库所使用的Java包的名称,并将AdRemover设置为只对属于这些包的类进行操作。我们写了两个AdRemover的实现: 一个使用Soot,一个使用ASM。
6.4.2 BetterPermissions: 细粒度的权限策略管理
在这种情况下,细粒度的策略是指用户指定哪些权限被授予应用程序。在现实世界中,用户只熟悉权限和应用,所以把策略限制在应用层面而不是更低的层面(如Android组件或Java方法)是非常合理的。然而,出于解释的目的,本节中的策略包含了Java方法和权限之间的映射。
为了使以用户为中心的策略存在,我们需要对每个希望控制的应用程序的字节码进行检测。回顾第2.1节,Android应用程序通过Android API与Android系统通信。该工具检测所有受一个或多个权限保护的API调用,并将这些调用重定向到一个策略服务。策略服务是独立的Android应用程序的一个Android服务组件。根据用户定义的策略,它授权或不授权应用程序调用受保护的方法。
当工具化的应用程序运行时,用户定义的策略由策略服务来执行。事实上,对于每一个工具化的方法,运行中的工具化程序都会调用策略服务,并检查策略。如果政策允许原始API方法的调用,则执行API调用。否则,就会执行一个假的实现,并返回一个假的默认值。
我们的原型工具在用户层面(也称为应用层面)执行用户自定的政策。它允许以前不能修改系统策略的用户为一组应用程序执行他们自己的策略。 修改代码以插入安全检查被称为内联参考监控(IRM),由Erlingsson等人和Evans等人首次提出[50, 51, 52]。
为控制或限制一个应用程序的权限,它的字节码必须被工具化。 所有需要一个或多个权限的方法调用[bartel2012automatically, 53]都被包裹在代码中,这些代码依次为:1:
1.询问策略服务,应用程序是否被授权调用该方法
2. 根据政策服务的回答,要么调用原始方法,要么调用假方法。
例如,节点7的getLocation(p1)方法调用(需要permis- sion GPS)在图中已经被调用策略服务所包裹。如果政策批准了这个调用,原始的getLocation(p1)就会被执行,否则就会调用一个假的方法,重新变成一个假的默认值。
总共有N个工具,其中N是应用程序字节码中所考虑的API调用的数量。
确定策略 下一步,如图6.3所示,是确定关于工具化应用的策略。用户为每个应用程序定义一组允许的权限。在幕后,策略会生成一个需要启用权限的所有Java方法的列表。这些方法被设置为授权。在图 6.3 中,只有 Instrumented NewsReader 的方法 getLocation() 被允许。
请注意,这一步可以先执行,只对未被策略授权的方法调用进行检测。然而,对每个需要一个或多个权限的API方法调用进行仪器化,使得在运行时改变策略成为可能。
策略服务 最后,当仪器化的应用程序运行时,策略服务会强制执行,如图6.4所示。对于每一个工具化的方法(这里的原始/工具化的方法是getLocation及其相关的权限GPS),运行中的应用程序会调用policyAccepts()(步骤A),并通过调用policyHas()(步骤B)检查策略。方法policyAccepts()如果政策允许原始方法,则返回true,如果不允许则返回false。如果政策允许原始方法,则调用原始方法(图6.4中就是这种情况,因为步骤C返回true)。否则,对应于原始方法的存根方法被执行。这里,存根处理方法getLocation没有被执行。我们将策略服务作为一个Android服务来实现,将工具化代码作为静态分析工具Soot的一个插件。
6.4.3 评估
我们现在检查我们的用例实现是否有效。对于这两个案例,我们针对一个真实世界的应用程序运行指令,并运行由此产生的修改后的应用程序。
AdRemover 我们通过在An- droid市场上随机选择一个应用程序来测试我们的工具是否有效。我们确保该测试应用程序使用AdRemover目前处理的两个广告模块之一。
首先,我们在安卓设备上运行未修改的测试应用程序,并确保它是一个工作的应用程序,并且它确实显示广告。
然后,我们将这个应用程序发送到我们的工具链(与Soot实现),在PC上运行。修改后的应用程序仍然有效,并且不再显示广告。我们在测试过程中监测网络连接,发现该应用程序不再发送任何广告请求。
最后,我们再次处理未修改的应用程序,这次是在智能手机上直接运行字节码操作。运行修改后的应用程序的结果与在标准PC上修改后的应用程序的结果相同。
BetterPermissions为了评估内粒度策略,我们选择了另一个随机的应用程序,并对其进行检测,以包住每个与GPS相关的权限敏感的API调用。该应用程序被检测,然后被重新打包成一个新的签名应用程序。我们在一个安卓设备上运行这个工具化的应用程序,并使用不同的策略对其进行测试。用户自定的策略如期执行了。
总而言之,这两种字节码转换的结果是应用程序能够正确运行。这些最初的结果很重要,因为这两个用例说明了使用字节码工具链可以实现什么。 对我们来说,同样重要的是,考虑到当前智能手机的内存和CPU限制,所考虑的工具链是否可以在Android应用程序的大型数据集上实际运行。 下一节将通过测量体内工具的执行时间和内存消耗来回答这个问题。
6.5 体内仪器的性能
在这一节中,我们介绍了应用第6.3节中提出的工具化过程的结果,并在图6.1中进行了总结。我们的目标是了解:1)在硬件资源受限的情况下,是否有可能在智能手机上操作字节码。2)它是否需要合理的时间。
6.5.1 措施
我们在一组130个Android应用程序上测量了工具化过程的各个步骤的执行时间。这个集合在第6.5.3节中有描述。我们在三个不同的安卓智能手机上运行仪器化过程,这些手机的配置在第6.5.2节中介绍。
整个过程的可行性是通过工具链的每一步(1:dex2jar,2:Soot/ASM,3:dx,4:customZip,5:签名)的时间来衡量。 运行每一步的时间和成功通过每一步的应用程序的数量也得到了测量。
对于该过程的第二步(步骤:对字节码进行分析),我们对ASM和Soot进行评估。对于ASM,我们测量了在AdRemover案例研究中对Java字节码进行分析所需的时间。AdRemover转换利用ASM树形API来执行6.4.1中描述的try/catch块操作。 通过测量生成AdRemover和BetterPermissions案例研究的Java类所需的时间来评估Soot(Ad- Remover是用ASM和Soot实现的)。
6.5.2 实验材料
我们在三个基于Android的智能手机设备上进行实验。它们的配置详见表6.1。主要的区别是处理器的时钟速度(0.8,1.2和1.4 GHz),主内存的总量(512,768和1024 MiB),Android版本(2.2,2.3.4和4.0.3)和Dalvik虚拟机的最大堆大小(24,32和48)。由于堆大小控制了单个进程可分配的最大内存,它也控制了可同时分配的最大对象数量。
核心的数量也是不同的。 然而,在实验过程中,我们并没有利用多核的优势。这种硬件符合6.3.1中提到的要求#3。
6.5.3 数据集
我们将整个实验协议应用于一组130个安卓应用,这些应用是从安卓市场的前500个应用中随机挑选出来的7。它们跨越了不同的领域,如娱乐、游戏、通信、多媒体、系统或新闻。这个数据集不是艺术性的,因为它只包括真实世界的应用。
为了更好地概述这些应用,图6.5显示了关键的应用指标,如图表。它们表明大多数(75%)的Android应用的Dalvik字节码小于614KiB,小于602个类,平均方法度小于3。
6.5.4 Dalvik到Java字节码的转换
使用dex2jar从Dalvik可执行代码到Java字节码的转换时间如图6.6所示。
观察1 在智能手机2和平板电脑1上,75%的应用将dex iles转换为jar的时间不超过60秒。在我们的Android应用数据集上,转换时间不超过250秒。
观察2 最大的Dalvik字节码(4000 KiB)的应用程序在智能手机2和平板电脑1上都能成功转换。
观察3 我们注意到,在Dalvik字节码大小小于4000KiB的情况下,转换时间与dex ile的大小(形式为a - X + b)是线性的。使用线性回归,我们发现对于智能手机2,a等于0.069,b等于0.3。对于平板电脑1,我们有,0.049和-0.4。转换时间和Dalvik字节码的大小之间的线性关系使我们能够在理论上预测转换任何大小的Dalvik字节码所需的时间(如果我们推断出大于4000KiB的大小)。例如,处理具有10 MiB的Dalvik字节码的Android应用程序的时间,对于智能手机2来说是700秒,对于平板电脑1来说是500秒。
结论1 在体内将Dalvik字节码转换为Java字节码在几分钟内是可行的。
局限性:智能手机1无法处理任何dex ile。另外,当使用智能手机2和平板电脑1时,分别有26个和11个dex ile导致转换的Android应用程序dex2jar崩溃。这种崩溃是OutOfMemory或StackOverflow异常。
智能手机1的结果是由Android的硬编码最大堆大小(32 MiB或48 MiB)解释的。对于其他两个设备,崩溃的原因是默认的8 KiB堆栈大小。总的来说,在智能手机2上有104个(80%)安卓应用程序被成功转换为jar ile,在平板电脑1上有119个(91%)。
然而,由于安卓设备变得越来越强大,安卓系统的默认堆大小也在增长。事实上,在安卓2.2系统中,堆的大小是24 MiB,在安卓2.3.4系统中是32 MiB,在安卓3.0系统中是48 MiB。这种持续的增长将使我们的工具链能够转换具有更大Dalvik字节码大小的Android应用程序。
此外,一些应用程序可能被混淆了,以防止Dex2jar将Dalvik字节码转换为Java类。 在实验过程中,我们没有遇到任何混淆的情况。 我们的工具链依靠的是独立的组件。因此,如果Dex2jar不能处理某些混淆技术,它可以很容易地被处理这些技术的同等组件取代。
6.5.5 字节码操纵的性能
本节介绍了我们使用两个不同的工具库进行体内字节码操作的性能测量: ASM和Soot。
用ASM进行操作
使用ASM对Java字节码进行转换的时间如图6.7所示。在这个实验中,6.2.1中描述的AdRemover转换是用ASM实现的。
观察4 所有在智能手机2上用dex2jar成功转换的104个应用程序都被ASM成功处理。
成功地被ASM在体内处理。它在不到600秒的时间内处理了每一个jar(最大4MB的大小)。
观察5 我们注意到,对于小于4000KiB的Dalvik字节码,转换时间与jar的大小(形式为a-X+b)呈线性关系。使用线性回归,我们发现对于智能手机2,a等于0.146。对于平板电脑1,我们有,0.025。
结论2 在智能手机上使用ASM操纵字节码是可行的。考虑到我们的转换和数据集,ASM并没有与智能手机资源不相容的特殊内存或CPU要求。
用Soot进行操作
我们现在考虑AdRemover转换的Soot实现。在130个An- droid应用程序中,只有3/130在智能手机2上被正确处理,19/130在平板电脑1上被正确处理。
观察6 只有最小的应用程序(就Dalvik字节码而言)可以被转换。例如,在智能手机2上转换任何小于或等于20KiB大小的jar需要不到30秒。然而,较大但较小的应用程序(在25%的四分位数),使用Soot工具需要长达18分钟。
结论3 在体内使用Soot只对最小的应用程序是可行的。我们假设,堆的大小是体内使用Soot的主要阻碍因素。为了验证这个假设,我们在一台台式电脑上进行了一个实验,包括分析我们的安卓应用数据集,这些应用具有不同的最大堆大小(从5Mib到50Mib,以5Mib为单位)。结果如图6.8所示。 Soot能够处理67个堆大小为50Mib的应用程序。 这些结果清楚地表明,最多有一半的Android应用可以在堆大小为50Mib的情况下被处理。在Java和Dalvik虚拟机上堆的使用(因此所需的最大尺寸)相似的假设下,这意味着内存实际上是在Android上使用Soot的主要阻塞因素。
6.5.6 Java字节码到Dalvik的转换
一旦一个应用程序在Java字节码层面上被检测到,就必须将其转换回Dalvik字节码。使用dx工具从Java类到dex ile的转换时间如图6.9所示。
观察7 智能手机2上的33/130个和平板电脑1上的39/130个应用程序的Java字节码已经成功转换为Dalvik字节码了。
观察8 jar iles从20到400 KiB的转换时间不超过80秒。
结论4 Dx工具是工具链的一个瓶颈。它只能正确处理25%到30%的应用程序。原因是它把每个Java类都放在内存中,并受到体内处理的内存限制,与Soot类似。这个工具是现成的,可以通过对类的处理来限制内存消耗,从而优化在资源有限的设备上运行。.
6.5.7 创建一个新的apk文件
从工具化的Dalvik字节码创建一个apk ile的时间如图6.10所示。注意,对于这一步,输入集不是前一步的输出。我们只有39/130个应用程序在前面的步骤中被正确处理过。在每一步,都有一些申请失败。对于剩下的91/130个无法计算出原始工具化的Dalvik字节码的应用程序,我们把应用程序的原始Dalvik代码作为输入。这样一来,上一步的问题就不会干扰第四步的结果。
观察9 121/130个输入被成功处理。前一个apk ile的大小和新apk的创建时间之间没有明显的关系。只有9/130个应用程序产生异常,因为它们的大小太大,因此不能被压缩工具处理。
观察10 对于95%的应用程序,无论设备和原始apk ile的大小如何,都需要少于ive秒。
结论5 在智能手机上创建apk文件是可行的。与转换字节码或用Soot操作字节码的时间相比,创建一个新的apk ile的时间可以忽略不计。
与Dalvik大小没有线性关系,正如图6.6和6.9中的情况一样。这可能是由于在生成apk文件时,除了字节码大小外,还有其他因素在起作用,比如处理媒体文件(图像、声音等),这些因素有时会支配Dalvik字节码大小。
6.5.8 对生成的apk文件进行签名
图6.11表示应用程序的签名时间。
观察11 120/130个安卓应用在平板电脑1上被成功签署。apk ile的大小和apk ile的签名时间之间没有明显的关系。 14/130个应用程序产生了一个异常,因为它们的大小太大,因此不能被处理(智能手机2上有14个,平板电脑1上有10个)。
观察结果12 对于95%的应用程序,无论设备和apk ile的大小,都需要最多12秒的时间来签署应用程序。
结论6 在智能手机上签署apk ile是可行的。与apk ile的创建步骤类似,计算时间可以忽略不计。 在智能手机1和智能手机2之间观察到的差异反映了其CPU时钟频率的差异。
6.5.9 结论
我们现在总结一下我们对安卓应用进行体内修改的实验结果。
可行性
表6.3总结了智能手机2的所有实验,并强调了整个方法的可行性。
计算了Soot和ASM版本的工具链的所有步骤的总执行时间。对于一个基于ASM的仪器,处理一个应用程序需要120秒的中位时间,也就是2分钟。我们认为用户会同意在开始使用一个应用程序之前等待2分钟,如果他们在这个工具化过程中得到更多的保证,可以获得更好的隐私。在这2分钟内,手机仍然可以使用,因为只使用了一个核心(大多数智能手机的特点是多核心CPU),而且只能使用虚拟机允许的最大堆内存(而不是所有内存)。
这些实验表明,在安卓系统上直接操作字节码是可行的。该过程中最昂贵的步骤是Dalvik到Java字节码的转换,反之亦然,以及Soot字节码操作步骤。
hopsasa
如何提高In Vivo Instrumentation的性能?
根据我们的分析,主要的阻塞因素是内存。 分析和转换应用程序所需的最大堆大小是许多转换步骤的一个问题。我们认为这个问题很容易解决:1)下一代更强大的硬件;2)即将推出的安卓操作系统和虚拟机版本,它们的最大堆大小可能会大大增加(例如,安卓4的堆大小被设定为48 MiB)。
Dalvik到Java的转换和Java到Dalvik的转换是两个非常耗时的步骤。它们使用未经修改的Dex2jar和dx版本。 有两种方法可以克服这些耗费资源的工具。
首先,这些工具从来没有被优化过以在资源有限的平台上运行。我们相信,在CPU和内存消耗方面有许多优化的机会。
第二,我们可以用更好的替代品来取代这些工具。例如,一个用于操作Dalvik字节码的类似ASM的li-brary将允许跳过Dalvik到Java和Java到Dalvik的转换。这样的工具正在出现,如ASMdex8。另一个解决方案包括直接从Dalvik字节码到Jimple字节码的双向转换,这两者都是基于寄存器的。我们确实正在开发一个Dalvik到Jimple的翻译原型,叫做Dexpler[12]。
总而言之,我们的结果表明,我们可以合理地想象在最多5分钟内对100%的数据集应用程序进行字节码操作。
对有效性的威胁
现在让我们来讨论一下对我们实验结果有效性的威胁。
实施错误:我们的结果是成立的,因为在实施过程中,涉及到的任何ive程序,以及我们编写的胶水和测量代码中都没有严重的错误。
数据集的可推广性: 我们的数据集可能无法代表现实世界中使用的Android应用程序。
线性推断: 我们为Dalvik到Java和Java到Dalvik的转换所建立的线性关系在字节码大小小于或等于300KiB时是成立的。对于尺寸更大的字节码,它可能不成立。在存在非线性奇异点的情况下,可能无法分析大型应用程序。
字节码操作时间: 我们关于字节码操作时间的结果是通过相对简单的转换获得的。可能的情况是,复杂的转换不是同一数量级的,而且消耗的内存要多得多。然而,对于第6.2节中介绍的用例,仪器化只包括监控和代理Java方法。
6.6 结论
我们在本章中提出并评估的工具链是一个里程碑,它回应了Stravou等人[123]最近提出的关于迫切需要进行字节码分析以在手机上进行体内安全检查的说法。我们已经1)提出了一个工具链,允许对Android字节码进行操作、仪器化和分析;2)表明有可能在合理的时间内直接在未修改的Android软件栈的未修改的智能手机上运行该工具链。具体来说,我们的实验表明,使用ASM,我们数据集中的39个(30%)应用可以在952秒内完成工具化(中位时间为120秒)。此外,我们还讨论了我们观察到的特殊限制,如Android系统的硬编码堆大小。
我们相信,这些不同的限制可以被迅速克服,至少有两个主要原因。首先,我们使用的是现成的Java工具,这些工具没有经过优化,无法在资源(内存/CPU)有限的环境中运行,而且可能有很大的操作难度。 第二,智能手机的硬件和操作系统的发展将使处理更大的安卓应用程序成为可能(例如,在安卓4上,默认的堆大小是以前版本的两倍)。
我们目前正在研究其他用例。特别是,我们正在实施一种在智能手机上设置和运行的行为恶意软件检测方法。这种方法包括对字节码进行检测,将API方法调用重定向到负责检测恶意行为的存根上。
第七章
相关工作
在过去的四年里,关于Android协议栈及其应用的研究急剧增加。 这可以解释为,安卓堆栈是流行的、开源的,这便于对系统进行分析和修改,而且有数以百万计的应用程序可供分析。
本章的其余部分组织如下。 第7.1节描述了与Dalvik字节码解析和类型化有关的研究。 第7.2节重点介绍了从Android系统中提取权限图的替代技术。 第7.3节总结了用于分析组件间和应用间通信的技术,以及在安卓应用中发现泄漏的技术。最后,第7.4节描述了与直接在设备上对Android应用程序进行仪器化有关的研究。
7.1 Dalvik的本地类型化
在实践中,Android应用程序的Java源代码或Java字节码是不可用的,只有Dalvik字节码才是。 Java语言出现于1995年,从那时起,已经开发了分析Java源代码和字节码的工具。 这就是为什么人们对将Dalvik字节码转换回Java字节码的工具感兴趣的原因,这样现有的工具就可以用来分析Android应用程序。 在第7.1.1节中,我们讨论了将Dalvik字节码转换成Java字节码的工具。
在第7.1.1节中,我们讨论了将Dalvik字节码转换为Java字节码的工具,在第7.1.2节中,我们讨论了使用中间表示法反汇编和/或组装Dalvik字节码的工具。
7.1.1 Dalvik到Java字节码的转换器
Ded [47] 和Dare [91] 是Dalvik字节码到Java字节码的转换器。一旦生成了Java字节码,Soot就被用来优化代码。Dex2jar [99]也从Dalvik字节码生成Java字节码,但没有使用任何外部工具来优化生成的Java字节码。Undx[115]也是一个Dalvik到Java字节码的转换器,但似乎已经无法使用。
另一方面,我们的方法不是直接生成Java字节码,而是Jimple代码。据我们所知,现有的工具没有直接将Dalvik字节码转换为Jimple代码。从Jimple代码中,由于Jimple代码是Soot的内部代码表示,我们也可以生成Java字节码。用我们的方法分析一个Android应用程序只需要一个步骤(即Dalvik字节码到Jimple),而不是像所有现有的方法那样需要两个步骤(即Dalvik字节码到Java字节码和Java字节码到Jimple)。
7.1.2 Dalvik 汇编/反汇编程序
Radare [96], Dedexer [95], Smali [62] 是Dalvik反汇编器。他们使用自己的Dalvik字节码的代表:他们不能利用现有的分析工具。例如,Dedexer生成的格式接近Jasmin,但包含Dalvik指令,阻碍了Java字节码的生成。 我们的工具使用Soot的内部表示法,它允许现有的工具分析/转换Dalvik字节码。
Androguard [40] 是一个Dalvik字节码分析器。它有一个反汇编器和模块来分析Dalvik字节码。Redexer[104]和AsmDex[90]是Dalvik字节码工具化框架。它们能够在字节码水平上对Android应用程序进行分析。
这些方法都不能进行高级静态分析,如数据流分析。另一方面,我们的方法将Dalvik字节码转换为Jimple,即Soot的内部表示。现有的对Jimple代码进行数据流分析的工具可以利用我们的工具来分析Android应用程序。
7.2 权限图的提取
7.2.1 关于Java权限模型
虽然Android的权限模型与Java中实现的不同,但下面的研究提出了相关的观点,以使我们的贡献得到重视。
Koved等人描述了一个新的静态分析[74],为Java2程序生成一个权限列表(在Java权限模型中)。Geay等人提出了一种改进的方法[58]。 我们也使用静态分析,但是在Android的环境下,特别是在连接Android API和服务的绑定机制方面,与Java环境不同。正如我们的评估所显示的,绑定器阻止了现成的Java静态分析工具来解决对服务的远程调用。
Pistoia等人[103]提出了一种静态分析,以识别应该被赋予特权的代码部分。这个问题在Android框架中不会出现,因为代码本身没有特权,而是在入口点进行访问控制。这意味着安卓框架的设计者必须注意创建由权限执行点保护的独特入口点,但不会影响我们的静态分析。
基于角色的访问控制(RBAC)机制是由Cen- tonze等人[28]使用静态分析来分析的。当一个受保护的操作操纵数据时,这个数据不应该直接或间接地被策略中未定义的路径所访问。 如果不是这样,该操作就被称为位置不一致。他们开发的工具可以检查JavaEE程序的RBAC策略是否是位置一致的,或者是否存在一些漏洞。安卓系统定义了保护操作的每项任务,而这些操作又会操作受保护的数据。 我们的目标是计算权限差距,这可能会显示出对最小特权原则的违反。安卓系统保护的操作是否是位置一致的,不在我们的方法范围之内。
同样与基于角色的访问控制有关,Pistoia等人[102]正式建立了RBAC模型,并静态地检查了基于JavaEE的RBAC系统的一致性。 我们检查Android应用程序的权限列表是否尊重最小特权原则。这些概念是相同的(安卓的权限可以近似于角色,我们检查安卓框架的每一个点都需要哪些角色),但目标系统却不一样。有趣的是,我们使用类似的方法来解决Binder问题,就像他们解决远程方法调用问题一样:不是静态分析Binder/RMI代码来解决方法,而是计算从远程方法的调用到远程方法本身的映射。但一个主要的区别是,在Android的情况下,系统服务和上下文必须事先被初始化,以模拟正确的系统状态。
7.2.2 关于Android的权限模型
Android的安全模型在灰色文献[49, 118]和官方文档[128]中都有描述。不同类型的问题已经被研究,如社会工程攻击[66],串通攻击[86],隐私泄露[59]和特权升级攻击[55,36]。相比之下,我们的方法并没有描述一个特定的弱点,而是描述了一种减少潜在漏洞的软件工程方法。
然而,我们并没有像[89, 93, 42, 31, 26]那样为Android描述一个新的安全模型。 例如,Quire[42]在运行时维护调用链和请求的数据来源,以防止某些类型的攻击。在我们的工作中,我们没有修改现有的Android安全模型,我们设计了一种方法来缓解其固有的问题。
此外,不同的作者根据经验探索了Android模型的使用。例如,Barrera等人[11]提出了一项关于如何使用权限的实证研究。特别是,他们使用了可视化技术,如自组织地图,来识别权限的模式,以及权限分组的模式。其他的实证研究包括Felt的关于权限模型的有效性的研究[54],以及Roesner的关于用户对基于权限的系统的反应的研究[109]。虽然我们的方法也包含经验性的部分,但它也是可操作的,因为我们设计了一种可操作的软件工程方法来驯服一般的基于权限的安全模型,特别是Android的模型。
Enck等人[48]提出了一种方法来检测危险的权限和恶意的权限组。他们设计了一种语言来表达安全专家所表达的规则。在安装时不成立的规则表明有潜在的安全问题,因此是一个高攻击面。我们的目标是不同的:我们的目标不是识别从专家那里发现的风险,而是识别应用程序的权限规范和平台资源和服务的实际使用之间的差距。与[48]相反,我们的方法是完全自动化的,在这个过程中没有专家参与。
PScout[6]是一个与我们的并行设计的静态分析。它也使用Soot,但只依赖
CHA,不使用Spark。我们的工作在第4.6.2节中对他们的部分结果进行了比较和验证。
最后,Felt等人[53]与我们同时进行了同样的研究。 他们发表了第一个版本的开发者资源(如API调用)和权限之间的地图。 有趣的是,我们采取了两种完全不同的方法来识别该地图:他们使用测试,而我们使用静态分析。因此,我们的工作验证了他们的大部分结果,尽管我们发现了一些差异,我们在第4.6.3节详细讨论了这些差异。但关键的区别是,我们的方法是完全自动化的,而他们的方法需要手动提供测试 "种子"(如输入值)。然而,在存在检测的情况下,如果测试是适当的,他们的方法会更有效。因此,我们认为这两种方法是互补的,无论是在基于权限的架构的概念层面,还是在具体的逆向工程和记录Android的权限方面。
Mustafa等人[88]致力于系统服务的分析。他们的方法是使用上下文敏感的后向切片方法,从权限检查方法开始提取子调用图。他们的分析更加精确,因为他们捕获了权限检查的条件。然而,他们只考虑了独立的系统服务,没有处理RPC。
另一方面,我们从Android的API入口点开始分析,并处理服务RPC链接。
7.3 Android应用程序中的数据泄漏
据我们所知,我们的工具IccTA是第一个通过代码工具无缝连接Android组件的方法,以执行基于组件间通信(ICC)的静态污点分析。通过使用代码工具技术,上下文的状态和数据(例如,一个意图)在组件之间传输。据我们所知,目前还没有其他现有的静态方法来检测Android隐私泄漏,处理ICC问题并在组件之间保持状态。
7.3.1 静态分析
有几种使用静态分析来检测隐私泄露的方法。PiOS[44]使用pro-gram slicing和可达性分析来检测可能的隐私泄漏。TAJ[130]使用相同的污点分析技术来识别网络应用中的隐私泄漏。然而,这些方法引入了大量的假阳性结果。CHEX[84]是一个工具,通过跟踪敏感源和外部可访问接口之间的污点来检测Android应用程序中的组件劫持漏洞。 然而,它最多只限于1个对象的敏感度,这在实践中导致了不精确性。 LeakMiner和AndroidLeaks表示有能力处理An- droid生命周期,包括回调方法,但这两个工具对上下文不敏感,因此无法精确分析许多实际情况。FlowDroid[5]引入了一种高度精确的污点分析方法,具有较低的误报率,但它不能识别基于ICC的隐私泄漏。IccTA通过对原始应用的代码进行检测,在保持高精度的同时,进行基于ICC的静态污点分析。
ComDroid[30]和Epicc[92]是两个解决ICC问题的工具,但它们主要关注ICC的漏洞,并没有污损数据。
SCanDroid[56]是一个用于分析基于ICC的隐私泄露的工具。 它修剪了所有对Android操作系统方法的调用边,并保守地假设基对象、参数和返回值继承了参数的污点。这种方法比我们的工具精确得多,因为我们在控制路径图中用我们的假主方法来模拟所有的Android操作系统方法(除了本地方法)。 另一个工具SEFA[136]也解决了ICC的问题。它执行全系统的数据流分析,以检测可能的漏洞(例如,被动的内容泄露)。SCanDroid和SEFA都使用一种匹配的方法来分析组件间的泄漏。 SCanDroid将所有向应用程序导入数据的方法定义为inow方法,将所有从应用程序导出数据的方法定义为outow方法。然后,它匹配inow和outow方法来连接两个组件。 SEFA将ICC方法定义为桥梁汇,以区别于敏感汇。它使用桥汇与其他组件匹配,从而连接两个组件。正如我们之前提到的,与我们的工具化方法相比,匹配方法有一些缺点。因此,即使我们不能在DroidBench上评估SCanDroid和SEFA,也来IccTA在设计上更加精确。
AsDroid[69]和AppIntent[140]是另两个使用静态分析来检测Android应用中权限泄露的工具。它们都试图分析隐私泄露的意图。分析泄漏的意图不在我们的方法范围之内。然而,我们认为有必要区分隐私泄漏是否是故意的。我们将此作为我们进一步的工作。
7.3.2 动态分析
另一方面,动态污点分析技术在运行时跟踪敏感数据。 Taint- Droid[45]是最复杂的动态污点跟踪系统之一。它跟踪第三方应用程序的私人数据。CopperDroid[105]是另一个动态测试工具,它观察Android组件和Linux系统之间的相互作用,以重建高水平的行为,并使用一些特殊的刺激技术来锻炼应用程序,以防止恶意活动。其他几个系统,包括AppFence[67]、Aurasium[138]、AppGuard[8]和BetterPermission[13]试图通过动态监测被测试的应用程序来缓解隐私泄漏问题。
然而,这些动态方法可以被特殊设计的方法所欺骗,以规避安全跟踪[114]。 因此,动态跟踪方法可能会错过一些数据泄漏,并产生一个不足的近似值。另一方面,静态分析方法可能会产生一个过度的近似值,因为所有应用程序的代码都被分析了,即使是在运行时永远不会执行的代码。在分析Android应用程序的数据泄露时,这两种方法是互补的。
7.4 Bytecode的In Vivo Instrumentation
7.4.1 监控应用程序
由于 "移动 "恶意软件的爆炸性增长和移动操作系统的日益复杂,在运行时监控智能手机应用程序是最近出现的一个想法。
Bose等人[23]旨在根据运行时的行为来检测恶意软件。为此,他们在Symbian OS模拟器中添加了挂钩,以跟踪操作系统和API调用。 换句话说,恶意软件检测只在模拟器中实现,在体外进行。相反,我们的目标是在活的用户环境中进行恶意软件检测,并表明这在中期内是可行的。
Enck等人[46]提出了一个名为TaintDroid的运行时监控框架,它允许污点跟踪和分析,以跟踪Android中的隐私泄漏。 他们的原型是基于运行Android应用程序的Dalvik虚拟机的一个修改版本。 同样,Costa等人[34]对移动设备的Java虚拟机(Java ME)进行了扩展,以增加运行时监控功能。相反,我们的可行性研究表明,在未修改的Android系统中实现运行时监控是可能的。
最近,Burgera等人[27]提出了一种基于收集的操作系统调用来检测恶意软件的方法。运行时监控可以在不同的颗粒度水平上进行。虽然Burgera等人描述的方法是在操作系统调用层面,但我们的目标是在API调用层面提供运行时监控,也就是说,更细粒度,更接近移动应用程序的应用域。
Davis等人[37]提出了一个Android应用重写框架的原型,并阐述了它在监控应用和实现内粒度访问控制方面的用途。
最后,Shabtai等人根据对各种系统指标的收集和分析来检测恶意软件,如CPU使用率、通过Wi-Fi发送的数据包数量等。这是一种检测恶意软件行为的间接方法。 同样,通过监测API调用,我们直接观察应用程序的行为。本论文中提出的经验结果表明,这实际上是可能的。
7.4.2 广告权限的分离
Shekhar等人[120]提出了一个新的Android广告系统,允许应用程序和它的广告模块在不同的进程中运行,因此有不同的权限设置。 这个新系统必须在开发阶段手动插入到应用程序中,因为没有提供自动化的应用程序修改。
Pearce等人[101]提出了一个广告框架的案例,该框架将被整合到Android平台内。每个开发者都能够使用定制的API,该API将在Android设备上可用。这种方法需要对安卓框架进行修改,并且一个特定的用户拥有一个嵌入该广告系统的安卓版本的设备。
7.4.3 许可政策
Erlingsson等人和Evans等人[50, 51, 52]是第一个操纵字节码来直接在Java程序中编织安全策略的人。 他们的Inline Reference Monitor(IRM)技术允许(1)将程序开发与策略定义完全分开;(2)拥有独立于程序运行的Java虚拟机的策略机制。我们还将安全策略直接编织在Android应用程序中,从而获得强大的Android应用程序,其安全策略独立于其所运行的Android系统。
与我们的工作最接近的是两个Dalvik字节码操作系统: I-Arm Droid[38]和Mr. Hide[72]。主要区别在于我们的方法是在体内运行,而他们的方法不是。
AppGuard[7, 9]也实现了体内字节码操纵。然而,该方法是基于dexlib一个字节码操作库,它不提供像Jimple与Soot那样的抽象代表。 因此,用他们的方法对字节码进行更高级的推理(例如在图上)是很困难的。
将感兴趣的方法重定向到一个监视器是IRM的基础。Von Styp-Rekowsky等人通过在运行时修改Dalvik函数指针的等价物提出了一种新的方法[125]。这种方法减少了开销,可以很容易地被我们的内层权限系统所采用。
Xu等人提出了Aurasium [137],这是另一个权限管理系统。它确实在C语言库的水平上运行,并将感兴趣的低级函数重定向到监控器。在这个低级别的操作使得注入假值和区分正常和Java级安全相关操作变得很困难。
Reddy等人[104]声称,通过创建 "以应用程序为中心的权限",即表达应用程序可以做什么的权限,而不是表达应用程序可以使用什么资源的当前Android权限,Android平台的安全性将得到改善。他们编写了一个库,允许所谓的 "以应用为中心的权限 "被管理。此外、
他们开始开发一个叫做 "redexer "的工具,其目的是自动重写现有的应用程序,以使它们使用这些新的权限。
Nauman等人[89]扩展了Android基于策略的安全模型,使其能够在运行时执行约束。他们创建的工具,称为Apex,允许用户表达对应用程序使用任何权限的限制: 例如,通过Apex可以向任何给定的应用程序授予SEND_SMS权限,同时确保该应用程序每天发送的短信不能超过用户指定的数量。 用户也有可能改变主意,完全阻止应用程序发送短信息。这是对安卓系统的一个重要改进,因为它允许用户指定一个更细化的政策,而不是必须在授予一个应用程序在安装时可能要求的所有权限或不安装该应用程序之间做出选择。 然而,这种方法需要在安卓框架的深处进行修改,因此需要得到谷歌的支持,并整合到安卓的未来版本中,才能被广泛使用。
通常情况下,一个权限可以保护对原始资源的访问,例如从相机拍摄的原始图片。Jana等人[71]提出的方法,通过在原始数据到达应用程序之前对其进行过滤,可以有一个更加细化的政策。假设你安装了一个应用程序,从相机拍摄的照片中识别人脸。在传统的基于权限的系统中,应用程序将被赋予CAMERA的权限。 用他们的方法,应用程序可以只被赋予一个新的FACE_RECOGNITION的细粒度权限。然后,来自相机的原始数据将被篡改,只保留关于人脸的信息,而不是关于拍摄照片的环境。由于应用程序只有关于人脸的信息,它不能再重新识别隐私敏感的环境信息(例如,墙上的街道名称,黑板上的文字)。
本文所描述的工作是独一无二的,该论文是一个重要的贡献。我们改进了以下领域的技术水平:(1)用Dexpler分析Dalvik字节码,这是一个通过Jimple表示法对Dalvik字节码进行完全类型化的工具;(2)基于权限的分析,用一个通用方法来分析基于权限的框架和提取权限,以及一个通用方法来分析基于权限的应用程序和ind权限差距。(4) 用IccTA对安卓系统进行数据泄漏分析;(5) 通过两个用例对安卓应用进行动态分析。
第八章
结论和未来工作
本章的组织结构如下。 第8.1节概述了本博士论文的贡献。第8.2节描述了未来工作的潜在方向。
8.1 结论
在这项工作中,我们从安全的角度分析了Android基于权限的系统及其应用。与传统的Java应用相比,Android应用具有不同的结构和编码。 需要新的工具和抽象技术来正确分析Android应用程序。
我们介绍了Dexpler,一个将Dalvik字节码转换为Jimple的工具,Jimple是Soot的内部表示。Dexpler已经在超过2.5万个包含1.35亿个方法的应用程序上进行了评估,发现99.99%的方法都是正确的类型。
第二,我们分析了安卓系统本身,以提取API方法和权限之间的映射。我们的工具对安卓系统的特性进行建模,如系统服务通信和服务身份反转。它提取了一个权限矩阵,其中有4962个与权限相关的入口点。我们分析了开发者是如何为Android应用程序编写权限列表的。我们提出了一个工具来静态分析安卓应用,以检查非必要的权限。我们发现,超过18%的应用程序至少声明了一个无用的权限。
第三,我们分析了安卓应用,以发现数据泄露。我们提出了IccTA,一个在安卓应用的组件之间和安卓应用之间识别数据泄露的工具。IccTA超过了现有的对安卓应用进行污点分析的工具,达到了95%的精确度和82%的召回率。
最后,我们研究了如何将动态分析与静态分析的结果结合起来使用。 我们提出了一个工具链,用于自动转换体内的Android应用程序,即直接在运行Android的设备上进行转换。我们在两个原型中使用该工具链。第一个原型允许用户选择细化的权限策略,并依赖于通过静态分析Android系统获得的权限映射。 第二个原型从应用程序中删除广告。
8.2 未来的工作和开放的研究问题
本节描述了潜在的未来研究方向。
8.2.1 框架分析
提取一个许可检查的通用模型
值得探索的是,如何将权限执行作为一个跨领域的问题来表达,以便根据安全规范,在应用程序或框架层面上自动添加或删除权限执行点。这样的工作将回答以下问题: 权限检查是否总是以同样的方式插入到代码中?有可能绕过权限检查吗?如何验证一个基于权限的系统?
提高精度
我们现在正在研究一种模块化的方法,这种方法能够协同分析本地代码和字节代码,并结合两者的权限信息。 结果的精度受限于调用图构造的精度和处理的Android组件的类型。此外,提高精度也可以提高该方法的可扩展性,因为更精确的调用图可能包含更少的边,因此需要更少的计算来提取权限信息。
重新审视实证评估
此外, 由于我们的方法是通用的, 我们可以将其应用于其他基于权限的系统, 如FirefoxOS或谷歌浏览器.
8.2.2 安卓应用静态分析的未来
我们还能实现应用分析的自动化吗?
我们只使用静态分析来分析Android应用程序。然而,有一些方法可以阻碍静态分析,如代码混淆、密集使用reection来隐藏方法调用、加密字节码、加密字符串、在虚拟机中执行代码等。在我们的分析中,我们会错过这类应用程序的泄漏。退一步讲,我们会发现这些技术只是减缓了静态分析Android应用程序的过程。它们迫使我们分两步进行分析: (1) 运行应用程序,找出方法值和字符串值;(2) 用第一步提取的信息再次运行静态分析。第一步能在多大程度上实现自动化还有待观察。读者可能认为,只有恶意软件的应用程序才会使用这种技术来隐藏他们的恶意代码,所以在一组应用程序中检测恶意软件是很容易的。然而,一些良性应用程序的作者并不希望让他们的代码变得易懂,也会使用混淆技术。随着混淆技术变得越来越普遍,仅靠静态分析是不够的。必须开发新的工具来动态地分析应用程序,与静态分析应用程序相结合。
新的混淆技术
一个好的开始是看一下所有分析Android应用的工具的局限性。例如,如果用于创建Intent对象的字符串是通过复杂的操作(如串联,通过多种方法ow)构建的,那么计算组件之间的链接的工具可能会失败。如果应用程序中经常使用深度访问路径,静态分析Android应用程序的工具可能无法正常工作。 最后,这些工具在字节码层面上工作,并不分析本地代码:将字节码转换为本地代码会使它们失去作用。就像哈希函数容易计算,但反转成本很高一样,是否有一些限制对攻击者来说很容易利用,但分析起来非常困难和昂贵?
泄漏分类
我们对Android应用程序中的泄漏的方法有以下限制:它没有给出关于它发现的泄漏的信息。事实上,它不能告诉你一个泄漏是应用程序的特性(例如,应用程序应该把联系人名单发送到你自己的远程服务器)还是恶意的(例如,一个恶意软件把你的联系人名单发送到互联网上的某个远程服务器)。解决这个问题的方法之一是分析应用程序的文本描述,并将其与我们的方法所给出的泄漏相匹配。这样一来,安卓应用只有在泄密事件与描述不匹配时才会被攻击。最近,人们对挖掘应用程序描述的方法感兴趣[60, 97, 63]。
安全应用程序的市场
开发者可以为每个应用程序提供一个预期行为的清单,而不是挖掘应用程序的描述。这个列表可以在运行应用程序时被动态检查(如果其行为不一致,应用程序将被停止)或在分析代码时被静态检查。此外,为了简化分析检测方法,应该限制或禁止应用中的类加载和本地代码。现有的方法是从官方文件或程序中的文本信息中提取规范[127, 68, 98]。此外,可以开发自动技术,而不是要求开发者写出规范的清单。
针对市场应用的可扩展的分析
我们的方法在小规模的应用程序上是可行的。 一个开放的研究问题是找出一个可扩展的方法来分析成千上万的应用市场。 一个可能的方向是将市场分析视为一个两步的过程。 在第一步中,每个应用程序都将被单独分析,并计算出应用程序的抽象。抽象将取决于人们想要解决的问题的种类(例如,应用间的泄漏)。在第二步中,抽象被结合起来以解决这个问题。以下的挑战必须得到解决: 如何精确地连接应用程序?当通信是双向的,如何处理抽象中的数据流?在这个问题上,Android的工作很少[20]。
在这篇论文中,我们(1)提出了Dexpler,它允许对Android应用进行分析和工具化,(2)分析了Android框架以提取权限图,我们利用权限图来识别Android应用中的权限缺口,(3)开发了IccTA来检测Android应用中的泄漏,(4)表明静态分析的结果可以对动态分析有用。简而言之,在这篇论文中,我们为分析安卓框架、安卓应用以及更广泛的基于权限的框架或应用奠定了基础。这些分析是理解基于权限的系统及其应用的第一步。它们为创建安全的基于权限的系统铺平了道路,在这个系统中,安全问题将被降到最低。