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

《Android安全攻防指南》

(2.3节)

本节将详细介绍Android软件栈中与安全最相关的组件,包括应用层、Android框架层、DalvikVM、用户空间的支持性原生代码与相关服务,以及Linux内核层。这将为我们理解后续章节对这些组件的详细介绍打下基础,并为我们攻击这些组件提供必要的知识。

2.3.1 Android应用层

为了了解如何评估和攻击Android应用层的安全性,你首先需要了解它们是如何工作的。本节讨论了Android应用、应用运行时和支持性IPC机制的安全相关部分。这也会为理解第4章奠定基础。

应用通常被分为两类:预装应用与用户安装的应用。预装应用包括谷歌、原始设备制造商(OEM)或移动运营商提供的应用,如日历、电子邮件、浏览器和联系人管理应用等。这些应用的程序包保存在/system/app目录中。其中有些应用可能拥有提升的权限或权能,因此人们会特别感兴趣。用户安装的应用是指那些由用户自己安装的应用,无论是通过Google Play商店等应用市场直接下载,还是通过pm installadb install进行安装。这些应用以及预安装应用的更新都将保存在/data/app目录中。

Android在与应用相关的多种用途中使用公共密钥加密算法。首先,Android使用一个特殊的平台密钥来签署预安装的应用包。使用这个密钥签署的应用的特殊之处它们拥有system用户权限。其次,第三方应用是由个人开发者生成的密钥签名的。对于预安装应用和用户安装应用,Android都使用签名机制来阻止未经授权的应用更新。

主要的应用组件

尽管Android应用由无数个组件组成,但本节将重点介绍那些与Android系统版本无关,在大多数应用中都值得关注的组件。这些组件包括AndroidManifest、Intent、Activity、BroadcastReceiver、Service和Content Provider。后面4类组件代表IPC通信端点(endpoint),它们有一些非常有趣的安全属性。

AndroidManifest.xml

所有的Android应用包(APK)都必须包括AndroidManifest.xml文件,这个XML文件含有应用的信息汇总,具体包括如下内容。

  • 唯一的应用包名(如com.wiley.SomeApp)及版本信息。
  • Activity、Service、BroadcastReceiver和插桩定义。
  • 权限定义(包括应用请求的权限以及应用自定义的权限)。
  • 关于应用使用并一起打包的外部程序库的信息。
  • 其他支持性的指令,比如共用的UID信息、首选的安装位置和UI信息(如应用启动时的图标)等。

Manifest文件中一个特别有趣的部分是sharedUserId属性。简单地说,如果两个应用由相同的密钥签名,它们就可以在各自的Manifest文件中指明同一个用户标识符。在这种情况下,这两个应用就会在相同的UID环境下运行,从而能使这些应用访问相同的文件系统数据存储以及潜在的其他资源。

Manifest文件经常是由开发环境自动产生,比如Eclipse或Android Studio,然后在构建过程中由明文XML文件转换为二进制XML文件。

Intent

应用间通信的一个关键组件是Intent。Intent是一种消息对象,其中包含一个要执行操作的相关信息,将执行操作的目标组件信息(可选),以及其他一些(对接收方可能非常关键的)标志位或支持性信息。几乎所有常用的动作——比如在一个邮件中点击链接来启动浏览器,通知短信应用收到条短信,以及安装和卸载应用,等等——都涉及在系统中传递Intent。

这类似于一个进程间调用(IPC)或远程过程调用(RPC)机制,其中应用组件可以通过编程方式和其他组件进行交互,调用功能或者共享数据。在底层沙箱(文件系统、AID等)进行安全策略实施的情况下,应用之间通常使用这个API进行交互。如果调用方或被调用方指明了发送或接收消息的权限要求,那么Android运行时将作为一个参考监视器,对Intent执行权限检查。

当在Manifest文件中声明特定的组件时,可以指明一个Intent Filter,来定义端点处理的标准。Intent Filter特别用于处理那些没有指定目标组件的Intent(即隐式Intent)。

