Menci's Blog
念念不忘,必有回响
Android 与 iOS 安全机制对比
  1. 1. 应用程序安装
  2. 2. 权限与隐私
  3. 3. 沙盒
  4. 4. 引导与系统完整性
  5. 5. 加密
  6. 6. 总结

本文曾为本人一门课程的期末报告,其中有些内容可能不够准确与全面,欢迎大家指正。

作为现代移动操作系统,Android 和 iOS 都有较为完善的安全机制。本文将从若干个方面分析对比其安全机制的异同。

应用程序安装

在 Android 设备上,应用程序往往以 apk 安装包(另有 obb 数据包与整体打包的 xapk 等格式,但核心仍是 apk)的形式发布。每个应用程序有其包名(通常是倒序分隔的域名,如 org.mozilla.firefox)和版本号,应用程序在发布前需要由发布者使用其 RSA 私钥进行数字签名。当安装与已存在的包名相同的包时,Android 系统会使用拒绝签名公钥不一致版本号更低的应用程序覆盖原有的应用程序。

基于 Android 系统的软件包安装机制,一个常见的攻击手段是重打包攻击:对软件包进行解包,加入恶意代码(比如窃取用户在该软件中登录所用的凭据)后重新打包并签名,并诱使用户安装。如果包名没有修改,且原软件包已安装,则 Android 系统会拒绝安装签名公钥不一致的新软件包,但攻击者可以修改包名并诱导用户安装看似一致的新安装包。开发者常使用签名验证的方式来防止重打包攻击,但攻击者也可通过逆向分析与 Hook 等手段绕过验证。Google 官方的 Play Store 也提供软件包分发服务,其能避免一部分威胁,但其对软件包内容审核较为宽松,导致一些名称相似的恶意软件能够在 Play Store 上发布。另一方面,Android 允许用户自行安装 apk 包,所以很多用户常常通过其他途径获得软件包并安装。所以,重打包攻击仍然是 Android 生态中的一大威胁。

在 iOS 设备上,软件包的格式为 ipa。但 ipa 的安装需要经过 Apple 的验证,iOS 仅接受被 Apple 允许在当前设备上安装的 ipa 软件包。且 iOS 有强制的代码签名检查机制,所有的可执行文件都必须具有可被 Apple 接受的签名,这在一定程度可以缓解了一些应用程序漏洞产生的影响。iOS 上软件分发的方式分为两大类:

一是通过 Apple 官方的渠道进行发布(App Store 和 TestFlight,后者一般仅用于发布测试版软件,且安装受到限制),这种情况下,设备需要登录 Apple ID,在软件包时会向 Apple 请求该软件包,并由 Apple 认证后发回带有针对该 Apple ID 的签名的软件包,iOS 会进行检验并安装。由于必须经过 Apple 的认证,通过该途径安装应用一般不会遭到重打包攻击(前提是 Apple 的审核不出漏洞,不会为带有恶意代码的重打包软件过审)。从一些第三方「应用商店」(如爱思助手)安装也是这种途径,第三方工具会重放它们提前获得的带有 Apple 签名的 ipa 进行安装,这样安装软件也不会带来安全隐患。

二是通过 Apple 签发的个人证书、开发者证书或企业证书进行签名。每个个人 Apple ID 都可以向 Apple 申请一个证书,单次为应用程序签名的有效期为 7 天(可续签),但 Apple 限定每台设备只能同时安装三个通过个人证书签名的使用。开发者证书可以付费购买,并可用于在已在该开发者账户上注册的设备上长期安装多个应用。企业证书的有效期和允许的软件包数量更大,并且可以向任意 iOS 设备分发。这三种方式均可在不经过 Apple 审核的情况下安装应用,但 iOS 在第一次运行前会警告用户其开发者未经验证,并引导用户在设置中选择信任。

iOS 信任开发者

使用个人证书签名安装软件包,并在设置中选择信任

这种私人发布的软件包,虽然签名过程仍然需要请求 Apple,但其安全性未经过 Apple 的审核,可能面临重打包攻击的威胁,然而由于其实施成本过高(个人证书有效期短且需要连接 PC 安装,开发者证书需要注册,企业证书价格贵且难以申请),所以实际应用较少。但不可否认的是,重打包攻击在 iOS 上仍然有一定可行性,在访问网页等操作时遇到不明的安装应用请求时也需要小心。

