理解Android系统
Android 是业界流行的开源移动平台,受到广泛关注并为多个手机制造商作为手机的操作系统平台。由于它的开放性,市面上又出现了它的很多改良定制版本。且广泛的应用在手机、汽车、电脑等领域。因此,研究其安全架构及权限控制机制具有非常的重要性。
本章从 Android 层次化安全架构入手,详细地介绍 Android 平台的安全架构及其权限控制机制,涵盖 Android 应用程序权限申请方法等,并从源代码实现层面来解析该机制。
1.1 系统的层级架构
Android架构,其实就是我们所说的“Java on Linux”的架构。然而,这是有点用词不当和不完全公正地对待该平台的复杂性和结构。整体架构组件,主要分为五层,包括,应用层(Applications)、应用程序框架(Application Framework)、核心库与运行环境层(Libraries and Runtime)、(Linux 内核层)Linux Kernel 层。
1.1.1 应用层
Android 的应用程序主要是用户界面(User Interface),通常以 JAVA 程序编写,其中还可以包含各种资源文件(放置在 res 目录中)。JAVA 程序及相关资源经过编译后,将生成一个 APK 包(Android Package的缩写,Android应用程序安装包的意思)。Android 本身提供了主屏幕(Home),联系人(Contact),电话(Phone),浏览器(Browsers)等众多的核心应用。同时应用程序的开发者还可以使用应用程序框架层的 API 实现自己的程序。
1.1.2 框架层
Android 的应用程序框架为应用程序层的开发者提供 APIs,它实际上是一个应用程序的框架。由于上层的应用程序是以 JAVA 构建的,因此本层次提供的首先包含了 UI 程序中所需要的各种控件: 例如: Views ( 视图组件 ) 包括 lists( 列表 ), grids( 栅格 ), text boxes( 文本框 ), buttons( 按钮 ) 等,甚至一个嵌入式的 Web 浏览器。一个 Android 的应用程序可以利用应用程序框架中的以下几个部分: Activity (活动)、Broadcast Intent Receiver (广播意图接收者)、Service (服务)、Content Provider (内容提供者)。
1.1.3 核心库与运行环境层
本层次对应一般嵌入式系统,相当于中间件层次。Android 的本层次分成两个部分一个是各种库,另一个是 Android 运行环境。本层的内容大多是使用 C++ 实现的。 在其中,各种库包括:
l C 库
C 语言的标准库,这也是系统中一个最为底层的库,C 库是通过 Linux 的系统调用来实现。
l 多媒体框架(Media Framework)
这部分内容是 Android 多媒体的核心部分,基于 PacketVideo(即 PV)的 OpenCORE,从功能上本库一共分为两大部分,一个部分是音频、视频的回放(Play Back),另一部分是则是音视频的纪录(Recorder)。
l SGL
2D 图像引擎。
l SSL
即 Secure Socket Layer 位于 TCP/IP 协议与各种应用层协议之间 , 为数据通讯提供安全支持。
l OpenGL ES 1.0
本部分提供了对 3D 的支持。
l 界面管理工具(Surface Management)
本部分提供了对管理显示子系统等功能。
l SQLite
一个通用的嵌入式数据库
l WebKit
网络浏览器的核心
l FreeType
位图和矢量字体的功能。
Android 的各种库一般是以系统中间件的形式提供的,它们均有的一个显著特点就是与移动设备的平台的应用密切相关。 Android 运行环境主要指的虚拟机技术—— Dalvik。Dalvik 虚拟机和一般 JAVA 虚拟机(Java VM)不同,它执行的不是 JAVA 标准的字节码(bytecode )而是 Dalvik 可执行格式(.dex)中执行文件。在执行的过程中,每一个应用程序即一个进程(Linux 的一个 Process)。 二者最大的区别在于 Java VM 是以基于栈的虚拟机(Stack-based),而 Dalvik 是基于寄存器的虚拟机(Register-based)。显然,后者最大的好处在于可以根据硬件实现更大的优化,这更适合移动设备的特点。
1.1.4 Linux内核层
Android 使用 Linux2.6 作为操作系统,Linux2.6 是一种标准的技术,Linux 也是一个开放的操作系统。Android 对操作系统的使用包括核心和驱动程序两部分,Android 的 Linux 核心为标准的 Linux2.6 内核,Android 更多的是需要一些与移动设备相关的驱动程序。主要的驱动如下所示:
l 显示驱动(Display Driver)
常用基于 Linux 的帧缓冲(Frame Buffer)驱动
l Flash 内存驱动(Flash Memory Driver)
l 照相机驱动(Camera Driver)
常用基于 Linux 的 v4l(Video for )驱动。
l 音频驱动(Audio Driver)
常用基于 ALSA(Advanced Linux Sound Architecture,高级 Linux 声音体系)驱动
l WiFi 驱动(Camera Driver)
基于 IEEE 802.11 标准的驱动程序
l 键盘驱动(KeyBoard Driver)
l 蓝牙驱动(Bluetooth Driver)
Binder IPC 驱动:Andoid 一个特殊的驱动程序,具有单独的设备节点,提供进程间通讯的功能。
l Power Management(能源管理)
1.2 系统的分区结构
分区是逻辑层存储单元用来区分设备内部的永久性存储结构。不同厂商和平台有不同的分区布局。两个不同的设备一般不具有相同的分区或相同的布局。然而,有几个分区则是在所有的Android设备中最常见的。Boot、Data、Recovery和Cache分区。通常的情况下NAND闪存的设备都具备一下分区布局:
l Boot Loader分区
中文名称“系统加载器”,他的作用相当于电脑的BIOS,在手机进入系统之前初始化软硬件环境、加载硬件设备,最终让手机成功启动。疙瘩厂商为了保障运营商的权益、手机能有稳定的运行环境、自家的系统价值、用户的使用安全等,都会给Boot Loader进行加密。加密后的Boot Loader仅能引导官方提供的骨架,任何第三方固件将不予以识别。
l Boot分区
存储着Android的Boot镜像,其中包含着Linux kernel (zImage) 与initrd等文件。
l Splash分区
主要是存储系统启动后第一屏显示的内容,一般都是一些公司的logo或者动画,存储在Boot Loader中。
l Radio分区
这个是基带所在的分区。
基带:Baseband 信源(信息源,也称发射端)发出的没有经过调制(进行频谱搬移和变换)的原始电信号所固有的频带(频率带宽),称为基本频带,简称基带。
l Recovery分区
存储着一个mini型的Android Boot 镜像文件,主要的作用是用来做故障维修和系统恢复(有点类似Windows上的WinPE)。
l System分区
存储着Android系统的镜像文件,镜像文件中包含着Android的Framework、Libraries、Binaries 和一些预装应用。系统挂载后即 /system 目录。
l User Data分区
也称为数据分区,它是设备的内部存储分区,如应用产生的图片、声音等数据文件。系统挂载后再 /data 目录下。
l Cache分区
用于存储各种实用的文件,如:恢复日志和OTA下载的更新包。在应用程序安装设备在SD卡,它也可能包含的Dalvik缓存文件夹,其中存储Dalvik虚拟机的缓存文件。
1.3 启动过程
也许,之前说了这么多系统的架构与分区你还是很迷糊的话,我们就从Android系统的启动过程来看看,从按下电源键开始那一刻系统都做了些什么。
1.3.1 Boot Loader加载阶段
Boot Loader是在物理电源按下之后第一个加载的。绝大部分的设备,在此阶段都会运行一些制造商自定义的初始化代码(如启动Clock、RAM、Media等)。并提供加载Recovery Image和进行Download Mode的支持。Boot Loader内部也是分为多个阶段的,在此我们不在详细讨论。
1.3.2 加载Kernel与initrd阶段
Boot分区加载Linux kernel 与 initrd 到RAM,最后跳转到Kernel继续完成启动。
1.3.3 初始化设备服务阶段
Android kernel 则会启动所有Android 系统设备所必须的服务。如初始化Memory 、初始化IO、内存保护、中断处理程序、CPU调度、设备驱动。最后还会文件系统挂载,启动第一个用户进程init。
1.3.4 加载系统服务阶段
init是Linux系统中用户空间的第一个进程,其进程PID是1,父进程为Linux Kernel核的0号进程。init具有特殊的初始化使命,它会加载一个初始化启动脚本文件init.rc,启动Android系统的一些核心服务,如针对通话的rild、针对VPN连接的mtpd、提供adb相关功能 adbd、支持存储外设的热插拔功能的vold 、负责进程孵化服务 的Zygote、Service Manager等。
1.3.5 虚拟机初始化阶段
其中启动的Zygote进程,会创建Dalvik VM,会启动第一个Java 组建系统服务,最后是Android Framework 服务,如Telephone Manager、Activity Manager、Window Manager、Package Manager。
1.3.6 启动完成
当系统完全启动之后,载入Home(桌面应用程序),然后做一些应用层的初始化工作,如播放一个全局的广播ACTION _BOOT _COMPLETED。
1.4 系统关键进程与服务
1.4.1 init进程详解
init进程,它是一个由内核启动的用户级进程。内核自行启动(已经被载入内存,开始运行,并已初始化所有的设备驱动程序和数据结构等)之后,系统就通过启动一个用户级程序init的方式来完成引导。init是第一个进程,进程的id为1。因为,系统的大部分服务都由它来启动,所以,init进程被赋予了很多及其重要的工作职责。
从以上代码(源码存在system\core\init\init.c)来看,init进程的工作量还是很大的,主要集中在如下几个事情:
1. 解析init.rc初始化脚本文件
2. 初始化属性服务(property service)
3. 进入无线for循环,建立子进程,对关键服务的异常进行重启和异常处理。
1.4.1.1 init.rc
init.rc是系统自定义的一个脚本文件,init进程通过解析init.rc脚本文件会执行一些操作,包括:
l 启动一些系统开启需要启动的Service和Deamons
l 指定不同的Service在不同的用户或用户组下运行
l 修改设置全局的属性服务
l 注册一些动作和命令在特定的时间执行
1.4.1.2 属性服务
在Windows系统上有一个东西叫注册表,用key-value键值对来存储系统和软件的一些信息,,进行相应的初始化或配置工作。在Android系统上也有类似的机制,称之为属性服务(property service)。
属性服务,它提供了一个持久(每次启动都会载入的存储文件),内存映射,关键值配置设备。许多操作系统和框架组件依赖这些属性,包括项目如网络接口配置,系统服务的开关,甚至安全相关的设置。
如,我们在adb下使用getprop命令得到当前系统的属性值:
[persist.radio.supp.notification]: [false]
[persist.service.tm2.tofile]: [true]
[persist.sys.actualpoweron]: [true]
[persist.sys.alarm.enable.uhb]: [1]
[persist.sys.auto-start.once]: [true]
[persist.sys.dalvik.vm.lib]: [libdvm.so]
[persist.sys.datacrash]: [0]
[persist.sys.hdcp_checking]: [never]
[persist.sys.logsystem.neversend]: [0]
[persist.sys.phb.debug.enable]: [1]
[persist.sys.phb.enable]: [1]
[persist.sys.powersystem.enable]: [0]
[persist.sys.powerup_reason]: [NORMAL]
[persist.sys.profiler_ms]: [0]
[persist.sys.strictmode.visual]: [false]
[persist.sys.thermalm]: [default]
[persist.sys.timezone]: [Asia/Shanghai]
[persist.sys.ui.hw]: [true]
[persist.sys.usb.config]: [mtp,adb]
......
1.4.2 ADB进程
adb是Android Debugging Bridge的简称,是在PC端用于管理模拟器或真机状态的工具,采用了C/S模型,包括三个部分:
1. ADB Client(客户端部分),运行在开发用的电脑上,可以在命令行中运行adb命令来调用该客户端,像ADB插件和DDMS这样的Android工具也可以调用adb客户端。
2. ADB Server(服务端部分),是运行在开发用电脑上的后台进程,用于管理客户端与运行在模拟器或真机的守护进程通信。
3. ADB Daemon(守护进程部分),运行于模拟器或手机的后台。
adb客户端都使用5037端口与adb服务端通信。当服务端与客户端建立连接之后,就可以使用adb命令来控制或者访问了。因为服务端管理着连接并且可以接收到从多个adb客户端的命令,所以可以从任何一个客户端或脚本来控制任何模拟器或手机设备。
常用的命令有:
说明 | 命令 |
显示当前运行的全部设备 | adb devices |
安装应用Hello.apk | adb install hello.apk |
缷载Hello.apk包 | adb uninstall hello.apk |
获取模拟器中的文件 | adb pull <remote> <local> |
向模拟器中写文件 | adb push <local> <remote> |
进入模拟器的shell模式 | adb shell |
重启设备 | adb reboot |
重新启动到recovery | adb reboot recovery |
重新启动到bootloader | adb reboot bootloader |
查看logcat | adb logcat |
ActivityManager相关操作 | shell下,am <command> |
PackageManager相关操作 | Shell下,pm <command> |
1.4.3 Vold进程
Vold的全称是volume daemon。Vold是负责完成系统 的CDROM, USB大容量存储,MMC卡等扩展存储的挂载任务自动完成的守护进程。它提供的主要特点是支持这些存储外设的热插拔。这里有GNU/Linux vold的介绍[http://vold.sourceforge.net/]。在Android上的这个vold系统和GNU/Linux的之间存在很 大的差异,这里我们主要是分析Android上的vold系统的处理过程。
Vold处理过程大致分为一下三个部分:
l 创建链接
在vold作为一个守护进程,一方面接受驱动的信息,并把信息传给应用层;另一方面接受上层的命令并完成相应。所以这里的链接一共有两条:
1. vold socket: 负责vold与应用层的信息传递;
2. 访问udev的socket: 负责vold与底层的信息传递;
这两个链接都是在进程的一开始完成创建的。
l 引导
这里主要是在vold启动时,对现有外设存储设备的处理。首先,要加载并解析vold.conf,
并检查挂载点是否已经被挂载;其次,执行MMC卡挂载; 最后,处理USB大容量存储。
l 事件处理
这里通过对两个链接的监听,完成对动态事件的处理,以及对上层应用操作的响应。
1.4.4 Zygote进程
Zygote是在设备开启的时候init启动的其中一个进程。 因为,其行为很像似受精卵的复制自身分裂的行为故此取名Zygote(受精卵)。
在Zygote进程的运行期间,其负责在系统的Framework层添加附加的服务与加载库的工作。Zygote进程同时也是Dalvik虚拟机的构造器,在Android应用执行时,它负责fork一个自身来执行该应用程序。样做的好处显而易见,Zygote进程是在系统启动时产生的,它会完成虚拟机的初始化,库的加载,预置类库的加载等操作,而在系统需要一个新的虚拟机实例时,Zygote通过复制自身,以最快的速度提供一个虚拟机实例。另外,对于一些只读的系统库,所有Dalvik虚拟机实例都和Zygote共享一块内存区域,大大节省了内存开销。
Zygote的二级任务就是启动一些系统服务的进程,这些进程包括所有的系统中所有的AID核心服务。
1.4.5 ServiceMananger服务
ServiceMananger是android中比较重要的一个进程,它是在init进程启动之后启动,从名字上就可以看出来它是用来管理系统中的service。比如:InputMethodService、ActivityManagerService等。在ServiceManager中有两个比较重要的方法:add_service、check_service。系统的service需要通过add_service把自己的信息注册到ServiceManager中,当需要使用时,通过check_service检查该service是否存在。
主函数(anrdroid4.0/frameworks/base/cmds/servicemanager/service_manager.c)
主函数
从它的主函数代码开始:
[java] view plaincopy
int main(int argc, char **argv)
{
struct binder_state *bs;
void *svcmgr = BINDER_SERVICE_MANAGER;
bs = binder_open(128*1024);
if (binder_become_context_manager(bs)) {
LOGE("cannot become context manager (%s)\n", strerror(errno));
return -1;
}
svcmgr_handle = svcmgr;
binder_loop(bs, svcmgr_handler);
return 0;
}
从main函数中可以看出,它主要做了三件事情:
l 打开/dev/binder设备,并在内存中映射128K的空间。
l 通知Binder设备,把自己变成context_manager
l 进入循环,不停的去读Binder设备,看是否有对service的请求,如果有的话,就去调用svcmgr_handler函数回调处理请求。
1.5 系统内核
Android操作系统是基于Linux实现的,然而Android的核心价值却不是Linux,所以说,Android的内核不是指Linux,本书不是一本介绍Linux的书。这就好比苹果的操作系统iOS是基于Unix实现的,然而iOS的核心价值却不是Unix。这一节,我们具体看看Android在Linux系统之上还做了哪些变化,特别是与安全相关的修改。
1.5.1 The Android Fork
一个进程,包括代码、数据和分配给进程的资源。fork函数通过系统调用创建一个与原来进程几乎完全相同的进程,也就是两个进程可以做完全相同的事,但如果初始参数或者传入的变量不同,两个进程也可以做不同的事。
一个进程调用fork函数后,系统先给新的进程分配资源,例如存储数据和代码的空间。然后把原来的进程的所有值都复制到新的新进程中,只有少数值与原来的进程的值不同。相当于克隆了一个自己。
1.5.2 Binder
Binder机制是Android系统内核中最重要的机制之一,Binder是机遇OpenBinder修改而来的一个IPC(Inter-Process Communication,进程间通信 )机制。传统的Linux的IPC机制使用的是管道、共享内存、消息队列、socket等。但是,在Android中偏偏就选择了Binder。
而在Android中,并没有使用这些,取而代之的是Binder机制。因为:
1)采用C/S的通信模式。而在Linux通信机制中,目前只有socket支持C/S的通信模式,但socket有其劣势,具体参看第二条。
2)有更好的传输性能。对比于Linux的通信机制,
socket:是一个通用接口,导致其传输效率低,开销大;
3)管道和消息队列:因为采用存储转发方式,所以至少需要拷贝2次数据,效率低;
共享内存:虽然在传输时没有拷贝数据,但其控制机制复杂(比如跨进程通信时,需获取对方进程的pid,得多种机制协同操作)。
4)安全性更高。Linux的IPC机制在本身的实现中,并没有安全措施,得依赖上层协议来进行安全控制。而Binder机制的UID/PID是由Binder机制本身在内核空间添加身份标识,安全性高;并且Binder可以建立私有通道,这是Linux的通信机制所无法实现的(Linux访问的接入点是开放的)。
综上所述,Android采用Binder机制是有道理的。既然Binder机制这么多优点,那么我们接下来看看它是怎样通过C/S模型来实现的。
Binder通信也是基于Service与Client的,所有需要IBinder通信的进程都必须创建一个IBinder接口。由系统中的Service Manager的守护进程管理着系统中的各个服务,它负责监听是否有其他程序向其发送请求,如果有请求就响应,如果没有则继续监听等待。每个服务都要在Service Manager中注册,而请求服务的客户端则向Service Manager请求服务。在Android虚拟机启动之前,系统会先启动Service Manager进程,Service Manager就会打开Binder驱动,并通知Binder Kernel驱动程序,这个进程将作为System Service Manager,然后该进程将进入一个循环,等待处理来自其他进程的数据。因此,我们也可以将Binder的实现大致分为:Binder驱动、Service Manager、Service、Client这几个部分,下面将分别对这几个部分进行详细分析。
为了完成进程间通信,Binder采用了AIDL(Android Interface Definition Language)来描述进程间的接口。在实际的实现中,Binder是作为一个特殊的字符型设备而存在的,设备节点为/dev/binder,其实现遵循Linux设备驱动模型,实现代码主要涉及以下文件:
kernel/drivers/staging/binder.h
kernel/drivers/staging/binder.c
1.5.3 Ashmem
Ashmem的含义是匿名共享内存(Anonymous Shared Memory),通过这种内核机制,可以为用户空间程序提供分配内存的机制。Ashmem设备节点名称:/dev/ashmem,主设备号为10(Misc Driver),次设备号动态生成;
Ashmem驱动程序在内核中的头文件和代码路径如下:
kernel/include/linux/ashmem.h
kernel/mm/ashmem.c
在用户空间C libutil库对Ashmem进行封装并提供接口:
system/core/include/cutils/ashmem.h:简单封装头文件;
system/core/libutils/ashmem-dev.c:匿名共享内存在用户空间的调用封装;
system/core/libcutils/ashmem-host.c:没有使用。
总之,Ashmem为Android系统提供了内存分配功能,实现类似malloc的功能。
1.5.4 Logger
Android操作系统在保存在Linux的日志机制之外,它也使用了另一套日志系统,我们称为Logger。该驱动程序主要是为了支持Logcat命令,用于查看日志缓冲区。它提供四个独立的日志缓冲区,根据信息的类型分为:主缓冲区,广播缓冲区,事件缓冲区和系统缓冲区。如下图所示
其中,主缓冲区往往是信息量最大的,因为,它主要是记录应用相关事件的来源。应用程序中通常调用的android.util.Log方法类就会写到主缓冲区中去。其中,被调用的方法,还会对应于日志条目的优先级,例如,Log.i方法“信息”,Log.d的“调试”,或Log.e的“错误”级别的日志。
开发者常使用的调试开发工具DDMS,就是使用了Logger机制将调试中的log通过usb数据线传输到PC机上显示出来的。