Android Verified Boot 2.0 安全启动原理详解

AVB(Android Verified Boot)是 Android 系统的可信启动链,从 Bootloader 一路到用户空间的每个分区都要经过密码学验证,确保系统未被篡改。本文深入讲解 AVB 2.0 的设计思想、数据结构和验证流程。

一、为什么需要 Verified Boot?

想象一个攻击场景:

  1. 攻击者把你的手机拿到手
  2. 通过某种方式(可能是漏洞或物理刷写)替换了 /system/bin/su 或修改了 /system/etc/init.rc
  3. 注入恶意代码:开机自动上传用户数据

如果系统没有完整性校验,这种攻击毫无障碍。AVB 的核心目标:让从 Bootloader 开始的每一行代码都被密码学验证过,任何修改都能被发现。


二、信任链的核心思想

复制代码
[Hardware Root of Trust]   eFuse 烧死的公钥哈希
        ↓ 验证
   [Bootloader]            验证 vbmeta
        ↓ 验证
   [vbmeta.img]            存放各分区的 hash 描述符
        ↓ 验证
   [boot, system, vendor, dtbo, ...]
        ↓ dm-verity 运行时验证
   [文件系统中每个 4KB 块]

链条上任何一环被篡改,后续环节都会校验失败,导致设备无法启动(或者进入只读保护模式)。


三、AVB 2.0 的核心组件

1. vbmeta 分区

vbmeta 是信任的"根",存放着所有其它分区的元数据(hash、签名、属性)。

布局:

复制代码
+-----------------------------+
|  AvbVBMetaImageHeader       |  256 字节
+-----------------------------+
|  Authentication Data        |  公钥 + 签名
|   - hash (SHA-512)          |
|   - signature (RSA / ED25519)|
+-----------------------------+
|  Auxiliary Data             |
|   - Descriptors:            |
|     • Hash Descriptor       |  保护小分区 (boot, dtbo)
|     • Hashtree Descriptor   |  保护大分区 (system, vendor)
|     • Chain Partition Desc  |  指向另一个 vbmeta
|     • Property Descriptor   |  附加属性 (com.android.build.system.fingerprint)
|     • Kernel Cmdline Desc   |  传给 Kernel 的额外 cmdline
+-----------------------------+

2. AvbVBMetaImageHeader 定义

c 复制代码
// external/avb/libavb/avb_vbmeta_image.h
struct AvbVBMetaImageHeader {
    uint8_t  magic[4];                          // "AVB0"
    uint32_t required_libavb_version_major;
    uint32_t required_libavb_version_minor;
    uint64_t authentication_data_block_size;
    uint64_t auxiliary_data_block_size;

    uint32_t algorithm_type;                    // 见下表
    uint64_t hash_offset;
    uint64_t hash_size;
    uint64_t signature_offset;
    uint64_t signature_size;
    uint64_t public_key_offset;
    uint64_t public_key_size;
    uint64_t public_key_metadata_offset;
    uint64_t public_key_metadata_size;
    uint64_t descriptors_offset;
    uint64_t descriptors_size;

    uint64_t rollback_index;                    // 防回滚版本号
    uint32_t flags;
    uint32_t rollback_index_location;
    uint8_t  release_string[48];                // "avbtool 1.3.0"
    uint8_t  reserved[80];
} __attribute__((packed));

3. 算法类型

c 复制代码
typedef enum {
    AVB_ALGORITHM_TYPE_NONE         = 0,    // 不签名(开发用)
    AVB_ALGORITHM_TYPE_SHA256_RSA2048 = 1,
    AVB_ALGORITHM_TYPE_SHA256_RSA4096 = 2,
    AVB_ALGORITHM_TYPE_SHA256_RSA8192 = 3,
    AVB_ALGORITHM_TYPE_SHA512_RSA2048 = 4,
    AVB_ALGORITHM_TYPE_SHA512_RSA4096 = 5,
    AVB_ALGORITHM_TYPE_SHA512_RSA8192 = 6,
} AvbAlgorithmType;

现代设备多用 SHA256_RSA4096,平衡了安全性与性能。


四、Hash Descriptor:小分区保护

对于像 boot, dtbo, vendor_boot 这种几十 MB 的小分区,直接做整体 SHA-256 即可。

