0
点赞
收藏
分享

微信扫一扫

Android Update Engine分析(十三) 提取 payload 的 property 数据

米小格儿 2022-01-23 阅读 160

Android Update Engine分析(十三) 提取 payload 的 property 数据

在《Android Update Engine 分析(九)delta_generator 工具的 6 种操作》中提到了,delta_generator 工具提供的 6 种操作,分别是:

  1. 生成 payload 和 metadata 数据的哈希值
  2. 更新 payload 和 metadata 数据的签名
  3. 使用公钥验证 payload 和 metadata 数据的签名
  4. 提取 payload 文件的 property 数据
  5. 对 old image 打 delta 补丁
  6. 生成 payload 数据

前面三篇分别分析了,如何生成 payload 的哈希,如何对 payload 的哈希签名,如何将 payload 的签名写回 payload 文件中。本篇分析如何提取 payload 文件的属性数据,包括 payload 的 “FILE_SIZE” 和 “FILE_HASH”,以及 metadata 数据的 “METADATA_SIZE” 和 “METADATA_HASH”。

本篇的内容也比较简单,主要包括:

  1. 分析代码如何从 payload 中提取属性数据。包括: “FILE_SIZE”, “FILE_HASH”, “METADATA_SIZE” 和 “METADATA_HASH
  2. 手动在命令行计算 payload 和 metadata 的属性数据

如果你只想大致了解 payload 属性数据生成的大致流程,或者如何在命令行手动获取 payload 的属性数据,请直接跳转到本文的第 3 节看总结。

1. 代码如何从 payload 中提取属性数据

1.1 delta_generator 的 “-properites_file” 参数

调用 ota_from_target_files 脚本生成和处理 payload.bin 数据的最后一步便是从最终的 payload.bin 文件中提取属性数据写入到 payload properties 文件中:

# 步骤 4. 提取签名后的 payload 文件的 properties 数据
# delta_generator -in_file=/tmp/signed-payload.bin \
#                 -properties_file=/tmp/signed-payload-properties.txt

在这里提取 payload 属性数据时,需要通过 “-properites_file” 参数传入生成的属性文件名。

1.2 “-properites_file” 参数的解析和使用

generate_delta_main.cc 文件的 Main 函数中解析 “-properties_file” 参数,并传递给 ExtractProperties 进行操作。

  // 如果调用 delta_gnerator 时提供了 --properties_file 参数,
  // 则提取 payload 文件的一些属性参数并存放到这个文件中
  if (!FLAGS_properties_file.empty()) {
    return ExtractProperties(FLAGS_in_file, FLAGS_properties_file) ? 0 : 1;
  }

ExtractProperties 进一步调用 PayloadSigner::ExtractPayloadProperties 去获取 payload 的属性数据,获取到属性数据后将其写入到指定的文件中。特别要指出的是,如果用于输出的文件名为 “-”, 则会将属性数据通过 printf 操作打印出来。

int ExtractProperties(const string& payload_path, const string& props_file) {
  brillo::KeyValueStore properties;
  // 实际提取 payload 属性数据的是 PayloadSigner::ExtractPayloadProperties 函数
  // 其属性数据就是多个 key-value 的键值对
  TEST_AND_RETURN_FALSE(
      PayloadSigner::ExtractPayloadProperties(payload_path, &properties));
  // 如果属性文件为 "-",即标准输出,则打印属性数据
  if (props_file == "-") {
    printf("%s", properties.SaveToString().c_str());
  } else {
    // 将属性数据写入文件中
    properties.Save(base::FilePath(props_file));
    LOG(INFO) << "Generated properties file at " << props_file;
  }
  return true;
}

1.3 提取 payload 属性数据的详细操作

通过多层转包,最终在 PayloadSigner::ExtractPayloadProperties 函数中执行提取 payload 属性数据的操作。