例如,假设一个应用的Manifest文件中包含了一个自定义的权限(com.wiley.permission.INSTALL_WIDGET)和一个Activity(com.wiley.MyApp.InstallWidgetActivity),后者使用这个权限来限制启动InstallWidgetActivity

在这里,我们看到了权限声明和Activity声明。还要注意,权限拥有签名的ProtectionLevel属性。这限定了可以请求这一权限的应用,它们必须是与初始定义这一权限的应用使用同一私钥进行签名的其他应用。

Activity

简单地说,Activity是一种面向用户的应用组件或用户界面(UI)。Activity基于Activity基类,包括一个窗口和相关的UI元素。Activity的底层管理是由被称为Activity管理服务(Activity Manager)的组件来进行处理的,这一组件也处理应用之间或应用内部用于调用Activity的发送Intent。这些Activity在应用的Manifest文件中定义,具体如下:

这里,我们可以看到Activity的定义,以及对样式/UI、屏幕方向等信息的指定。其中launchMode属性值得关注,因为它会影响Activity的启动方式。在这种情况下, singleTask值表示在同一时间只能有一个特定Activity实例存在,而不是每次调用时启动一个单独的实例。这一应用的当前实例(如果有的话)将接收并处理调用该Activity的Intent。

Broadcast Receiver

另一种类型的IPC端点是Broadcast Receiver。它们通常会在应用希望接收一个匹配某种特定标准的隐式Intent时出现。例如,一个应用想要接收与短消息关联的Intent,它需要在Manifest文件中注册一个Receiver,使用Intent Filter来匹配android.provider.Telephony.SMS_RECEIVED动作。

注意
Broadcast Receiver也可以使用registerReceiver方法在运行时以编程方式注册,这个方法可以被重载以对Receiver设置权限。

在Broadcast Receiver上设置权限要求可以限定哪些应用能够往这个端点发送Intent。

Service

Service是一类在后台运行而无需用户界面的应用组件,用户不用直接与Service所属应用进行交互。Android系统上一些常见的Service例子包括SmsReceiverServiceBluetoothOppService。虽然这些Service都运行在用户直接可见视图之外,但与其他Android应用组件一样,它们也可以利用IPC机制来发送和接收Intent。

Service必须在应用的Manifest文件中声明,例如,以下是一个Service的简单定义,同时设置了Intent Filter:

Service通常可以被停止、启动或绑定,所有这些动作都通过Intent来触发。在最后一种情况中,绑定一个Service后,另外一组IPC或RPC过程将会提供给调用者。这些过程取决于Service的具体实现,并更深入地利用了Binder服务(将在2.3.5节讨论)。

Content Provider

Content Provider是为各种通用、共享的数据存储提供的结构化访问接口。例如,Contacts Provider(联系人提供者)和Calendar Provider(日历提供者)分别对联系人信息和日历条目进行集中式仓库管理,这两项内容可以被其他应用(使用适当权限)访问。应用还可以创建自己的Content Provider,并且可以选择暴露给其他应用。通过这些Provider公开的数据的后台通常是SQLite数据库,或是直接访问的系统文件路径(如播放器对MP3文件编排的索引和共享路径)。

像其他的应用组件一样,对Content Provider的读写能力也可以用权限进行控制。考虑如下从一个AndroidManifest.xml文件中截取的代码片段:

该应用声明了一个名为MyProvider的Content Provider,对应于实现Provider功能的类。然后,它声明了一个名为com.wiley.example.permission.WRITEwritePermission,表明只有携带这一自定义权限的应用才能写入这个Provider。最后,它指明了Provider将采取动作的authorities或内容统一资源描述符(URI)。Content URI采用content://[authorityname]的格式,可以额外包含路径和参数信息(如content://com.wiley.example.data/foo),而这些信息对Provider的底层实现可能非常关键。

在第4章中,我们将展示了一系列发现和攻击这些IPC端点的手段。

2.3.2 Android框架层

