Android 4.4引入了许多安全性增强功能,最值得注意的是SELinux处于强制模式。经过验证的启动是一项安全功能,该功能最初引起了媒体的关注,因为它的目的可能是“终止所有自定义固件”,但尚未对其进行详细描述。这篇文章将简要说明经过验证的启动的工作原理,然后说明如何在Nexus设备上进行配置和启用。
经验证的dm-verity引导
Android经过验证的启动实现基于dm-verity device-mapper块完整性检查目标。设备映射器是一个Linux内核框架,提供了一种实现虚拟块设备的通用方法。它用于实现卷管理(LVM),全盘加密(dm-crypt),RAID甚至分布式复制存储(DRBD)。)。设备映射器的工作原理是将虚拟块设备映射到一个或多个物理块设备,并可选地修改传输中的数据。例如,dm-crypt在将读取的物理块提交到磁盘之前对其进行解密,并对写入的块进行加密。因此,磁盘加密对于虚拟dm-crypt块设备的用户是透明的。设备映射器目标可以彼此堆叠,从而可以实现复杂的数据转换。
如前所述,dm-verity是块完整性检查的目标。这意味着当从磁盘读取每个设备块时,它将透明地验证每个设备块的完整性。如果该块签出,则读取成功,如果未成功,则读取会生成I / O错误,就好像该块已物理损坏一样。在后台,dm-verity是使用预先计算的哈希树实现的,该哈希树包括所有设备块的哈希。树的叶节点包括物理设备块的哈希,而中间节点是其子节点的哈希(哈希的哈希)。根节点称为根哈希并基于较低级别的所有哈希值(请参见下图)。因此,即使单个设备块中的更改也将导致根哈希的更改。因此,为了验证哈希树,我们只需要验证其根哈希即可。在运行时,dm-verity会在读取每个块时计算其哈希值,并使用预先计算的哈希树对其进行验证。由于从物理设备读取数据已经是一项耗时的操作,因此哈希和验证所增加的等待时间相对较低。

