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 是如何和电脑通信的。

相关推荐
只可远观1 小时前
Android XML命令式和Jetpack Compose声明式UI
android·xml
jixunwulian1 小时前
AI边缘智能网关工业安全领域的边缘智能预警方案应用
网络·人工智能·安全
他是龙5511 小时前
DVWA 靶场深度解析:文件包含 & 文件上传(Low → Impossible)
android
_李小白1 小时前
【Android车载学习笔记】第一天:Android Automotive OS介绍
android·笔记
aqi002 小时前
FFmpeg开发笔记(一百零一)跨平台的开源音视频移动框架MobileFFmpeg
android·ffmpeg·音视频·直播·流媒体
TechWayfarer2 小时前
网络安全视角:利用IP定位API接口识别机房与基站流量(合规风控篇)
开发语言·网络·数据库·python·安全·网络安全
SL-staff2 小时前
企业文档私有化部署的安全设计:加密存储、传输与审计日志
安全·私有化部署·数据安全·加密·安全架构·合规·企业文档
念越2 小时前
HTTPS 安全内核:对称与非对称加密的博弈,数字证书一战定局
java·网络·网络协议·安全·https