AVB add_hash_footer 后镜像二进制内容分析

15 阅读10分钟

1. 创建最小测试镜像

首先创建一个全零的最小测试镜像文件:

dd if=/dev/zero of=test.img bs=28 count=1

说明:原始镜像最小需要 28 字节,因为 avbtool.py 需要读取前 28 字节来判断是否为 sparse image。

2. 计算分区大小

MAX_VBMETA_SIZE = 64 * 1024
MAX_FOOTER_SIZE = 4096
PARTITION_SIZE = 64 * 1024 + 4096 + 28 = 69660

首次尝试(失败)

python avbtool.py add_hash_footer \
    --partition_size 69660 \
    --partition_name test \
    --image test.img \
    --key rsa2048.pem \
    --algorithm SHA256_RSA2048 \
    --rollback_index 1612483200

报错信息

avbtool.py: Partition size of 69660 is not a multiple of the image block size 4096.

修正后(4K 对齐)

# 4K 对齐后分区大小 = 73728 (4096 × 18)
python avbtool.py add_hash_footer \
    --partition_size 73728 \
    --partition_name test \
    --image test.img \
    --key rsa2048.pem \
    --algorithm SHA256_RSA2048 \
    --rollback_index 1612483200

3. 镜像整体结构

add_hash_footer 处理后的镜像布局:

┌────────────────────────────┐
│   raw data (28 byte)       │
│   + 0x00 padding 4096 byte │  ← 补齐到 block_size 的整数倍
├────────────────────────────┤
│   vbmeta_blob              │  ← VBMeta 元数据块
├────────────────────────────┤
│   footer_blob_with_padding │  ← 尾部元数据(64 字节 + 填充)
└────────────────────────────┘

4. vbmeta_blob 整体布局

offset 0   ┌──────────────────────────────┐
           │     Header (256 byte)        │  ← AvbVBMetaHeader
offset 256 ├──────────────────────────────┤
           │  Authentication Block        │  ← hash + signature (64 字节对齐)
           ├──────────────────────────────┤
           │  Auxiliary Block             │  ← descriptors + pub_key (64 字节对齐)
           └──────────────────────────────┘

4.1 Header(头部,固定 256 字节)

对应 AvbVBMetaHeader,使用大端序

字段类型字节说明
magic4s4"AVB0" (0x41564230)
required_libavb_version_majorL4主版本号
required_libavb_version_minorL4次版本号
authentication_data_block_sizeQ8认证块大小
auxiliary_data_block_sizeQ8辅助块大小
algorithm_typeL4算法类型
hash_offset / hash_size2Q16Hash 偏移和大小
signature_offset / signature_size2Q16签名偏移和大小
public_key_offset / public_key_size2Q16公钥偏移和大小
public_key_metadata_offset / public_key_metadata_size2Q16公钥元数据偏移和大小
descriptors_offset / descriptors_size2Q16描述符偏移和大小
rollback_indexQ8回滚索引
flagsL4标志位
reserved04x4保留
release_string47sx48版本字符串
reserved80x80保留
合计256

Header 二进制分析

$ xxd test.img