[图片来自Android dm-verity 文档,根据知识共享署名2.5许可。
由于dm-verity取决于设备所有块上预先计算的哈希树,因此需要以只读方式安装基础设备,以使验证成为可能。大多数文件系统在其超级块或类似的元数据中记录安装时间,因此,即使在运行时未更改任何文件,如果基础块设备以读写方式安装,块完整性检查也将失败。这可以看作是一种限制,但是它对于保存系统文件的设备或分区非常有效,这些文件或分区只能通过OS更新来更改。任何其他更改都表示操作系统或磁盘损坏,或试图将操作系统或伪装成系统文件的恶意程序。dm-verity的只读要求也非常适合Android的安全模型,该模型仅将应用程序数据托管在读写分区上,而将OS文件保留在只读状态系统分区。
Android实施
dm-verity最初是为了在Chrome OS中实现经过验证的引导而 开发的,并已在3.4版中集成到Linux内核中。通过CONFIG_DM_VERITY
内核配置项启用它。与Chrome OS一样,Android 4.4也使用内核的dm-verity目标,但是根哈希的加密验证和已验证分区的安装与Chrome OS不同。
用于验证的RSA公钥嵌入在启动分区的 verity_key下文件名,用于验证dm-verity映射表。该映射表保存目标设备的位置和哈希表的偏移量,以及根哈希和盐。映射表及其签名是verity元块的一部分,它直接在目标设备的最后一个文件系统块之后写入磁盘。通过将验证标志添加到设备的fstab文件的特定于Android的fs_mgr标志中,可以将分区标记为可验证分区。当Android的文件系统管理器在fstab中遇到verify标志时,它将从fstab中指定的块设备加载verity元数据,并使用verity_key验证其签名。如果签名检查成功,则文件系统管理器将解析dm-verity映射表并将其传递给Linux设备映射器,后者使用该映射表中包含的信息来创建虚拟dm-verity块设备。然后,将该虚拟块设备安装在fstab中指定的安装点处,以代替相应的物理设备。结果,针对预生成的哈希树透明地验证了来自底层物理设备的所有读取。修改或添加文件,甚至以读写模式重新安装分区,都会导致完整性验证失败和I / O错误。
我们必须注意,由于dm-verity是内核功能,为了使其提供的完整性保护有效,必须信任设备启动的内核。在Android上,这意味着需要验证引导分区,该引导分区还包括根文件系统RAM磁盘(initrd)和verity公共密钥。此过程是特定于设备的,通常在设备引导加载程序中实现,通常是通过使用存储在硬件中的不可修改的验证密钥来验证引导分区的签名。
启用验证启动
在官方文档描述了在Android上启用开机验证所需的步骤,但缺乏对需要实际工具和命令的具体信息。在本节中,我们显示创建和签名dm-verity哈希表所需的命令,并演示如何配置Android设备以使用它。以下是所需步骤的摘要:
- 为该系统分区生成哈希树。
- 为该哈希树构建一个dm-verity表。
- 在该dm-verity表上签名以生成表签名。
- 将表签名和dm-verity表捆绑到verity元数据中。
- 将真实性元数据和哈希树写入系统分区。
- 在设备的fstab文件中启用经过验证的引导。
如前所述,dm-verity只能与在运行时以只读方式安装的设备或分区(例如Android的系统分区)一起使用。尽管经过验证的引导可以应用于其他只读分区,例如托管专有固件blob的分区,但此示例使用系统分区,因为保护OS文件可带来可观的设备安全性收益。
dm-verity哈希树通过专用的veritysetup程序生成。veritysetup 可以直接在块设备上运行,也可以使用文件系统映像并将哈希表写入文件。它应该产生与平台无关的输出,但是在台式机Linux上生成的哈希表与Android不太一致,因此对于本示例,我们将直接在设备上生成哈希树。为此,我们首先需要编译Android的veritysetup。Github上提供了一个生成静态链接的veritysetup二进制文件的项目。它使用OpenSSL后端进行哈希计算,仅作了少许修改(以不太方便的方式…),以允许使用不同大小的off_t
数据类型,在当前版本的Android仿生库中为32位。
为了将哈希树直接添加到系统分区,我们首先需要确保在最后一个文件系统块之后有足够的空间来容纳哈希树和verity元数据块(32k)。由于大多数设备通常使用整个系统分区,因此您可能需要修改BOARD_SYSTEMIMAGE_PARTITION_SIZE
设备中的值BoardConfig.mk
以允许存储真实性数据。调整系统分区的大小后,将veritysetup二进制文件传输到设备的高速缓存或数据分区,然后启动允许根外壳程序通过ADB访问的恢复。为了生成哈希树并将其写入设备,我们使用veritysetup format命令,如下所示。
# veritysetup --debug --hash-offset 838893568 --data-blocks 204800 format \ /dev/block/mmcblk0p21 /dev/block/mmcblk0p21 ... # Updating VERITY header of size 512 on device /dev/block/mmcblk0p21, offset 838893568. VERITY header information for /dev/block/mmcblk0p21 UUID: 0dd970aa-3150-4c68-abcd-0b8286e6000 Hash type: 1 Data blocks: 204800 Data block size: 4096 Hash block size: 4096 Hash algorithm: sha256 Salt: 1f951588516c7e3eec3ba10796aa17935c0c917475f8992353ef2ba5c3f47bcb Root hash: 5f061f591b51bf541ab9d89652ec543ba253f2ed9c8521ac61f1208267c3bfb1
此示例是在Nexus 4上执行的,请确保您为手机使用了正确的block device,而不是/ dev / block / mmcblk0p21。该–hash偏移,因为我们写的散列树来保存文件系统数据在同一设备需要的参数。它以字节(不是块)指定,并且需要指向verity元数据块之后的位置。根据您的文件系统大小进行调整,以使hash_offset> filesystem_size + 32k。下一个参数–data-blocks指定文件系统使用的块数。默认块大小为4096,但您可以使用–data-block-size参数指定其他大小。该值需要与分配给文件系统的大小相匹配 BOARD_SYSTEMIMAGE_PARTITION_SIZE
。如果命令成功执行,则将输出计算出的根哈希值和所使用的盐值,如上所示。除根哈希外的所有内容都保存在哈希表的超级块(第一个块)中。确保保存根哈希,这是完成Verity设置所必需的。
一旦有了根哈希值和盐值,就可以生成并签名dm-verity表。该表是一行,其中包含块设备的名称,块大小,偏移量,salt和根哈希值。您可以使用gentable.py 脚本(首先编辑相应的常量值)来生成它或根据veritysetup的输出手动编写它。请参阅dm-verity的文档有关格式的详细信息。对于我们的示例,它看起来像这样(单行,为便于阅读而拆分):
1 / dev / block / mmcblk0p21 / dev / block / mmcblk0p21 4096 4096 204800 204809 sha256 \ 5f061f591b51bf541ab9d89652ec543ba253f2ed9c8521ac61f1208267c3bfb1 \ 1f951588516c7e3eec3ba10796aa17935c0c917475f8992353ef2ba5c3f47bcb
接下来,生成一个2048位RSA密钥,并使用OpenSSL对表进行签名。您可以在Github上使用波纹管命令或sign.sh脚本。
$ openssl dgst -sha1 -sign verity-key.pem -out table.sig table.bin
获得签名后,您可以生成验证元数据块,其中包括魔术数字(0xb001b001
)和元数据格式版本,后跟RSA PKCS#1.5签名blob和表字符串,并用零填充至32k。您可以通过传递签名和表文件,使用mkverity.py脚本生成元数据块,如下所示:
$ ./mkverity.py table.sig table.bin verity.bin
接下来,使用dd 或类似工具将生成的verity.bin文件写入系统分区,就在最后一个文件系统块之后,verity哈希表开始之前。使用传递给veritysetup的相同数量的数据块,所需的命令(在恢复中也需要执行)变为:
#dd if = verity.bin of = / dev / block / mmcblk0p21 bs = 4096 seek = 204800
最后,您可以使用veritysetup verify命令检查分区的格式是否正确,如下所示,其中最后一个参数是根哈希:
# veritysetup --debug --hash-offset 838893568 --data-blocks 204800 verify \ /dev/block/mmcblk0p21 /dev/block/mmcblk0p21 \ 5f061f591b51bf541ab9d89652ec543ba253f2ed9c8521ac61f1208267c3bfb1
如果验证成功,请重新启动设备并验证设备是否正常启动。如果是这样,则可以继续下一步:将验证密钥添加到启动映像并启用自动完整性验证。
用于验证的RSA公钥必须采用mincrypt格式(验证OTA文件签名时,库存恢复也应使用该格式),这是mincrypt RSAPublicKey
结构的序列化。这种结构的有趣之处在于,ts不仅包含模数和公共指数值,还包含mincrypt的RSA实现使用的预先计算的值(基于蒙哥马利简化)。因此,将OpenSSL RSA公钥转换为mincrypt格式需要一些模块化操作,而不仅仅是二进制格式转换。您可以使用pem2mincrypt工具(从安全adb的 实现中偷偷窃取的转换代码)转换PEM密钥。转换密钥后,将其包括在启动映像的根目录下的verity_key 文件名下。最后一步是修改设备的fstab文件,以便对系统分区启用块完整性验证。只需添加验证标志即可,如下所示:
/dev/block/platform/msm_sdcc.1/by-name/system /system ext4 ro, barrier=1 wait,verify
接下来,验证您的内核配置是否已启用CONFIG_DM_VERITY
,如果需要,请启用它并构建您的启动映像。一旦有了boot.img,就可以尝试使用fastboot boot boot.img来启动设备(不刷新)。如果哈希表和验证元数据blcok已正确生成并写入,则设备应启动,并且/ system应该是自动创建的设备映射器虚拟设备的安装,如下所示。如果启动成功,则可以将启动映像永久刷新到设备。
# mount|grep system /dev/block/dm-0 /system ext4 ro,seclabel,relatime,data=ordered 0 0
现在,在读取相应文件时,对系统分区的任何修改都将导致读取错误。不幸的是,通过基于文件的OTA更新进行的系统修改(在不更新真实性元数据的情况下修改文件块)也会使哈希树无效。如官方文档中所述,为了与经过dm-verity验证的引导兼容,OTA更新也应在块级别进行,以确保同时更新文件块,哈希树和元数据。这需要更改当前的OTA更新基础结构,这可能是尚未将经过验证的引导部署到生产设备的原因之一。
摘要
从4.4版开始,Android就包含了基于dm-verity device-mapper目标的经过验证的引导实现。通过将哈希表和签名的元数据块添加到系统分区并在设备的fstab文件中指定verify标志来启用dm-verity 。在启动时,Android会验证元数据签名,并使用随附的device-mapper表在/ system上创建和安装虚拟块设备。结果,所有对/ system的读取都针对dm-verity哈希树进行了验证,对系统分区的任何修改都会导致I / O错误。