作为应用和运行时之间的连接纽带,Android框架层为开发者提供了执行通用任务的部件——程序包及其类。这些任务可能包括管理UI元素、访问共享数据存储,以及在应用组件中传递消息等。也就是说,框架层中包含任何仍然在DalvikVM中执行的非应用特定代码。

通用的框架层程序包位于android.*名字空间中,如android.contentandroid.telephony。Android也提供了许多Java标准类(在java.*和的javax.*名字空间中),以及一些第三方程序包,如Apache HTTP客户端库和SAXXML解析器。Android框架层还包括许多用于管理内部类所提供功能的服务。这些被称为管理器的服务由system_server(将在2.3.3节“Zygote”小节中讨论)在系统初始化之后启动。表2-1显示了其中的一些服务器,以及它们在框架层中的描述与角色。

表2-1 框架层中的管理器

框架层服务 描述
Activity管理器 管理Intent的解析与目标、应用/Activity的启动等
视图系统 管理Activity中的视图(用户可见的UI组合)
程序包管理器 管理系统上之前或正在进入安装队列的程序包相关信息
电话管理器 管理与电话服务、无线电状态、网络与注册信息相关的信息与任务
资源管理器 为诸如图形、UI布局、字符串数据等非代码应用资源提供访问
位置管理器 提供设置和读取(GPS、手机、Wi-Fi)位置信息的接口,位置信息包括具体定位信息、经纬度等
通知管理器 管理不同的事件通知,比如播放声音,震动,LED闪灯,以及在状态栏中显示图标等

使用ps命令,并指明system_server的PID和-t选项,可以从结果中看到一些管理器是以system_server进程中的线程运行的。

2.3.3 DalvikVM

DalvikVM是基于寄存器而不是栈的。虽然有人说Dalvik是基于Java的,但它并不是Java,因为Google并不使用Java的Logo,而且Android的应用模型也与JSR(Java标准规范要求)没有关系。Android应用开发者要记住,DalvikVM虽然看起来和感觉上都像Java,但实际上并不是。整体的开发流程大致如下:

  1. 开发者以类似Java的语法进行编码;
  2. 源代码被编译成.class文件(也类似于Java);
  3. 得到的类文件被翻译成Dalvik字节码;
  4. 所有类文件被合并为一个Dalvik可执行文件(DEX)文件;
  5. 字节码被DalvikVM加载并解释执行。

作为一个基于寄存器的虚拟机,Dalvik拥有大约64 000个虚拟寄存器。不过通常只会用到最前16个,偶尔会用到前256个。这些寄存器被指定为虚拟机内存的存储位置,用于模拟微处理器的寄存器功能。就像实际的微处理器一样,DalvikVM在执行字节码时,使用这些寄存器来保持运行状态,并跟踪一些值。

DalvikVM是专门针对嵌入式系统的约束(如内存小和处理器速度慢)而设计的。因此,在DalvikVM设计时考虑到了速度和运行效率。但虚拟机毕竟只是对底层CPU寄存器机的一个抽象,本质上就意味着在运行效率上有所损失,而这也正是谷歌力求减轻这些副作用的原因。

为了在这些约束中发挥更大的能力,DEX文件在被虚拟机解释执行之前会进行优化处理。对于从一个Android应用中启动的DEX文件,这种优化通常只在应用第一次启动时进行一次。优化过程的结果是一个优化后的DEX文件(ODEX)。需要注意,ODEX文件是无法在不同版本的DalvikVM之间或是不同设备之间进行移植的。

与Java虚拟机类似,DalvikVM使用Java Native Interface(JNI)与底层原生代码进行交互。这一功能允许在Dalvik代码和原生代码之间相互调用。欲了解DalvikVM、DEX文件格式以及JNI on Android的更详细信息,可查阅Dalvik官方文档,网址为http://milk.com/kodebase/dalvik-docs-mirror/docs/

Zygote