c 复制代码
struct AvbHashDescriptor {
    AvbDescriptor parent_descriptor;
    uint32_t image_size;            // 分区大小
    uint8_t  hash_algorithm[32];    // "sha256"
    uint32_t partition_name_len;
    uint32_t salt_len;
    uint32_t digest_len;            // hash 长度 (32 for SHA-256)
    uint32_t flags;
    uint8_t  reserved[60];
    // 后面紧跟:
    //   partition_name (UTF-8)
    //   salt
    //   digest (SHA-256 of "salt + image")
};

验证流程

Bootloader 加载 boot 分区时:

c 复制代码
bool verify_hash_partition(const char *name, void *data, size_t size,
                           const AvbHashDescriptor *desc) {
    // 1. 计算 SHA-256(salt || data)
    uint8_t computed[32];
    SHA256_CTX ctx;
    SHA256_Init(&ctx);
    SHA256_Update(&ctx, desc->salt, desc->salt_len);
    SHA256_Update(&ctx, data, size);
    SHA256_Final(computed, &ctx);

    // 2. 与 vbmeta 中记录的 digest 比较
    return memcmp(computed, desc->digest, desc->digest_len) == 0;
}

五、Hashtree Descriptor:大分区保护(dm-verity)

system / vendor 这种几个 GB 的分区,要做整体 hash 太慢。AVB 使用 Merkle Hash Tree:

复制代码
         Root Hash                  <- 存在 vbmeta 中
       /     |     \
      H0    H1     H2               <- Level 2
     /|\   /|\    /|\
    ... ... ... ... ... ...
   /                            \
  Leaf hash of 4KB block 0..N    <- Level 0,每个 4KB 数据块一个 hash

每个 4KB 数据块都有一个 SHA-256 hash,这些 hash 再聚合成上层 hash,层层向上直到 root hash。这个 hash tree 存在分区数据之后。

Hashtree Descriptor 结构

c 复制代码
struct AvbHashtreeDescriptor {
    AvbDescriptor parent_descriptor;
    uint32_t dm_verity_version;
    uint64_t image_size;             // 实际数据大小
    uint64_t tree_offset;            // hash tree 起始偏移
    uint64_t tree_size;              // hash tree 大小
    uint32_t data_block_size;        // 4096
    uint32_t hash_block_size;        // 4096
    uint32_t fec_num_roots;          // FEC 校验
    uint64_t fec_offset;
    uint64_t fec_size;
    uint8_t  hash_algorithm[32];     // "sha256"
    uint32_t partition_name_len;
    uint32_t salt_len;
    uint32_t root_digest_len;
    uint32_t flags;
    // ...
};

运行时验证:dm-verity

dm-verity 是 Linux 内核的设备映射模块,它的工作方式:

复制代码
应用程序读 system 上某个文件
        ↓
   VFS → ext4/erofs
        ↓
   dm-verity 拦截块设备读
        ↓
   1. 读出请求的数据块
   2. 计算它的 SHA-256
   3. 向上找对应的 hash 块,比较
   4. 如此层层向上,直到 root hash
   5. 如果 root hash == vbmeta 中记录的 -> 数据有效
        ↓
   应用拿到数据 (或 kernel panic / IO error)

每个数据块第一次读时都会走这套流程,但 hash 块会被缓存,所以稳定后开销很小。

Kernel cmdline

Bootloader 启动 kernel 时,会拼接 dm-verity 参数到 cmdline:

复制代码
dm="1 vroot none ro 1,0 6291456 verity 1 PARTUUID=xxx PARTUUID=xxx 4096 4096 786432 786432 sha256 abcd...(root hash) feedface...(salt)"

Kernel 解析这串字符串,在 init 之前就设置好 dm-verity 设备。


六、Chain Partition Descriptor:信任链扩展

vbmeta 默认会一个个分区检查,但有些场景下,我们希望 vbmeta_vendor、vbmeta_system 各自独立(便于厂商单独升级)。

Chain Descriptor 就是用来"委托":

c 复制代码
struct AvbChainPartitionDescriptor {
    AvbDescriptor parent_descriptor;
    uint32_t rollback_index_location;
    uint32_t partition_name_len;
    uint32_t public_key_len;
    uint32_t flags;
    uint8_t  reserved[60];
    // partition_name + public_key
};

它的语义是:"vendor_boot 分区的验证元数据请去 vbmeta_vendor 分区找,公钥是这个 RSA-4096 公钥"。

形成的信任图:

