Android 系统服务漏洞挖掘技术研究
2019-06-27邹圳周安民
邹圳,周安民
(四川大学电子信息学院,成都610065)
0 引言
近年来,随着移动互联网与信息技术的不断发展,智能手机得到了迅猛的发展和广泛的应用。在当前智能手机市场份额中,Android 系统占据了主要部分。根据国际数据中心(IDC)2018 年第三季度的报告[1],Android 在全球市场的占比达到了86.8%。因此,针对Android 平台的安全性研究得到了普遍的关注。
作为Android 系统的核心组件,系统服务(System Service)是指在后台运行的系统级进程或应用程序,例如窗口管理服务WindowManager、通知管理服务NotificationManager、电源管理服务PowerManager 等。系统服务对系统功能进行了封装,并通过接口的形式为上层的应用程序提供服务。对于应用开发者而言,只需要了解这些接口的使用方式,通过Binder 通信机制即可与系统服务进行交互,方便地获取系统信息与进行系统控制。然而对于攻击者而言,系统服务也成为了攻击Android 系统的一个突破口。因此,系统服务安全的重要性不言而喻。
为了系统地对Android 平台的各种系统服务进行安全性分析,本文在研究Binder 通信机制的基础上提出了一种面向Android 系统服务的自动化漏洞挖掘框架。针对Android 不同版本系统的实验结果表明,该框架能够有效识别Android 中存在的系统服务,并构造带有畸形参数的请求进行模糊测试,具有一定实用价值。
1 相关工作
由于开源和开放的特点,Android 系统的安全性一直吸引着众多研究人员的关注。在Android 漏洞挖掘技术方面,目前常用的方法主要分为静态分析技术和动态检测技术。静态分析技术[2-4]主要采用代码审计的思想,通过逆向工程等手段获取程序源代码,并结合程序的函数调用关系、数据流和控制流等特征对程序代码进行分析,从中找出潜在的安全问题。静态分析技术具有简单高效、易于实现自动化等优点,可用于组件劫持、信息泄露、权限提升等漏洞挖掘的相关研究工作。静态分析技术的缺点在于过于依赖漏洞特征,存在漏报和误报问题。动态检测技术[5]目前主要以模糊测试技术为主,该技术被广泛地应用于Android 系统与应用程序的组件间通信机制的安全性研究中。例如Mulliner 等[6]通过构造异常输入数据来对Android 系统的短信模块进行模糊测试。Intent Fuzzer 及其相关改进工具[7-9]通过发送Intent 命令的方式来测试Android应用程序中Activity、Service、Broadcast Receiver 这3 类暴露组件的健壮性。DroidFuzzer[10]通过构建MIME 数据来对Android 应用程序的Activity 组件进行模糊测试。然而,上述工具大多着眼于Intent 通信机制,而且目标多为Android 平台下的各种应用程序。
因此,本文在研究Binder 通信机制的基础上,通过对Android 系统服务进行统计与分析,结合模糊测试技术对系统服务的安全性展开研究。
2 背景知识
2.1 系统服务
系统服务在整个Android 系统中占据着十分重要的地位,它们将Android 提供的各种功能如获取地理位置、发送短信、检查网络连接等进行封装,并以应用程序编程接口(Application Programming Interface,API)的形式为上层的应用程序提供服务[11]。Android 系统服务主要分为三类:第一类是Java 系统服务,由Java 语言编写,通过AIDL 文件进行封装,运行在System Server进程中,这类服务在所有系统服务中占据了绝大部分。其次是本地守护进程,在init.rc 文件中定义并在Android 系统初始化的过程中由init 进程启动,启动后会常驻在系统中,这类服务的数量比较少,其中包括负责apk 软件包的安装与卸载的installd 守护进程,负责多媒体处理的MediaServer 守护进程,等等。最后是Native 系统服务,由C 或C++语言编写,运行在本地守护进程中,例如MediaServer 守护进程中就包括AudioFlinger、AudioPolicyService、MediaPlayerService、CameraService 等Native 服务,这类服务同样也只占很小部分。本文主要对Java 系统服务的安全性进行研究,Android 系统上一些常用的Java 系统服务如表1 所示。
表1 一些常用的系统服务
当上层的应用程序通过API 调用系统服务的时候,实际上是在通过Binder 通信的方式与系统服务进行进程间通信(Inter Process Communication,IPC)。系统服务等待着上层应用程序发出请求,接收到请求后进行响应同时将结果返回给应用程序。如果恶意的应用程序向系统服务传递了包含畸形参数的外部输入数据,可能导致系统服务发生崩溃、设备重启等拒绝服务的情况,甚至可能造成任意代码执行的严重后果。
2.2 Binder通信
为了保护应用和系统免受恶意应用的攻击,Android 采用了基于用户的Linux 保护机制,为每个Android 应用设置一个内核级应用沙盒。默认情况下,应用不能彼此交互,而且对操作系统的访问权限会受到限制。在应用间需要交互的情况下,需要使用IPC 进程间通信方式[12]。与Linux 中常用的Pipe(管道)、Signal(信号)、Message(消息队列)、Shared Memory(共享内存)、Semaphore(信号量)、Socket(套接字)等基于文件系统的IPC 方式不同,Android 系统选择了采用C/S 架构、基于权限机制的Binder 通信作为主要IPC 方式[13]。
Android 系统的Binder 通信模型由四大部分构成,分 别 是Client、Server、Service Manager、Binder Driver。其中,Client、Server、Service Manager 运行在用户空间,Binder Driver 运行在内核空间。Binder Driver 提供了设备文件/dev/binder,用户空间可通过open、ioctl 等方式与其进行交互;Service Manager 的作用为对系统中的服务进行管理,Server 通过addService()方法将自己注册到Service Manager,Client 则通过getService()方法获取Server 信息;Client 和Server 采用了典型的Proxy-Stub 设计模式,由AIDL 工具生成代理Proxy 和Stub 对象,两端各自调用其中的transact()和onTransact()方法实现对待传送的Parcel 数据的发送与接收,Client 和Server 之间的IPC 通信是通过底层的驱动程序来间接实现的。整个Binder 通信机制的系统架构如图1所示。
图1 Binder通信架构图
2.3 AIDL
AIDL(Android Interface Definition Language),是Android 系统使用的一种接口定义语言。与其他IDL语言类似,AIDL 主要用于定义Android 系统中Client和Server 端在进行基于Binder 的IPC 进程间通信时双方都认可的接口规范。在AIDL 机制中,Android 系统会提供一系列的工具将开发者定义的.aidl 文件编译生成Client 端与Server 端的相关代码。由于AIDL 对Binder 机制的封装,使得开发者能够更简单方便地实现IPC 通信,而不必关心Binder 的具体细节。
图2 展示了一个.aidl 文件示例。其中定义了get-Pid 和basicTypes 两个接口方法,getPid 不带任何参数,basicTypes 则接收六个不同数据类型的参数。
图2 IRemoteService.aidl文件示例
当开发者在.aidl 文件中声明完相关接口和方法的定义后,Android SDK 工具会自动生成与.aidl 文件同名的.java 接口文件,该文件的核心为其中的stub 内部类,该类中包含了.aidl 文件中定义的方法声明,并且为定义的每个方法分配了一个整型的唯一标识code,以及供Client 端调用的代理类Proxy,和用于接收Binder 通信数据的onTransact()方法。
3 方案设计
本文在Binder 通信机制的基础上提出了一种针对Android 系统服务的漏洞挖掘框架。其基本思路是:首先对Android 系统服务展开统计和分析,获取系统服务和接口函数的相关信息,为后续的模糊测试用例生成提供指导依据。然后根据一定的测试用例生成策略构造请求数据,通过Binder 驱动将请求数据发送给系统服务。最后通过Android 的logcat 日志系统对系统服务的运行状态进行监控,记录进程异常、崩溃、死亡等信息与相应的请求数据。
3.1 系统服务信息获取
在对系统服务进行模糊测试之前,首先需要获取系统服务以及每个服务内定义的所有接口函数的相关信息,包括系统服务名、类名,接口函数的参数个数、参数类型,等等。通过分析这些信息有利于针对性地指导测试用例的生成,大大减少无用的测试用例,从而提高fuzzer 的效率。
(1)获取服务名称及句柄
本文采用Java 反射机制来获取系统服务列表。在Binder 通信机制中,由Service Manager 负责对Android系统中所有的系统服务进行管理。作为一个特殊的系统服务,Service Manager 维持了一个服务列表,其中记录了所有的系统服务名称以及句柄。其他服务在被使用之前,需要通过add_service 方法向Service Manager进行注册,登记自己的服务名以及句柄。同样的,客户端在调用系统服务之前,也需要通过check_service 方法向Service Manager 查询,通过目标系统服务的名称获取到相关联的句柄,并以该句柄作为目的地址发起通信请求。由于Service Manager 具有@hide 隐藏属性,因此利用Java 的反射机制获取android.os.ServiceManager 实例,获取其中的listServices 方法,该方法将返回一个字符串列表,其中即包含需要的所有系统服务信息。
(2)获取定义方法信息
系统服务信息获取完毕后,可以类似地利用Java的反射机制尝试获取单个系统服务中定义的可供外界调用的所有方法信息。通过向Class.forName()方法传入系统服务类名,可以获取到该系统服务的实例对象,然后通过调用getDeclaredMethods()方法将返回一个Method 对象的数组,数组中存储了该系统服务类中定义的所有方法对象,包括方法名以及对应的参数信息。
此外,系统服务中定义的所有方法都将分配一个方法号,方法号从1 开始逐次递增,单个服务中定义的N 个方法的方法号即为1 到N。此方法号即为通过transact()函数调用Binder 服务端时填充的第一个参数code。由AIDL 机制的特点可知,可以通过反射获取系统服务类中的stub 子类对象,从中获取到每个方法对应的不同code 值。
获取这些系统服务的相关信息主要有以下两个作用:①确定模糊测试的测试目标;②在测试用例生成阶段使用,根据方法参数信息指导测试用例数据的生成。
3.2 测试用例生成策略
基于之前获取的系统服务信息,针对Binder 通信中通常携带的各种数据类型,本文采用一定的生成策略对不同数据类型的数值进行填充,如表2 所示。
表2 数据类型填充模板
除了这些常见的数据类型以外,有时还会出现某些特殊的数据类型导致无法直接构造测试用例的情况,例如Bundle、Interface 作为方法调用的参数等,本文统一使用null 对该类型的数据进行填充。
通过以上的测试用例生成策略,可以生成与系统服务的目标测试方法相对应的测试用例,以便通过目标方法的初始参数检查,提高模糊测试的效率。
3.3 测试用例发送
为了能够将测试用例成功发送给系统服务,本文实现了一个第三方应用程序作为Binder 通信的客户端。考虑到在Android 系统中对某些系统服务的调用需要一定的权限,因此在该应用程序的AndroidManifest.xml 文件中预先请求了第三方应用程序可以获取的所有权限。该应用程序通过使用Java 的反射技术来获取系统服务的句柄,进而调用相应的系统服务,因此具有良好的系统兼容性,可以在不同版本的Android 系统上使用。
应用程序首先尝试获取所有可以获取的系统权限,然后通过反射获得目标系统服务的句柄IBinder 对象,并且使用IBinder 对象调用transact()函数,该函数最终将触发系统服务端的onTransact()函数,将测试用例原样地传送给系统服务的目标方法。根据transact(int code,Parcel data,Parcel reply,int flags)的函数原型可知,需要对其中的各个参数进行相应的填充。参数code 表示目标方法号,在系统服务信息收集阶段已获取;参数data 表示写入了各种数据类型的Parcel 对象,等待发往Binder 通信的服务端,这里将依据待测试方法的参数类型写入对应的测试用例;参数reply 则为空的Parcel 对象,等待存储系统服务的返回数据;最后的参数flags 则表示Binder 通信的服务端在执行完相应操作后是否会返回数据,这里采用默认值0 进行填充,即等待系统服务返回数据。填充完毕后即可通过transact()函数发起对系统服务的请求调用。
3.4 错误日志监控
在测试用例发送给目标系统服务之后,需要对系统服务的运行状态进行监控。本文借助adb 的Logcat工具实现在模糊测试期间的日志监控。记录的日志信息主要包括以下两类:
(1)E/F 级别日志:该类别的日志表明系统服务产生了错误(error)或者严重错误(fatal),时常伴随着系统服务进程的崩溃;
(2)包含有“exception”、“crash”字符串的日志:“exception”表示系统服务抛出了捕获或者未捕获的异常,未捕获的异常可能意味着更为严重的安全问题;“crash”则表示系统服务发生崩溃,有的crash 甚至会导致system_process 进程崩溃,造成设备重启的严重情况。
日志监控部分的伪代码如下:
输入:测试用例testcases
输出:系统服务产生崩溃时的日志以及相应的输入测试用例
Outputs out=executeCMD("adb logcat");
if("E"or"F"in out.logs.level)or("exception"or"crash"in out.logs.message)then
saveFile(out);
saveFile(testcases);
end if
通过对系统服务的异常运行状态进行监控,并记录产生异常的系统服务与接口方法,以及造成异常的相应测试用例,为后续定位漏洞以及对源代码的安全审计提供了可能。
4 实验与分析
为了验证系统的有效性,本文分别在Android 7.0、Android 8.0、Android 9.0 三个不同AOSP 版本的系统虚拟机上进行了实验,发现了系统服务中的一些接口方法由于对接收的Binder 通信数据校验不严格,结果造成自身进程甚至system_process 进程的崩溃,导致系统拒绝服务(DoS)的后果。
4.1 实验环境
系统的实验环境为安装有Genymotion Android Emulator 软件的64 位Windows 10 系统计算机,Intel Xeon E3-1231 v3 CPU,内存8GB,如表3 所示。
表3 实验环境
4.2 实验结果与分析
对Android 7.0、Android 8.0、Android 9.0 三个版本的系统进行测试后发现,Android 7.0 上拥有117 个系统服务,总共包含2312 个接口方法,经过测试后发现其中102 个接口方法会抛出异常;Android 8.0 上拥有126 个系统服务,总共包含2688 个接口方法,经过测试后发现其中181 个接口方法会抛出异常;Android 9.0上拥有140 个系统服务,总共包含3106 个接口方法,经过测试后发现其中166 个接口方法会抛出异常。测试结果如表4 所示。
表4 测试结果
在抛出异常之后,如果系统服务内部存在相应的异常处理机制,那么正常情况下无法造成严重的后果。如果系统服务只是对异常进行简单的捕获,而没有进行相应的处理,或者没有捕获到异常,则有可能导致系统服务进程的崩溃。由于Android 系统中的所有系统服务均运行在名为system_server 的系统进程中,某些情况下系统服务的崩溃甚至会导致system_server进程的死亡,进而导致整个Android 系统的重启。在测试流程中发现某些系统服务的崩溃可以造成整个设备重启,如表5 所示。
表5 造成Android 系统重启的服务与方法
由上述的实验结果可知,本文的设计方案可以很好地应用于Android 系统服务的安全性研究中,挖掘其中可能存在的漏洞,从而提升Android 系统的安全性。
5 结语
本文提出了一种针对Android 系统服务的漏洞挖掘技术方案。该方法通过获取系统服务和接口方法的相关信息,基于此基础上构建覆盖范围广泛的测试用例,并以Binder 通信的方式将测试用例发送给目标服务进行测试,最后的实验结果表明了技术方案的可行性和测试工具的可用性。
本文的研究工作尚存在一定的局限性,未来可通过结合符号执行等方法对系统服务的程序执行路径展开分析,借此改进测试用例的生成方案,以提高模糊测试效率。