bool PayloadSigner::ExtractPayloadProperties(
    const string& payload_path, brillo::KeyValueStore* properties) {
  DeltaArchiveManifest manifest;
  brillo::Blob payload_metadata;
  uint64_t major_version, metadata_size;
  uint32_t metadata_signature_size;
  // 获取 payload 文件大小
  uint64_t file_size = utils::FileSize(payload_path);

  TEST_AND_RETURN_FALSE(
      // 解析 payload 文件的 metadata 部分, 包括:
      // - magic
      // - file_format_version
      // - manifest_size
      // - metadata_signature_size
      // - manifest
      PayloadSigner::LoadPayloadMetadata(payload_path,
                                         &payload_metadata,
                                         &manifest,
                                         &major_version,
                                         &metadata_size,
                                         &metadata_signature_size));

  // 将 payload 文件大小保存到 "FILE_SIZE" 中
  properties->SetString(kPayloadPropertyFileSize, std::to_string(file_size));
  // 将 metadata 数据大小保存到 "METADATA_SIZE" 中
  properties->SetString(kPayloadPropertyMetadataSize,
                        std::to_string(metadata_size));

  brillo::Blob file_hash, metadata_hash;
  TEST_AND_RETURN_FALSE(
      // 计算整个 payload 文件的哈希值, 相当于执行命令:
      // $ sha256sum payload.bin
      HashCalculator::RawHashOfFile(payload_path, file_size, &file_hash) ==
      static_cast<off_t>(file_size));
  // 计算 metadata 数据的哈希值, 相当于执行命令:
  // $ dd if=payload.bin bs=1 count=21238 | sha256sum
  TEST_AND_RETURN_FALSE(HashCalculator::RawHashOfBytes(
      payload_metadata.data(), payload_metadata.size(), &metadata_hash));

  // 将 payload 文件的哈希进行 base64 编码后保存到 "FILE_HASH" 中, 相当于执行命令:
  // $ sha256sum payload.bin | awk '{print $1}' | xxd -r -ps | base64
  properties->SetString(kPayloadPropertyFileHash,
                        brillo::data_encoding::Base64Encode(file_hash));
  // 将 metdata 数据的哈希进行 base64 编码后保存到 "METADATA_HASH" 中, 相当于执行命令:
  // $ dd if=payload.bin bs=1 count=21238 | sha256sum | awk '{print $1}' | xxd -r -ps | base64
  properties->SetString(kPayloadPropertyMetadataHash,
                        brillo::data_encoding::Base64Encode(metadata_hash));
  return true;
}

复述下这里的操作:

  1. 解析 payload 文件头部的 metadata 数据,获取 metadata 相关信息
  2. 计算整个 payload.bin 文件的 sha256 哈希
  3. 计算 metadata 数据的 sha256 哈希
  4. 将 payload.bin 文件和 metadata 数据的哈希进行 base64 编码,和相关数据的大小一起组成键值对返回,所有的键值对包括:"FILE_SIZE", “FILE_HASH”, “METADATA_SIZE”, “METADATA_HASH

2. 手动提取 payload 的属性数据

2.1 分析 payload 的 metadata

在《Android Update Engine 分析(十)生成 payload 和 metadata 的哈希》的第 4 节(“4. 命令行手工计算 payload 和 metadata 的哈希”) 详细演示过如何通过命令行提取 metadata 和 payload 的哈希,这里直接分析 payload 头部的 metadata 数据:

payload 数据的头部 128 字节如下:

$ hexdump -Cv -n 128 payload.bin 
00000000  43 72 41 55 00 00 00 00  00 00 00 02 00 00 00 00  |CrAU............|
00000010  00 00 52 de 00 00 01 08  18 80 20 20 cb c2 e7 7d  |..R.......  ...}|
00000020  28 88 02 60 03 6a d1 07  0a 04 62 6f 6f 74 32 27  |(..`.j....boot2'|
00000030  08 80 c0 a3 09 12 20 b5  58 f2 3f 83 f2 01 ad 53  |...... .X.?....S|
00000040  09 88 ac 77 d3 48 1a bd  1f 76 6e 15 b4 8a d5 cf  |...w.H...vn.....|
00000050  ec 3f 56 b8 ef c7 90 3a  27 08 80 c0 a3 09 12 20  |.?V....:'...... |
00000060  7c ea 74 e8 96 57 ac bd  01 a1 fc eb 65 dc 1e 5e  ||.t..W......e..^|
00000070  a1 c7 7b 28 d0 c5 94 97  5c e9 84 aa db 82 71 41  |..{(....\.....qA|
00000080
$
  • offset 0: 4 字节的 magic [00 00 00 00 00 00 00 02] “CrAU”
  • offset 4: 8 字节的 file_format_version,[00 00 00 00 00 00 00 02], 大端格式,其值为 2
  • offset 12: 8 字节的 manifest_size,[00 00 00 00 00 00 52 de], 大端格式,其值为 0x52de,即十进制的 21214
  • offset 20: 4 字节的 metadata_signature_size,[00 00 01 08], 大端格式,其值为 0x0108,即十进制的 264

Payload 文件布局

2.2 获取 metadata 的大小和哈希

从 payload 文件的布局图可以看到,metadata 包含头部的 header 数据,以及 manifest 数据。
因此 metadata 的大小为: 24 bytes (header size) + 21214 (manifest size) = 21238

可以使用如下命令在命令行计算 metadata 的哈希值并进行 base64 编码:

$ dd if=payload.bin bs=1 count=21238 2>/dev/null | sha256sum | awk '{print $1}' | xxd -r -ps | base64
7+X3DTTfipevtV2qMEqoW576XsFgJGZd0ysjWNQATIM=

因此,对于 metadata:

  • “METADATA_SIZE=21238”
  • “METADATA_HASH=7+X3DTTfipevtV2qMEqoW576XsFgJGZd0ysjWNQATIM=”

2.3 获取 payload 的大小和哈希

$ ls -l payload.bin | awk '{print $5}'
263861841
$ sha256sum payload.bin | awk '{print $1}' | xxd -r -ps | base64
MgP07a/t+5s+y6CVP0hGok6DoVQx6Q6hg8E2bOnCu3k=

3. 总结

代码解析 payload 数据的头部 24 bytes 数据获取 manifest 的大小,从而确定整个 metadata 的大小,进而计算 metadata 的 sha256 哈希并进行 base64 编码。

代码同时获取 payload 整个文件的大小,并计算 payload 的 sha256 哈希并进行 base64 编码。

最终提取的属性数据包括:

  • payload 文件: “FILE_SIZE”, “FILE_HASH
  • metadata 数据: “METADATA_SIZE”, “METADATA_HASH

整个过程可以在命令行通过手工的方式计算:

#
# metadata
#
$ metadata_size=$((24 + 16#$(xxd -s 12 -l 8 -ps payload.bin))); \
  echo $metadata_size; \
  dd if=payload.bin bs=1 count=$metadata_size 2>/dev/null | sha256sum | awk '{print $1}' | xxd -r -ps | base64
21238
7+X3DTTfipevtV2qMEqoW576XsFgJGZd0ysjWNQATIM=

#
# payload
#
$ ls -l payload.bin | awk '{print $5}'; \
  sha256sum payload.bin | awk '{print $1}' | xxd -r -ps | base64
263861841
MgP07a/t+5s+y6CVP0hGok6DoVQx6Q6hg8E2bOnCu3k=

最后上一张我在 Android OTA 讨论群里验证哈希的截图。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gw4A0JRE-1642867170878)(WEBRESOURCE9d4db9528cb839840cf890ce8eb7dd9f)]

4. 其它

洛奇工作中常常会遇到自己不熟悉的问题,这些问题可能并不难,但因为不了解,找不到人帮忙而瞎折腾,往往导致浪费几天甚至更久的时间。

所以我组建了几个微信讨论群(记得微信我说加哪个群,如何加微信见后面),欢迎一起讨论:

  • 一个密码编码学讨论组,主要讨论各种加解密,签名校验等算法,请说明加密码学讨论群。
  • 一个Android OTA的讨论组,请说明加Android OTA群。
  • 一个git和repo的讨论组,请说明加git和repo群。

在工作之余,洛奇尽量写一些对大家有用的东西,如果洛奇的这篇文章让您有所收获,解决了您一直以来未能解决的问题,不妨赞赏一下洛奇,这也是对洛奇付出的最大鼓励。扫下面的二维码赞赏洛奇,金额随意:

收钱码

公众号

举报

相关推荐

0 条评论