ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Android AVB 2.0 深度解析:vbmeta.img 生成、avbtool 参数与启动验证链路

Android AVB 2.0 深度解析:vbmeta.img 生成、avbtool 参数与启动验证链路 1. 从一次刷机翻车说起为什么AVB 2.0值得每个Android底层从业者吃透去年帮一个做车机方案的朋友排查问题设备刷完自定义镜像后卡在开机第一屏串口日志里反复打印dm-verity校验失败最后定位到根因是vbmeta.img里的哈希描述符和实际boot分区内容对不上。这个坑让我意识到很多人对 Android Verified Boot也就是 AVB 2.0的理解停留在刷机时多刷一个 vbmeta 分区的层面真出问题时完全不知道从哪下手。AVB 2.0 是 Android 8.0 之后全面铺开的镜像校验与启动验证机制核心产物就是vbmeta.img这个看起来只有几 KB 的小文件。它做的事情说起来简单给boot、system、vendor、dtbo等关键分区建立一条从硬件信任根到系统分区的完整信任链任何一环被篡改启动就会中断或降级。但真正落地时涉及avbtool的参数怎么配、描述符怎么生成、fstab里的avb标志怎么加、dm-verity和dm-linear怎么协同、--flags里那些位到底控制什么行为每一步都有细节。这篇内容适合三类人一是做 Android 系统定制、ROM 移植的工程师二是做车机、IoT 设备、平板方案需要自己签名和打包镜像的开发者三是想搞懂为什么改了 system 分区设备就起不来这个经典问题的进阶玩家。我会从vbmeta.img的生成讲起一路拆到启动验证的完整链路把avbtool的实操参数、描述符结构、常见报错和排查思路都摊开讲。文中所有命令和参数都基于 AOSP 里自带的avbtool你可以直接对照自己的环境复现。需要先说明一点AVB 的完整信任链最终依赖硬件的信任根比如熔断的 eFuse 或 TEE 里的公钥这部分因芯片平台而异本文聚焦在通用工具链和镜像生成这一层硬件相关的部分只做原理性说明。2. vbmeta.img 到底是什么拆开这个几 KB 的小文件看结构2.1 vbmeta 分区的物理布局与头部字段vbmeta.img本质上是一个遵循 AVB 规范的元数据容器它的头部结构定义在 AOSP 的avbtool源码里字段是固定偏移的。理解这个布局你才能在avbtool报错时知道它在抱怨哪一段。偏移字段长度说明0magic4 字节固定为AVB0用于识别这是 AVB 元数据4required_libavb_version_major4 字节主版本号AVB 2.0 为 18required_libavb_version_minor4 字节次版本号12authentication_data_block_size8 字节认证数据块大小含签名和公钥20auxiliary_data_block_size8 字节辅助数据块大小含描述符28algorithm_type4 字节签名算法类型如 SHA256_RSA204832hash_offset8 字节哈希在认证块中的偏移40hash_size8 字节哈希长度48signature_offset8 字节签名偏移56signature_size8 字节签名长度64public_key_offset8 字节公钥偏移72public_key_size8 字节公钥长度80public_key_metadata_offset8 字节公钥元数据偏移88public_key_metadata_size8 字节公钥元数据长度96descriptors_offset8 字节描述符起始偏移104descriptors_size8 字节描述符总大小112rollback_index8 字节防回滚索引120flags4 字节全局标志位124rollback_index_location4 字节防回滚索引存储位置128release_string48 字节版本字符串如avbtool 1.2.0176reserved80 字节保留字段这个表你不需要背但要知道两件事第一magic必须是AVB0如果avbtool报 Invalid magic 说明文件根本不是 vbmeta 格式第二flags和rollback_index是控制启动行为的关键后面会专门讲。2.2 描述符vbmeta 真正干活的单元头部只是外壳真正描述哪个分区用什么方式校验的是描述符descriptor。每个描述符有一个统一的头部包含 tag、长度和跟随的 tag 特定数据。AVB 2.0 里常见的描述符类型有四种Hash Descriptortag1对某个分区做整体哈希校验适合boot、dtbo这类小分区。它记录分区名、哈希算法、盐值和 digest。Hashtree Descriptortag2对某个分区建立哈希树配合dm-verity做块级校验适合system、vendor这类大分区。它记录分区名、哈希算法、块大小、树高度、盐值、root digest 等。Kernel Cmdline Descriptortag3向内核命令行追加参数比如androidboot.vbmeta.device_state。Chain Partition Descriptortag4把另一个分区的 vbmeta 链接进来实现链式验证常见于vbmeta_system、vbmeta_vendor这种拆分场景。一个vbmeta.img里可以同时挂多个描述符avbtool在生成时会按你传入的参数依次追加。理解这一点很重要当你看到avbtool info_image输出里有一串 descriptor每一个都对应一个被校验的分区缺一个就可能导致那个分区不受保护。2.3 认证块与辅助块签名和描述符各占一块vbmeta.img在头部之后分成两个块认证数据块authentication data block和辅助数据块auxiliary data block。认证块里放的是公钥、公钥元数据、哈希和签名这部分内容会被签名保护辅助块里放的是描述符和可选的公钥元数据它本身不被签名直接覆盖但它的哈希会参与认证块的哈希计算。这个设计的意义在于描述符可以灵活增删但任何改动都会导致认证块里的哈希变化进而导致签名验证失败。所以攻击者没法偷偷往描述符里加东西而不被发现。你在调试时如果手动改过描述符签名必然失效必须重新用avbtool生成。3. 用 avbtool 生成 vbmeta.img参数逐个拆解3.1 环境准备与 avbtool 的获取avbtool是纯 Python 脚本不依赖编译直接从 AOSP 源码里拿就行。路径通常在external/avb/avbtool你也可以从已编译的out/host/linux-x86/bin/avbtool拿到。它依赖 Python 3 和pycryptodome用于 RSA 签名装依赖的命令pip3 install pycryptodome验证工具可用python3 avbtool version # 输出类似avbtool 1.2.0提示不同 Android 版本自带的 avbtool 版本不同生成的 vbmeta 头部required_libavb_version字段会有差异。用高版本工具生成的镜像刷到低版本 bootloader 上可能被拒绝建议用和目标平台 AOSP 版本一致的 avbtool。3.2 生成密钥RSA 还是 ECDSA位数怎么选签名密钥决定了信任链的根。avbtool支持 RSA 和 ECDSA 两类算法常见组合如下算法参数密钥长度适用场景签名速度SHA256_RSA20482048 位最通用兼容性最好中等SHA256_RSA40964096 位安全性要求高较慢SHA256_RSA81928192 位极少用部分 bootloader 不支持慢SHA512_RSA20482048 位需要 SHA512 摘要中等SHA256_ECDSA_NIST_P256256 位签名快体积小快生成 RSA2048 密钥对openssl genrsa -out vbmeta_key.pem 2048 openssl rsa -in vbmeta_key.pem -pubout -out vbmeta_key_pub.pem生成 ECDSA P256 密钥对openssl ecparam -name prime256v1 -genkey -noout -out vbmeta_ec_key.pem openssl ec -in vbmeta_ec_key.pem -pubout -out vbmeta_ec_key_pub.pem选哪个我的经验是如果平台 bootloader 没有特殊要求优先 RSA2048兼容性最稳。ECDSA 虽然快但部分老平台的 bootloader 实现不完整容易在验证阶段报算法不支持。密钥一定要保管好私钥泄露等于信任链彻底失效任何人都能签出合法镜像。3.3 生成带哈希描述符的 vbmeta以 boot 分区为例假设你已经有了boot.img要给它生成一个哈希描述符并打包进 vbmetapython3 avbtool make_vbmeta_image \ --output vbmeta.img \ --key vbmeta_key.pem \ --algorithm SHA256_RSA2048 \ --padding_size 4096 \ --hash_algorithm sha256 \ --salt $(xxd -p -l 32 /dev/urandom | tr -d \n) \ --include_descriptors_from_image boot.img \ --prop com.android.build.boot.fingerprint:my_device_boot \ --set_hashtree_disabled_flag这里几个参数值得展开--padding_size 4096把 vbmeta 填充到 4096 字节对齐方便分区刷写。不填的话文件可能只有几百字节某些 flash 工具会报错。--salt盐值防止相同内容产生相同哈希建议每次随机生成。注意--salt是全局盐如果要对多个分区分别设盐得用add_hash_descriptor子命令。--include_descriptors_from_image boot.img从已有的 boot.img 里提取描述符。前提是 boot.img 本身已经用avbtool add_hash_descriptor处理过否则这个参数会失败。--set_hashtree_disabled_flag设置全局标志禁用 hashtree 验证。这个标志要慎用后面讲 flags 时会细说。更常见的做法是分两步先给 boot.img 加描述符再生成 vbmeta。# 第一步给 boot.img 添加哈希描述符 python3 avbtool add_hash_descriptor \ --image boot.img \ --partition_name boot \ --partition_size $(stat -c%s boot.img) \ --hash_algorithm sha256 \ --salt $(xxd -p -l 32 /dev/urandom | tr -d \n) # 第二步从 boot.img 提取描述符生成 vbmeta python3 avbtool make_vbmeta_image \ --output vbmeta.img \ --key vbmeta_key.pem \ --algorithm SHA256_RSA2048 \ --padding_size 4096 \ --include_descriptors_from_image boot.img3.4 给 system 分区建哈希树dm-verity 的根基system、vendor这类大分区不能用整体哈希因为校验时要读整个分区太慢。AVB 用的是哈希树hashtree配合内核的dm-verity做块级按需校验。生成命令python3 avbtool add_hashtree_footer \ --image system.img \ --partition_name system \ --partition_size 2147483648 \ --hash_algorithm sha256 \ --salt $(xxd -p -l 32 /dev/urandom | tr -d \n) \ --block_size 4096 \ --do_not_generate_fec关键参数说明--partition_size分区实际大小必须和分区表里一致否则哈希树计算会错位。--block_size块大小通常 4096要和文件系统块大小匹配。--do_not_generate_fec不生成前向纠错FEC数据。FEC 能在数据损坏时尝试恢复但会额外占用空间。量产设备一般开启 FEC调试阶段可以关掉省空间。执行完后system.img末尾会追加哈希树和 AVB 尾部结构同时镜像里嵌入了描述符。然后把这个 system.img 通过--include_descriptors_from_image挂到 vbmeta 上。注意add_hashtree_footer会修改原镜像务必在副本上操作或者提前备份。我第一次操作时直接改了原始 system.img结果哈希树追加进去后镜像大小变了分区刷写直接失败。3.5 链式分区vbmeta_system 与 vbmeta_vendor 的拆分逻辑Android 10 之后Google 引入了动态分区和链式 vbmeta把system、product、system_ext的验证信息放到vbmeta_system.imgvendor、odm放到vbmeta_vendor.img主vbmeta.img通过链式描述符引用它们。生成链式描述符python3 avbtool make_vbmeta_image \ --output vbmeta_system.img \ --key vbmeta_key.pem \ --algorithm SHA256_RSA2048 \ --padding_size 4096 \ --include_descriptors_from_image system.img \ --include_descriptors_from_image system_ext.img \ --include_descriptors_from_image product.img python3 avbtool make_vbmeta_image \ --output vbmeta.img \ --key vbmeta_key.pem \ --algorithm SHA256_RSA2048 \ --padding_size 4096 \ --chain_partition vbmeta_system:1:vbmeta_system_pub.pem \ --chain_partition vbmeta_vendor:2:vbmeta_vendor_pub.pem--chain_partition的格式是分区名:rollback_index位置:公钥文件。这里的公钥是子 vbmeta 的签名公钥主 vbmeta 用它来验证子 vbmeta 的签名。链式结构的好处是各分区可以独立更新不用每次重签整个 vbmeta。4. 启动验证链路从 bootloader 到 dm-verity 的完整走查4.1 bootloader 阶段验证 vbmeta 的签名设备上电后bootloader或更底层的 BL1/BL2首先从vbmeta分区读取元数据用烧录在设备里的公钥通常在 eFuse 或 RPMB 里验证 vbmeta 的签名。这一步验证的是认证块包括公钥、哈希和签名。验证通过后bootloader 解析描述符对每个被描述的分区做校验对哈希描述符的分区如boot计算整个分区的哈希和描述符里的 digest 比对。对哈希树描述符的分区如system读取哈希树的 root digest和描述符里的比对具体的块级校验留给内核的dm-verity。如果任何一步失败根据flags的设置设备可能直接停止启动、进入 recovery或者显示警告后继续仅限解锁状态。4.2 flags 字段控制验证失败后的行为vbmeta头部的flags字段和描述符里的 flags 共同决定验证失败时的行为。常见的标志位标志名值含义HASHTREE_DISABLED1禁用 hashtree 验证仅对哈希描述符生效VERIFICATION_DISABLED2完全禁用验证设备视为已解锁VERIFICATION_DISABLED_UNTIL_REBOOT4本次启动禁用验证重启后恢复VERIFICATION_DISABLED_THIS_BOOT8仅本次启动禁用--set_hashtree_disabled_flag对应 HASHTREE_DISABLED--set_verification_disabled_flag对应 VERIFICATION_DISABLED。量产设备绝对不能设 VERIFICATION_DISABLED否则等于关掉了整个安全启动。调试阶段如果只是想临时跳过验证用fastboot --disable-verification flash vbmeta vbmeta.img更合适它不会改镜像本身。4.3 内核阶段dm-verity 如何按需校验 system 分区bootloader 验证完 vbmeta 后把哈希树的 root digest、盐值、块大小等信息通过内核命令行传给内核格式类似androidboot.vbmeta.devicePARTUUIDxxx androidboot.vbmeta.avb_version1.2 androidboot.vbmeta.hash_algsha256 androidboot.vbmeta.size6144 androidboot.vbmeta.digestxxxxx内核启动时dm-verity驱动根据这些参数建立虚拟块设备。当系统读取system分区的某个块时dm-verity会沿着哈希树逐层校验直到 root digest 匹配。任何一块数据被篡改读取就会返回 I/O 错误表现为应用崩溃或系统服务异常。这里有个容易混淆的点dm-verity是按需校验不是启动时全量校验。所以篡改 system 分区后设备可能能启动但访问到被篡改的块时才会报错。这也是为什么有些改机行为能骗过启动但一用就崩。4.4 fstab 里的 avb 标志别漏了这一行dm-verity要生效fstab里对应的分区条目必须带avb标志。以system分区为例/dev/block/bootdevice/by-name/system /system ext4 ro,barrier1 wait,avbvbmeta,avb_keys/avb/q-gsi.avbpubkeyavbvbmeta表示这个分区的验证信息来自vbmeta分区avb_keys指定额外的公钥路径用于 GSI 等场景。如果漏了avb标志即使 vbmeta 里有描述符dm-verity也不会挂载分区等于没保护。提示Android 10 之后 fstab 通常在vendor分区的etc/fstab或first_stage_ramdisk里动态分区场景下路径会变。排查时用adb shell cat /proc/mounts看实际挂载参数比翻源码快。5. 踩坑实录那些年我在 AVB 上翻过的车5.1 哈希不匹配从报错日志反推问题最常见的报错是avbtool verify_image输出Hash mismatch。排查链路是这样的先确认镜像有没有被二次修改。任何对boot.img的改动哪怕改一个字节都会导致哈希变化。检查--salt是否一致。生成描述符和验证时用的盐必须相同盐不同哈希必然不同。检查--partition_size。如果实际分区大小和描述符里记录的不一致哈希树计算会错位。用avbtool info_image --image vbmeta.img看描述符里的 digest和avbtool calculate_vbmeta_digest算出来的对比。我遇到过一次是因为构建脚本里boot.img被mkbootimg重新打包了一次时间戳变了哈希自然对不上。解决办法是确保签名和打包的顺序先打包再签名签完不要再动。5.2 分区大小对不上padding 和 partition_size 的坑--partition_size必须和分区表里的实际大小严格一致。如果分区表里system是 2GB你传了 2147483648正好 2GB但实际镜像加上哈希树后超过了这个值avbtool会报Image size exceeds partition size。解决办法有两个一是调大分区表里的分区大小二是用--do_not_generate_fec省掉 FEC 空间。FEC 通常占分区大小的 1% 到 2%大分区上这个空间不小。另一个坑是--padding_size。vbmeta 本身很小但某些平台的 bootloader 要求 vbmeta 分区至少 4096 字节不 padding 会报Invalid vbmeta size。这个参数加上就完事成本极低。5.3 链式验证失败公钥和 rollback_index 的对应关系链式 vbmeta 报错通常是Chain partition verification failed。排查要点--chain_partition里指定的公钥必须是子 vbmeta 的签名公钥不是私钥也不是主 vbmeta 的公钥。rollback_index 位置要唯一。主 vbmeta 里给vbmeta_system分配了位置 1给vbmeta_vendor分配了位置 2子 vbmeta 生成时要用--rollback_index_location指定对应的位置否则防回滚机制会错乱。子 vbmeta 的--algorithm要和主 vbmeta 验证时用的算法兼容。混用 RSA 和 ECDSA 在某些平台上会失败。5.4 解锁状态与验证降级userdebug 和 user 的差异userdebug版本默认允许adb root和dm-verity降级user版本则严格验证。如果你在userdebug上测试通过切到user后启动失败大概率是某个分区的描述符缺失或签名不对。判断当前设备验证状态adb shell getprop ro.boot.verifiedbootstate # green验证通过 # yellow验证通过但用了自定义密钥 # orange验证被禁用解锁状态 # red验证失败ro.boot.veritymode则反映dm-verity的状态enforcing表示严格校验disabled表示关闭。这两个属性是排查 AVB 问题的第一手信息。6. 几个容易被忽略的实操细节6.1 公钥的嵌入方式vbmeta 内嵌 vs 设备烧录avbtool生成的 vbmeta 里内嵌了公钥但 bootloader 验证时用的是设备里烧录的公钥两者必须匹配。设备公钥的烧录方式因平台而异有的通过fastboot flash写入特定分区有的在产线用专用工具熔断。调试阶段如果换了密钥记得同步更新设备里的公钥否则验证必然失败。6.2 动态分区与 AVB 的配合Android 10 引入动态分区后super分区里包含多个逻辑分区AVB 的描述符要针对逻辑分区而不是super整体。生成时用--partition_name指定逻辑分区名--partition_size用逻辑分区大小。super分区本身的元数据由liblp管理和 AVB 是两套机制别混在一起。6.3 验证工具链verify_image 和 info_image 的日常用法日常调试最常用的两个命令# 查看 vbmeta 的完整信息包括所有描述符 python3 avbtool info_image --image vbmeta.img # 验证镜像的哈希是否和描述符一致 python3 avbtool verify_image --image boot.img --key vbmeta_key_pub.peminfo_image的输出里重点看Descriptors段落每个描述符的Partition Name、Hash Algorithm、Salt、Digest都要和预期一致。verify_image则直接告诉你通过还是失败失败时会指出是哪一步不匹配。6.4 一个真实案例车机项目里的 vbmeta 拆分回到开头那个车机项目。设备用的是 Android 11动态分区system和vendor分别在vbmeta_system和vbmeta_vendor里。问题出在vbmeta_vendor的 rollback_index_location 和主 vbmeta 里分配的位置不一致导致链式验证时读到的防回滚索引是错的验证直接失败。修复方法是在生成vbmeta_vendor.img时显式指定python3 avbtool make_vbmeta_image \ --output vbmeta_vendor.img \ --key vbmeta_key.pem \ --algorithm SHA256_RSA2048 \ --rollback_index_location 2 \ --include_descriptors_from_image vendor.img同时在主 vbmeta 里用--chain_partition vbmeta_vendor:2:vbmeta_vendor_pub.pem对应上。改完后设备正常启动ro.boot.verifiedbootstate显示green。这个案例说明一个道理AVB 的每个参数都不是孤立的rollback_index、公钥、算法、分区名任何一处对不上都会导致验证失败。排查时不要只盯着报错的那一行要把整条链路的参数都过一遍。6.5 关于防回滚rollback_index 的实际作用rollback_index是防降级攻击的机制。设备里存储了一个当前的最小允许索引如果 vbmeta 里的索引低于这个值验证失败。这样攻击者就没法刷入旧版本有漏洞的镜像。实际使用中rollback_index的更新要谨慎。一旦设备里的索引被提升就再也降不回去除非解锁并清除。量产设备升级时新镜像的rollback_index要大于等于当前值否则升级会失败。调试阶段可以先用 0量产前再规划好版本策略。6.6 签名性能大分区哈希树生成的时间成本给 2GB 的system分区生成哈希树在普通开发机上大概要几十秒到一两分钟取决于磁盘 I/O 和 CPU。如果构建流程里每次都要重新生成会显著拖慢迭代速度。我的做法是把哈希树生成和签名拆成独立步骤只在镜像内容真正变化时才重新生成日常调试用--do_not_generate_fec进一步提速。另外avbtool是单线程的大分区上 CPU 利用率不高瓶颈通常在磁盘读写。用 SSD 能明显改善机械盘上生成 4GB 分区的哈希树可能要几分钟。6.7 与 GSI 的兼容avb_keys 的用途刷 GSI通用系统镜像时GSI 的签名密钥和设备厂商的密钥不同。为了让设备能启动 GSIfstab里要加avb_keys指向 GSI 的公钥同时 vbmeta 里要允许验证降级。这也是为什么刷 GSI 通常需要先解锁 bootloader。量产设备一般不支持 GSI因为解锁会破坏安全启动的信任链。7. 写在最后AVB 调试的几条个人经验搞 AVB 这几年最大的体会是报错信息往往只是表象真正的问题在参数配置的某个角落。Hash mismatch可能是盐值不对也可能是镜像被改过Chain partition verification failed可能是公钥不匹配也可能是 rollback_index 位置错了。排查时不要急着改代码先把avbtool info_image的输出完整看一遍把每个描述符的参数和预期对照问题通常就浮出来了。另一个经验是密钥和参数要版本化管理。签名密钥、盐值、rollback_index、分区大小这些都应该记录在构建配置里而不是散落在各个脚本中。我见过太多项目因为换了密钥没同步更新设备公钥导致整批设备启动失败。把 AVB 相关的配置集中管理能省掉大量返工。最后调试阶段多用userdebug版本它允许adb root和验证降级排查效率高很多。但切记量产前一定要在user版本上完整验证一遍确保所有分区的描述符齐全、签名正确、fstab里的avb标志没漏。这一步偷懒后面就是批量返修。
返回列表