权限与隐私

在 Android 6.0 之前,Android 应用程序在安装时请求所有权限。从 Android 6.0 开始,应用程序需要在运行时获取权限。大部分商业应用程序都会请求 READ_PHONE_STATE 权限,用于读取设备的 IMEI,作为用户的标识符,用于广告推送等用途。这使得用户极容易被跟踪。

值得一提的是,Android 提供了一个特权接口 AppOpsManager,可以用来对应用程序的权限进行高级管理。例如,在某个拥有 READ_PHONE_STATE 权限的应用程序读取 IMEI 时,为其返回无效数据(而不是产生异常使程序无法运行),以在应用程序正常工作的前提下保护隐私。访问该接口需要 appop 权限,该权限无法通过一般方式获取,只能通过 pm grant 授予(而调用 pm grant 需要 ADB Shell 或 root 的权限)。可能是出于广告相关的商业利益考量,Google 没有提供对应的用户界面,市面上有一些调用该接口管理其他应用程序权限的应用程序,但由于操作较为复杂,且大部分用户并不关心隐私,所以使用者非常少。

AppOps 权限管理

通过 AppOpsManager 接口,设置某第三方应用读取 IMEI 时返回空数据

一些第三方 Android 发行版拥有更强的权限管理,如 LineageOS 可在应用程序每次尝试调用需要敏感权限(如定位)的接口时询问用户,或进行记录。使得用户可以察觉应用程序未经预料的访问敏感数据,或过于频繁地访问隐私数据的行为。

Android 可向应用程序提供较高的权限,如修改系统设置、连接 Wi-Fi 网络等。另外,Android 中存在一个特殊权限 —— Accessibility,该权限需要应用程序进行申请,并由用户在系统设置中手动选择启用,启用时系统还会进行警告。拥有该权限的应用程序可拦截/模拟用户的输入操作,以实现无障碍访问等辅助功能。但若恶意应用程序诱使用户授予该权限,则可以在一定程度上控制整个设备的行为,对设备安全产生极大的威胁。

iOS 的权限管理模型与 6.0 之后的 Android 类似 —— 应用程序在运行时向用户申请权限。在 iOS 下,应用程序可以申请的权限较少,一方面这提升了系统的安全性,但另一方面也使得某些功能无法实现。严格的权限限制是一把双刃剑,Apple 选择了收紧权限,并由系统软件提供这部分功能。

与 Android 下可直接获取不同,较新版本的 iOS 不允许应用程序获取几乎所有的设备标识符(包括但不限于 IMEI、Wi-Fi MAC 地址,蓝牙 MAC 地址等),并要求应用程序使用 Apple 专门提供的广告 ID 进行追踪。且该 ID 可被用户禁用或重置。这在一定程度上保护了用户隐私。

iOS 广告 ID

在 iOS 系统设置中配置广告 ID

相对于获取设备标识符,定位权限往往是用户更为担忧的。iOS 14 提供了限制应用程序获得的位置信息精度的功能,应用程序在请求定位权限时,用户可将「精确」设为关闭,以使得应用程序仅获得大致的位置信息。该位置信息能够帮助应用程序定位用户所在的城市,但无法进行精确地追踪。

iOS 关闭精确定位

在某第三方应用请求权限时,选择为其提供不精确的位置信息

沙盒

在 Android 和 iOS 下,一般的应用软件都运行在沙盒中。

Android 的沙盒机制基于 Linux 内核提供的多用户功能。每个应用程序在安装时被分配一个用户 ID(UID),其数据目录被标记为仅该 UID 可访问。并使用用户组(groups)机制来为应用程序授予访问系统资源的权限(如,对应用户拥有 sdcard_rw 组的应用程序,可以访问 /sdcard 路径)。该机制较为简单,并且受益于 Linux 内核的可靠性,其对于基本的安全性有很大的保障。

