初识安卓——基础概念介绍

一:安卓体系结构

Android体系架构分为四层:应用层、应用框架层、库层、内核层。

1 应用层

        这是 Android 系统中最高的一层,包含了用户直接与之交互的应用程序。Android 自带了一些核心应用,如电话、短信、日历、浏览器、相机等,此外,用户还可以通过 Google Play 等应用商店下载和安装第三方应用。这一层的应用都是用 Java 或 Kotlin 编写,并运行在应用框架层之上。

2 应用框架层

        Android开发人员接触最多的就是框架层,该层提供了各种各样的系统API,开发人员通过使用这些API来构建上一层的各种各样的APP。这些API包括且不限于:Activity Manager(控制Activity的生命周期等)、Notification Manager(提供通知相关的功能)、Content Provider(实现应用程序间的数据共享)、Resource Manager(管理非代码资源,比如布局文件,图片资源,字符资源等等)、View(提供常见的视图控件)、Alarm Manager(提供闹钟相关服务)等等。

3 库层

第三层包含两部分内容:

        第一部分是Native C\C++系统库层,主要提供一系列第三方类库,常见的有系统C库、多媒体库(播放媒体文件)、SGL(2D图像引擎)、Free Type(渲染位图和矢量字体)、Sqlite(轻量级数据库)、SSL(Secure Socket Layer)、Webkit(提供网络工具)等等;

       第二部分是运行环境,包括Dalvik虚拟机和Java核心库。Dalvik虚拟和Oracle的JVM的区别:Dalvik是基于寄存器的,JVM是基于栈的。JVM运行.class文件,每个.class文件对应一个类;Dalvik虚拟机将.class文件转为.dex文件,只有一个.dex文件,包含了所有的类,并且通过性能优化转为.odex文件。基于寄存器的虚拟机运行速度更快,文件更小,效率更高,适合移动端。这是因为,虽然Dalvik生成的代码更多,但是它需要的指令更少。而加载代码的开销要小于指令分发。

4 内核层

这是 Android 系统的最底层,基于 Linux 内核,负责底层硬件的抽象和与硬件设备的交互。它提供了设备驱动程序、内存管理、进程管理、网络协议栈、安全设置等服务。Linux 内核使 Android 能够在不同的硬件平台上运行,并且确保了系统的安全性和稳定性。

驱动程序:内核包含了一系列驱动程序,用于管理设备硬件,如显示器、相机、蓝牙、Wi-Fi、音频等。

电源管理:管理设备的电源使用,以提高电池寿命。

文件系统:提供了数据存储和检索的功能。

安全模块:如 SELinux,增强了系统的安全性。

二:Binder

        进程隔离:和其它类UNIX的系统一样,Android的进程地址空间是独立的,也就是说一个进程不能直接访问其他进程的内存空间。

        IPC(进程间通信机制):早于安卓之前就有一些IPC机制,例如文件、信号、套接字、管道、信号量、共享内存和消息队列等等。安卓系统只是采用了其中的一部分,例如本地套接字。

        Binder :Binder是 Android 系统中最重要的进程间通信机制(IPC),它用于在不同进程间传递数据和消息。Binder 提供了高效、安全的通信方式,使应用程序能够调用系统服务或其他进程中的方法。

        AIDL: AIDL是 Android Interface Definition Language 的缩写。它是一种用于在 Android 应用程序的不同组件(如进程间)之间进行通信的接口定义语言。通过 AIDL,开发者可以定义应用程序中不同进程之间的接口,使得这些进程能够共享数据或调用彼此的服务。

        通常,AIDL 会用在需要跨进程通信的场景中,比如一个服务在后台运行,另一个应用或组件需要与这个服务进行通信。AIDL 文件定义了一个接口,该接口列出了可供远程调用的方法。编译器会根据这个定义自动生成一些辅助代码,开发者可以使用这些代码来实现进程间的通信。

        简单来说,AIDL 是 Android 平台上实现跨进程通信(IPC)的关键工具。

