《Android安全攻防指南》第2章 Android的安全设计与架构 (1)

第2章 Android的安全设计与架构

(2.1-2.2节)

    Android系统由许多承担安全检查与策略执行任务的机制构成。与任何现代操作系统一样,Android中的这些安全机制互相交互,交换关于主体(应用、用户)、客体(其他应用、文件和设备)以及将要执行操作(读、写、删除等)的各种信息。安全策略执行通常不会发生故障,但偶尔也会出现一些裂缝,为滥用提供了机会。本章将讨论Android系统的安全设计与架构,为分析Android平台的整体攻击面打好基础。

Android的总体架构有时被描述为“运行在Linux上的Java”,然而这种说法不够准确,并不能完全体现出这一平台的复杂性和架构。Android的总体架构由5个主要层次上的组件构成,这5层是:Android应用层、Android框架层、Dalvik虚拟机层、用户空间原生代码层和Linux内核层。图2-1显示了这些层是如何构成Android软件栈的。

【插入原书P26图】

图2-1 Android系统的总体架构

  • 图字:

  • Source:来源
    Creative Commons Share-Alike 3.0 license:知识共享(相同方式共享)3.0版协议

  • Stock Android Apps: 出厂Android应用
    Phone:电话
    AlarmClock:闹钟
    Email:电子邮件
    Settings:设置
    Camera:照相机
    Gallery:图库
    Mms:多媒体短信息服务
    DeskClock:桌面时钟
    Calendar:日历
    Browser:浏览器
    Bluetooth:蓝牙
    Calculator:计算器
    Contacts:联系人

  • Your Apps/Market Apps:自主安装的应用、商店中的应用

  • System Services:系统服务 Power Manager:电源管理器 Mount Service:挂载服务 Status Bar Manager:状态栏管理器 Activity Manager:活动管理器 Notification Manager:通知管理器 Sensor Service:传感器服务 Package Manager:包管理器 Location Manager:位置管理器 Window Manager:窗口管理器 Battary Manager:电池管理器

  • Dalvik/Android Runtime/Zygote:Dalvik/Android运行时/Zygote

  • Libraries:库
    Hardware Abstraction Layer:硬件抽象层
    Native Daemons:原生守护进程
    Init/Toolbox:启动例程/工具箱
    Linux Kernel:Linux内核

Android应用层允许开发者无须修改底层代码就对设备的功能进行扩展和提升,而Android框架层则为开发者提供了大量的用来访问Android设备各种必需设备的API,也就是充当应用层与Dalvik虚拟机(DalvikVM)层之间的“粘合剂”。API中包含各种构件(building block)以允许开发者执行通用任务,比如管理UI元素、访问共享数据存储,以及在应用组件间传递信息等。

Android应用和Android框架都是用Java语言开发的,并在DalvikVM中运行。DalvikVM的作用主要是为底层操作系统提供一个高效的抽象层。DalvikVM是一种基于寄存器的虚拟机,能够解释执行Dalvik可执行格式(DEX)的字节码;另一方面,DalvikVM依赖于一些由支持性原生代码程序库所提供的功能。

Android系统中的用户空间原生代码组件包括系统服务(如vold和DBus)、网络服务(如dhcpd和wpa_supplicant)和程序库(如bionic libc、WebKit和OpenSSL)。其中一些服务和程序库会与内核级的服务与驱动进行交互,而其他的则只是便利底层原生操作管理代码。

Android的底层基础是Linux内核,Android对内核源码树作了大量的增加与修改,其中有些代码存在一些独特的安全后果。我们会在第3章、第10章和第12章中更加详细地讨论这些话题。内核级驱动也提供了额外的功能,比如访问照相机、Wi-Fi以及其他网络设备。需要特别注意Binder驱动,它实现了进程间通信(IPC)机制。

第2.3节将详细介绍每一层上的关键组件。

