Android体系结构
- Linux内核层
- Android建立在Linux内核层之上,同任何其他UNIX系统一样,内核层提供硬件驱动、网络、文件系统访问控制与进程管理功能。
- Android内核新增的一些特性,包括内存管理机制、唤醒锁机制、匿名共享机制、闹铃、网络访问控制机制及Binder机制。
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/27bb6dd40f7ff22c73a26f56bc041a45.png)
- 原生用户空间层
- 在内核层之上,即使原生用户空间层,包括init程序(第一个启动进程,用于启动其他进程)、一些原生守护进程和系统使用的数百个原生共享库。
- Dalvik虚拟机
- Dalvik的设计考虑到移动终端缓解,而且不能执行Java字节码(.class文件),它的原生输入格式是Dalvik可执行文件爱你(DEX),并且被打包成.dex文件。
- Dalvik是基于寄存器的,而JVM是基于栈的。
- Dalvik使用更少的指令来达到同样的目的,通常情况下,基于寄存器的虚拟机使用的指令更少,但其生成的代码要比基于栈的虚拟机多,然而,在大多数体系结构上,加载代码的开销要小于指令分发,所以基于寄存器虚拟机的解释执行效率更高。
- Java运行时库
- Java语言的实现需要一组运行时库,主要是在Java.*和javax.*中定义的包。
- Android核心的Java库主要由Java开发,但也有一些原生代码依赖项,原生代码使用标准的Java原生接口(JNI),链接到Android的Java库,JNI机制允许Java代码调用原生代码,反之亦然,从系统服务和应用程序都可以直接访问到Java运行时库层。
- 系统服务
- 系统服务实现大多数基本的Android特性,包括显示和触屏支持、电话和网络连接,大多数系统服务都是用Java实现的,而一些基础服务则是以原生代码的形式编写的。
- 除少数外,每个系统服务都定义了一个远程接口,可以从其他服务和应用来调用它,加上服务发现、中介和Binder提供的IPC机制,系统服务有效地在Linux之上建立一个面向对象的操作系统。
- 进程间通信
Binder通信原理 - Android框架库
- Android框架库,有时也被称为framework,framework包括所有的Java标准运行时库之外的Java库和大部分Android下的顶层包。
- framework也包括Android应用的基本包,比如activity、service和content provider的基类,GUI组件(在Android.view.*和android.content.*包中),也包含与硬件设备交互的类,和系统利用更高层服务提供的类。
- 几乎所有的Android内核层以上的操作系统功能均被实现为系统服务,但它不直接暴露给framework,而是通过managers的外部封装类进行访问,通常,每个manager对于一个系统服务。
- 应用程序
- Android体系架构栈上的最高层就是应用程序,它们是与用户直接交互的程序。
- 系统应用被包含在产品设备上只读的OS镜像中,它们不能被用户卸载或安装,因此,这些应用看作是安全的,比用户安装的应用具有更多的权限,系统应用可以是Android操作系统的核心部分,也可以是简单的预装应用程序,
- 虽然系统应用不能被卸载或更改,但只要使用同一个私钥签名,就可以一直被更新,甚至有一些可以被用户安装的应用所覆盖。
- 用户自安装应用存放在一个专用的,具有读写权限的分区(通常是/data目录),可以随意卸载,每个应用使用一个专有的安全沙箱,通常不会影响到其他应用或者访问其他应用的数据,另外,应用只访问哪些被明确授予使用权限的资源。
- AndroidManifest文件在应用安装时解析,向系统注册它定义ID包和组件,Android需要每个应用使用一个由该应用开发者控制的密钥进行签名,这确保了已安装的应用不会被另一个称为同一个包名的应用程序替换。
Android的安全模型介绍
- 在传统Linux系统中,一个UID可以给登录系统并通过shell执行命令的物理用户,也可以给在后台执行的系统服务。
- Android本来是为只能手机设计的,由于智能手机是私人设备,所以不需要在系统内注册不同的物理用户,物理用户是隐私的,所以UID被用来区别应用程序,这就构成了Android的沙箱基础。
- 在每个应用程序的安装阶段,Android自动为每个应用程序赋予独一无二的UID,通常称作app ID,应用执行时就在特定进程内以该UID运行,另外,每个应用都有一个只有它具有读写权限的专用数据目录,因此,应用程序是隔离的,或者沙箱封装化的,包括进程级和文件级。
- Android没有传统的/etc/passwd文件,并且它的系统UID是静态定义在android_filesystem_config.h头文件中,系统服务的UID从1000开始的,1000是system用户(AID_SYSTEM),具有特殊的权限,对于应用程序,自动从1000开始产生UID,对应用户名是app_XXX或uY_aXXX格式,XXX即是从AID_APP起的偏移,Y是Android的user ID。
- 在单用户设备上,应用单数据目录已在/data/data/目录下,是以包名创建的,多用户设备使用不同的命名模式,所有数据目录下的文件均属于特定的Linux用户。
total 52
drwxrws--x 4 u0_a123 u0_a123_cache 4096 2020-06-27 21:44 cache
drwxrws--x 2 u0_a123 u0_a123_cache 4096 2020-06-03 10:13 code_cache
drwxrwx--x 2 u0_a123 u0_a123 4096 2020-06-03 10:18 databases
drwxrwx--x 2 u0_a123 u0_a123 4096 2020-06-03 10:13 files
lrwxrwxrwx 1 root root 38 2020-06-03 10:13 lib -> /system/product/app/YouTube/lib/x86
drwxrwx--x 2 u0_a123 u0_a123 4096 2020-06-03 10:13 no_backup
drwxrwx--x 2 u0_a123 u0_a123 4096 2020-07-24 18:38 shared_prefs
- 应用程序UID和包的其他元数据一起由/data/system/packages.xml文件管理,同时被写入了/data/system/packages.list文件。
com.google.android.youtube 10123 0 /data/user/0/com.google.android.youtube default:targetSdkVersion=28 3003 0 1419573700
- 应用程序使用同一个UID安装,叫做共享用户ID,在这种情况下,它们共享文件,甚至在同一进程中运行,为了模块化考虑,共享用户ID在系统应用中被广泛使用,因为系统应用需要在不同的包之间共享使用数据资源。
- 为了共享同一个UID,应用需要使用同一个密钥进行代码签名,另外,因为对一个已安装应用的新版本增加共享ID,会导致它改变自身的UID,系统是不允许这种情况发生的,共享用户ID不可以追加,只有应用一开始就设置使用共享ID才行。
- 权限
- Android应用是沙箱隔离的,它们只能访问自己的文件和一些设备上全局可访问的资源,Android可赋予应用额外的、更细粒度的访问权限,从而是应用更具备更丰富的功能,这些访问权限称为permission,它们可以控制硬件设备、网络连接、数据或OS服务的访问,
- 应用程序通过在AndroidManifest.xml文件中进行定义,来请求访问权限,在应用程序安装时,Android检查请求权限列表,从而决定是否给予授权,一旦授权,权限不可撤销,并且无需再次确认,这些权限对应用一直都有效。
- 一些权限被授权给Android OS部分的哪些应用,因为它们是预装或者使用OS同一密钥进行签名的,第三方应用可以自定义权限,以及类似保护级别权限的限制,从而对由同一作者创建的其他应用,增加访问应用服务和资源的限制。
- 检查可以在不同层次上执行:请求底层的系统资源,诸如文件,Linux内核检查调用进程的UID或GID,并与资源所有者和访问控制位进行比较,当访问更高级别的Android组件时,可以由Android OS执行,或者由每个组件执行,或者两者同时参与。
- IPC
- Android使用内核驱动和用户空间库组合实现IPC机制,Binder的内核驱动确保了调用者的UID和PID不能被伪造,很多服务依赖于Binder提供的UID和PID,动态访问通过IPC暴露的敏感API。
- EX:蓝牙管理服务,只允许系统应用以system的UID运行时,方能默认启动蓝牙功能:
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/2f6a5cdddf5204ea9172c2a24afd6fab.png)
- 影响一个服务的通过IPC暴露出来的所有方法的更粗细粒度的权限,可以由服务声明时指定,并通过系统自动执行。
- 系统使用应用包数据库来确定被调用组件所需的权限,然后将调用者的UID映射为包名,再检索调用者授权的权限集,如果所需的权限在这个权限集中,则此次调用成功,否则,调用失败,系统会抛出一个SecurityExceptio异常。
- 代码签名和平台密钥
- 所有Android应用,包括系统应用,必须由开发者签名,因为Android APK文件是Java Jar包格式的拓展,所使用的代码签名方法也是基于JAR签名的,Android使用APK签名来确保应用更新包来自于同一作者,从而建立应用程序之间的信任关系。这些安全特性都是通过比较已安装应用和更新包的签名证书来实现的。
- 系统应用是用一些平台密钥签名的,不同的系统组件之间可以共享资源,并且当它们被相同的平台密钥签名时,可以在同一个进程中运行,平台密钥由哪些维护Android版本的人生成和控制。
- 多用户支持
- 每个用户都有唯一的user ID,该ID从0开始生成,并且每个用户都有自己的数据目录/data/system/users/<user ID>/,这也叫做用户的系统目录,该目录存放用户相关的设置,比如主屏参数、账户信息以及当前安装的应用程序列表,虽然应用程序的二进制文件是用户间共享的,但每个用户均各自有一个该应用的数据目录。
- 为了区分每个物理用户安装的应用,Android给应用程序赋予一个新的有效UID,该UID基于物理用户的user ID和应用在但用户系统下的UID,这种复合结构的UID确保了,即使有不同用户安装了同一应用,该应用在各自用户下都有属于自己的沙箱环境。
- Android提供了专用的共享存储,它对每个物理用户均是全局可读的,首次初始化设备的用户叫做设备所有者,并且只有他可以管理其他用户,或者执行影响整个设备的管理任务。
- SELinux
- 传统的Android安全模型严重依赖应用的UID和GID,虽然这是内核提供的功能,并且每个应用的文件都是私有的,但是无法禁止应用自身把文件改成全局可访问的,也无法禁止恶意应用程序利用系统文件或原生套接字过于宽松的访问标志位进行攻击。
- 这些漏洞在Linux默认的访问控制模型即自主访问控制中,是不可避免的,自主在这里表示一旦用户获得某一资源的访问控制权限,那么用户可根据自己的意愿,将该资源传递给其他用户,比如设置某个文件的访问权限控制位为全局可读。
- 与之相对的是强制访问控制(MAC),它确保对资源的访问控制,符合一个系统范围的授权规则,该规则也叫做策略,策略可由管理元进行更改,用户不可以覆盖或绕过,比如不可以授权用户访问自己的文件。
- 在Android,SELinux用于将核心系统守护进程和用户隔离到不同该的安全域,并且为每个域定义不同更多访问策略,Android4.4版本及其之后,SELinux以强制模式部署,违法系统策略将会产生运行时错误,但强制策略只应用域核心系统守护进程。
- 系统更新
- 一次Android的更新动作可能需要修改基带固件、引导加载程序和其他无法从Android直接访问的设备部分,更新过程通常使用专用、小型的可访问所有的硬件资源的OS,这一OS就是所谓的恢复操作系统,简称recovery。
- OTA更新是通过下载一个OTA包文件,该文件是一个可被recovery解释执行的小脚本文件,通过重启设备到recovery模式来执行更新。
- 在生产设备中,recovery只接受设备制造商签名的更新包,已签名更新文件通常是zip格式,包括一个在文件注释段的数字签名,recovery在安装更新前会提取并验证该签名。在某些设备上,设备所有者可以替换掉某些recovery OS,并且禁用系统更新时的签名验证功能,允许安装第三方的更新包,切换设备的引导加载程序到一种允许替换recovery和系统镜像模式,叫做引导加载解锁,这通常需要清除所有用户数据,用以确保潜在的恶意第三方系统镜像不会获取已存在的用户数据。
- 验证启动模式
- 验证模块使用哈希树提供透明的块设备完整性检查,哈希树的每个节点是一个哈希值,其中叶子节点存放的物理数据块的哈希值,而中间节点存放的子节点的哈希值,因根节点的值基于所有其他节点,所以只要根节点的哈希值是受信任的,那么也就验证了该树的其他部分。
- 验证过程使用包含在引导分区的RSA公钥,设备块在运行时被检查,方法是计算读到的块的哈希值,并与哈希树上的记录值做比较,如果不匹配,那么读操作会导致一个I/O错误,表示文件系统出了问题,因为所有检查都是由内核执行的,引导进程需要先验证内核的完整性,验证启动模式方可工作。而这个进程是设备相关的,通常使用不可改变的、硬件相关的密钥,烧录到设备中,这个密钥即用来验证每级引导加载程序的完整性,并最终验证内核的完整性。