在很多时候,Android 的沙盒机制并没有将不同的应用程序进行隔离,应用程序仍然可以通过系统提供的途径相互通信。例如,应用程序之间可以通过 Intent 来相互传递消息,可以以此唤起另一个应用程序的 Activity 等。其中较为不安全的一点是,应用程序可以将数据写入到 /sdcard 路径中,以便其他任何应用程序访问。很多应用程序将敏感数据放置在该路径下,这可能会导致隐私追踪数据泄露的问题。自 Android 10 起,Google 已经不鼓励,甚至将要禁止应用程序使用 /sdcard 共享数据。

较新版本的 Android 还通过 SELinux 实现了系统组件的 DAC(强制访问控制),对系统特权进程进行更严格的权限控制,尽量仅授予必需系统资源的访问权限,以进一步更加系统的安全性,防止一些高权限系统组件在受到漏洞攻击时泄露过大的权限。

相比之下,iOS 的沙盒机制更为复杂,也更为严格。与 Android 相同,iOS 的应用程序的文件系统访问也被限制在其私有的数据目录下。但与 Android 基于 UNIX 用户的隔离不同,iOS 下所有的第三方应用程序均运行在 mobile 用户下,该用户本身拥有访问绝大多数系统资源的权限,但 iOS 对系统调用进行了限制:iOS 根据应用程序所拥有的权限,为其维护一个沙盒配置文件(Sandbox Profile),并在内核中系统调用的关键路径上增加了一个 MAC 层,以检查应用程序是否有权限进行当前的系统调用,仅当有权限才会继续执行。这种限制方式与 Linux 下的 ptrace / seccamp 类似。

