zljfly 2017-03-13
Android系统服务即由Android提供的各种服务,比如WIFI,多媒体,短信等等,几乎所有的Android应用都要使用到系统服务。系统服务在为用户提供便利的同时,也存在着一些风险。比如,如果一个应用获取到了系统服务中的短信服务,那么他就可能会查看用户的短信信息,用户隐私就有可能暴露。此外,如果在使用系统服务的过程中,使用了异常的外部数据,有可能会导致系统服务崩溃,甚至是远程代码执行,内存破坏等等严重后果。因此Android系统服务的安全问题需要重视。
在以前的工作发现主要的漏洞和攻击主要包括特权提升攻击,恶意软件攻击,重打包,组件劫持攻击等类型。尽管安全研究人员已经针对Android上层app的漏洞挖掘做了大量的工作,但是针对Android系统服务的漏洞挖掘一直被安全人员所普遍忽视。
通过Binder机制可以对Android的系统服务漏洞进行深入的挖掘。本文基于Android的Binder机制编写了一套漏洞挖掘框架。
下面我们首先介绍一下先验知识。
1 基础知识1.1 Android的Binder机制1.1.1 Binder概述
Binder其实也不是Android提出来的一套新的进程间通信机制,它是基于OpenBinder来实现的。Binder是一种进程间通信机制,它是一种类似于COM和CORBA分布式组件架构,是提供远程过程调用(RPC)功能。
什么是Binder
直观来说,Binder是Android中的一个类,它继承了IBinder接口 从IPC角度来说,Binder是Android中的一种跨进程通信方式,Binder还可以理解为一种虚拟的物理设备,它的设备驱动是/dev/binder,该通信方式在Linux中没有 从Android Framework角度来说,Binder是ServiceManager连接各种Manager(ActivityManager、WindowManager,etc)和相应ManagerService的桥梁 从Android应用层来说,Binder是客户端和服务端进行通信的媒介,当你bindService的时候,服务端会返回一个包含了服务端业务调用的Binder对象,通过这个Binder对象,客户端就可以获取服务端提供的服务或者数据,这里的服务包括普通服务和基于AIDL的服务这里写图片描述
图1-1 Binder架构图1.1.2 为什么使用Binder
1.1.3 Aidl机制
AIDL (Android Interface Definition Language) 是一种IDL 语言,用于生成可以在Android设备上两个进程之间进行进程间通信(interprocess communication, IPC)的代码。如果在一个进程中(例如Activity)要调用另一个进程中(例如Service)对象的操作,就可以使用AIDL生成可序列化的参数。
AIDL IPC机制是面向接口的,像COM或CORBA一样,但是更加轻量级。它是使用代理类在客户端和服务端传递数据。只有你允许客户端从不同的应用程序为了进程间的通信而去访问你的service,以及想在你的service处理多线程。如果不需要进行不同应用程序间的并发通信(IPC),或者你想进行IPC,但不需要处理多线程的。使用AIDL前,必须要理解如何绑定service。
AIDL IPC机制是面向接口的,像COM或Corba一样,但是更加轻量级。它是使用代理类在客户端和实现端传递数据。
1.2 Fuzz技术模糊测试定义为“通过向应用提供非预期的输入并监控输出中的异常来发现软件中的故障(faults)的方法”。典型而言,模糊测试利用自动化或是半自动化的方法重复地向应用提供输入。显然,上述定义相当宽泛,但这个定义阐明了模糊测试的基本概念。
用于模糊测试的模糊测试器(fuzzer)分为两类:一类是基于变异(mutation-based)的模糊测试器,这一类测试器通过对已有的数据样本进行变异来创建测试用例;而另一类是基于生成(generation-based)的模糊测试器,该类测试器为被测系统使用的协议或是文件格式建模,基于模型生成输入并据此创建测试用例。这两种模糊测试器各有其优缺点
模糊测试各阶段采用何种模糊测试方法取决于众多因素。没有所谓的一定正确的模糊测试方法,决
定采用何种模糊测试方法完全依赖于被测应用、测试者拥有的技能、以及被进行模糊测
试的数据的格式。但是,不论对什么应用进行模糊测试,不论采用何种模糊测试方法,
模糊测试执行过程都包含相同的几个基本阶段。
<1>确定测试目标
只有有了明确的测试目标后,我们才能决定使用的模糊测试工具或方法。如果要在安全审计中对一个完全由内部开发的应用进行模糊测试,测试目标的选择必须小心谨慎。但如果是要在第三方应用中找到安全漏洞,测试目标的选择就更加灵活。要决定第三方应用模糊测试的测试目标,首先需要参考该第三方应用的供应商历史上曾出现过的安全漏洞。在一些典型的安全漏洞聚合网站如 SecurityFocus 18 和 Secunia 19 上可以查找到主要软件供应商历史上曾出现过的安全漏洞。如果某个供应商的历史记录很差,很可能意味着这个供应商的代码实践 (code practice)能力很差,他们的产品有仍有很大可能存在未被发现的安全漏洞。除应用程序外,应用包含的特定文件或库也可以是测试目标。
如果需要选择应用包含的特定文件或者库作为测试目标,你可以把注意力放在多个应用程序之间共享的那些二进制代码上。因为如果这些共享的二进制代码中存在安全漏洞,将会有非常多的用户受到影响,因而风险也更大。
<2>.确定输入向量
几乎所有可被利用的安全漏洞都是因为应用没有对用户的输入进行校验或是进行必要的非法输入处理。是否能找到所有的输入向量(input vector)是模糊测试能否成功的关键。如果不能准确地找到输入向量,或是不能找到预期的输入值,模糊测试的作用就会受到很大的局限。有些输入向量是显而易见的,有些则不然。寻找输入向量的原则是:从客户端向目标应用发送的任何东西,包括头(headers)、文件名(file name)、环
境变量(environment variables),注册表键(registry keys),以及其他信息,都应该被看做是输入向量。所有这些输入向量都可能是潜在的模糊测试变量。
<3>.生成模糊测试数据
一旦识别出输入向量,就可以依据输入向量产生模糊测试数据了。究竟是使用预先确定的值、使用基于存在的数据通过变异生成的值、还是使用动态生成的值依赖于被测应用及其使用的数据格式。但是,无论选择哪种方式,都应该使用自动化过程来生成数据。
<4>.执行模糊测试数据
该步骤紧接上一个步骤,正是在这个步骤,“模糊测试”变成了动词。在该步骤中,一般会向被测目标发送数据包、打开文件、或是执行被测应用。同上一个步骤一样,这个步骤必须是自动化的。否则,我们就不算是真正在开展模糊测试。
<5>.监视异常
一个重要但经常容易被忽略的步骤是对异常和错误进行监控。设想我们在进行一次模糊测试,在测试中,我们向被测的 Web 服务器发送了 10000 个数据包,最终导致了服务器崩溃。但服务器崩溃后,我们却怎么也找不到导致服务器崩溃的数据包了。如果这种事真的发生了,我们只能说这个测试毫无价值。模糊测试需要根据被测应用和所决定采用的模糊测试类型来设置各种形式的监视。
<6>.判定发现的漏洞是否可能被利用
如果在模糊测试中发现了一个错误,依据审计的目的,可能需要判定这个被发现的错误是否是一个可被利用的安全漏洞。这种判定过程是典型的手工过程,需要操作者具有特定的安全知识。这个步骤不一定要由模糊测试的执行者来进行,也可以交给其他人来进行。
2.漏洞挖掘思路fuzz在协议和接口安全测试中比较简单粗暴,试错成本低。Fuzzing是一种基于缺陷注入的自动软件测试技术。通过编写fuzzer工具向目标程序提供某种形式的输入并观察其响应来发现问题,这种输入可以是完全随机的或精心构造的。Fuzzing测试通常以大小相关的部分、字符串、标志字符串开始或结束的二进制块等为重点,使用边界值附近的值对目标进行测试。
2.1 fuzz的切入点和目标2.1.1 fuzz的切入点
为了更好的挖掘漏洞,选择fuzz接口需要满足这几个要求:
1)这个接口是开放的,是可以被低权限进程调用的
2)这个接口距离fuzz目标(系统服务)比较接近,中间路径最好透传,这样比较容易分析异常
3)从简原则
根据上面的分析,BpBinder中的transact函数就是一个很好的fuzz接口,但这个函数在底层无法直接调用。
底层transact方法介绍
在c层中,BBinder::transact中会调用onTransact,这个onTransact才是真正处理业务的。需要注意的是,因为我们的binder实体在本质上都是继承于BBinder的,而且我们一般都会重载onTransact()函数,所以onTransact()实际上调用的是具体binder实体的onTransact()成员函数。也就是说,onTransact的具体实现一般在上层的binder实体,而不在BBinder。BBinder没有实现一个默认的onTransact()成员函数,所以在远程通信时,BBinder::transact()调用的onTransact()其实是Bnxxx或者BnInterface的某个子类的onTransact()成员函数,举个例子,BnMediaRecorder中实现了一个onTransact函数,通过switch-case,根据不同code进行分发处理。
我们从BpBinder往上层找,很容易发现,Java层IBinder的transact函数最终调用到BpBinder,且参数是原封不动的“透传”到底层,考虑到java层的可视化和扩展性,可以选择IBinder的公有方法transact作为fuzz接口。
transact的四个参数介绍。我们可以构造这四个参数进行测试。
code是int类型,指定了服务方法号
data是parcel类型,是发送的数据,满足binder协议规则,下面会有详述
reply也是parcel类型,是通信结束后返回的数据
flag是标记位,0为普通RPC,需要等待,调用发起后处于阻塞状态直到接收到返回,1为one-way RPC,表示“不需要等待回复的”事务,一般为无返回值的单向调用。
2.1.2fuzz的目标
Binder其实是提供了一种进程间通信(IPC)的功能。这些系统服务,通过binder协议抽象出一个个的“接口”,供其他进程调用,是一个重要的潜在的攻击面。如果没有做好权限控制,会让低权限的第三方应用/病毒/木马利用,后果不堪设想。
系统服务具有高权限,是我们需要重点关注的对象,而低权限进程(农民)可以利用binder call去调用系统服务,从低权限到高权限,存在一个跨安全域的数据流,这里就是一个典型的攻击界面。所以,我们选择系统服务作为fuzz的目标。
系统服务的分类1.Binder体系的java服务(有Stub接口,也就是AIDL封装)
2.Binder体系的Native服务
3.socket体系的init服务(通常见于init.rc)
4.其他服务
2.2 fuzz引擎fuzz引擎实际是构造transact(int code,Parcel data,Parcel reply,int Flags)函数的四个函数,然后调用Ibinder.transact()来调用系统服务。
2.2.1如何获取Ibinder对象
我们要取到对端的IBinder对象,才可以调用这个服务。系统其实有一些隐藏API可以利用。先通过反射出ServiceManager(hide属性)中的listServices获取所有运行的服务名称。获取到String类型的服务名称后,再反射getService获取对应的服务IBinder对象。
2.2.2 code如何生成
code也称为TransactionID,标定了服务端方法号。
每个服务对外定义的方法都会分配方法号,而且是有规律的,第一个服务方法code使用1,第二个是2,,第三个使用3,依次类推,如果有N个方法,就分别分配1-N个连续的服务号。
对于Java服务,必定有Stub类,可以通过反射出mInterfaceToken+”$Stub”类中所有成员属性,其中以”TRANSACTION_”开头的int型就是该方法对应的。
2.2.3 data如何构造data由“RPC header+参数1+参数2+….”来构成的。但我们不需要自己去构造RPC header,直接调用writeInterfaceToken函数,传入interface name就可以了。interface name是接口名称,只要取得IBinder对象,就可以直接getInterfaceDescriptor来获取interface name,也就是接口方法的描述符。对于Java层服务的方法,可以通过反射获取method对象,然后用getParameterTypes获取所有的类型。
2.2.4 fuzz系统和逻辑设计
一共分为四个部分:
1)测试数据产生器产生器就是用上述方法产生transact需要用到的的四个参数
2)fuzz引擎用于执行具体的transact调用过程,调用ibinder的transact()函数
3)监视器用于监控fuzz结果和异常
4)日志模块用于记录fuzz结果,通过对异常日志的分析可以发现漏洞
# 3.漏洞挖掘的结果
通过对Android系统服务的漏洞挖掘,目前一共发现了32个漏洞,其中在AOSP版本的虚拟机上发现了20个,在第三方厂商定制的系统服务中发现了12个,目前漏洞已经提交Google,小米,魅族等厂商,并且得到了高危漏洞的确认。这些漏洞主要是造成重启,这样就可以构造拒绝服务攻击(dos),还有一些会导致显示进程崩溃等干扰性破坏。
我个人觉得Android系统在安全方面的提升主要体现在以下几个方面:
1.建立更加完善的Android漏洞,提交相应完善制度,加快补丁发布。
2.完善Android文件的加密,同时在硬件上完善,比如TrustZone。
3.通过更加细粒度的授权机制,来保护用户的安全和隐私。
4.缩小Android的碎片化。
5.提高开发者的审核门槛,应用市场加强恶意应用的检查。
随着Android的版本升级和对漏洞的不断完善,Android系统正在变得越来越安全。