Android设备启动时,Zygote进程是最先运行的进程之一。接下来,Zygote负责启动其他服务以及加载Android框架所使用的程序库。然后,Zygote进程作为每个Dalvik进程的加载器,通过复制自身进程副本(也被称为forking,分支)来创建进程。这种优化方案可以避免重复那些不必要且消耗大量资源的加载过程,即启动Dalvik进程(包括应用)时加载Android框架及其依赖库。作为优化结果,核心库、核心类和对应的堆结构会在DalvikVM的所有实例之间共享。这也给攻击带来了一些有趣的可能性,你会在第12章中阅读到更详细的内容。

Zygote的第二大功能是启动system_server进程,这个进程容纳了所有系统核心服务,并在system的AID用户环境中以特权权限运行。接下来,system_server进程启动所有在表2-1中介绍的Android框架层服务。

注意
system_server进程是如此重要,以至于杀死这一进程会让设备看上去像重新启动了一样。然而,实际上只有设备的Dalvik子系统重新启动了。

在初始启动后,Zygote通过RPC和IPC机制为其他Dalvik进程提供程序库访问,这是承载Android应用组件的进程实际启动的机制。

2.3.4 用户空间原生代码层

操作系统用户空间内的原生代码构成了Android系统的一大部分,这一层主要由两大类组件构成:程序库和核心系统服务。本节将讨论这两大类组件,并详述一些属于这两大类的单独组件。

1. 程序库

Android框架层中的较高层次类所依赖的许多底层功能都是通过共享程序库的方式来实现,并通过JNI进行访问的。在这其中,许多程序库都也是在其他类Unix系统中所使用的知名开源项目。比如,SQLite提供了本地数据存储功能,Webkit提供了可嵌入的Web浏览器引擎,FreeType提供了位图和矢量字体渲染功能。

供应商特定的程序库,即那些为某一设备型号提供硬件支持的代码库,保存在/vendor/lib(或/system/vendor/lib)路径。其中包括对图形显示设备、GPS收发器或蜂窝式无线电的底层支持库等。非厂商特定的程序库则保存在/system/lib路径中,通常会包括一些外部项目,比如像下面这些库。

  • libexif:一个JPEG EXIF格式的处理库。
  • libexpat:Expat的XML解析器。
  • libaudioalsa/libtinyalsa:ALSA音频库。
  • libbluetooth:BlueZ Linux蓝牙库。
  • libdbus:D-Bus的IPC库。

这些只是Android的大量程序库中的一小部分,一个运行Android 4.3的设备中包含了超过200个共享程序库。

然而,并非所有的底层程序库都是标准的,Bionic就是一个值得注意的特例。Bionic是BSD C运行时库的一个变种,旨在提供更小的内存使用空间,更好的优化,同时避免产生GNU公共许可证(GPL)授权问题。这些差异也带来了少许代价。Bionic的libc并不像GNU libc那么完整,甚至比不上Bionic源头的BSD libc实现。Bionic中也包含了大量自己的代码,为了努力降低C运行时库的内存使用空间,Android开发者还实现了一个自定义的动态链接器和线程API。

这些库是使用原生代码开发的,因而很容易出现内存破坏漏洞,这使得该层成为探索Android安全性时的一个特别有趣的部分。

2. 核心服务

核心服务是指建立基本操作系统环境的服务与Android原生组件。这些服务包括初始化用户空间的服务(如init)、提供关键调试功能的服务(如adbddebugggerd)等。注意,某些核心服务可能是硬件或版本特定的,本节当然不能详尽描述所有的用户空间服务。

Init

init程序通过执行一系列命令对用户空间环境进行初始化。然而, Android使用自定义的init实现。代替从/etc/init.d路径执行基于运行级别的shell脚本,Android基于从/init.rc中找到的指令来执行命令。对于设备特定的指令,可能存在一个名为/init.[hw].rc的文件,这里[hw]是特定设备的硬件代号。以下是HTC One V手机上/init.rc文件中的内容代码片段。

这些初始化脚本指定几个任务,包括:

  • 通过service指令,启动在开机时应该运行的服务或守护进程;
  • 通过每个服务条目下缩进的参数,指定服务应该在哪个用户和用户组环境下运行;
  • 设置向Property服务公开的系统范围属性与配置选项;
  • 通过on指令,注册在特定事件发生时,如修改系统属性或装载文件系统,要执行的动作或命令。