1 Binder驱动

        Binder 驱动 是 Android 系统中的一个内核模块(/dev/binder),位于 Linux 内核空间。它是 Android 特有的Binder机制的核心实现,负责在内核态和用户态之间传递数据。

        Binder 驱动的主要作用是作为中介,协调客户端进程和服务端进程之间的数据交换。它负责创建和管理 Binder 对象的引用、传递 IPC 消息、处理跨进程的方法调用、以及管理系统资源。

        Binder 驱动在客户端和服务端之间传递 IPC 消息。客户端通过系统调用(如 ioctl)将请求发送到内核,Binder 驱动接收到请求后,将其路由到目标服务端。服务端处理完请求后,通过 Binder 驱动将响应数据传回客户端。

        Binder 驱动为每一个创建的 Binder 对象分配一个唯一的句柄,用于标识该对象。句柄在内核中维护,并与内核中的实际 Binder 实例相关联。当客户端请求访问一个远程 Binder 对象时,Binder 驱动为客户端分配一个引用(也称为 Binder Proxy),这个引用是一个指向远程对象的代理,通过它,客户端可以间接地调用远程对象的方法。

        例如,进程A创建了一个Binder对象,然后将之传递给进程B,进程B再将之传递给进程C,那么这三个进程的调用,均由同一个Binder对象处理。进程A会通过内存地址直接引用Binder对象(因为它在A的内存空间中),二=然而B和C只会收到Binder对象的句柄。

        内核负责维护活动的 Binder 对象与它们在其他进程中的句柄之间的映射关系。因为一个Binder 对象的标识符是独一无二的,并且由内核负责维护,所以用户空间进程除非通过IPC 机制处理,否则不可能创建一个 Binder 对象副本或获取相关引用。因此 Binder 对象是唯一的、不可伪造的、可通信的对象,可以作为安全令牌使用。这也使得 Android 可以使用基于权能的安全模型(capability-based security)。

Binder 驱动的工作流程:

  1. 客户端调用:客户端进程想要与远程服务进行通信时,会创建一个 Binder 引用,并通过系统调用(如 ioctl)将请求发送给 Binder 驱动。

  2. 内核处理:Binder 驱动接收到请求后,根据 Binder 引用找到对应的服务端对象,并将请求消息放入服务端进程的请求队列中。

  3. 服务端处理:服务端进程的 Binder 线程从请求队列中取出消息,执行相应的操作。处理完毕后,将结果返回给 Binder 驱动。

  4. 返回结果:Binder 驱动将服务端的响应消息传回客户端进程,客户端进程接收并处理该响应,完成一次完整的 IPC 调用。

        Binder的内核驱动为A和B进程的内存空间中各定义一块Binder内存。A与B通信,内核将信息从A的那块内存直接复制到B的那块内存,并告诉B信息被复制到内存中的哪里。

2 Binder对象

        Binder对象代表了服务端进程中可供客户端调用的一个接口或服务。通过 Binder 对象,客户端可以远程调用服务端进程中的方法,从而实现跨进程通信。在客户端进程中,Binder 对象的表现形式是一个 Binder Proxy,它是 Binder 对象在客户端的代理。客户端通过 Binder Proxy 发送请求,而这些请求最终由服务端的 Binder 对象处理。

创建:Binder 对象通常是在服务端进程中创建的。服务端在启动时,会实例化其实现的 AIDL 接口(即 Binder 对象),并将其注册到系统服务管理器(ServiceManager)中。客户端进程通过 ServiceManager 获取到对应的 Binder Proxy,从而能够与服务端通信。

生命周期:Binder 对象的生命周期与服务端进程的生命周期密切相关。当服务端进程被销毁时,其 Binder 对象也会被销毁。相应地,客户端对该 Binder 对象的引用将失效。

工作机制:当客户端调用 Binder Proxy 上的方法时,这个调用会被封装成一个 IPC 请求,发送到内核的 Binder 驱动。Binder 驱动会将该请求转发给对应的服务端进程中的 Binder 对象。Binder 对象接收到请求后,会执行相应的处理逻辑,并将结果返回给客户端。这个过程对开发者是透明的,类似于本地方法调用。

