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

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

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

2.1 理解Android系统架构

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

{%}

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

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节将详细介绍每一层上的关键组件。

2.2 理解安全边界和安全策略执行

安全边界,有时也会称为信任边界,是系统中分隔不同信任级别的特殊区域。一个最直接的例子就是内核空间与用户空间之间的边界。内核空间中的代码可以对硬件执行一些底层操作并访问所有的虚拟和物理内存,而用户空间中的代码则由于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/GID(用户组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的定义。以下是一个简化过的示例。

#define AID_ROOT         0 /*传统的unix根用户*/

#define AID_SYSTEM    1000 /*系统服务器*/

#define AID_RADIO     1001 /*通话功能子系统,RIL*/
#define AID_BLUETOOTH 1002 /*蓝牙子系统*/
...
#define AID_SHELL     2000 /*adb shell与debug shell用户*/
#define AID_CACHE     2001 /*缓存访问*/
#define AID_DIAG      2002 /*访问诊断资源*/

/*编号3000系列只用于辅助用户组们,表示出了内核所支持的Android权能*/
#define AID_NET_BT_ADMIN 3001 /*蓝牙:创建套接字*/
#define AID_NET_BT       3002 /*蓝牙:创建sco、rfcomm或l2cap套接字*/
#define AID_INET         3003 /*能够创建AF_INET和AF_INET6套接字*/
#define AID_NET_RAW      3004 /*能够创建原始的INET套接字*/
...
#define AID_APP            10000 /*第一个应用用户*/

#define AID_ISOLATED_START 99000 /*完全隔绝的沙箱进程中UID的开始编号 */
#define AID_ISOLATED_END   99999 /*完全隔绝的沙箱进程中UID的末尾编号*/
#define AID_USER          100000 /*每一用户的UID编号范围偏移*/

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

注意 尽管所有的AID条目都映射到一个UID和GID,但是UID在描述系统上的一个用户时并不是必需的。例如,AID_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对于每个应用的进程都是独特的。

app_16   4089   1451 304080 31724 ... S com.htc.bgp
app_35   4119   1451 309712 30164 ... S com.google.android.calendar
app_155  4145   1451 318276 39096 ... S com.google.android.apps.plus
app_24   4159   1451 307736 32920 ... S android.process.media
app_151  4247   1451 303172 28032 ... S com.htc.lockscreen
app_49   4260   1451 303696 28132 ... S com.htc.weather.bg
app_13   4277   1451 453248 68260 ... S com.android.browser

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

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

345 passwd* getpwuid(uid_t uid) { // NOLINT:实现不良函数
346   stubs_state_t* state = __stubs_state();
347   if (state == NULL) {
348     return NULL;
349   }
350
351   passwd* pw = android_id_to_passwd(state, uid);
352   if (pw != NULL) {
353     return pw;
354   }
355   return app_id_to_passwd(uid, state);
356 }

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

static passwd* android_iinfo_to_passwd(stubs_state_t* state,
                                       const android_id_info* iinfo) {
  snprintf(state->dir_buffer_, sizeof(state->dir_buffer_), "/");
  snprintf(state->sh_buffer_, sizeof(state->sh_buffer_),
"/system/bin/sh");

  passwd* pw = &state->passwd_;
  pw->pw_name= (char*) iinfo->name;
  pw->pw_uid = iinfo->aid;
  pw->pw_gid = iinfo->aid;
  pw->pw_dir = state->dir_buffer_;
  pw->pw_shell = state->sh_buffer_;
  return pw;
}

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以及它所申请的权限。

<package name="com.android.chrome"
codePath="/data/app/com.android.chrome-1.apk"
nativeLibraryPath="/data/data/com.android.chrome/lib"
flags="0" ft="1422a161aa8" it="1422a163b1a"
ut="1422a163b1a" version="1599092" userId="10082"
installer="com.android.vending">
<sigs count="1">
<cert index="0" />
</sigs>
<perms>
<item name="com.android.launcher.permission.INSTALL_SHORTCUT" />
<item name="android.permission.NFC" />
...
<item name="android.permission.WRITE_EXTERNAL_STORAGE" />
<item name="android.permission.ACCESS_COARSE_LOCATION" />
...
<item name="android.permission.CAMERA" />
<item name="android.permission.INTERNET" />
...
</perms>
</package>

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

...
    <permission name="android.permission.INTERNET" >
        <group gid="inet" />
    </permission>

    <permission name="android.permission.CAMERA" >
        <group gid="camera" />
    </permission>

    <permission name="android.permission.READ_LOGS" >
        <group gid="log" />
    </permission>

    <permission name="android.permission.WRITE_EXTERNAL_STORAGE" >
        <group gid="sdcard_rw" />
    </permission>
...

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

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才能访问这些目录。

root@android:/ # ls -l /data/data
drwxr-x--x  u0_a3   u0_a3  ... com.android.browser
drwxr-x--x  u0_a4   u0_a4  ... com.android.calculator2
drwxr-x--x  u0_a5   u0_a5  ... com.android.calendar
drwxr-x--x  u0_a24  u0_a24 ... com.android.camera
...
drwxr-x--x  u0_a55  u0_a55 ... com.twitter.android
drwxr-x--x  u0_a56  u0_a56 ... com.ubercab
drwxr-x--x  u0_a53  u0_a53 ... com.yougetitback.androidapplication.virgin.
mobile
drwxr-x--x  u0_a31  u0_a31 ... jp.co.omronsoft.openwnn

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

root@android:/data/data/com.twitter.android # ls -lR

.:
drwxrwx--x u0_a55   u0_a55            2013-10-17 00:07 cache
drwxrwx--x u0_a55   u0_a55            2013-10-17 00:07 databases
drwxrwx--x u0_a55   u0_a55            2013-10-17 00:07 files
lrwxrwxrwx install  install           2013-10-22 18:16 lib ->
/data/app-lib/com.twitter.android-1
drwxrwx--x u0_a55   u0_a55            2013-10-17 00:07 shared_prefs

./cache:
drwx------ u0_a55   u0_a55            2013-10-17 00:07
com.android.renderscript.cache

./cache/com.android.renderscript.cache:

./databases:
-rw-rw---- u0_a55   u0_a55     184320 2013-10-17 06:47 0-3.db
-rw------- u0_a55   u0_a55       8720 2013-10-17 06:47 0-3.db-journal
-rw-rw---- u0_a55   u0_a55      61440 2013-10-22 18:17 global.db
-rw------- u0_a55   u0_a55      16928 2013-10-22 18:17 global.db-journal

./files:
drwx------ u0_a55   u0_a55            2013-10-22 18:18
com.crashlytics.sdk.android

./files/com.crashlytics.sdk.android:
-rw------- u0_a55   u0_a55         80 2013-10-22 18:18
5266C1300180-0001-0334-EDCC05CFF3D7BeginSession.cls

./shared_prefs :
-rw-rw---- u0_a55   u0_a55        155 2013-10-17 00:07 com.crashlytics.prefs.
xml
-rw-rw---- u0_a55   u0_a55        143 2013-10-17 00:07
com.twitter.android_preferences.xml

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

root@android:/ # mount
...
/dev/block/dm-2 /mnt/sdcard vfat rw,dirsync,nosuid,nodev,noexec,relatime,
uid=1000,gid=1015,fmask=0702,dmask=0702,allow_utime=0020,codepage=cp437,
iocharset=iso8859-1,shortname=mixed,utf8,errors=remount-ro 0 0
...
root@android:/ # ls -l /mnt
...
d---rwxr-x system    sdcard_rw           1969-12-31 19:00 sdcard

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

3. IPC权限

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

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

    <manifest android:versionCode="1" android:versionName="1.0"
    package="com.wiley.MyApp"
    ...
    <permission android:name="com.wiley.permission.INSTALL_WIDGET"
    android:protectionLevel="signature" />
    ...
    <activity android:name=".InstallWidgetActivity"
    android:permission="com.wiley.permission.INSTALL_WIDGET"/>
    
    

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

  • Activity

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

    ...
            <activity android:theme="@style/Theme_NoTitle_FullScreen"
    android:name="com.yougetitback.androidapplication.ReportSplashScreen"
    android:screenOrientation="portrait" />
            <activity android:theme="@style/Theme_NoTitle_FullScreen"
    android:name="com.yougetitback.androidapplication.SecurityQuestionScreen"
    android:screenOrientation="portrait" />
            <activity android:label="@string/app_name"
    android:name="com.yougetitback.androidapplication.SplashScreen"
    android:clearTaskOnLaunch="false" android:launchMode="singleTask"
    android:screenOrientation="portrait">
                <intent-filter>
                    <action android:name="android.intent.action.MAIN" />
                </intent-filter>
    ...
    
    

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

  • Broadcast Receiver

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

    <receiver android:name=".MySMSReceiver">
      <intent-filter android:priority:"999">
        <action android:name="android.provider.Telephony.SMS_RECEIVED" />
      </intent-filter>
    </receiver>
    
    

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

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

  • Service

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

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

            <service
    android:name="com.yougetitback.androidapplication.FindLocationService">
                <intent-filter>
                    <action
    android:name="com.yougetitback.androidapplication.FindLocationService" />
                </intent-filter>
            </service>
    
    

    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文件中截取的代码片段:

    <provider android:name="com.wiley.example.MyProvider"
    android:writePermission="com.wiley .example.permission.WRITE"
    android:authorities="com.wiley .example.data" />
    
    

    该应用声明了一个名为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进程中的线程运行的。

root@generic:/ # ps -t -p 376
USER     PID   PPID   ... NAME
system   376     52   ... system_server
...
system   389    376   ... SensorService
system   390    376   ... WindowManager
system   391    376   ... ActivityManager
...
system   399    376   ... PackageManager

2.3.3 DalvikVM

DalvikVM是基于寄存器而不是栈的。虽然有人说Dalvik是基于Java的,但它并不是Java,因为谷歌并不使用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)、提供关键调试功能的服务(如adbd和debugggerd)等。注意,某些核心服务可能是硬件或版本特定的,本节当然不能详尽描述所有的用户空间服务。

  • init

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

    service dbus /system/bin/dbus-daemon --system --nofork
        class main
        socket dbus stream 660 bluetooth bluetooth
        user bluetooth
        group bluetooth net_bt_admin
     
    service bluetoothd /system/bin/bluetoothd -n
        class main
        socket bluetooth stream 660 bluetooth bluetooth
        socket dbus_bluetooth stream 660 bluetooth bluetooth
     
    # init.rc does not yet support applying capabilities, so run as root and
    # let bluetoothd drop uid to bluetooth with the right linux capabilities
        group bluetooth net_bt_admin misc
        disabled
     
    service bluetoothd_one /system/bin/bluetoothd -n
        class main
        socket bluetooth stream 660 bluetooth bluetooth
        socket dbus_bluetooth stream 660 bluetooth bluetooth
    # init.rc does not yet support applying capabilities, so run as root and
    # let bluetoothd drop uid to bluetooth with the right linux capabilities
        group bluetooth net_bt_admin misc
        disabled
        oneshot
    # Discretix DRM
    service dx_drm_server /system/bin/DxDrmServerIpc -f -o allow_other \
     /data/DxDrm/fuse
     
    on property:ro.build.tags=test-keys
        start htc_ebdlogd
     
    on property:ro.build.tags=release-keys
        start htc_ebdlogd_rel
     
    service zchgd_offmode /system/bin/zchgd -pseudooffmode
        user root
        group root graphics
        disabled
    
    

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

    • 通过service指令,启动在开机时应该运行的服务或守护进程;

    • 通过每个服务条目下缩进的参数,指定服务应该在哪个用户和用户组环境下运行;

    • 设置向Property服务公开的系统范围属性与配置选项;

    • 通过on指令,注册在特定事件发生时,如修改系统属性或装载文件系统,要执行的动作或命令。

  • Property服务

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

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

    {%}

    图 2-2 Android系统的Property服务

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

    root@android:/ # getprop
    [dalvik.vm.dexopt-flags]: [m=y]
    [dalvik.vm.heapgrowthlimit]: [48m]
    [dalvik.vm.heapsize]: [128m]
    ...
    [dhcp .wlan0.dns1]: [192.168.1.1]
    [dhcp .wlan0.dns2]: []
    [dhcp .wlan0.dns3]: []
    [dhcp .wlan0.dns4]: []
    [dhcp .wlan0.gateway]: [192.168.1.1]
    [dhcp .wlan0.ipaddress]: [192.168.1.125]
    [dhcp .wlan0.leasetime]: [7200]
    ...
    [ro.htc.appupdate.exmsg.url]:
        [http://apu-msg.htc.com/extra-msg/rws/and-app/msg]
    [ro.htc.appupdate.exmsg.url_CN]:
        [http://apu-msg.htccomm.com.cn/extra-msg/rws/and-app/msg]
    [ro.htc.appupdate.url]:
        [http://apu-chin.htc.com/check-in/rws/and-app/update]
    ...
    [service.brcm.bt.activation]: [0]
    [service.brcm.bt.avrcp_pass_thru]: [0]
    
    

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

    [ro.secure]: [0]
    [ro.serialno]: [HT26MTV01493]
    [ro.setupwizard.enterprise_mode]: [1]
    [ro.setupwizard.mode]: [DISABLED]
    [ro.sf.lcd_density]: [240]
    [ro.telephony.default_network]: [0]
    [ro.use_data_netmgrd]: [true]
    [ro.vendor.extension_library]: [/system/lib/libqc-opt.so]
    
    

    你会在第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。

     % adb devices
     * daemon not running. starting it now on port 5037 *
     * daemon started successfully *
     List of devices attached
     D025A0A024441MGK device
     HT26MTV01493 device
     
     % adb -s HT26MTV01493 shell
     root@android:/ #
    
    

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

    root@android:/ # busybox pgrep -l adbd
    2103 /sbin/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的过程。

    obbFile = "path/to/some/obbfile";
    storageRef = (StorageManager) getSystemService(STORAGE_SERVICE);
    storageRef.mountObb(obbFile, "SuperSecretKey", obbListener);
    obbContent = storageRef.getMountedObbPath(obbFile);
    
    

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

  • 其他服务

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

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

    服务描述UID、GID和辅助用户组
    netd在Android 2.2以上版本中存在,由网络管理服务用于配置网络接口,运行PPP守护程序(pppd)、以太网与其他类似服务UID:0 / root
    GID:0 / root
    mediaserver负责启动媒体相关服务,这些服务包括Audio Flinger、Media Player Service、Camera Service和Audio Policy ServiceUID: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分支

在早期,谷歌创建了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的通信流图。

{%}

图 2-3 Binder的通信流

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

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

const int pid = ipc->getCallingPid();
const int uid = ipc->getCallingUid();
    if ((uid != AID_GRAPHICS) &&
            !PermissionCache::checkPermission(sReadFramebuffer,
                pid, uid)) {
        ALOGE("Permission Denial: "
                "can't read framebuffer pid=%d, uid=%d", pid, uid);
        return PERMISSION_DENIED;
}

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

// IRemoteService.aidl
 package com.example.android;

//在此声明任何非默认类型导入声明

 /**范例服务接口*/
 interface IRemoteService {
     /**请求这一服务的进程ID,做点“有趣”的事情*/
     int getPid();

     /**显示一些用作AIDL参数和返回值的基本类型*/
     void basicTypes(int anInt, long aLong, boolean aBoolean,
             float aFloat,
             double aDouble, String aString);
   }

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

3. ashmem

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

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

 int fd = ashmem_create_region("SomeAshmem", size);
 if(fd == 0) {
     data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
      ...

在较高层次上,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显示了日志事件的流图以及辅助日志记录器的组件。

{%}

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

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

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

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

$ adb -d logcat
--------- beginning of /dev/log/system
D/MobileDataStateTracker( 1600): null: Broadcast received :
 ACTION_ANY_DATA_CONNECTION_STATE_CHANGEDmApnType=null != received
 apnType=internet
D/MobileDataStateTracker( 1600): null: Broadcast received:
ACTION_ANY_DATA_CONNECTION_STATE_CHANGEDmApnType=null != received
apnType=internet
D/MobileDataStateTracker( 1600): httpproxy: Broadcast received:
ACTION_ANY_DATA_CONNECTION_STATE_CHANGEDmApnType=httpproxy != received
apnType=internet
D/MobileDataStateTracker( 1600): null: Broadcast received:
ACTION_ANY_DATA_CONNECTION_STATE_CHANGEDmApnType=null != received
apnType=internet
...
--------- beginning of /dev/log/main
...
D/memalloc( 1743): /dev/pmem: Unmapping buffer base:0x5396a000
size:12820480 offset:11284480
D/memalloc( 1743): /dev/pmem: Unmapping buffer base:0x532f8000
size:1536000 offset:0
D/memalloc( 1743): /dev/pmem: Unmapping buffer base:0x546e7000
size:3072000 offset:1536000
D/libEGL  ( 4887): loaded /system/lib/egl/libGLESv1_CM_adreno200.so
D/libEGL  ( 4887): loaded /system/lib/egl/libGLESv2_adreno200.so
I/Adreno200-EGLSUB( 4887): <ConfigWindowMatch:2078>: Format RGBA_8888.
D/OpenGLRenderer( 4887): Enabling debug mode 0
V/chromium( 4887): external/chromium/net/host_resolver_helper/host_
resolver_helper.cc:66: [0204/172737:INFO:host_resolver_helper.cc(66)]
DNSPreResolver::Init got hostprovider:0x5281d220
V/chromium( 4887): external/chromium/net/base/host_resolver_impl.cc:1515:
[0204/172737:INFO:host_resolver_impl.cc(1515)]
HostResolverImpl::SetPreresolver preresolver:0x013974d8
V/WebRequest( 4887): WebRequest::WebRequest, setPriority = 0
I/InputManagerService( 1600): [unbindCurrentClientLocked] Disable input
method client.
I/InputManagerService( 1600): [startInputLocked] Enable input
method client.
V/chromium( 4887): external/chromium/net/disk_cache/
hostres_plugin_bridge.cc:52: [0204/172737:INFO:hostres_
plugin_bridge.cc(52)] StatHubCreateHostResPlugin initializing.. .
...

这个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。

2.4 复杂的安全性,复杂的漏洞利用

在仔细观察了Android的设计与架构之后,我们已经清楚地了解到,Android操作系统是一种非常复杂的系统。设计者坚持了最低权限原则,也就是说任何特定组件都应该只能访问它真正所需要访问的东西。在本书中,你将看到他们使用这一原则的大量证据。不过,这虽然有助于提高安全性,却也增加了复杂性。

进程隔离和减少特权往往是安全系统设计中的基石。无论对于开发者还是攻击者,这些技术的复杂性也让系统都变得更加复杂,从而增加两方的开发成本。当攻击者在打磨他的攻击工具时,他必须花时间去充分了解问题的复杂性。对于像Android这样一个系统,单单攻击一个安全漏洞,可能不足以获取到系统的完全控制权。攻击者可能需要利用多个安全漏洞才能达到目的。总之,要成功地攻击一个复杂系统,需要一个复杂的漏洞利用。

一个能够很好地说明这一点的真实例子是,用于root HTC J Butterfly手机的“diaggetroot”漏洞利用。为了获取root访问控制权,它利用了多个互为补充的安全问题。这个特殊的漏洞利用会在第3章中详细讨论。

2.5 小结

本章概述了Android安全设计和架构。我们引入了Android沙箱及Android使用的权限模型,包括Android对Unix系统UID/GID映射关系的特殊实现AID,以及在整个系统中实施的限制和权能。

我们也深入介绍了Android的逻辑层次,包括应用层、Android框架层、DalvikVM、用户空间原生代码和Linux内核。对于每个层次,我们都讨论了主要组件,特别是那些与安全相关的组件。我们强调了Android开发者对Linux内核所作出的重要添加与修改。

本章对Android总体设计较高层次上的介绍将有助于理解后续章节,这些章节将进一步深入到本章介绍的层次和组件中。

下一章将解释如何完全控制Android设备的方法及其理由。我们将讨论几种通用的方法,并介绍一些依赖于特定安全漏洞的已公开技术。

目录