Property服务

Property服务位于Android的初始化服务中,它提供了一个持续性的(每次启动)、内存映射的、基于键值对的配置服务。许多操作系统和框架层的组件都依赖于这些属性,其中包括网络接口配置、无线电选项甚至安全相关设置,其中的细节将在第3章中讨论。

属性可以通过多种方式进行读取和设置。例如,分别使用命令行实用程序getpropsetProp进行读取和设置,在原生代码中分别使用libcutils库中的property_getproperty_set函数以编程方式读取和设置,或使用android.os.SystemProperties类以编程方式读取和设置(这个类函数又会继续调用上述原生函数)。Propery服务的概述如图2-2所示。

【插入原书P44图】

图2-2 Android系统的Property服务

  • 图字:
  • Property setter:属性设置器
    Property consumer:属性读取器
    Unix domain socket:Unix域套接字
    Read:读取
    Write:写入
    Property service:Propert服务
    Load:加载
    property_workspace(shared memory):property_workspace(共享内存)
    persistent file:持久文件

在Android设备(在本例中是一台HTC One V手机)上运行getprop命令,可以看到输出结果中包含DalvikVM配置、当前设置壁纸、网络接口配置设置和厂商特定的更新URL等。

被设置为“只读”的一些属性不可更改,即便是root用户(尽管有一些设备特有的例外情况)。这些属性以ro为前缀。

你会在第3章读到更多关于Property服务及其安全影响的细节。

无线接口层

将在第11章中详细介绍的无线接口层(RIL),为智能手机提供了手机本身应该有通讯功能。如果没有这个组件,Android设备将无法拨打电话,发送或接收短信,或者在没有Wi-Fi网络时上网。因此,它会在任何拥有蜂窝数据或电话功能的Android设备上运行。

debuggerd

Android的基本崩溃报告功能是由一个称为debuggerd的守护进程提供的,当调试器守护进程启动时,它将打开到Android日志功能的一个连接,然后在一个抽象名字空间套接字开始监听客户端的连入。每次程序开始运行,链接器会安装信号处理程序,然后处理某些信号。

当要捕获的某个信号发生时,内核执行信号处理函数debugger_signal_handler。这个函数连接到之前提到的由DEBUGGER_SOCKET_NAME定义的套接字上,连接之后,链接器将通知套接字的另一端(即debuggerd)目标进程已经崩溃了。这会通知debuggerd应该调用它的处理流程并创建一个崩溃报告。

ADB

Android调试桥(ADB)是由几个部件组成的,包括在Android设备上的adbd守护进程,在宿主工作站上运行的adb服务器,以及相应的adb命令行客户端。adb服务器管理客户端与在目标设备上运行的守护进程之间的连接,便于各种任务操作,比如执行一个shell、调试应用(通过Java调试网络协议)、套接字和端口转发、文件传输,以及安装/卸载应用包等。

作为一个简单的例子,你可以运行adb devices命令来列出你连接的设备。因为ADB在我们的主机上尚未运行,因此它会被初始化,在5037/tcp上监听客户端连接。然后你可以通过序列号来指明一个目标设备,并运行adb shell命令,这会获得一个在设备上运行的命令行shell。

通过对进程列表进行grep搜索(此例中使用pgrep)也可以看到,ADB守护进程adbd已在目标设备上运行。

ADB对于使用Android设备和模拟器进行开发是非常关键的,因此我们将在本书中频繁使用它。你可以从http://developer.android.com/tools/help/adb.html找到如何使用adb命令的详细信息。

Volume守护进程

Volume守护进程,或称为vold,是Android系统上负责安装和卸载各种文件系统的服务。例如,插入SD卡时,vold会处理这一事件,检查SD卡的文件系统错误(如通过启动fsck)并将SD卡安装到相应的路径(也就是/mnt/sdcard)。当卡被用户取出后,vold会卸载目标卷。