3 Binder安全令牌

        3.1 UID、PID、GID的概念

        先介绍一下安卓UID、PID、GID的概念:

        UID (User ID)

        定义:UID 是 Android 系统中用来标识每个应用程序或用户的唯一标识符。

       UID 用于访问控制和权限管理。每个应用程序在安装时都会被分配一个唯一的 UID,不同的 UID 之间无法访问彼此的资源(如文件、数据等),除非通过特定的权限(如共享 UID 或使用 Content Provider)。

        系统用户的 UID 通常在 1000 到 1999 之间分配,例如 root 的 UID 是 0,system 的 UID 是 1000。应用程序的 UID 通常从 10000 开始,每个应用都有一个唯一的 UID。

        PID (Process ID)

        定义:PID 是 Android 中每个进程的唯一标识符。

        操作系统通过 PID 管理和调度进程。每个正在运行的进程都有一个唯一的 PID,PID 是动态分配的,在进程终止后,该 PID 可能会被重新分配给新的进程。

        PID 是由操作系统分配的整数,通常从 1 开始依次递增。

        GID (Group ID)

        定义:GID 是用于标识进程所属组的唯一标识符。

        GID 用于文件系统权限管理。同一个组中的进程可以共享特定的资源权限(如文件的读写权限)。

        和 UID 类似,系统组的 GID 通常在 1000 到 1999 之间分配,而应用程序组的 GID 通常从 10000 开始。

        3.2 Binder对象权限检查

        在 Android 中,Binder 对象可以表示权能,在这种使用方式下,它又被称作 Binder 令牌(Binder token)。一个 Binder 令牌可以是一种权能,也可以是一个目标资源。一个进程拥有一个 Binder 令牌,授权该进程对 Binder 对象的完全访问控制权限,从而使其能够在目标对象上执行 Binder 事务处理操作。如果 Binder 对象实现了多个动作(基于Binder整动态处理的code 参数,选择要执行的动作),只要调用者拥有一个 Binder 对象的引用,就可以执行任意操作,如果想实现更细粒度的访问控制,那么操作者需要实现必要的权限检查例程,这通常利用调用者进程的 PID 和EUID 来实现。
        在 Android 中,一个通常的模式是:对于在 system (UID 1000) 或 root (UID 0)权限下运行的调用进程允许执行所有操作,而对于其他进程来执行额外的权限检查例程。因此要想访问一个重要的 Binder 对象,例如能够控制系统服务的对象,拥有如下两种方式:

        一是通过限制谁可以获取 Binder 对象的引用;另一个是在执行该Binder 对象的动作之前,检测调用者的身份标识。(这个检查是可选的,在需要时由Binder 对象自己实现。)

        3.3 Binder作为安全令牌使用

        Binder 对象可以没有其他功能实现,只被作为权能使用。在这种使用模式下,多个协作的进程可同时拥有同一个 Binder 对象,而作为服务端(处理一些客户端的请求)的进程使用 Binder 令牌来验证它的客户端,就像 Web 服务器使用会话 Cookie一样。
        另一种使用方式是在 Android 框架内部使用,对于应用通常是不可见的。而在公开 API中可见的一种 Binder 令牌使用方式是窗口令牌(window token)。每个 Activity 的顶层窗口都与一个 Binder 令牌(窗口令牌)相关联,这些令牌均由Android 的窗口管理器(管理应用程序窗口的系统服务)记录保存。应用程序可以获得它们自己的窗口令牌,但不能访问其他应用的窗口令牌。每个窗口请求都必须提供一个与该应用相关联的窗口令牌,因此确保了窗口请求是由应用本身或系统发起的。

        3.4 利用服务发现机制访问Binder对象

        虽然基于安全考虑,Android 控制着对Binder 对象的访问,并且与 Binder 对象通信的唯一方法是使用该对象的引用,但是很多 Binder 对象(尤其是系统服务)需要全局可被访问。然而分发所有系统服务的引用到每个进程是不切实际的,所以需要一种机制,来允许进程按需发现和获取系统服务的引用。
        为了使用服务发现机制, Binder 框架中拥有一个专门的上下文管理器(context manager)用于维护到 Binder 对象的引用。Android上下文管理器的实现就是 servicemanager 原生守护进程。它在开机的早期阶段就已启动,因此系统服务启动时就可以进行注册。服务通常传递服务名和一个 Binder引用到服务管理器进行注册。一旦服务注册成功,任何客户端都可以通过服务名获得它的 Binder 引用。然而,大部分系统服务有额外的权限检查,所以获得引用不能自动确保对它所有功能的访问。因为在服务管理器上注册之后,任何人都可以访问该Binder 引用,所以为了安全性的考量,只有一小部分在白名单内的系统进程才可以注册系统服务。比如,只有以 UID 1002(AID_BLUETOOTH)执行的进程,才可以注册bluetooth系统服务。

三、应用程序沙箱

1 概述

        Android 应用程序沙箱是基于 Linux 用户和文件系统权限的安全模型。每个应用在安装时都会被分配一个唯一的用户 ID (UID),并在其自己的沙箱环境中运行。这意味着每个应用被隔离在自己的“盒子”中,无法访问其他应用的数据或代码,除非通过特定的方式授权。