# 从偏移 4096 开始的 Header 数据
0001000: 4156 4230 0000 0001 0000 0000 0000 0000  AVB0............
0001010: 0000 0140 0000 0000 0000 0300 0000 0001  ...@............
0001020: 0000 0000 0000 0000 0000 0000 0000 0020  ............... 
0001030: 0000 0000 0000 0020 0000 0000 0000 0100  ....... ........
0001040: 0000 0000 0000 00c8 0000 0000 0000 0208  ................
0001050: 0000 0000 0000 02d0 0000 0000 0000 0000  ................
0001060: 0000 0000 0000 0000 0000 0000 0000 00c8  ................
0001070: 0000 0000 601c 8a80 0000 0000 0000 0000  ....`...........
0001080: 6176 6274 6f6f 6c20 312e 312e 3000 0000  avbtool 1.1.0...

字段解析

字段十六进制值解析结果
magic41 56 42 30"AVB0"
required_libavb_version_major00 00 00 011
required_libavb_version_minor00 00 00 000
authentication_data_block_size00 00 00 00 00 00 01 40320 (0x140)
auxiliary_data_block_size00 00 00 00 00 00 03 00768 (0x300)
algorithm_type00 00 00 011 (SHA256_RSA2048)
hash_offset00 00 00 00 00 00 00 000
hash_size00 00 00 00 00 00 00 2032
signature_offset00 00 00 00 00 00 00 2032
signature_size00 00 00 00 00 00 01 00256
public_key_offset00 00 00 00 00 00 00 C8200 (0xC8)
public_key_size00 00 00 00 00 00 02 08520 (0x208)
public_key_metadata_offset00 00 00 00 00 00 02 D0720 (0x2D0)
public_key_metadata_size00 00 00 00 00 00 00 000
descriptors_offset00 00 00 00 00 00 00 000
descriptors_size00 00 00 00 00 00 00 C8200 (0xC8)
rollback_index00 00 00 00 60 1C 8A 801612483200
flags00 00 00 000
release_string61 76 62 74 6F 6F 6C 20 31 2E 31 2E 30 00..."avbtool 1.1.0"

注意:offset/size 字段是相对于各自所在 block 起点的偏移,而非相对于整个 blob。

4.2 Authentication Block(认证块)

布局

┌──────────────────────┐
│  Hash (32 byte)      │  ← SHA256 哈希值
├──────────────────────┤
│  Signature (256 byte)│  ← RSA 签名
├──────────────────────┤
│  Padding (0x00)      │  ← 补齐到 64 字节倍数
└──────────────────────┘

二进制数据

# Hash (32 字节) - 偏移 4352 (0x1100)
0001100: c309 88de 75e2 2ffb c20a 7630 bf9b f096  ....u./...v0....
0001110: 55f2 ef4b 9cc1 f9b3 2b02 f016 6f4c 11a2  U..K....+...oL..

# Signature (256 字节) - 偏移 4384 (0x1120)
0001120: ca12 5a82 ef95 16c6 58f9 8590 6306 dd29  ..Z.....X...c..)
# ... (中间省略)
0001210: 8054 7ed5 0e14 5173 3791 cbf9 f8b3 0ded  .T~...Qs7.......

# Padding - 偏移 4640 (0x1220)
0001220: 0000 0000 0000 0000 0000 0000 0000 0000  ................

关键计算

  • hash_offset = 0signature_offset = hash_size (紧跟 hash 之后)
  • authentication_data_block_size = round_to_multiple(hash_size + signature_size, 64) = 320

Hash 计算

hash = SHA256(Header + Auxiliary)  # 不含 Authentication 自身

手动验证Hash:

# 提取 Header 和 Auxiliary 数据
$ dd if=test.img of=header_data_blob.bin skip=4096 ibs=1 count=256
$ dd if=test.img of=aux_data_blob.bin skip=4672 ibs=1 count=768  # 4096+256+320=4672
$ cat header_data_blob.bin aux_data_blob.bin > header_aux.bin

# 验证 Hash
$ sha256sum header_aux.bin
c30988de75e22ffbc20a7630bf9bf09655f2ef4b9cc1f9b32b02f0166f4c11a2  header_aux.bin

签名计算

Signature = RSA_sign(PKCS1-v1_5_Padding || Hash)

SHA256_RSA2048 的 padding 格式:

padding = [
    0x00, 0x01] + [0xFF] * 202 + [0x00] + [
    0x30, 0x31, 0x30, 0x0D, 0x06, 0x09, 0x60, 0x86,
    0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05,
    0x00, 0x04, 0x20,
]

手动验证签名:

# 生成 padding + hash
$ printf '\x00\x01' > padding.bin
$ dd if=/dev/zero bs=1 count=202 2>/dev/null | tr '\000' '\377' | \
    dd of=padding.bin bs=1 seek=2 conv=notrunc
$ printf '\x00\x30\x31\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x01\x05\x00\x04\x20' >> padding.bin
$ sha256sum header_aux.bin | cut -d' ' -f1 | xxd -r -p > hash.bin
$ cat padding.bin hash.bin > padding_and_hash.bin

# 签名
$ openssl rsautl -sign -inkey rsa2048.pem -raw < padding_and_hash.bin > signature.bin

# 验证签名
$ xxd signature.bin
0000000: ca12 5a82 ef95 16c6 58f9 8590 6306 dd29  ..Z.....X...c..)
......
00000f0: 8054 7ed5 0e14 5173 3791 cbf9 f8b3 0ded  .T~...Qs7.......

注意padding + hash 总长度等于 RSA 模数字节数 (256)。

4.3 Auxiliary Block(辅助块)

布局

+--------------------------+  aux offset 0
|     Descriptors          |  descriptors_offset=0, descriptors_size=N
+--------------------------+
|     Public Key           |  public_key_offset=N
+--------------------------+
| Public Key Metadata      |  public_key_metadata_offset=N+key_size
+--------------------------+
|   Padding (\0)           |  补齐到 64 字节倍数
+--------------------------+

Descriptors 详细分析

此处的 Descriptor 类型为 AvbHashDescriptor

固定头部 (132 字节),使用大端序:

偏移字段格式字节说明
0tagQ8= 2,标识 descriptor 类型
8num_bytes_followingQ8后续总字节数(不含 tag 和自身)
16image_sizeQ8镜像数据大小
24hash_algorithm32s32算法名,NUL 填充
56partition_name_lenL4分区名长度
60salt_lenL4Salt 长度
64digest_lenL4Digest 长度
68flagsL4标志位
72reserved60s60保留,全 0
合计132
# 从偏移 4672 (0x1240) 开始
0001240: 0000 0000 0000 0002 0000 0000 0000 00b8  ................
0001250: 0000 0000 0000 001c 7368 6132 3536 0000  ........sha256..
0001260: 0000 0000 0000 0000 0000 0000 0000 0000  ................
0001270: 0000 0000 0000 0000 0000 0004 0000 0020  ............... 
0001280: 0000 0020 0000 0000 0000 0000 0000 0000  ... ............

字段解析

字段十六进制值解析结果
tag00 00 00 00 00 00 00 022 (HashDescriptor)
num_bytes_following00 00 00 00 00 00 00 B8184
image_size00 00 00 00 00 00 00 1C28 (原始镜像大小)
hash_algorithm73 68 61 32 35 36 00..."sha256"
partition_name_len00 00 00 044
salt_len00 00 00 2032
digest_len00 00 00 2032
flags00 00 00 000

变长尾部(紧接固定头之后,偏移 4804 / 0x12C4):

拼接顺序:partition_name + salt + digest + padding

ret = desc + encoded_name + self.salt + self.digest + padding
# 从 0x12C4 开始
00012c0: 0000 0000 7465 7374 f8c7 3426 02a2 c0c2  ....test..4&....
00012d0: 4dbb eaf7 465c be86 c82a 946d ed8b 1a8c  M...F\...*.m....
00012e0: 7959 2e33 6c27 0d0e 6f09 c32f 7494 e149  yY.3l'..o../t..I
00012f0: bb37 c2d8 df2c 98c0 005e 157b 677f bbf2  .7...,...^.{g...
数据长度说明
partition_name74 65 73 744"test"
saltf8c7 3426 02a2 c0c2 4dbb eaf7 465c be86 c82a 946d ed8b 1a8c 7959 2e33 6c27 0d0e32盐值
digest6f09 c32f 7494 e149 bb37 c2d8 df2c 98c0 005e 157b 677f bbf2 38e7 84d3 4a46 5c0e32SHA256(salt + origin_data)

Digest 验证

# 提取 salt
$ dd if=test.img of=salt.bin skip=4808 ibs=1 count=32  # 0x12C8

# 拼接 salt + 原始数据
$ cat salt.bin <(dd if=/dev/zero bs=28 count=1 2>/dev/null) > salt_origin.bin

# 验证 digest
$ sha256sum salt_origin.bin
6f09c32f7494e149bb37c2d8df2c98c0005e157b677fbbf238e784d34a465c0e  salt_origin.bin

Public Key

# public_key_offset = 0xC8,从 0x1308 开始
0001300: 38e7 84d3 4a46 5c0e 0000 0800 4cbb a51d  8...JF\.....L...
# ... (520 字节)
0001500: e6d0 7b84 e708 8d5a 80de d3f6 f533 c17e  ..{....Z.....3.~

Public Key Metadata

# public_key_metadata_offset = 0x2D0,从 0x1510 开始
# public_key_metadata_size = 0,此处为空
0001510: 0000 0000 0000 0000 0000 0000 0000 0000  ................

5. footer_blob_with_padding

布局说明

add_hash_footer 会在镜像末尾预留 4096 字节用于存储 AvbFooter,其中:

  • 前 4032 字节 (4096 - 64):全 0 填充
  • 后 64 字节AvbFooter 结构体

AvbFooter 结构体 (64 字节)

使用大端序 (!),格式串 '!4s 2L Q Q Q 28x'

偏移字段字节说明
0magic4'AVBf' (0x41564266)
4version_major4主版本号
8version_minor4次版本号
12original_image_size8原始镜像大小 (28 字节)
20vbmeta_offset8VBMeta blob 偏移 (0x1000)
28vbmeta_size8VBMeta blob 大小 (0x540)
36reserved28全 0 填充
合计64

二进制数据

# 文件末尾 4096 字节区域
# 偏移 0x1000-0x1FBF: 4032 字节的 0x00 填充
# 偏移 0x1FC0: AvbFooter 起始位置

# 4032 字节的 0x00 填充
0011000: 0000 0000 0000 0000 0000 0000 0000 0000  ................
0011010: 0000 0000 0000 0000 0000 0000 0000 0000  ................
... (中间省略) ...
0011fa0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
0011fb0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
0011fc0: 4156 4266 0000 0001 0000 0000 0000 0000  AVBf............  ← 64 字节 AvbFooter 起始
0011fd0: 0000 001c 0000 0000 0000 1000 0000 0000  ................
0011fe0: 0000 0540 0000 0000 0000 0000 0000 0000  ...@............
0011ff0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
0012000:                                         ← 文件结束 (73728 字节)

字段解析

偏移十六进制数据解析结果
0x1FC041 56 42 66"AVBf" (magic)
0x1FC400 00 00 01主版本号 = 1
0x1FC800 00 00 00次版本号 = 0
0x1FCC00 00 00 00 00 00 00 1C原始镜像大小 = 28
0x1FD400 00 00 00 00 00 10 00vbmeta_offset = 4096 (0x1000)
0x1FDC00 00 00 00 00 00 05 40vbmeta_size = 1344 (0x540)
0x1FE400... (28 字节)reserved = 全 0

6. 完整布局总结

偏移 0x0000
┌─────────────────────────────────┐
  原始数据 (28 字节)               
  + 0x00 填充到 4096 byte            原始镜像分区
├─────────────────────────────────┤
  vbmeta_blob (0x540 byte)          包含 Header + Authentication + Auxiliary
  - Header: 256 byte             
  - Authentication: 320 byte     
  - Auxiliary: 768 byte          
├─────────────────────────────────┤
  0x00 padding                      填充至 4096 字节边界前的剩余空间
├─────────────────────────────────┤
  Footer (4096 byte)             
  ├─ 0x00 padding (4032 byte)        4032 字节全 0
  └─ AvbFooter (64 byte)            最后 64 字节
     ├─ magic: "AVBf"            
     ├─ version: 1.0             
     ├─ original_image_size: 28  
     ├─ vbmeta_offset: 0x1000    
     ├─ vbmeta_size: 0x540       
     └─ reserved: 0              
└─────────────────────────────────┘
偏移 0x12000   文件结束 (73728 字节 = 4096 × 18)

7. 关键要点

  1. 分区大小必须 4K 对齐partition_size 必须是 4096 的倍数
  2. Authentication Block 包含 Hash 和 Signature,其大小为 320 字节(64 字节对齐)
  3. Hash 计算范围Header + Auxiliary,不含 Authentication 自身
  4. 签名机制:对 PKCS1-v1_5_Padding + Hash 进行 RSA 签名
  5. Footer 固定在文件末尾 64 字节,包含 VBMeta 位置信息
  6. 原始数据vbmeta_blob 之前,已被填充到 4096 字节边界

C289B571-FC59-480F-B744-7075111137F6.jpeg