复制代码
            vbmeta (root)
           /      |       \
       boot     dtbo    [chain: vbmeta_system]
                              ↓
                          system, system_ext, product

七、回滚保护(Rollback Protection)

光验证签名还不够,假如旧版本 boot 有漏洞,攻击者把整个旧 boot.img(签名是合法的!)刷回来,绕过补丁怎么办?

AVB 通过 rollback_index 解决:每次升级版本递增,Bootloader 把"已见过的最大 index"存在 TEE 安全存储里(RPMB),启动时:

c 复制代码
uint64_t current_index   = vbmeta->rollback_index;
uint64_t stored_index    = read_from_rpmb(slot);

if (current_index < stored_index) {
    // 试图刷旧版本!拒绝!
    return AVB_SLOT_VERIFY_RESULT_ERROR_ROLLBACK_INDEX;
}

if (current_index > stored_index) {
    // 升级了,在启动成功后写回 RPMB
    pending_update = current_index;
}

RPMB 是 eMMC/UFS 内的硬件级写保护区,即使 root 也无法绕过。


八、设备状态:LOCKED / UNLOCKED

AVB 状态机:

状态 屏幕颜色 含义
GREEN 无警告 LOCKED,签名验证成功,使用 OEM 密钥
YELLOW 黄色 LOCKED,使用用户自定义密钥 (avb_custom_key)
ORANGE 橙色 UNLOCKED,所有验证被跳过
RED 红色 LOCKED,验证失败,无法启动

Bootloader 在每次启动时绘制对应警告。开发者在解锁状态下可以自由刷写,但失去 AVB 保护。


九、实操:用 avbtool 给镜像签名

Android 源码自带 avbtool(Python 写的,在 external/avb/)。

签名 boot.img

bash 复制代码
# 给 boot.img 生成 hash 描述符
avbtool add_hash_footer \
    --partition_name boot \
    --partition_size 134217728 \
    --image boot.img \
    --algorithm SHA256_RSA4096 \
    --key testkey_rsa4096.pem \
    --rollback_index 1

给 system.img 生成 hashtree(dm-verity)

bash 复制代码
avbtool add_hashtree_footer \
    --partition_name system \
    --partition_size 3221225472 \
    --image system.img \
    --algorithm SHA256_RSA4096 \
    --key testkey_rsa4096.pem \
    --hash_algorithm sha256 \
    --rollback_index 1

生成 vbmeta

bash 复制代码
avbtool make_vbmeta_image \
    --output vbmeta.img \
    --algorithm SHA256_RSA4096 \
    --key testkey_rsa4096.pem \
    --include_descriptors_from_image boot.img \
    --include_descriptors_from_image system.img \
    --include_descriptors_from_image vendor.img \
    --include_descriptors_from_image dtbo.img \
    --rollback_index 1

查看 vbmeta 内容

bash 复制代码
avbtool info_image --image vbmeta.img

输出片段:

复制代码
Minimum libavb version:   1.0
Header Block:             256 bytes
Authentication Block:     576 bytes
Auxiliary Block:          3520 bytes
Algorithm:                SHA256_RSA4096
Rollback Index:           1
Descriptors:
    Hash descriptor:
      Image Size:         67108864 bytes
      Hash Algorithm:     sha256
      Partition Name:     boot
      Salt:               aabbcc...
      Digest:             11223344...
    Hashtree descriptor:
      Image Size:         3221225472 bytes
      Tree Offset:        3221225472
      Tree Size:          25690112 bytes
      Data Block Size:    4096
      Hash Block Size:    4096
      Hash Algorithm:     sha256
      Partition Name:     system
      ...

十、C 代码:简化版 vbmeta 解析

c 复制代码
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <arpa/inet.h>

#define AVB_MAGIC "AVB0"

struct AvbVBMetaImageHeader {
    char     magic[4];
    uint32_t required_libavb_version_major;
    uint32_t required_libavb_version_minor;
    uint64_t authentication_data_block_size;
    uint64_t auxiliary_data_block_size;
    uint32_t algorithm_type;
    uint64_t hash_offset;
    uint64_t hash_size;
    uint64_t signature_offset;
    uint64_t signature_size;
    uint64_t public_key_offset;
    uint64_t public_key_size;
    uint64_t public_key_metadata_offset;
    uint64_t public_key_metadata_size;
    uint64_t descriptors_offset;
    uint64_t descriptors_size;
    uint64_t rollback_index;
    uint32_t flags;
    uint32_t rollback_index_location;
    char     release_string[48];
    uint8_t  reserved[80];
} __attribute__((packed));