2 沙箱的工作机制

        2.1 基于 Linux 的用户隔离

        每个应用程序在安装时会被分配一个唯一的用户 ID (UID)。在 Android 系统中,这个 UID 用来隔离应用程序的进程和数据。不同应用有不同的 UID,这就确保了一个应用无法直接访问另一个应用的文件。

        应用的数据文件存储在特定的目录中,该目录只有该应用的 UID 有权限访问。默认情况下,其他应用甚至系统用户都无法访问这些文件。

        2.2 进程隔离

        每个应用程序运行在其自己的进程中,由操作系统创建并分配给它。这个进程空间由操作系统的进程管理机制控制,确保进程之间的内存隔离。

        Android 支持多任务处理,但即使多个应用同时运行,它们的进程仍然被严格隔离,防止跨进程的数据泄露。

        2.3 安全的 IPC(进程间通信)

        Android 使用 Binder 机制来处理进程间的通信 (IPC)。尽管应用程序彼此隔离,但通过 Binder,可以实现受控的进程间通信。应用需要通过权限机制显式地暴露接口,其他应用才能通过 Binder 访问这些接口。

3 应用沙箱与权限管理

        3.1 权限请求

        某些操作(如访问网络、使用摄像头、读取联系人)需要特定的权限。应用程序必须在 AndroidManifest.xml 文件中声明所需的权限,并且用户在安装时或运行时(针对某些高敏感权限)需要授权这些权限。

        从 Android 6.0 (API Level 23) 开始,引入了动态权限机制,某些敏感权限需要用户在运行时进行授权,而不仅仅是在安装时。

        3.2 防止权限滥用

        最小权限原则: Android 强调应用只申请它们实际需要的最小权限。用户可以通过应用设置查看并调整权限,进一步加强了用户对应用权限的控制。

·        权限隔离: 即使应用申请了多个权限,这些权限的作用范围仍然受到沙箱机制的严格控制,避免应用对权限的滥用。

4 共享数据的机制

        尽管应用程序被沙箱隔离,但 Android 提供了几种安全的方式让应用程序共享数据或进行交互:

        4.1 内容提供者 (Content Providers)

        定义: 内容提供者是 Android 提供的一种组件,用于在不同应用之间共享数据。应用可以通过内容提供者暴露其数据,并通过权限机制控制其他应用的访问权限。

        URI 访问控制: 访问内容提供者的数据通常通过 URI 实现,开发者可以细粒度地控制哪些数据可以被共享。

        4.2 Intent

        定义:Intent 是 Android 用于应用间通信的机制。通过 Intent,一个应用可以请求另一个应用执行某项操作,前提是该应用暴露了相应的 Activity 或 Service。

        隐式 Intent 和显式 Intent: 显式 Intent 明确指定目标组件,而隐式 Intent 则依赖系统根据 Intent Filter 进行匹配,应用可以在 Intent 中包含权限信息来确保安全性。

        4.3 文件共享

        临时文件访问: Android 允许通过 FileProvider 机制来临时共享文件。应用可以生成一个包含文件路径的 URI,授权其他应用临时访问这个文件。

        外部存储: 对于需要共享的文件,应用可以使用外部存储,但此类文件的访问权限受到外部存储的管理策略影响。

四、安卓代码签名和平台密钥

1 安卓代码签名

        1.1 概念

        代码签名是指对应用程序的 APK 文件进行数字签名,以保证应用程序的完整性和来源可信。每个 Android 应用在安装时都需要经过签名,这确保了应用程序的代码在分发和安装过程中没有被篡改。

        1.2 签名方式

        Android 使用 Java 的 jarsigner 工具或 Android Studio 提供的内置工具对 APK 进行签名。每个开发者都有一个私有的签名证书,用于对其应用进行签名。这个证书包含了开发者的公钥和其他标识信息,生成证书时通常使用 Java 的 keytool 工具。在签名时,开发者使用其私钥对 APK 中的所有文件生成一个数字签名,这些签名会和 APK 一起打包。安装时,Android 系统使用与开发者私钥对应的公钥来验证签名,以确保文件没有被修改。

        1.3 签名的作用

        Android 要求应用的更新版本必须使用与原版本相同的签名证书进行签名,以确保只有原开发者才能发布更新;通过签名,Android 系统可以确保只有特定开发者签名的应用才能访问特定权限或资源;只有使用相同签名证书的应用才能共享 UID,从而实现数据和资源共享。

2.安卓平台密钥

        2.1 概念

        平台密钥是 Android 系统的一个重要安全机制,用于签名 Android 系统组件(如系统应用和服务)。它是一个高权限的密钥,通常由设备制造商持有,用来签名系统的核心组件。

        2.2 作用

        平台密钥确保了系统应用和服务的完整性和来源的可信度。系统组件(如 SettingsSystemUI)使用平台密钥签名,以便在系统中享有较高的权限。

        Android 系统中有些特定的权限(如 signature 权限)仅授予使用平台密钥签名的应用。这些权限允许应用执行一些普通应用无法执行的操作,比如访问系统 API、修改系统设置等。

        2.3 使用场景

        系统应用签名:系统应用使用平台密钥进行签名,确保它们可以访问一些普通应用无法访问的资源和服务。

        设备制造商控制:平台密钥通常由设备制造商管理,并用于签名设备出厂时的系统映像和系统应用。

