前言

vbmeta 分区是 Android AVB 机制的重要组成部分,其中包含了设备上部分分区的校验信息。有时候出于调试需要,我们需要修改设备上一些分区的内容,此时为了能使设备顺利启动,我们还需要额外禁用 vbmeta 分区中的校验。
众所周知,我们可以通过执行 fastboot --disable-verity --disable-verification flash vbmeta vbmeta.img 来刷入一个 vbmeta 镜像,并同时禁用这个镜像中的校验。但是,fastboot 并不会修改原镜像,也没有提供导出这个禁用了校验的镜像的能力。那么有没有什么办法能够从原镜像获得禁用了校验的镜像呢?

调查

首先,可以看看 fastboot 是怎么做的:

system/core/fastboot/fastboot.cpp

static void rewrite_vbmeta_buffer(struct fastboot_buffer* buf, bool vbmeta_in_boot) {
    // Buffer needs to be at least the size of the VBMeta struct which
    // is 256 bytes.
    if (buf->sz < 256) {
        return;
    }

    std::string data;
    if (!android::base::ReadFdToString(buf->fd, &data)) {
        die("Failed reading from vbmeta");
    }

    uint64_t vbmeta_offset = 0;
    if (vbmeta_in_boot) {
        // Tries to locate top-level vbmeta from boot.img footer.
        uint64_t footer_offset = buf->sz - AVB_FOOTER_SIZE;
        if (0 != data.compare(footer_offset, AVB_FOOTER_MAGIC_LEN, AVB_FOOTER_MAGIC)) {
            die("Failed to find AVB_FOOTER at offset: %" PRId64, footer_offset);
        }
        const AvbFooter* footer = reinterpret_cast<const AvbFooter*>(data.c_str() + footer_offset);
        vbmeta_offset = be64toh(footer->vbmeta_offset);
    }
    // Ensures there is AVB_MAGIC at vbmeta_offset.
    if (0 != data.compare(vbmeta_offset, AVB_MAGIC_LEN, AVB_MAGIC)) {
        die("Failed to find AVB_MAGIC at offset: %" PRId64, vbmeta_offset);
    }

    fprintf(stderr, "Rewriting vbmeta struct at offset: %" PRId64 "\n", vbmeta_offset);

    // There's a 32-bit big endian |flags| field at offset 120 where
    // bit 0 corresponds to disable-verity and bit 1 corresponds to
    // disable-verification.
    //
    // See external/avb/libavb/avb_vbmeta_image.h for the layout of
    // the VBMeta struct.
    uint64_t flags_offset = 123 + vbmeta_offset;
    if (g_disable_verity) {
        data[flags_offset] |= 0x01;
    }
    if (g_disable_verification) {
        data[flags_offset] |= 0x02;
    }

    unique_fd fd(make_temporary_fd("vbmeta rewriting"));
    if (!android::base::WriteStringToFd(data, fd)) {
        die("Failed writing to modified vbmeta");
    }
    buf->fd = std::move(fd);
    lseek(buf->fd.get(), 0, SEEK_SET);
}

可以很简单的发现,fastboot 在刷入镜像前在缓冲区中对镜像的第 123 字节的最后两位进行了置 1 操作,其中 --disable-verity 对应第 123 字节的最后一位, --disable-verification 则对应第 123 字节的倒数第二位。

不妨进一步看看:

external/avb/libavb/avb_vbmeta_image.h

typedef struct AvbVBMetaImageHeader {
  ......

  /* 120: Flags from the AvbVBMetaImageFlags enumeration. This must be
   * set to zero if the vbmeta image is not a top-level image.
   */
  uint32_t flags;

  ......
} AVB_ATTR_PACKED AvbVBMetaImageHeader;

可以看到,vbmeta 镜像从第 120 字节开始存储的是一个名为 flags 的大端 32 位无符号类型整数,也就是说这个整数的覆盖范围是第 120 到 123 字节。上面的修改实际上正好修改了这个整数的最低两位。

那么,除了上面提到的两个“禁用校验”位,这个 flags 中其它的位有什么含义吗?

external/avb/libavb/avb_vbmeta_image.h

/* Flags for the vbmeta image.
 *
 * AVB_VBMETA_IMAGE_FLAGS_HASHTREE_DISABLED: If this flag is set,
 * hashtree image verification will be disabled.
 *
 * AVB_VBMETA_IMAGE_FLAGS_VERIFICATION_DISABLED: If this flag is set,
 * verification will be disabled and descriptors will not be parsed.
 */
typedef enum {
  AVB_VBMETA_IMAGE_FLAGS_HASHTREE_DISABLED = (1 << 0),
  AVB_VBMETA_IMAGE_FLAGS_VERIFICATION_DISABLED = (1 << 1)
} AvbVBMetaImageFlags;

好吧,看起来并没有,目前这个 flags 仅仅只服务于“禁用校验”。
可以看到,--disable-verity 对应的是 FLAGS_HASHTREE_DISABLED--disable-verification 对应的是 FLAGS_VERIFICATION_DISABLED

实践

于是按照上面的逻辑,写了个 binary ,直接作用于本地的 vbmeta 镜像,生成禁用校验的镜像。

这个 binary 传入的参数除了可以是 vbmeta 镜像路径,还可以是 vbmeta 分区对应的 block device ,比如 /dev/block/bootdevice/by-name/vbmeta ,这将直接修改存储器上的 vbmeta 分区(当然运行时需要 root )。

binary 是静态编译的,应该适用于采用 Linux 内核的所有平台(前提是包含了你的架构),Windows 的话,自己写一个或者移植一下吧 :) 。