作者:柯懂湘、曲乐炜、闫晗、林道正
Unix Domain Socket (UDS)是安卓中重要的本地进程间通信方式之一,其自身具备访问控制能力,可以安全地传递通信数据。但由于UDS服务通常不会和第三方应用进行直接通信,且SElinux限制了普通程序对UDS服务的访问,导致厂商往往忽视UDS服务自身的安全性。因此,由设计或实现上的缺陷及错误配置等原因引入的各种安全漏洞使UDS成为了一个可以获得权限提升的隐藏攻击面。
针对安卓生态系统中的UDS服务,百度安全的研究员柯懂湘和闫晗在北京时间5月12日下午以线上的形式分享了议题《Unix Domain Socket: A Hidden Door Leading to Privilege Escalation in the Android Ecosystem》,该议题主要讨论了UDS的应用场景及安全风险,绕过访问控制的漏洞利用方法,以及针对UDS服务的自动化分析方法。
议题解读
一、基本概念
1.Unix Domain Socket概念
(1)定义
Unix Domain Socket (UDS) 是一种本地进程间通信方式,用于在同一台主机的两个不同进程之间传输数据。如图 1所示,UDS的通信模型以及应用程序编程接口(Application Programming Interface, API)与网络Socket十分相似。不同之处在于,UDS并非基于网络协议,所有通信过程均在内核中完成。因此,UDS更加适合本地进程间通信。
图 1 Unix Domain Socket通信模型
(2)Socket类型和命名空间
UDS有3种Socket类型,分别为SOCK_STREAM、SOCK_DGRAM和SOCK_SEQPACKET。其中SOCK_STREAM是面向数据流的类型,类似于管道。SOCK_DGRAM是面向数据报的类型,类似于消息队列。SOCK_SEQPACKET是面向连接的类型,保留了消息边界。
每个UDS都拥有一个名称。既可以使用FILESYSTEM命名空间,将名称绑定到一个文件系统路径,也可以使用与文件系统的无关的ABSTRACT命名空间。图 2分别说明了FILESYSTEM命名空间和ABSTRACT命名空间的UDS名称示例。
(3)安卓API
安卓为使用UDS提供了诸多封装的API。在Java层,LocalServerSocket是最常用的UDS API。LocalServerSocket封装了多个底层API,包括bind、listen等。可用于创建指定名称的UDS服务端,并开启监听。API定义如图 3所示。
在JNI层和Native层,可以使用标准的POSIX Socket API创建UDS,也可以使用安卓提供的封装API如socket_local_server、socket_local_client。API定义如图 4、图 5所示。
图 4 JNI层和Native层UDS API(POSIX)
图 5 JNI层和Native层UDS API(安卓)
2.访制控制方法
访问控制是UDS的关键安全措施之一。在安卓中,有多种方法可以对UDS进行访问控制。如果UDS绑定到一个文件系统路径,可以通过文件权限限制哪些用户可以向其socket文件进行写入。UDS socket文件权限如图 6所示。
UDS服务也可以调用getsockopt获取客户端进程的PID、UID、GID等信息,进而利用这些信息判断客户端进程是否拥有对UDS服务的访问权限。getsockopt定义如图 7所示。
最常见的UDS服务访问控制方式则是SELinux。SELinux中,与UDS相关的类别有三种,分别是sock_file、unix_stream_socket和unix_dgram_socket。其中,sock_file用于socket文件对象,其余两个类别用于进程对象。SELinux规则可以限制哪些域能够向特定的sock_file进行写入(如图 8所示),也可以限制哪些域能够连接特定进程创建的UDS(如图 9所示)。
图 8 UDS访问控制-SELinux限制sock_file
图 9 UDS访问控制-SELinux限制unix_dgram_socket
同时,安卓也提供了一些用于设置SELinux策略的宏,如图 10所示。
二、应用场景
1.应用场景分类
我们广泛研究了大量设备上的UDS服务,并总结了UDS的主要应用场景。我们在宏观上将UDS的应用场景分为两大类。第一类是诊断工具,例如日志管理工具、异常管理工具等。第二类是复杂的HAL模块,例如GPS、电源等。分类如图 11所示。
2.真实示例
(1)日志管理中的UDS
在安卓设备中,通常有两个日志管理系统。其一是安卓标准的日志管理系统,其二是供应商自行实现的日志管理系统。安卓标准的日志管理系统的核心是守护进程logd。无论是安卓应用中使用的Log API还是LOGCAT命令,它们发出的请求均由logd处理。logd的架构如图 12所示。其包含三个模块,每个模块开启一个UDS监听。用户可向logdw socket写入日志,从logdr socket读取日志,以及向logd socket发送命令。这也是LOGCAT的工作原理。
供应商实现的日志管理系统更为复杂,架构如图 13所示。顶层是日志管理应用DebuggerUI,DebuggerUI的下层是负责收集不同模块日志的多个守护进程。其中mobile_log_d最为重要,它可以收集内核模块日志。每个守护进程监听一个UDS,用于接收DebuggerUI发送的命令。也就是说,mobile_log_d等守护进程通过UDS向上层暴露了管理接口。
(2)异常管理中的UDS
在AOSP中,TombstoneD和System Server均利用UDS接收异常信息。许多厂商自行开发的异常管理工具,例如Android Exception Engine(AEE)也会使用UDS。AEE中与UDS相关的架构如图 14所示。aee_aed监听三个UDS。aed和aed32的UDS用于接收异常信息,rttd的UDS提供Dump运行时信息的接口。AEE对Activity Manager Service和crash dump进行了插桩。当安卓应用发生异常时,Activity Manager Service将连接到aed UDS,向其发送异常信息。当native发生异常时,crash dump将连接到aed32 UDS,向其发送tombstone的文件路径。tombstone文件包含了详细的异常信息。
(3)HAL中的UDS
UDS的另一个应用场景是HAL(Hardware Abstraction Layer)。一个复杂的HAL通常由HAL Service和守护进程两部分组成,如图 15所示。其中HAL Service向安卓Framework暴露标准接口,而守护进程实现具体功能。HAL Service和守护进程之间通过UDS进行通信。
GPS模块是一个典型的例子,其架构如图 16如所示。GPS模块包含两个HW Binder Service和三个守护进程。每个守护进程都通过UDS向HW Binder Service暴露了接口。这种通信模型在其他硬件模块中同样十分常见。
3.UDS服务的特点
通过对UDS服务的分析,我们发现UDS服务通常以高权限运行,且经常应用在一些敏感的任务中,如操纵系统文件、维护敏感信息、与驱动交互等。这意味着UDS服务是很有价值的攻击目标。另外,重要但容易被忽视的一点是,UDS服务通常是完整数据流图中的一个节点,如图 17所示。除了直接连接UDS服务之外,还有许多其他的攻击路径。我们可以利用上游节点将恶意的攻击载荷转发给UDS服务,这种攻击思路在许多场景中都是有效的。
三、常见漏洞
在UDS服务中,常见的漏洞包括配置错误、访问控制失效、内存破坏、逻辑漏洞等。
1.配置错误
配置错误通常出现在诊断工具中,可以导致敏感接口暴露。常见的漏洞模式有两种:
-
打包配置错误,导致内部调试工具被打包到发行版的ROM中
-
例如在我们发现的CVE-2020-11836漏洞中,厂商将AEE错误地配置为调试模式(图 18)。利用暴露的接口,攻击者可以获取一些正常情况下root权限才能获取的敏感信息。
2.访问控制失效
访问控制失效可以导致UDS成为攻击面。常见的漏洞模式包括:
-
下游厂商关闭SELinux(在IoT设备中尤其常见)
-
仅在客户端SDK中进行权限校验,攻击者可以绕过客户端SDK直接访问UDS服务
-
-
例如在我们发现的CVE-2021-0364漏洞中,我们可以将攻击载荷发送至上层应用,利用上层应用的转发绕过SELinux限制,实现命令注入。
3.内存破坏
内存破坏通常由不正确的解析和处理导致。常见的内存破坏漏洞包括:
-
-
-
-
4.逻辑漏洞
逻辑漏洞通常由缺少输入校验导致。由于几乎所有的UDS服务都具有访问控制,无法被untrusted_app直接访问,因此开发者通常会认为UDS服务是安全的,从而忽略对输入的合法性校验。逻辑漏洞可能导致命令注入、信息泄露、任意文件读写等后果。
四、绕过访问控制
针对具有访问控制的UDS服务,我们提出了一种攻击思路。首先绘制该UDS服务完整的数据流图,然后尝试利用其他节点将攻击载荷转发至目标UDS服务。例如在图 19所示的数据流图中,我们发现HW Binder Service可以直接访问目标UDS服务,Framework可以基于HW Binder Service的转发间接访问目标UDS服务。而HW Binder Service和Framework分别暴露了HIDL和AIDL接口。攻击者虽然不能直接连接目标UDS,但可以借助HW Binder Service或Framework的接口,向目标UDS间接发送攻击载荷。
五、自动化分析方法
1.信息收集
1)二进制文件路径:ls -al /proc/pid/exe
2)SELinux上下文:cat/proc/pid/attr/current
1)导出:cat /sys/fs/selinux/policy
我们基于Python和ADB将现有工具整合,开发了一个UDS服务信息自动化收集工具。工具的输出如图 20所示。输出枚举了设备中所有的UDS服务,并列举了SELinux上下文、进程二进制文件路径等信息。
2.漏洞挖掘
对于白盒漏洞挖掘,我们可以利用多种工具提升挖掘效率,例如CodeQL、Soot等。图 21展示了我们利用CodeQL挖掘命令注入漏洞的例子。我们可以利用CodeQL执行静态的污点分析,查看从ReceivedBuffer读取的数据是否能够流向SystemCmd。CodeQL和静态污点分析十分有效,但缺点在于需要对各种库函数进行建模,尤其是C++的一些容器类。
3.访问控制绕过
绕过访问控制的关键在于绘制UDS服务完整的数据流图。想要自动化生成数据流图,其关键在于如何自动化地找到各种进程间通信方式的两端。事实上,查找UDS通信的对端是十分困难的,因为内核并没有提供这样的接口。但仍然有一些更好的替代方法。为了查找哪些进程可以连接指定的UDS服务,我们可以利用sesearch工具分析SELinux规则。其难点在于查找域和进程对应关系。为了查找指定Binder的所属进程及客户端进程,我们可以利用bindump工具进行分析。但该工具基于debugfs实现,在Android 11上不受支持。总体而言,存在一些能够提升数据流图绘制效率的方法,但自动化程度的整体水平不高。
六、漏洞挖掘成果
我们对多家厂商的UDS服务进行了漏洞挖掘。截至2022年4月20日,我们已累计发现12处UDS服务安全漏洞,覆盖4家知名厂商,获得9个CVE和3家厂商的致谢。问题列表如表 1所示,目前相关厂商已修复了所有安全漏洞并输出了补丁。
七、安全建议
尽管访问控制可以降低UDS服务遭受攻击的风险,但访问控制无法解决所有安全问题。因为在安卓生态中,UDS服务通常是完整数据交互图中的一个节点。访问控制可以约束哪些进程能够访问UDS服务,但无法约束上游服务如何使用UDS服务。我们建议UDS服务的开发者更加关注服务实现本身的安全性,而非仅依赖访问控制来保障安全。
八、总结
-
我们深入分析了安卓设备中Unix Domain Socket这一攻击面,总结了Unix Domain Socket在安卓上的主要应用场景,梳理了每种场景存在的安全风险
-
我们提出了一些绕过Unix Domain Socket访问控制的漏洞利用思路,以及针对Unix Domain Socket服务的自动化分析方法
-
在实践中,我们累计发现了12处UDS服务安全漏洞,获得9个CVE编号,并给出相应的修复建议
原文始发于微信公众号(百度安全实验室):Black Hat Asia 2022议题解读:Unix Domain Socket:安卓生态系统中通往权限提升的暗门