五、SELinux简介

1.SELinux基本概念

        SELinux(Security-Enhanced Linux)是 Android 系统中用于强制访问控制(MAC)的一种安全机制。它最初由美国国家安全局(NSA)开发,并被引入 Android 以增强系统的安全性。        

        SELinux 是一种内核级的安全模块,通过定义并执行安全策略来控制进程和资源之间的交互。它可以防止进程以不受信任的方式访问系统资源,从而减少潜在的安全漏洞。

2.强制访问控制(MAC)

        SELinux 通过强制访问控制(MAC)实现其安全目标。不同于传统的自主访问控制(DAC),MAC 不允许用户或进程随意更改访问权限。系统管理员通过策略文件明确规定了哪些进程可以访问哪些资源,这些策略是强制执行的,无法被用户或进程随意修改。

3.SELinux在安卓中的引入

        3.1 引入背景

        Android 从 4.3(Jelly Bean)版本开始引入 SELinux,最初以“宽容模式”(permissive mode)运行,即仅记录违反 SELinux 策略的行为,不实际阻止。自 Android 5.0(Lollipop)起,SELinux 进入“强制模式”(enforcing mode),开始实际执行策略并阻止不符合策略的操作。

        3.2 SELinux 的目标

        减少攻击面:通过限制进程可以执行的操作,SELinux 可以有效减少潜在的攻击面。例如,限制应用访问系统资源的能力。

        增强系统稳定性:SELinux 防止恶意软件或受感染的应用程序破坏系统的关键组件,从而增强系统的整体稳定性和安全性。

4. SELinux策略

        4.1 策略类型

        SELinux 策略由一系列规则组成,定义了系统中不同类型的对象(如文件、进程、设备)的安全上下文(Security Context),并指定了哪些操作是允许的。主要包括:

        1.类型策略(Type Enforcement, TE):这是 SELinux 中最常用的策略,定义了进程可以访问哪些类型的对象以及可以执行哪些操作。

        2.角色策略(Role-Based Access Control, RBAC):基于角色的访问控制策略,限制不同角色可以执行的操作。

        3.多级安全策略(Multi-Level Security, MLS):用于更高级别的安全控制,适用于需要复杂安全分级的环境。

        4.2 安全上下文

        SELinux 为每个进程、文件、网络端口等分配一个安全上下文(Security Context),它通常由用户、角色、类型和敏感度级别组成。一个典型的安全上下文可能是这样的:

u:r:init:s0
  • u 代表用户(user)
  • r 代表角色(role)
  • init 代表类型(type)
  • s0 代表敏感度级别(sensitivity level)

        4.3 策略编译与加载

        SELinux 策略通常以源代码形式编写,定义了系统中所有允许的行为。然后,这些策略被编译成二进制格式并加载到内核中,内核根据这些策略对进程的操作进行实时检查和控制。

        4.4 SELinux 在 Android 中的实现

        初始化与加载

        Android 系统启动时,会加载预先定义好的 SELinux 策略,并根据这些策略初始化系统的安全上下文。这些策略文件通常由设备制造商定义,并包含了对系统关键进程、服务和资源的严格控制。

        日志与调试

        在 SELinux 强制模式下,所有违反 SELinux 策略的行为都会被阻止,并记录在系统日志中。开发者可以使用 dmesglogcat 查看这些日志,从而调试和修复策略相关的问题。

        宽容模式和强制模式

        宽容模式(Permissive Mode):系统记录策略违规行为但不实际阻止操作。这种模式主要用于调试和测试。

        强制模式(Enforcing Mode):系统不仅记录违规行为,还实际阻止这些行为,强制执行 SELinux 策略。

5. SELinux 的实际应用

        5.1 应用隔离

        通过 SELinux,Android 能够严格隔离应用程序,防止恶意应用获取系统级权限或访问其他应用的敏感数据。

        5.2 防止权限升级攻击

        SELinux 限制了进程能够执行的操作,从而减少权限升级攻击的可能性,即使某个应用获得了不应有的权限,SELinux 也会限制其危害。

        5.3 保护系统进程

        关键的系统进程如 initzygote(Zygote 是 Android 系统的核心进程之一,通过预加载类和资源,以及高效的 fork 机制,它显著提高了应用程序的启动速度和系统性能)等受到 SELinux 的严格保护,只有被明确允许的进程才能与之交互。

  • 29
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值