vold也处理Android Secure Container(ASEC)文件的安装与卸载。当应用包存储到FAT等不安全的文件系统上时,ASEC会对其进行加密处理。它们会在应用加载时通过环回(loopback)设备进行安装,通常挂接到/mnt/asec。

不透明二进制块(OBB)也是由vold进行安装和卸载的。这些文件与应用共同打包,以存储由一个共享密钥加密的数据。然而与ASEC容器不同的是,对OBB的安装和卸载是由应用自身而非系统来执行的。以下代码片段演示了使用SuperSecretKey作为共享密钥创建一个OBB的过程。

鉴于vold是以root身份运行的,它的功能和潜在的安全漏洞都让它成为一个诱人的目标。你可以在第3章看到针对vold和其他类似服务进行特权提升攻击的详细介绍。

其他服务

在许多Android设备上还运行着许多其他服务,提供一些不一定是必需的额外功能(取决于设备和服务)。表2-2重点介绍其中的一些服务、它们的用途及在系统中的权限级别(、GID和运行用户所属的辅助用户组,这些会在系统的init.rc文件中指明)。

表2-2 用户空间的原生服务

服务 描述 UID、GID和辅助用户组
netd 在Android 2.2以上版本中存在,由网络管理服务用于配置网络接口,运行PPP守护程序(pppd)、以太网与其他类似服务 UID:0 / root
:0 / root
mediaserver 负责启动媒体相关服务,这些服务包括Audio Flinger、Media Player Service、Camera Service和Audio Policy Service UID:1013 / media
GID:1005 / audio
用户组:1006 / camera
1026/ drmpc
3001 / net_bt_admin
3002 / net_bt
3003 / inet
3007 / net_bw_acct
dbus-daemon 管理D-Bus特有的IPC/消息传递(主要针对非Android特有的组件) UID:1002 / bluetooth
GID:1002 / bluetooth
用户组:3001 / net_bt_admin
installd 管理设备上的应用程序包安装(以程序包管理器的名义),包括对应用程序包(APK)中Dalvik可执行字节码(DEX)的初始优化 UID:1012 / install
GID:1002 / install
4.2之前的版本:
UID:0 / root
GID:0 / root
keystore 负责对系统上键值对的安全存储(通过用户定义的口令进行保护) UID:1017 / keystore
GID:1017 / keystore
用户组:1026 / drmpc
drmserver 提供对数字版权保护的底层操作,应用通过与高层次上的DRM程序包与这个服务进行交互 UID:1019 / drm
GID:1019 / drm
用户组:1026 / drm rpc
3003 / inet
serviceman-ager 作为注册/注销应用服务的Binder IPC端点的仲裁者 UID:1000 / system
GID:1000 / system
surface-flinger 在Android 4.0以上版本中存在的显示合成器,负责创建进行演示的图形帧、屏幕,并发送给显示卡驱动 UID:1000 / system
GID:1000 / system
Ueventd 在Android 2.2以上版本中存在的用户空间守护程序,处理系统和设备事件并采取相应动作,比如装载恰当的内核模块 UID:0 / root
GID:0 / root

如前所述,这份清单并不详尽。对比定制设备与Nexus设备的进程列表、init.rc文件以及文件系统,通常会发现大量的非标准服务。这些服务非常能够引起人的兴趣,因为它们的代码质量与Android设备中的核心服务无法相比。

2.3.5 内核

尽管Android的根基——Linux内核文档相当完备而且已经被深入理解,但是Linux内核和Android使用的内核还是有很多显著的差异。本节将介绍其中的一些变化,特别是那些和Android安全相关的。

1. Android分支

在早期,Google创建了Linux内核的一个Android分支,因为许多修改和添加已经不再与Linux内核主代码树相互兼容。总体而言,这其中包括了大约250个补丁,涉及文件系统支持、网络处理调整,以及进程和内存管理功能等。根据一位内核工程师的说法,绝大部分的补丁“代表着Android开发者在Linux内核中发现的一些局限性”。2012年3月,Linux内核维护者将Android特有的内核修改合并到了主代码树。表2-3显示了一些对主代码树的添加与修改,本节将详细介绍其中的一部分。

