前言
本文根据英文原文“Uncovering Cross-Context Inconsistent Access Control Enforcement in Android”整理撰写。原文发表在Network and Distributed Systems Security Symposium (NDSS). 2022。在完成英文原文工作时,周昊为香港理工大学在读博士。本文较原文有所删减,详细内容可参考原文
01
介绍
操作系统(例如Windows,Linux,Android,iOS)通常采用访问控制来防止未授权应用或用户访问系统敏感功能和用户隐私数据。例如,Linux只允许具有特定用户标识符(UID)的进程访问特定文件。除了基于UID的访问控制,Android还采用了基于权限的访问控制。具体的,Android只允许具有特定UID和权限的Android应用(包括Android App和由C或C++编写的native应用)调用敏感函数。
然而,由于操作系统的代码规模过于庞大,开发者不一致地实现了针对敏感系统函数的访问控制是很常见的。未授权应用可利用其侵害用户隐私并对系统造成破坏。例如,研究人员发现,不一致的UID检查可被攻击者利用对Android系统服务发动DoS攻击。另外,不一致的权限检查可被恶意应用利用窃取用户隐私数据。
Android系统服务提供了Java接口和native接口供Android应用调用敏感系统函数。基于系统服务的实现语言,它们可以被分为Java系统服务和native系统服务。约束系统服务调用的访问控制的实现需要是一致的。具体的,由于在相同上下文中Android应用可以通用多个接口调用一个系统服务的一个敏感函数,这些接口的访问控制需要是一致的。另外,由于Java系统服务会依赖native系统服务实现其功能,在Java上下文(即Java服务)中实现的访问控制需要和在native上下文(即native服务)中实现的访问控制一致。已有的研究发现,Java上下文(即Java服务)中存在不一致的访问控制。然而,研究人员忽略了对跨上下文不一致的访问控制的研究。
为了弥补空缺,在本文中,我们研究了Android框架中跨上下文不一致的访问控制,并设计了工具IAceFinder来自动发现不一致的访问控制。具体的,IAceFinder识别Java上下文和native上下文中的实现的访问控制,并比较它们以发现不一致。该流程包含三个步骤。第一步,为了识别出不同上下文中实现的访问控制,IAceFinder静态分析Android框架的Java库(即.jar文件)和native库(即.so文件)以构建Java系统服务和native系统服务的函数调用图。第二步,IAceFinder分析函数调用图找到Java系统服务和native系统服务中的访问控制。由于,Java系统服务的敏感函数通过JNI接口与native系统服务进行交互,JNI接口是不同上下文(即Java上下文和native上下文)间的桥梁。一个JNI接口由一对Java JNI接口和native JNI接口组成。前者在Java上下文中被调用,后者在native上下文中被执行。因此,Java系统服务中约束Java JNI接口调用的访问控制需要和native系统服务中限制native JNI接口执行的访问控制一致。于是,IAceFinder识别Java系统服务中约束Java JNI接口调用的访问控制和native系统服务中限制native JNI接口执行的访问控制。第三步,针对每个系统服务调用的JNI接口,IAceFinder比较约束Java JNI接口调用的访问控制和限制native JNI接口执行的访问控制来发现跨上下文不一致的访问控制。
在设计和实现IAceFinder过程中,我们解决了3个技术难点。
(1)由于一个native库的函数会调用另一个native库的导出函数,单独分析每个native库无法完整构建native系统服务的函数调用图,因为被调用函数的实现不在被分析的native库中。为了解决该问题,在分析一个native库时,我们会同时分析它依赖的其他native库。
(2)由于native系统服务由C++语言编写,C++语言的多态特性使得我们很难准确区分继承相同父类的子类的对象,影响构建的native系统服务调用图的准确性。为了缓解该问题,我们采用指针分析来构建更准确的函数调用图。
(3)Android系统的远程调用机制Binder使得native系统服务提供的接口和其函数之间没有显式的调用关系,因此函数调用图中不包含native系统服务接口和native系统服务函数的调用关系。为了解决该问题,我们静态分析native系统服务的Binder机制将系统服务接口和相关的系统服务函数的调用关系添加到系统服务的函数调用图中。
我们使用IAceFinder检测了14个开源Android系统中跨上下文不一致的访问控制。14个系统包含2个Google官方AOSP系统和12个第三方开源Android 10和Android 11系统。在这些系统中,IAceFinder发现了23个跨上下文不一致的访问控制,它们可以被攻击者利用窃取用户隐私和损害系统功能。我们将发现的bug报告给了Google和第三方系统维护者并获得了Google漏洞奖励。
02
背景
1.访问控制
Android系统主要使用权限检查和UID检查来保护敏感系统函数。因此,在本文中,我们主要分析基于权限的访问控制和基于UID的访问控制。
基于权限的访问控制只允许具有必要权限的Android应用访问用户隐私数据(例如联系人信息),获取敏感设备信息(例如麦克风状态),使用关键系统功能(例如相机功能)。根据权限对应的安全风险,Android将权限划分为四个保护级别。保护级别是privileged和signature的权限只能被特权系统应用(例如系统服务,ADB shell)获得,它们对应高特权。保护级别是dangerous和normal的权限可以被一般应用获得,它们对应低特权。
表1 Android框架中的部分UID信息
基于UID的访问控制只允许具有特定UID的Android应用调用敏感系统函数。表1列举了Android框架中的部分UID及其别名信息。具体的,对于普通Android应用,它们的UID值范围为[10,000, 19,999]。对于特权系统应用(例如系统服务和ADB shell),它们的UID值小于10,000。一般认为,系统应用具有高特权,普通应用具有低特权。值得注意,被ADB shell执行的native应用具有ADB shell的UID和权限。
2. Android系统服务和Binder
Android系统服务是Android系统的重要组成部分,系统服务为Android应用提供了调用接口。由于系统服务接口通常调用敏感的系统服务函数,系统服务在接口调用过程中执行访问控制以防止敏感函数被未授权应用调用。基于实现系统服务使用的编程语言,我们将系统服务划分为Java系统服务和native系统服务。
由于Android应用和系统服务运行在不同的进程,Android应用可利用进程间通信机制Binder实现与系统服务的交互。Android应用使用Binder proxy与系统服务的Binder stub通信。具体的,Android应用调用定义在Binder proxy类中的系统服务本地接口以调用定义在Binder stub类中的系统服务远程接口。值得注意,一对Binder proxy类和Binder stub类继承自同一个接口类,该接口类声明了接口函数。因此,一对系统服务本地接口和系统服务远程接口的函数名,参数类型,返回值类型都是相同的。
3.Android系统服务的交互
我们用一个例子(如图1)来解释Android应用和系统服务之间的交互,以及Java系统服务和native系统服务之间的交互。
图1 创建安全的虚拟显示屏的过程涉及所有三类系统服务的交互,包括Android应用与Java系统服务的交互(即Interact-J),Android应用与native系统服务的交互(即Interact-N),和Java系统服务和native系统服务的交互(即Interact-JN)
图1展示了Android应用创建一个安全的虚拟显示屏的流程。该过程涉及Java系统服务Display Manager服务和native系统服务SurfaceFlinger服务。前者管理创建的虚拟显示屏,后者负责创建虚拟显示屏。
(1)Android应用和Java系统服务的交互。为了和Java系统服务(例如Display Manager服务)交互,Android应用使用Java Binder proxy(例如IDisplayManager$Stub$Proxy对象)向Java Binder stub(例如IDisplayManager$Stub对象)发送交互请求。该过程(即图1中的Interact-J)包含三个步骤。
第一步,Android应用调用定义在类ServiceManager中的函数getService,获得Binder proxy。之后,应用调用实现在类IDisplayManager$Stub$Proxy中的函数createVirtualDisplay请求Display Manager服务创建虚拟显示屏(即S1)。
第二步,createVirtualDisplay(即本地接口)调用类BinderProxy中的函数transact发送请求给Binder stub(即S2)。
第三步,为了创建虚拟显示屏(即S3),Binder stub的函数onTransact处理请求并调用Display Manager服务的函数createVirtualDisplay(即远程接口)。
由于createVirtualDisplay执行敏感功能(即创建安全的虚拟显示屏),Display Manager服务检查调用该函数的应用的权限和UID(即E1)。
(2)Android应用和native系统服务的交互。为了和native系统服务(例如SurfaceFlinger服务)交互,Android应用使用native Binder proxy(例如BpSurfaceComposer对象)向native Binder stub(例如BnSurfaceComposer对象)发送交互请求。该过程(即图1中的Interact-N)包含三个步骤。
第一步,Android应用调用类IServiceManager的函数getService,获得Binder proxy。之后,应用调用类BpSurfaceComposer中的函数createDisplay请求SurfaceFlinger服务创建虚拟显示屏(即S5)。
第二步,createDisplay(即本地接口)调用类BpBinder中的函数transact发送请求给Binder stub(即S6)。
第三步,为了创建虚拟显示屏(即S7),Binder stub的函数onTransact处理请求并调用SurfaceFlinger服务的createDisplay(即远程接口)。
相似的,由于createDisplay执行敏感功能(即创建安全的虚拟显示屏),SurfaceFlinger服务对调用该函数的应用执行访问控制(即E2)。
(3)Java系统服务和native系统服务的交互。由于native系统服务拥有Java系统服务不具备的功能(例如访问硬件),Java系统服务的部分功能的实现依赖于native系统服务。例如,创建安全的虚拟显示屏时,Display Manager服务使用JNI接口访问native上下文以请求SurfaceFlinger创建虚拟显示屏。该过程(即图1中的Interact-JN)包含两个步骤。
第一步,Java系统服务Display Manager服务的函数createVirtualDisplay调用Java JNI接口SurfaceFlinger.nativeCreateDisplay以访问native上下文(即S4-J)。JNI接口在不同上下文中有不同的命名约定,且Java JNI接口和native JNI接口具有一一对应关系。例如,Java JNI接口nativeCreateDisplay对应的native JNI接口为android::nativeCreateDisplay。Android系统调用registerNativeMethods,RegisterMethodsOrDie,或jniRegisterNativeMethods记录Java JNI接口和native JNI接口的对应关系。
基于此,第二步,由于Java JNI接口的调用会执行对应的native JNI接口,android:nativeCreateDisplay函数会被执行(即S4-N)。之后,BpSurfaceComposer对象请求SurfaceFlinger创建安全的虚拟显示屏(即)。
(本文只选取原文中部分章节,更多精彩内容敬请期待后续出版的《网络安全研究进展》)
作者简介
周昊,香港理工大学博士后。2022年10月毕业于香港理工大学,导师为罗夏朴副教授。主要研究方向包括系统安全和程序分析,工作集中于Android系统漏洞挖掘和Android应用行为分析,获得20余个CVE漏洞编号并得到著名手机厂商谷歌、三星、小米、Vivo、荣耀的致谢。在S&P、USENIX Security、CCS、NDSS、ICSE、FSE、ASE、ISSTA和TIFS、TSE等系统安全和软件工程CCF-A类会议和期刊上共发表论文20余篇,以第一作者及共同第一作者发表CCF-A类论文7篇。获得软件工程CCF-A类会议ISSTA 2022杰出论文奖。
相关阅读
【NDSS 2022论文分享】EMS:试验数据驱动的高效变异模糊测试系统