安全边界,有时也会称为信任边界,是系统中分隔不同信任级别的特殊区域。一个最直接的例子就是内核空间与用户空间之间的边界。内核空间中的代码可以对硬件执行一些底层操作并访问所有的虚拟和物理内存,而用户空间中的代码则由于CPU的安全边界控制,无法访问所有内存。

Android操作系统应用了两套独立但又相互配合的权限模型。在底层,Linux内核使用用户和用户组来实施权限控制,这套权限模型是从Linux继承过来的,用于对文件系统实体进行访问控制,也可以对其他Android特定资源进行控制。这一模型通常被称为Android**沙箱**。以DalvikVM和Android框架形式存在的Android运行时实施了第二套权限模型。这套模型在用户安装应用时是向用户公开的,定义了应用拥有的权限,从而限制Android应用的能力。事实上,第二套权限模型中的某些权限直接映射到底层操作系统上的特定用户、用户组和权能(Capability)。

2.2.1 Android沙箱

Android从其根基Linux继承了已经深入人心的类Unix进程隔离机制与最小权限原则。具体而言,进程以隔离的用户环境运行,不能相互干扰,比如发送信号或者访问其他进程的内存空间。因此,Android沙箱的核心机制基于以下几个概念:标准的Linux进程隔离、大多数进程拥有唯一的用户ID(UID),以及严格限制文件系统权限。

Android系统沿用了Linux的UID/(用户组ID)权限模型,但并没有使用传统的passwd和group文件来存储用户与用户组的认证凭据,作为替代,Android定义了从名称到独特标识符Android ID(AID)的映射表。初始的AID映射表包含了一些与特权用户及系统关键用户(如system用户/用户组)对应的静态保留条目。Android还保留了一段AID范围,用于提供原生应用的UID。Android 4.1之后的版本为多用户资料档案和隔离进程用户增加了额外的AID范围段(如Chrome沙箱)。你可以从AOSP树的system/core/include/private/android_filesystem_config.h文件中找到AID的定义。以下是一个简化过的示例。

除了AID,Android还使用了辅助用户组机制,以允许进程访问共享或受保护的资源。例如,sdcard_rw用户组中的成员允许进程读写/sdcard目录,因为它的加载项规定了哪些用户组可以读写该目录。这与许多Linux发行版中对辅助用户组机制的使用是类似的。

注意
尽管所有的AID条目都映射到一个UID和GID,但是UID在描述系统上的一个用户时并不是必需的。例如,AIDD_SDCARD_RW映射到sdcard_rw,但是它仅仅用作一个辅助用户组,而不是系统上的UID。

除了用来实施文件系统访问,辅助用户组还会被用于向进程授予额外的权限。例如,AID_INET用户组允许用户打开AF_INETAF_INET6套接字。在某些情况下,权限也可能以Linux权能的形式出现,例如,AID_INET_ADMIN用户组中的成员授予CAP_NET_ADMIN权能,允许用户配置网络接口和路由表。本节最后还会介绍与网络相关的其他相似用户组。

在4.3及之后的版本中,Android提升了对Linux权能的使用,比如Android 4.3将二进制程序/system/bin/run-as从原先设置成set-UID root权限,修改为使用Linux权能来访问特权资源。在这里,这一权能方便了对packages.list文件的访问。

注意 对Linux权能的完整讨论已经超出了本章的范围。你可以分别从Linux内核的Documentation/security/credentials.txt文档和capabilities的用户手册页面获得更多关于Linux进程安全和Linux权能的信息。

在应用执行时,它们的UID、GID和辅助用户组都会被分配给新创建的进程。在一个独特UID和GID环境下运行,使得操作系统可以在内核中实施底层的限制措施,也让运行环境能够控制应用之间的交互。这就是Android沙箱的关键所在。

下面的代码给出了在一台HTC One V手机上运行ps命令后的输出结果,注意,最左侧显示的UID对于每个应用的进程都是独特的。

通过使用应用包中的一种特殊指令,应用也可以共享UID,这一点我们会在2.3.1节详细讨论。

