Android应用层组件的保护策略(上)
接触安卓应用层代码审计已经一年多了,期间分析过大量的APP,无论是厂商的还是企业的,保护程度参差不齐,本文从攻击者的角度,讲一些常见的组件攻击方式和思路。 下篇将从开发者的角度,如何保护自己的组件不被第三方APP进行调用,即使是出于业务需求必须要开放的,也可以保证安全性,同样也讲一些常见防御设计。
声明:未经允许本文禁止转载。
一、权限介绍
假设安卓里四大组件各位读者已经很熟悉了,除了Receiver可以动态注册外,其他组件均需要在Manifest里进行声明,以便让操作系统、程序本身代码、外部应用进行调用,官网链接是 https://developer.android.com/guide/topics/manifest/manifest-intro.html,里面有大量的关于Manifest定义的介绍。
关于权限,在Manifest里定义为permission,系统自带的定义位于 frameworks/base/core/res/AndroidManifest.xml
,保护分为4个level,normal, dangerous, signature, signatureOrSystem。顾名思义,normal随便申请,dangerous需要用户到settings里手动开启,signature要两个APP签名一致,signatureOrSystem额外允许system权限的应用去交互。
但仔细阅读各个权限的声明时,发现有一个protectionLevel叫privileged,privileged app 是比system app更加高级的权限,是4.4以上的一个新特性,由于关系不大,本文不做描述。
本文假设已经完全控制了某untrusted app,根据这个权限去对手机上的其他APP进行攻击,主要讲常见攻击的思路。
二、Activity
官方文档是https://developer.android.com/guide/topics/manifest/activity-element.html ,Activity标签里需要关注的是两个,一是exported=true
,二是intent-filter
。其他细节可以是launchMode是否为singleTask/singleInstance
,permission
是否有定义。
exported。
当exported=true的时候,可以被第三方应用直接开起来,也可以用shell命令开起来,一般是出于业务考虑才会这样做,属于可遇而不可求的情况。例如使用下面的代码:
1 | am start -n com.example.package/.TargetActivity |
(或者使用setClassName)
正常情况下,还可能带一些action或者Bundle传递一下额外数据,但有一个问题,一旦这个Activity开起来以后,我们是无法在untrusted app里对其进行finish操作,也是很尴尬的一件事。
Intent-filter。最常见的就是浏览器,以浏览器为例,本身是没有exported的,但是由于定义了intent-filter,所以会接受指定的action进行启动,通过setData的方式传入一个Uri,进行解析。
Intent-filter里也会定义一些scheme/host/path等uri的限定词,造成攻击面的缩小。 可以用以下代码来开起来,可以指定包名,否则会弹selector。
1 | am start -a android.intent.action.VIEW -d 'http://a.baidu.com' |
LaunchMode。
与攻击关系不大,这里提一下因为如果我们需要多次触发Activity的onCreate的话,在GUI上显示出来的是多少个,一般不大会用得到。 Permission。加适当的permission就可以防止第三方APP来start该activity,一般来说是最常见的是同一个厂商的APP只加signature,相互开个后门相互调起什么的。 攻击Activity的案例最常见的就是webview,毕竟有明显的GUI感知,开发者是用来给用户操作的,常用于钓鱼,例如这篇文章https://acmccs.github.io/papers/p829-liA.pdf ;也可以用于开指定url利用JavaScriptInterface进行额外的操作,再蠢一点还可能有远程的本地文件读。
三、Service
官方文档是https://developer.android.com/guide/topics/manifest/service-element.html。
Service有应用服务和系统服务,应用服务的标签里关注属性的有exported,intent-filter,permission。系统服务可以在Java层注册也可以在Native层注册,native层注册的Service,一般是系统服务,在Manifest里可以不写,使用aosp提供的native API,可以直接于serviceManager通信来获取到IBinder,没有什么权限检测,如果开发者不小心的话,可能造成crash或者DOS,更严重的可能是内存破坏后进行提权。本文讲的是组件的安全,所以对系统服务的安全稍作提及。
exported。当Service被exported=true时,untrusted app有机会对其进行bindService和startService的操作,网上有大量文章讲他们的区别,但我们关注的二者的区别在于是否需要与其有后续的通信和触发的代码,bindService需要提供ServiceConnection,实现该接口的onServiceConnected可以拿到IBinder,也就可以与远端的Service进行通信;而startService只是开起来,无法进行后续的操作;bindService触发的是onBind,而startService触发的是onStartCommand,在实际逆向中,一般只会选择实现其中的某一个方法,另一个留空。
Intent-filter。和Activity相同,用于某些exported=false,需要根据action来触发的service,用法不常见。
Permission。为了保护不被第三方bind或者start,一般会加permission。
在日常分析中,service是经常出问题的一个环节,而且后台运行经常是没有图形界面的,攻击起来也非常舒服,因为出于业务考虑,service经常是要对外开放的,攻击的方式也各式各样,一般是直奔onBind和onStartCommand去跟代码,目前还没有自动化的方法,只能由人来审计。还有一点经常被忽略的地方,如果进程第一次被调起,Service时是会触发Application的构造的,某些App在构造过程会检查是否同意条款等等,就会被用户察觉到。
对于系统服务,毕竟是system权限,所以可以用于提权,在shell里service list 一下,找那些不认识的,大概率就是厂商的系统服务了,难点在找binary文件,有些在非root情况下是拿不到的,很难受。例如电池监控、传感器、绘制图形之类的,各个厂商都有自己的OEM Service。
关于应用服务的问题,一般都是因为开发者没有安全意识,近年来已经好转了很多,将在防御篇进行详细的描述,案例在厂商修复前不可以透露。关于系统服务的关注程度还不是很高,举几个我分析过的已经被公开的问题:某厂商A服务(2016年12月),某厂商AAA、BBB服务(2017年12月,等CVE公开后即公开细节)。
某厂商系统服务A服务:binder传入几个数字和一个buffer,其中有一个数字表示buffer的大小,malloc后进行了memcpy和其他操作,没有对malloc出来的结果进行任何检查。所以这里存在两个问题,一是可以利用malloc任意大小进行内存耗尽的DOS,二是可以进行malloc无穷大,返回null,导致下文的空指针crash,带崩整个操作系统。
某厂商系统服务AAA服务,BBB服务:AAA服务会传递Frame,Binder传来的是一个文件,自己先创建一块2M大的空间,再按照binder传来的进行一次memcpy,导致越界写任意数据,可能造成的代码执行。BBB服务是,同样没有对输入的数据长度进行校验,创建固定大小的内存,进行memcpy以及其他系列操作,就有机会进行提权操作,等CVE发布后会有更加详细的分析。本文只是拿来举个例子。
四、Receiver
官方文档 https://developer.android.com/guide/topics/manifest/receiver-element.html。
Receiver与Service的攻击方式非常类似,但Receiver只能有一次的数据传递,和Service的startService很像,而且从用法来看,Receiver本身是更加开放的一个组件。主要关注的依然是exported,intent-filter,permission。另外,receiver有静态声明和动态注册两种,前者在Manifest里,后者使用registerReceiver来注册。代码只有onReceive一处入口,因为只能传一次数据,所以常见的操作是传json-string,来包含更多的内容。
可以使用shell命令或者代码来发送Broadcast。
1 | am broadcast -a com.leadroyal.GO |
exported。
Receiver默认的exported是true,除非被刻意指定为false,发出去是可以收到的。
Intent-filter。
包含一个或多个action,也是Receiver最常见的入口,指定action即可触发。需要注意的是很多action普通用户是无法发出去的,定义同样在frameworks/base/core/res/AndroidManifest.xml里,是protected-broadcast,见多了就知道哪些action让发哪些action不让发。
Permission。
正因为Receiver比较开放,所以这个地方经常被写掉,导致攻击失效。
对于动态注册的Receiver,默认是完全公开的,所有人都可以给它发消息,为了保护,google提供了API在注册时仅允许指定的包对其进行发消息,但基本没有见人用过,所以可以认为在裸奔。
同样Receiver也是可能出问题的环节,其设计意图就是让别人调用它,只能通过人力去审计逻辑,因为是公开的,所以这方面的攻击已经有很多案例了。包括已经修复的某厂商PUSH_SDK的问题(2016年12月),以及360对已有主流PUSH_SDK的研究(2017年12月),唉,可惜这个大新闻被360给搞走了,其实我早有计划做研究第三方提供的SDK,但日常工作太繁忙了,非常可惜。
2016年12月某厂商的推送服务(已修复):建立在前人的基础上,Receiver是公开的,可以打开富文本、纯文字、URL各种,缺乏校验,加上ZipEntry的目录穿越,最终造成了本地untrusted app,对使用了该服务APP均可以造成文件写。
再讲一下2017年12月的新闻,主流推送SDK的攻击思路:推送服务由于其特殊性,必须要公开,因为需要相互守护,即手机里使用了同一家公司提供的推送服务越多,只要有一个存活,其他应用也可以及时收到消息,甚至有厂商例如华为、小米有内置的常驻后台的连接,为旗下产品保驾护航。建立在这种机制下,必然有开发者为了追求抢占市场而忽视安全性,这样就可以让untrusted app有机可乘,伪造一条消息发送给Receiver,轻则钓鱼,重则文件读写和提权,根据实际的情况考虑。再次表示,没来得及验证自己的思路真是可惜,被360的研究人员抢先了一步,非常可惜,非常可惜,非常可惜。
五、Provider
官方文档https://developer.android.com/guide/topics/manifest/provider-element.html。
Provider和前三种完全不一样,提供的是结构化查询的数据库服务,使用content:// 的URI去访问,一般不具有太多的逻辑代码,需要配合其他漏洞完成操作。数据库文件存放于私有目录的databases目录下,可以root后拖出来手动修改,标签里值得关注的是exported, permission, read_permission, write_permission。
exported。只有exported=true,才有机会被其他应用访问到,默认是false,但经常被指定为true,因为Provider本身是提供查询服务的,需要开放。
Permission等。
访问时必须要满足permission,写入要write,查询要read,更新要write和read。 这部分无法单独使用,最可能的情况就是泄漏了读写权限,让untrusted app读取到了不该读的东西,或者写入了恶意数据,在运行过程中,程序使用这些恶意数据就会出现问题。而开发者不一定会实现>全部的接口,所以仍然需要人来手动分析。 例如Message短信、Contact通讯录就是很好的例子,可以看作一个可以读写的数据库,除了恶作剧之外也没有什么危害,但如果有人将坏人的号码备注为银行的号码,就会形成诈骗了。
六、总结
组件的攻击大多是逻辑漏洞和开发者的疏忽,能找到的话就是中彩票了,随着近年来安全研究的发展和谷歌的努力,这些问题正在慢慢减少,可能需要多个漏洞组合去使用才有效,就像mp2o-2017的打华为一样,用了十几个逻辑漏洞去打。
下篇将会从开发者的角度来分析如何安全开发,避免权限和信息的泄漏。
其实有很多正面案例和反面教材,如果将来有时间和精力的话,在厂商修补后会再更新一部分内容与大家分享,敬请期待~
本文有不对的地方欢迎留言指正,感激不尽!