iOS 应用之间的隔离更为严格 —— 首先,由于后台机制的不同,iOS 的应用程序没有期望常驻后台的 Service 组件,难以在后台保持运行,所以,自然地,一个应用程序也不被期望直接向另一个运行中的应用程序发送消息(使用共享目录中的 UNIX Socket 可能是一种可行的方案,但意义不大)。iOS 系统提供以下几种应用间通信的方式:

  • 当需要调用另一个应用程序提供的功能供用户临时进行操作(如调用移动支付软件进行付款)时,往往使用系统提供的 URL Scheme 功能 —— 应用程序可以定义其接受的 URL 前缀(如 Alipay 定义了 alipay:// 前缀),并由欲调用的应用程序通知系统进行调用,系统会启动目标应用程序,将 URL 传递过去,由目标应用程序进行解析并处理。
  • 当需要调用另一个应用程序来发送/打开特定的文件时,可以使用 Share Sheet 功能。该功能被调用后,系统会弹出一个菜单,要求用户选择要调用的应用程序,用户选择后,iOS 系统会将文件复制到目标应用的沙盒中,并启动目标应用,将调用信息告知。
  • 当需要与另一个(或一些)特定的应用程序共享凭据(如 Gmail 与 Chrome 可共享 Google 账号的登录信息),可以使用 Keychain 接口,该接口仅提供跨应用程序的数据存储,没有传递消息的功能。

可见,iOS 的应用间通信接口被高度特化,且难以在用户无感知的情况下调用另一个应用程序。这在一定程度上避免了串谋权限攻击和一些隐私泄露问题。

引导与系统完整性

现代 Android 设备采用了 Verified Boot 技术来保证系统引导的安全性。设备在出厂时附带厂商的公钥,作为信任根(Root of Trust),在设备启动时,引导加载程序(固化在 ROM 中)会对操作系统的完整性进行检验。被检验的数据包括 boot 分区(Linux 内核和引导所需文件)、system 分区(Android 系统)和 vendor 分区(设备厂商提供的系统文件)等。对于 bootvendor 等较小的分区,引导加载程序会在引导时进行验证,对于较大的 system 分区,系统会保存其默克尔树(Merkle Tree),在使用时动态的验证被访问到的部分,如果验证不通过则产生 I/O 错误。

Android 引导加载程序往往是被锁定(LOCKED)的。位于锁定状态下的引导加载程序仅接受厂商在 ROM 中内置的信任根,而拒绝加载未经厂商验证的操作系统,这使得攻击者即使获得物理访问或者 root 权限,也无法对系统进行持久化的修改,因为任何修改都会使得自底向上信任链失效,进而使设备拒绝引导/工作。

另一方面,Android 引导加载程序往往允许用户进行解锁。位于解锁(UNLOCKED)状态下的引导加载程序允许用户自定义被加载的操作系统。一些实现允许用户将自己的公钥作为信任根,并安装自行签名的操作系统;用户也可完全关闭引导验证功能,让引导加载程序加载任何操作系统。攻击者在拥有设备的物理访问后,可通过解锁引导加载程序来修改操作系统,进而完全控制设备并窃取用户数据,考虑到这一安全隐患,引导加载程序在解锁时会清空所有用户数据。同时,为了避免用户的设备在不知情的情况下被解锁并植入恶意软件,已解锁的引导加载程序会在设备启动时显示警告信息,告知用户其设备正在加载自定义操作系统。

Android 设备往往使用 Recovery 环境更进行系统更新与还原,该模式位于 recovery 分区中,包含一个由厂商预先写入的小型操作系统,同时,该分区同样受 Verified Boot 的保护。Recovery 模式往往仅接受由厂商验证过的操作系统。为了防止降级以利用旧版本的安全漏洞,有些厂商也会拒绝安装比现有版本更低的操作系统。在解锁引导加载程序后,用户也可替换原有的 Recovery 环境,方便进行自定义操作系统的安装与维护。

Android 引导加载程序解锁后的警告

Android 引导加载程序解锁后的警告

Android 自定义 Recovery

Android 自定义 Recovery

iOS 也采用了信任链的方式来保证系统引导的安全性。iOS 设备启动时首先加载被固化到硬件中的安全引导程序 SecureROM,该程序正常情况下会验证并加载闪存中的引导加载程序 iBoot,并由 iBoot 对 Darwin 内核进行验证并加载。Darwin 内核在加载用户态的可执行程序时,会验证其数字签名,如果系统的可执行文件没有 Apple 的签名,则拒绝执行。同样,即使攻击者在 iOS 系统中获得了 root 权限,也无法通过对系统产生持久化的影响。

SecureROM 的另一个功能是 DFU(Device Firmware Upgrade) 模式。在 iOS 系统损坏时,可进入 DFU 模式以通过 USB 连接到计算机,并重新安装操作系统。在 iOS 设备上,操作系统的安装需要 Apple 的签名,且该签名与每次安装有关(被签名随机数由 SecureROM 在安装操作系统时生成,并被持久存储以在每次引导时验证),以避免设备安装被修改过的操作系统,或已被 Apple 禁止安装的 iOS 版本。在已取得 root 权限的情况下,可通过重放曾被成功签名的随机数的方式安装旧版本系统,但在(下文中提到的)SEP 未被攻破的前提下,SEP OS 无法降级,将可能导致兼容性问题。

值得一提的是,SecureROM 在加载后,会禁用 SecureROM 所在存储区域的读权限,使得在 iOS 中,即使获得了高权限,也无法读取 SecureROM 的代码,给安全人员的分析带来了极大的困难。但安全人员仍然发现了其中 DFU 模式下的一个 use-after-free 漏洞(checkm8,意为「checkmate」,即象棋中的「绝杀无解」,寓意漏洞在 ROM 中不可修复),并通过该漏洞实现了任意代码执行。checkra1n 是一个利用该漏洞的工具,其利用该漏洞来控制 DFU 模式下设备的引导,并在进入 iOS 后安装一个向用户提供 root 权限的系统应用程序。

checkra1n 工具运行

使用 checkra1n 工具攻击处于 DFU 模式的 iOS 设备

checkra1n 攻击成功

被 checkra1n 工具所控制的 iOS 设备引导(verbose 模式)

在设备丢失或被盗时,非法持有者无法解开设备的密码锁,可能会通过重新安装操作系统的方式使用设备。iOS 设备在用户登录 Apple ID 后,会将设备记录到 Apple 的服务器上(即「Find My iPhone」功能,也就是通常所说的「ID 锁」),当该设备重新安装操作系统后,Apple 会要求用户再次登录该 Apple ID 以激活设备。这使得非法持有者无法使用设备。Android 也有类似的机制,但在引导加载程序解锁后不再有效。所以,为了保证安全,Android 设备对引导加载程序的解锁,往往需要用户在系统内和引导加载程序中进行两次确认。

加密

Android 和 iOS 都具有加密文件系统以保护用户数据的功能。

Android 的加密分为两种:全盘加密(Full Disk Encryption)文件级加密(File-Based Encryption)

全盘加密引入于 Android 4.4,在 Android 10 中去除。其原理是,系统在初始化时随机生成一个 128 位的主密钥,并使用此密钥与 AES 算法将数据分区(data)加密。当用户设置密码时,主密钥被使用用户设置的密码加密后储存起来(根据设备支持情况,储存在闪存或硬件安全存储中)。当用户修改密码时,主密钥不会改变,所以数据不需要被重新加密。全盘加密有一个缺点 —— 由于应用程序依赖于数据分区,而在设备启动后,用户第一次输入密码前,设备的数据分区是未被解密的,所以用户无法使用包括闹钟、电话在内的任何功能

为了解决解锁前无法使用任何设备功能的问题,Android 7.0 引入了文件级加密。文件级加密可对每个文件单独进行加密,并且支持对不同文件使用不同的密钥加密。一般来说,文件级加密体系中有 CE(Credential Encrypted,凭据加密)和 DE(Device Encrypted,设备加密)两个密钥,前者用于加密用户的大部分数据,后者用于加密需要在解锁设备前访问的数据。CE 所用的密钥被以类似于全盘加密主密钥的形式加密存储,DE 所用的密钥被直接存储。应用程序可以选择其存储的特定数据使用 CE 还是 DE 进行加密 —— 如闹钟应用程序可将设定的闹钟列表存储在 DE 中,在设备启动后解锁前即可工作。而安全敏感的数据,如 Google 账户凭据则存放在 CE 中,仅在用户解锁设备后可访问。

iOS 的加密与 Android 的文件级加密类似,同样是对每个文件分别加密。此外,为了防止物理攻击,每台 iOS 设备的 TPM 模块(被称为 Secure Enclave Processor,SEP)中会内置一个独一无二的主密钥,文件加密的密钥均由该密钥派生而来,且无法在可信的操作系统之外获得,这使得攻击者无法卸下闪存芯片并使用其他设备对用户密码进行暴力破解。搭载了类似的硬件安全密钥存储模块的 Android 设备也能够达到同样的安全性。

iOS 的密钥管理

iOS 使用 SEP 管理密钥

相对于 Android 的文件夹机密,iOS 增加了一个更强的文件加密级别:对于极度敏感的数据(如用户的电子邮件),系统会在用户解锁时(使用用户密码和 TPM 中的密钥)对其密钥进行解锁,并在设备锁定时将解锁后的文件密钥从内存中删除,使得一些情况下,即使攻击者获得了较高权限,也无法在未解锁时读取敏感数据。

Android 和 iOS 都具有密码尝试次数过多后清除用户数据的功能,其实现方式是将被加密的密钥从闪存 / TPM 中清除,使得现有的数据无法再被解密

总结

总的来说,Android 的安全体系较为开放,偏向于使用较为简单的安全策略来达到足够的安全性;而 iOS 的安全体系较为严格,偏向于使用多层次的安全策略,与硬件级的安全模块相结合,并强制加入 Apple 的控制,来将威胁降至最低。Android 的安全策略很大程度上受厂商定制的影响,一些厂商定制的 Android 设备可能具有更高或更低的安全性。

Android 的绝大多数安全功能依赖于 Linux 内核的安全性,而 Linux 内核少有可利用的漏洞。相比之下,iOS 的 Darwin 内核被爆出过更多的高危漏洞,安全性低于 Linux。较新版本的 iOS 加入了内核完整性保护(Kernel Integrity Protection)的功能,禁止一些敏感内存区域(如中断向量表)被修改,一定程度上降低了内核漏洞的危害。

在应用程序方面,早期的 Android 更加开放,表现的更加信任开发者。而 iOS 则尽可能少授予应用程序权限,以保证用户的隐私。较新版本的 Android 也有收紧权限控制的趋势。在系统底层,Android 与 iOS 都使用引导信任链来保证系统的完整性,并使用加密来保护用户数据。