// AVB 头部全部是 big-endian,需要转换
#define BE64(x) (((uint64_t)htonl((uint32_t)(x)) << 32) | htonl((uint32_t)((x) >> 32)))

int main(int argc, char **argv) {
    if (argc < 2) {
        fprintf(stderr, "Usage: %s vbmeta.img\n", argv[0]);
        return 1;
    }

    int fd = open(argv[1], O_RDONLY);
    if (fd < 0) { perror("open"); return 1; }

    struct AvbVBMetaImageHeader hdr;
    if (read(fd, &hdr, sizeof(hdr)) != sizeof(hdr)) {
        perror("read");
        return 1;
    }
    close(fd);

    if (memcmp(hdr.magic, AVB_MAGIC, 4) != 0) {
        fprintf(stderr, "Not a vbmeta image\n");
        return 1;
    }

    printf("Magic:                 %.4s\n", hdr.magic);
    printf("libavb version:        %u.%u\n",
           ntohl(hdr.required_libavb_version_major),
           ntohl(hdr.required_libavb_version_minor));
    printf("Algorithm type:        %u\n", ntohl(hdr.algorithm_type));
    printf("Auth block size:       %lu\n", BE64(hdr.authentication_data_block_size));
    printf("Aux block size:        %lu\n", BE64(hdr.auxiliary_data_block_size));
    printf("Rollback index:        %lu\n", BE64(hdr.rollback_index));
    printf("Release string:        %s\n", hdr.release_string);
    return 0;
}

十一、性能影响

AVB 的验证开销主要发生在两处:

  1. Bootloader 启动阶段:验证 vbmeta + boot/dtbo 的签名,几十毫秒
  2. 运行时 dm-verity:每读一个 4KB 块都要计算 hash

实测开销:

  • 启动时间增加约 100~300 ms
  • IO 性能下降约 13%(顺序读)、510%(随机读)
  • CPU 占用增加约 1~2%

ARMv8 上 SHA-256 有硬件加速指令(SHA256HSHA256H2),所以开销可以接受。


十二、踩坑实战

  1. 解锁 Bootloader 后 boot 报 vbmeta 红屏 :用 fastboot --disable-verity --disable-verification flash vbmeta
  2. dm-verity corruption 死循环:某个块损坏会直接进 recovery 模式,需要重刷分区
  3. chain partition 的公钥要对:vbmeta 中的 chain 公钥必须和 vbmeta_system 的签名公钥一致
  4. rollback_index 不能乱降:降级前要从 RPMB 清除,否则永远启不来
  5. avbtool 版本要匹配:不同 Android 版本对应不同的 libavb,旧 avbtool 签不出新格式

十三、总结

AVB 2.0 是 Android 安全启动链的核心:

  • vbmeta 作为信任根,串起所有分区
  • Hash Descriptor / Hashtree Descriptor 分别处理小分区和大分区
  • dm-verity 让 system 在运行时也持续被保护
  • Rollback Index + RPMB 防止版本回退攻击
  • Chain Partition 让信任链可以委托给子 vbmeta

理解 AVB 不仅对做 ROM 开发的工程师重要,做安全审计、应急响应也非常实用。下一篇我们继续讲 Fastboot 协议,看 Bootloader 是如何和电脑通信的。

相关推荐
沐怡旸6 小时前
深入解析 Android Performance Analyzer (APA) 底层架构与技术原理
android
李斯维14 小时前
从历史的角度看 Android 软件架构
android·架构·android jetpack
plainGeekDev16 小时前
Activity 间传值 → Navigation 参数
android·java·kotlin
用户416596736935516 小时前
Android WebView 加载 file:// 离线页面调试教程
android·前端
plainGeekDev16 小时前
onActivityResult → ActivityResult API
android·java·kotlin
随遇丿而安21 小时前
第10周:Activity 基础功能与生命周期优化
android
alexhilton1 天前
Android车载OS中的Remote Compose
android·kotlin·android jetpack
落魄Android在线炒饭2 天前
Android 自定义HAL开发篇之 HIDL篇——从入门到实战(上)
android
plainGeekDev2 天前
广播接收器 → Flow + Lifecycle
android·java·kotlin
plainGeekDev2 天前
EventBus → SharedFlow
android·java·kotlin