☰
Android AVB 2.0 深度解析:vbmeta.img 生成、avbtool 参数与启动验证链路
2026/9/28 1:11:48 网站建设 项目流程

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 为 1
8required_libavb_version_minor4 字节次版本号
12authentication_data_block_size8 字节认证数据块大小,含签名和公钥
20auxiliary_data_block_size8 字节辅助数据块大小,含描述符
28algorithm_type4 字节签名算法类型,如 SHA256_RSA2048
32hash_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.0
176reserved80 字节保留字段

这个表你不需要背,但要知道两件事:第一,magic必须是AVB0,如果avbtool报 "Invalid magic" 说明文件根本不是 vbmeta 格式;第二,flags和rollback_index是控制启动行为的关键,后面会专门讲。

2.2 描述符:vbmeta 真正干活的单元

头部只是外壳,真正描述"哪个分区用什么方式校验"的是描述符(descriptor)。每个描述符有一个统一的头部,包含 tag、长度和跟随的 tag 特定数据。AVB 2.0 里常见的描述符类型有四种:

  • Hash Descriptor(tag=1):对某个分区做整体哈希校验,适合boot、dtbo这类小分区。它记录分区名、哈希算法、盐值和 digest。
  • Hashtree Descriptor(tag=2):对某个分区建立哈希树,配合dm-verity做块级校验,适合system、vendor这类大分区。它记录分区名、哈希算法、块大小、树高度、盐值、root digest 等。
  • Kernel Cmdline Descriptor(tag=3):向内核命令行追加参数,比如androidboot.vbmeta.device_state。
  • Chain Partition Descriptor(tag=4):把另一个分区的 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,要给它生成一个哈希描述符并打包进 vbmeta:

python3 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.img

3.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.img,vendor、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.device=PARTUUID=xxx androidboot.vbmeta.avb_version=1.2 androidboot.vbmeta.hash_alg=sha256 androidboot.vbmeta.size=6144 androidboot.vbmeta.digest=xxxxx

内核启动时,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,barrier=1 wait,avb=vbmeta,avb_keys=/avb/q-gsi.avbpubkey

avb=vbmeta表示这个分区的验证信息来自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。排查链路是这样的:

  1. 先确认镜像有没有被二次修改。任何对boot.img的改动(哪怕改一个字节)都会导致哈希变化。
  2. 检查--salt是否一致。生成描述符和验证时用的盐必须相同,盐不同哈希必然不同。
  3. 检查--partition_size。如果实际分区大小和描述符里记录的不一致,哈希树计算会错位。
  4. 用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.pem

info_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 的签名密钥和设备厂商的密钥不同。为了让设备能启动 GSI,fstab里要加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标志没漏。这一步偷懒,后面就是批量返修。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询