表2-3 Android对Linux内核的主要修改

内核修改 描述
Binder IPC机制,提供额外的一些特性,比如对调用者和被调用者的安全验证。它已被大量的系统和框架服务所使用
ashmem 匿名共享内存,一种基于文件的共享内存分配器,使用Binder IPC来允许进程识别内存区域文件描述符
pmem 进程内存分配器,用于管理大块、连续的共享内存区域
日志记录器 系统范围的日志功能
RAM_CONSOLE 在内核错误后,在RAM中存储内核日志消息,以便查看
OOM修改 “Out Of Memory”-killer在内存空间低的时候杀掉进程,在Android分支中,OOM在内存即将用尽时,较传统Linux内核能更快地杀掉进程
wakelocks 电源管理特性,使得设备进入低功率省电模式,同时保持可响应状态
Alarm Timers AlarmManager的内核接口,用于指示内核调度“醒来”时间
Paranoid Networking 将网络操作和功能特性限制在特定的用户组ID
timed output/gpio 允许用户空间程序在一定时间后修改和重置GPIO寄存器
yaffs2 对yaffs2 Flash文件系统的支持

2. Binder

对Android的Linux内核最为重要的一个添加也许是Binder驱动。Binder是一个基于OpenBinder修改版本的IPC机制,OpenBinder最初由Be公司开发,后来又由Palm公司开发和维护。Android的Binder代码量相对较小(大约有4000行源码,存在于2个文件中),但是对于大部分的Android功能都是非常关键的。

概括地说,Binder内核驱动是整个Binder架构的粘合剂。Binder作为一个架构,以客户端—服务器模型运行,允许一个进程同时调用多个“远程”进程中的多个方法。Binder架构将底层细节进行了抽象,使得这些方法调用看起来就像是本地函数调用。图2-3显示了Binder的通信流图。

【插入原书P51图】

图2-3 Binder的通信流

图字:
Process A:进程A
Proxy:代理
Binder Driver:Binder驱动
Process B with Threads:带有线程的进程B

Binder也使用进程ID(PID)和UID信息作为一种标识调用进程的手段,允许被调用方作出访问控制的决策。通常会调用Binder.getCallingUidBinder.getCallingPid等函数,或者调用checkCallingPermission等高层次上的检查函数。

在实际情况中会遇到的一个例子是ACCESS_SURFACE_FLINGER权限。这一权限通常只授予图形系统用户,并允许访问Surface Flinger图形服务的Binder IPC接口。此外,调用者的用户组成员关系(以及随后所需要的权限)会通过一系列对前述函数的调用进行检查,如以下代码片段所示。

在更高的层次上所暴露的IPC方法,如那些由绑定服务所提供的IPC方法,通常会通过Android接口定义语言(AIDL)提炼成一个抽象接口。AIDL允许两个应用使用“协商确定”或者标准化的接口,来发送和接收数据,使得接口独立于具体的实现。AIDL类似于其他的接口定义语言文件,比如C/C++中的头文件。以下是一个AIDL代码片段的示例。

这个AIDL的例子定义了一个简单的接口——IRemoteService,包含两个方法:getPidbasicTypes。如果一个应用绑定到暴露此接口的服务,随之就可以在Binder支持下调用前面提到的这两个方法。

3. ashmem

匿名共享内存服务,简称ashmem,是另一个在Linux内核Android分支中添加的代码模块。ashmem驱动基本上提供了基于文件、通过引用计数的共享内存接口。它广泛应用于大多数Android核心组件中,包括Surface Flinger、Audio Flinger、系统服务器和DalvikVM等。ashmem能够自动收缩内存缓存,并在全局可用内存较低时回收内存区域,因而非常适用于低内存环境。

在底层使用ashmem很简单,只需调用ashmem_create_region并对返回的文件描述符使用mmap函数:

在较高层次上,Android框架层中提供了MemoryFile类,作为ashmem驱动的封装器。此外,进程可以使用Binder机制在以后共享这些内存对象,并利用Binder的安全特性来限制访问。作为一起安全事件,在2011年年初,ashmem被证明存在一个非常严重的安全缺陷,允许通过Android属性进行特权提升,关于这一点,我们将在第3章中进行详细介绍。

4. pmem

另一个Android特有的自定义驱动是pmem,用来管理1~16MB(或更多,取决于具体实现)的大块物理上连续的内存区块。这些区块是特殊的,可以在用户空间进程和其他内核驱动(比如GPU驱动)之间共享。与ashmem不同的是,pmem驱动需要一个分配进程,为pmem的内存堆保留一个文件描述符,直到所有其他索引都关闭。

5. 日志记录器

虽然Android内核仍然维护自己基于Linux内核的日志机制,但它也使用另一个日志记录子系统,即俗称的“日志记录器”(logger)。作为logcat命令的支持,这个驱动用于查看日志缓冲区。它根据信息的类型,提供了4个独立的日志缓冲区:main(主缓冲区)、radio(无线电缓冲区)、event(事件缓冲区)与system(系统缓冲区)。图2-4显示了日志事件的流图以及辅助日志记录器的组件。

主缓冲区通常是日志数量最大的,并且是应用相关事件的日志源。应用通常从android.util.Log类中调用一个方法,而调用的方法对应于不同的日志条目优先级别,例如,Log.i方法记录“信息性”日志,Log.d方法记录“调试”日志,而Log.e方法记录“错误”日志(很像syslog)。

【插入原书P53图】

图2-4 Android日志记录系统架构

  • 图字:
  • Overview of Android Logging System:Android日志记录系统概览
    User:用户
    Kernel:内核
    Native program:原生程序
    Java program:Java程序
    Target:目标
    main:主缓冲区
    event:事件缓冲区
    radio:无线电缓冲区
    system:系统缓冲区
    ADT in Eclipse:Eclipse中的ADT
    Host:主机
    logger:日志记录器

系统缓冲区也是许多信息的来源,即由系统进程生成的系统级事件。这些进程利用android.util.Slog类中的println_native方法,而println_native方法又会调用特定的原生代码,将日志写入这个缓冲区。

日志消息可以使用logcat命令来获取,而主缓冲区与系统缓冲区作为默认的日志信息源。在以下代码中,我们运行adb -d logcat命令,来看看连接的设备上发生了什么。

这个logcat命令是如此常用,以至于ADB为在目标设备上运行它提供了一个快捷方式。在整本书中,我们会大量使用logcat命令来监视进程和整个系统的状态。

6. Paranoid Networking

Android内核基于一个调用进程的辅助用户组来限制网络操作,而这个调用进程就是被称为Paranoid Networking的内核修改模块。在高层次上,这个模块将一个AID(以及随后的GID)映射到应用层的权限声明或请求上。例如,Manifest文件中的权限android.permission.INTERNET有效地映射到AID_INET AID(或GID 3003)上。这些用户组、UID以及它们相应的权能在内核源码树的include/linux/android_aid.h文件中定义,详见表2-4。

表2-4 根据用户组定义的网络权能

AID定义 用户组ID和名称 权能
AID_NET_BT_ADMIN 3001 / net_bt_admin 允许创建任意蓝牙套接字,以及可以诊断和管理蓝牙连接
AID_NET_BT 3002 / net_bt 允许创建SCO、RFCOMM或L2CAP(蓝牙)套接字
AID_INET 3003 / inet 允许创建AF_INET或AF_INET6套接字
AID_NET_RAW 3004 / net_raw 允许使用RAW和PACKET套接字
AID_NET_ADMIN 3005 / net_admin 授予CAP_NET_ADMIN权能,允许对网络接口、路由表和套接字的操纵

你可以从AOSP代码库中的system/core/include/private/android_filesystem_config.h文件中找到其他Android特有的GID。