实际上,进程显示的用户与用户组名称是由一种POSIX函数的Android专有实现所提供的,这种函数通常就是用来设置和获取这些值的。例如,考虑在Bionic库的stubs.cpp文件中定义的getpwuid函数。

与它的同胞函数一样,getpwuid函数会调用一些额外的Android专有函数,如android_id_to_passwd()app_id_to_passwd()函数。这些函数会把Unix的口令结构填充上相应的AID映射信息表。android_id_to_passwd()函数会调用android_iinfo_to_passwd()函数来完成这一替换。

2.2.2 Android权限

Android的权限模型是多方面的,有API权限、文件系统权限和IPC权限。在很多情况下,这些权限都会交织在一起。正如前面提到的,一些高级权限会后退映射到低级别的操作系统权能,这可能包括打开套接字、蓝牙设备和文件系统路径等。

要确定应用用户的权限和辅助用户组,Android系统会处理在应用包的AndroidManifest.xml文件中指定的高级权限(Manifest文件和权限会在2.3.1节详细描述)。应用的权限由PackageManager在安装时从应用的Manifest文件中提取,并存储在/data/system/packages.xml文件中。这些条目然后会在应用进程的实例化阶段用于向进程授予适当的权限(比如设置辅助用户组GID)。下面的代码片段显示了packages.xml文件中的Chrome浏览器条目,包括这个应用的唯一UID以及它所申请的权限。

权限至用户组的映射表存储在/etc/permissions/platform.xml文件中。它被用来确定应用设置的辅助用户组GID。下面的代码片段显示了一些映射。

 

在应用包条目中定义的权限后面会通过两种方式实施检查:一种检查在调用给定方法时进行,由运行环境实施;另一种检查在操作系统底层进行,由库或内核实施。

1. API权限

API权限用于控制访问高层次的功能,这些功能存在于Android API、框架层,以及某种情况下的第三方框架中。一个使用API权限的常见例子是READ_PHONE_STATE,这个权限在Android文档中定义为允许“对手机状态的只读访问”。应用若申请该权限,随后就会授予该权限,从而可以调用关于查询手机信息的多种方法,其中包括在TelephonyManager类中定义的方法,如getDeviceSoftwareVersiongetDeviceId等。

前面提到过,一些API权限与内核级的安全实施机制相对应。例如,被授予INTERNET权限,意味着申请权限应用的UID将会被添加到inet用户组(GID 3003)的成员中。该用户组的成员具有打开AF_INETAF_INET6套接字的能力,而这是一些更高层次API功能(如创建HttpURLConnection对象)所必需的。

在第4章中,我们还将讨论了API权限及实施检查机制中的一些疏忽和问题。

2. 文件系统权限

Android的应用沙箱严重依赖于严格的Unix文件系统权限模型。默认情况下,应用的唯一UID和GID都只能访问文件系统上相应的数据存储路径。注意,以下代码清单中的UID和GID(分别在第2列和第3列)对于目录都是唯一的,它们的权限被设置为只有这些UID和GID才能访问这些目录。

相应地,由这些应用创建的文件也会拥有相应的权限设置。以下代码清单中显示了某个应用的数据目录,子目录和文件的属主和权限都被只设置给该应用的UID和GID。

正如前面所提到的,特定的辅助用户组GID用于访问共享资源,如SD卡或其他外部存储器。作为一个例子,注意在HTC One V手机上运行mountls命令的输出结果,特别是/mnt/sdcard的路径。

这里你可以看到SD卡被使用GID 1015进行挂载,对应为sdcard_rw用户组。应用请求WRITE_EXTERNAL_STORAGE权限后,会将自己的UID添加到这个组中,得到对这一路径的写权限。

3. IPC权限

IPC权限直接涉及应用组件(以及一些系统的IPC设施)之间的通信,虽然与API权限也有一些重叠。这些权限的声明和检查实施可能发生在不同层次上,包括运行环境、库函数,或直接在应用上。具体来说,这个权限集合应用于一些在Android Binder IPC机制之上建立的主要Android应用组件。关于这些组件和Binder的详细信息,本章后面会详细描述。