可以看到,安卓启动流程中存在3处校验:
1.Bootloader完整性校验
2.vbmeta完整性校验
3.boot等分区完整性校验
而Boot ROM主要是通过硬件机制和芯片设计来保证的。Boot
ROM 自身的代码通常不需要在启动时进行重新校验。
vbmeta完整性校验
在我们的安全启动和升级中,vbmeta分区包含各个关键分区(如 boot、system、vendor 等)的元数据,包括这些分区的哈希值、签名信息。它由Bootloader加载并且进行校验,除校验自身的完整性之外,也包含boot、system、vendor 等分区的完整,那我们就需要进一步分析vbmeta的数据格式。使用avbtool.py(脚本在external/avb/avbtool.py)解析vbmeta.img,信息如下:
SQLMinimum libavb version: 1.0Header Block: 256 bytesAuthentication Block: 320 bytesAuxiliary Block: 1344 bytesPublic key (sha1): xxxxxxxxxxxxxxxxxxba86e5364c66f4f2ea8aAlgorithm: SHA256_RSA2048Rollback Index: 0Flags: 0Rollback Index Location: 0Release String: 'avbtool 1.1.0'Descriptors:Kernel Cmdline descriptor:Flags: 0Kernel Cmdline: xxxxxxxx1e34dfe7 2 restart_on_corruption ignore_zero_blocks"'Hash descriptor:Image Size: 6148096 bytesHash Algorithm: sha256Partition Name: bootSalt: xxxxxxxxxxxxxxxxxx284bf950defaea1c71e9642d99441eb6bdb6814xxxxxxxDigest: xxxxxxxxxxxxxxxxxx9f367d1b44a4ad0169dfe501034658393b31281xxxxxxxFlags: 0Hashtree descriptor:Version of dm-verity: 1Image Size: 503840768 bytesTree Offset: 503840768Tree Size: 0 bytesData Block Size: 4096 bytesHash Block Size: 4096 bytesFEC num roots: 0FEC offset: 0FEC size: 0 bytesHash Algorithm: sha1Partition Name: systemSalt: xxxxxxxxxxxxxx7e076c223f55x17a9c6a3d2xxaRoot Digest: xxxxxxxxxxxxxx30562aaea487xa392b0564bxx8Flags: 0
avb_vbmeta_image.h(文件在external/avb/libavb/avb_vbmeta_image.h)中描述:
C/* Binary format for header of the vbmeta image.** The vbmeta image consists of three blocks:** +-----------------------------------------+* | Header data - fixed size |* +-----------------------------------------+* | Authentication data - variable size |* +-----------------------------------------+* | Auxiliary data - variable size |* +-----------------------------------------+** The "Header data" block is described by this struct and is always* |AVB_VBMETA_IMAGE_HEADER_SIZE| bytes long.** The "Authentication data" block is |authentication_data_block_size|* bytes long and contains the hash and signature used to authenticate* the vbmeta image. The type of the hash and signature is defined by* the |algorithm_type| field.** The "Auxiliary data" is |auxiliary_data_block_size| bytes long and* contains the auxiliary data including the public key used to make* the signature and descriptors.** The public key is at offset |public_key_offset| with size* |public_key_size| in this block. The size of the public key data is* defined by the |algorithm_type| field. The format of the public key* data is described in the |AvbRSAPublicKeyHeader| struct.** The descriptors starts at |descriptors_offset| from the beginning* of the "Auxiliary Data" block and take up |descriptors_size|* bytes. Each descriptor is stored as a |AvbDescriptor| with tag and* number of bytes following. The number of descriptors can be* determined by walking this data until |descriptors_size| is* exhausted.*/typedef struct AvbVBMetaImageHeader {/* 0: Four bytes equal to "AVB0" (AVB_MAGIC). */uint8_t magic[AVB_MAGIC_LEN];/* 4: The major version of libavb required for this header. */uint32_t required_libavb_version_major;/* 8: The minor version of libavb required for this header. */uint32_t required_libavb_version_minor;/* 12: The size of the signature block. */uint64_t authentication_data_block_size;/* 20: The size of the auxiliary data block. */uint64_t auxiliary_data_block_size;/* 28: The verification algorithm used, see |AvbAlgorithmType| enum. */uint32_t algorithm_type;/* 32: Offset into the "Authentication data" block of hash data. */uint64_t hash_offset;/* 40: Length of the hash data. */uint64_t hash_size;/* 48: Offset into the "Authentication data" block of signature data. */uint64_t signature_offset;/* 56: Length of the signature data. */uint64_t signature_size;/* 64: Offset into the "Auxiliary data" block of public key data. */uint64_t public_key_offset;/* 72: Length of the public key data. */uint64_t public_key_size;/* 80: Offset into the "Auxiliary data" block of public key metadata. */uint64_t public_key_metadata_offset;/* 88: Length of the public key metadata. Must be set to zero if there* is no public key metadata.*/uint64_t public_key_metadata_size;/* 96: Offset into the "Auxiliary data" block of descriptor data. */uint64_t descriptors_offset;/* 104: Length of descriptor data. */uint64_t descriptors_size;/* 112: The rollback index which can be used to prevent rollback to* older versions.*/uint64_t rollback_index;/* 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;/* 124: The location of the rollback index defined in this header.* Only valid for the main vbmeta. For chained partitions, the rollback* index location must be specified in the AvbChainPartitionDescriptor* and this value must be set to 0.*/uint32_t rollback_index_location;/* 128: The release string from avbtool, e.g. "avbtool 1.0.0" or* "avbtool 1.0.0 xyz_board Git-234abde89". Is guaranteed to be NUL* terminated. Applications must not make assumptions about how this* string is formatted.*/uint8_t release_string[AVB_RELEASE_STRING_SIZE];/* 176: Padding to ensure struct is size AVB_VBMETA_IMAGE_HEADER_SIZE* bytes. This must be set to zeroes.*/uint8_t reserved[80];}
基于以上数据结构以及vbmeta信息可以发现,vbmeta分区的数据结构如下:
包含:
1.Header
Block:
•头部包含元数据信息,如版本号、大小。
2. Auxiliary Block
•Hash Tree: 用于验证各个分区(如system、vendor)完整性的哈希树,仅大分区采用哈希树的形式。
•Hash: 用于验证boot分区等单一分区的hash。
•public_key:用于签名验证的public_key。
3.Authentication
Block
•hash: 用于验证完整性的hash。
•signature: 对整个vbmeta分区内容的数字签名,确保数据的完整性和真实性。
首先来看vbmeta分区自身完整性校验代码,校验逻辑在avb_vbmeta_image.c(文件在external/avb/libavb/avb_vbmeta_image.c),
Cswitch (h.algorithm_type) {case AVB_ALGORITHM_TYPE_SHA256_RSA2048:case AVB_ALGORITHM_TYPE_SHA256_RSA4096:case AVB_ALGORITHM_TYPE_SHA256_RSA8192:avb_sha256_init(&sha256_ctx);avb_sha256_update(&sha256_ctx, header_block, sizeof(AvbVBMetaImageHeader));avb_sha256_update(&sha256_ctx, auxiliary_block, h.auxiliary_data_block_size);computed_hash = avb_sha256_final(&sha256_ctx);break;case AVB_ALGORITHM_TYPE_SHA512_RSA2048:case AVB_ALGORITHM_TYPE_SHA512_RSA4096:case AVB_ALGORITHM_TYPE_SHA512_RSA8192:avb_sha512_init(&sha512_ctx);avb_sha512_update(&sha512_ctx, header_block, sizeof(AvbVBMetaImageHeader));avb_sha512_update(&sha512_ctx, auxiliary_block, h.auxiliary_data_block_size);computed_hash = avb_sha512_final(&sha512_ctx);break;default:avb_error("Unknown algorithm.\n");goto out;}if (avb_safe_memcmp(authentication_block + h.hash_offset,//认证块中hash的位置computed_hash,h.hash_size) != 0) {avb_error("Hash does not match!\n");ret = AVB_VBMETA_VERIFY_RESULT_HASH_MISMATCH;goto out;}verification_result = avb_rsa_verify(auxiliary_block + h.public_key_offset, // 公钥位置h.public_key_size, // 公钥大小authentication_block + h.signature_offset, // 签名位置h.signature_size, // 签名大小authentication_block + h.hash_offset, // 哈希值位置h.hash_size, // 哈希大小algorithm->padding, // 填充算法algorithm->padding_len); // 填充长度
上述代码中跳过一些长度检查、魔术字节(Magic)验证、版本检查、区块大小检查,从哈希校验开始看,根据VBMeta头中的algorithm_type字段,选择使用不同的哈希算法(如 SHA-256 或 SHA-512)对VBMeta分区的特定数据块(header块+auxiliary 块)进行哈希计算。得到的hash值与Authentication Block中Hash值进行hash值的比较,紧接着,进行签名验证,使用 Auxiliary Block中的公钥,调用avb_rsa_verify进行签名验证,验证代码如下:
Cbool avb_rsa_verify(const uint8_t* key,size_t key_num_bytes,const uint8_t* sig,size_t sig_num_bytes,const uint8_t* hash,size_t hash_num_bytes,const uint8_t* padding,size_t padding_num_bytes) {uint8_t* buf = NULL;IAvbKey* parsed_key = NULL;bool success = false;if (key == NULL || sig == NULL || hash == NULL || padding == NULL) {avb_error("Invalid input.\n");goto out;}parsed_key = iavb_parse_key_data(key, key_num_bytes);// 用于解析传入的公钥数据,将公钥从其原始格式转换为 IAvbKey 结构体if (parsed_key == NULL) {avb_error("Error parsing key.\n");goto out;}//... 签名长度检查等modpowF4(parsed_key, buf);///* Check padding bytes.** Even though there are probably no timing issues here, we use* avb_safe_memcmp() just to be on the safe side./if (avb_safe_memcmp(buf, padding, padding_num_bytes)) {avb_error("Padding check failed.\n");goto out;}* /* Check hash. */if (avb_safe_memcmp(buf + padding_num_bytes, hash, hash_num_bytes)) {avb_error("Hash check failed.\n");goto out;}success = true;out:if (parsed_key != NULL) {iavb_free_parsed_key(parsed_key);}if (buf != NULL) {avb_free(buf);}return success;}
该函数首先将公钥解析为IAvbKey 结构体,并通过modpowF4函数对Authentication Block进行RSA解密操作,得到的hash与上一步计算出的hash进行比较。
以上就是vbmeta自身完整性校验的过程,从上述校验中可以发现,我们修改vbmeta的Auxiliary Block中的public key以及Authentication Block中的hash、signature,就可以绕过vbmeta自身完整性校验呢?答案是否定的,除了以上的对vbmeta完整性校验以外,Bootloader 还会将vbmeta.img中的公钥与 Bootloader 中的 Root of Trust 公钥进行比较。具体来说,Bootloader 会检查:
•公钥哈希匹配: Bootloader 计算vbmeta中的公钥哈希值,并与 Root of Trust 中的预置公钥哈希值进行比较。如果两者匹配,表示vbmeta镜像中的公钥是可信的。
•公钥证书链: 在某些实现中,可能涉及到证书链的验证,Bootloader会验证vbmeta镜像中的公钥是否可以通过一个证书链被信任。
以uboot为例(不同的厂商,bootloader程序也不一致)
代码avb_verify.c(文件在common/avb_verify.c)
C/*** validate_vmbeta_public_key() - checks if the given public key used to sign* the vbmeta partition is trusted** @ops: AvbOps, contains AVB ops handlers* @public_key_data: public key for verifying vbmeta partition signature* @public_key_length: length of public key* @public_key_metadata:* @public_key_metadata_length:* @out_key_is_trusted:** @return:* AVB_IO_RESULT_OK, if partition was found and read operation succeed*/static AvbIOResult validate_vbmeta_public_key(AvbOps *ops,const u8 *public_key_data,size_t public_key_length,const u8*public_key_metadata,size_tpublic_key_metadata_length,bool *out_key_is_trusted){if (!public_key_length || !public_key_data || !out_key_is_trusted)return AVB_IO_RESULT_ERROR_IO;*out_key_is_trusted = false;if (public_key_length != sizeof(avb_root_pub))return AVB_IO_RESULT_ERROR_IO;if (memcmp(avb_root_pub, public_key_data, public_key_length) == 0)*out_key_is_trusted = true;return AVB_IO_RESULT_OK;}
上述代码中分别进行长度检查,公钥匹配,与avb_root_pub进行比较,uboot中的公钥硬编码在代码中的,若发现某些bootloader的公钥也是硬编码的,vbmeta就可以在不解锁Bootloader情况下进行替换了。
boot完整性校验
接下来说说boot分区的校验逻辑,代码在 avb_slot_verify.c 中。
Cstatic AvbSlotVerifyResult load_and_verify_hash_partition(AvbOps* ops,const char* const* requested_partitions,const char* ab_suffix,bool allow_verification_error,const AvbDescriptor* descriptor,AvbSlotVerifyData* slot_data) {AvbHashDescriptor hash_desc;const uint8_t* desc_partition_name = NULL;const uint8_t* desc_salt;const uint8_t* desc_digest;char part_name[AVB_PART_NAME_MAX_SIZE];AvbSlotVerifyResult ret;AvbIOResult io_ret;uint8_t* image_buf = NULL;bool image_preloaded = false;uint8_t* digest;size_t digest_len;const char* found;uint64_t image_size;size_t expected_digest_len = 0;uint8_t expected_digest_buf[AVB_SHA512_DIGEST_SIZE];const uint8_t* expected_digest = NULL;//...// Although only one of the type might be used, we have to defined the// structure here so that they would live outside the 'if/else' scope to be// used later.AvbSHA256Ctx sha256_ctx;AvbSHA512Ctx sha512_ctx;size_t image_size_to_hash = hash_desc.image_size;// If we allow verification error and the whole partition is smaller than// image size in hash descriptor, we just hash the whole partition.if (image_size_to_hash > image_size) {image_size_to_hash = image_size;}if (avb_strcmp((const char*)hash_desc.hash_algorithm, "sha256") == 0) {avb_sha256_init(&sha256_ctx);avb_sha256_update(&sha256_ctx, desc_salt, hash_desc.salt_len);avb_sha256_update(&sha256_ctx, image_buf, image_size_to_hash);digest = avb_sha256_final(&sha256_ctx);digest_len = AVB_SHA256_DIGEST_SIZE;} else if (avb_strcmp((const char*)hash_desc.hash_algorithm, "sha512") == 0) {avb_sha512_init(&sha512_ctx);avb_sha512_update(&sha512_ctx, desc_salt, hash_desc.salt_len);avb_sha512_update(&sha512_ctx, image_buf, image_size_to_hash);digest = avb_sha512_final(&sha512_ctx);digest_len = AVB_SHA512_DIGEST_SIZE;} else {avb_error(part_name, ": Unsupported hash algorithm.\n");ret = AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA;goto out;}if (hash_desc.digest_len == 0) {/* Expect a match to a persistent digest. */avb_debug(part_name, ": No digest, using persistent digest.\n");expected_digest_len = digest_len;expected_digest = expected_digest_buf;avb_assert(expected_digest_len <= sizeof(expected_digest_buf));/* Pass |digest| as the |initial_digest| so devices not yet initialized get* initialized to the current partition digest.*/ret = read_persistent_digest(ops, part_name, digest_len, digest, expected_digest_buf);if (ret != AVB_SLOT_VERIFY_RESULT_OK) {goto out;}} else {/* Expect a match to the digest in the descriptor. */expected_digest_len = hash_desc.digest_len;expected_digest = desc_digest;}if (digest_len != expected_digest_len) {avb_error(part_name, ": Digest in descriptor not of expected size.\n");ret = AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA;goto out;}if (avb_safe_memcmp(digest, expected_digest, digest_len) != 0) {//比较计算出的哈希值与描述符中的预期哈希值。如果不匹配,则返回验证错误。avb_error(part_name,": Hash of data does not match digest in descriptor.\n");ret = AVB_SLOT_VERIFY_RESULT_ERROR_VERIFICATION;goto out;}ret = AVB_SLOT_VERIFY_RESULT_OK;out:/* If it worked and something was loaded, copy to slot_data. */if ((ret == AVB_SLOT_VERIFY_RESULT_OK || result_should_continue(ret)) &&image_buf != NULL) {AvbPartitionData* loaded_partition;if (slot_data->num_loaded_partitions == MAX_NUMBER_OF_LOADED_PARTITIONS) {avb_error(part_name, ": Too many loaded partitions.\n");ret = AVB_SLOT_VERIFY_RESULT_ERROR_OOM;goto fail;}loaded_partition =&slot_data->loaded_partitions[slot_data->num_loaded_partitions++];loaded_partition->partition_name = avb_strdup(found);loaded_partition->data_size = image_size;loaded_partition->data = image_buf;loaded_partition->preloaded = image_preloaded;loaded_partition->verify_result = ret;image_buf = NULL;}fail:if (image_buf != NULL && !image_preloaded) {avb_free(image_buf);}return ret;}
由以上代码可以发现,根据哈希算法(SHA-256或 SHA-512)计算boot分区的哈希值,与读取出来的存在boot分区的hash进行比较,若通过,完整性校验通过。此处的作为对比的expected_digest通过read_persistent_digest从boot镜像中读取digest,这里要说明的是,boot.img在构建的时候会存储public_key和signature。该signature生成主要分为两步:
1.sha256/sha512后的boot.img得到digest,保存到vbmeta中。
2.digest通过private_key进行加签得到signature,和public_key一起保存在boot.img中。
总结
所以我们可以总结出如下的防御方案:
bootrom检验uboot的完整性,uboot检验vbmeta,公钥写opt 或者存hsm用于验证时候调用,这种情况下即使vbmeta的签名可被修改,但publickey对其校验仍然会校验失败:
虽然此项技术要求在主机厂正常的信息安全技术要求里已经被提为必须向,但还是有很多Tier1友友考虑到成本问题会考虑不使用hsm抑或是走偏离不实施安全启动与升级,此篇技术文章也是比较详尽的拆解了固件逆向的可能性,告诉大家我们制定正向解决方案的来源依据。
本文由 猪皮苏、朱文勇、Mr Huang、关江辉 原创,转载请注明来源。
球分享
球点赞
球在看