前言
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 的话,自己写一个或者移植一下吧 :) 。
找到特征后,用magisk附赠的magiskboot也能做到这一点:
```
# Orig: '\x00\x00\x00\x00\x00\x00\x00\x00avbtool '
# To : '\x00\x00\x00\x03\x00\x00\x00\x00avbtool '
/data/adb/magisk/magiskboot hexpatch /dev/block/bootdevice/by-name/vbmeta '0000000000000000617662746F6F6C20' '0000000300000000617662746F6F6C20'
```
不错