news 2026/4/20 15:52:36

手把手教你从A/B OTA的boot.img里挖出recovery.img(以Essential PH-1为例)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
手把手教你从A/B OTA的boot.img里挖出recovery.img(以Essential PH-1为例)

深度解析:从A/B分区设备的boot.img中精准提取recovery.img的技术实践

Essential PH-1这类采用A/B分区的设备,其系统更新机制与传统Android设备有着本质区别。最显著的特征就是recovery分区不再独立存在,而是被整合到了boot.img中。这种设计虽然提升了系统更新的可靠性和无缝体验,却给需要提取recovery.img进行定制开发的用户带来了新的挑战。本文将详细剖析这一技术实现原理,并提供一个完整的解决方案。

1. A/B分区机制与recovery整合的技术背景

现代Android设备采用A/B分区设计的主要目的是实现无缝系统更新(Seamless Updates)。在这种机制下,设备会维护两套完整的系统分区(A和B),当更新系统时,会在后台更新非活动分区,用户无需长时间等待更新完成,只需简单重启即可切换到新系统。

这种设计带来的关键变化包括:

  • recovery功能被整合到boot分区中,不再需要独立的recovery分区
  • 系统更新过程更加可靠,即使更新失败也能回退到原系统
  • 减少了用户可见的更新等待时间

在传统的非A/B分区设备中,recovery.img是一个独立的分区镜像,包含了完整的恢复环境和工具。而在A/B分区设备中,恢复功能被实现为boot.img中ramdisk的一部分,通常被称为"recovery-as-boot"模式。

A/B分区与传统分区的关键差异对比:

特性A/B分区设备传统分区设备
recovery位置整合到boot.img的ramdisk中独立recovery分区
系统更新方式后台更新非活动分区直接更新当前分区
更新失败处理自动回退到原系统可能导致系统无法启动
用户影响几乎无感知需要等待更新完成

理解这一技术背景对于后续正确提取recovery.img至关重要。开发者需要明确,在A/B分区设备上寻找独立的recovery.img是徒劳的,必须从boot.img入手。

2. 获取boot.img的完整流程

要从设备获取boot.img,有几种不同的方法,具体取决于你的设备和可用的资源。以下是三种常见的获取方式及其详细步骤:

2.1 从官方固件包提取

大多数厂商会提供完整的固件包,其中包含boot.img。以Essential PH-1为例:

  1. 下载官方固件包(通常为.zip格式)
  2. 解压后查找payload.bin文件
  3. 使用payload-dumper工具提取boot.img:
git clone https://github.com/vm03/payload_dumper cd payload_dumper pip install -r requirements.txt python payload_dumper.py payload.bin

提取完成后,你会在output目录中找到boot.img。

2.2 从设备直接dump

如果你已经root了设备,可以直接从设备上dump出boot分区:

adb shell su dd if=/dev/block/bootdevice/by-name/boot_a of=/sdcard/boot.img exit adb pull /sdcard/boot.img

2.3 从LineageOS等第三方ROM提取

如果你正在编译LineageOS,可以从构建输出中获取boot.img:

cd $OUT ls boot.img

无论采用哪种方式获取boot.img,都建议在操作前验证文件的完整性:

file boot.img # 应显示为Android bootimg ls -lh boot.img # 检查文件大小是否合理

3. 解析boot.img结构:深入理解Android启动镜像格式

Android的boot.img是一个结构化的二进制文件,遵循特定的格式标准。要准确提取其中的recovery部分,必须首先理解其内部结构。

3.1 boot.img头部结构

boot.img的开头是一个固定大小的头部,包含了加载内核和ramdisk所需的所有信息。我们可以通过分析system/core/mkbootimg/include/bootimg/bootimg.h头文件来理解其结构:

struct boot_img_hdr_v0 { uint8_t magic[BOOT_MAGIC_SIZE]; // "ANDROID!" uint32_t kernel_size; // 内核大小 uint32_t kernel_addr; // 内核加载地址 uint32_t ramdisk_size; // ramdisk大小 uint32_t ramdisk_addr; // ramdisk加载地址 uint32_t second_size; // 第二阶段加载器大小 uint32_t second_addr; // 第二阶段加载器地址 uint32_t tags_addr; // 内核标签地址 uint32_t page_size; // 页大小(通常为4096) uint32_t header_version; // 头部版本 uint32_t os_version; // OS版本信息 uint8_t name[BOOT_NAME_SIZE]; // 产品名称 uint8_t cmdline[BOOT_ARGS_SIZE]; // 内核命令行 uint32_t id[8]; // 唯一标识 uint8_t extra_cmdline[BOOT_EXTRA_ARGS_SIZE]; // 额外命令行 };

3.2 使用hexdump分析实际boot.img

我们可以使用hexdump工具查看boot.img的实际内容:

hexdump -C -n 64 boot.img

输出示例:

00000000 41 4e 44 52 4f 49 44 21 a4 1c f2 00 00 80 00 00 |ANDROID!........| 00000010 1a 1c cc 00 00 00 20 02 00 00 00 00 00 00 f0 00 |...... .........| 00000020 00 10 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 00000030 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|

从输出中可以解析出:

  • 魔数:41 4E 44 52 4F 49 44 21 ("ANDROID!")
  • 内核大小:a4 1c f2 00 (小端,0x00f21ca4 = 15867044字节)
  • 内核地址:00 80 00 00 (0x00008000)
  • ramdisk大小:1a 1c cc 00 (0x00cc1c1a = 13382682字节)
  • ramdisk地址:00 00 20 02 (0x02200000)

3.3 计算ramdisk的实际偏移

根据boot.img的格式规范,各部分内容都是按页大小对齐的。通常页大小为4096字节(0x1000)。计算ramdisk偏移的步骤如下:

  1. 头部占用1页:4096字节
  2. 内核大小:15867044字节
    • 计算页数:ceil(15867044 / 4096) = 3875页
  3. ramdisk偏移 = 头部 + 内核 = 4096 + 15867044 = 15871140字节
    • 十六进制:0x00F23000

可以使用以下命令验证:

printf "0x%X\n" $(( (15867044 + 4096 + 4095) / 4096 * 4096 ))

4. 精确提取ramdisk中的recovery内容

理解了boot.img结构后,我们可以准确提取包含recovery的ramdisk部分。

4.1 使用dd提取ramdisk

根据前面计算的偏移量,使用dd命令提取ramdisk:

dd if=boot.img of=ramdisk-recovery.img bs=4096 skip=3875

参数说明:

  • if=boot.img:输入文件
  • of=ramdisk-recovery.img:输出文件
  • bs=4096:块大小为4096字节(1页)
  • skip=3875:跳过3875个块(头部1块 + 内核3874块)

4.2 验证提取的ramdisk

提取完成后,应该验证文件的有效性:

file ramdisk-recovery.img

预期输出类似于:

ramdisk-recovery.img: gzip compressed data, from Unix

如果输出不符合预期,可能是偏移计算错误,需要重新检查。

5. 解压和处理ramdisk内容

提取出的ramdisk-recovery.img实际上是经过gzip压缩的cpio归档文件,需要进一步处理才能得到可用的文件系统。

5.1 解压gzip压缩

首先重命名文件以反映其实际格式,然后解压:

mv ramdisk-recovery.img ramdisk-recovery.gz gunzip -v ramdisk-recovery.gz

解压后会得到ramdisk-recovery文件。

5.2 识别解压后的格式

再次使用file命令确认文件类型:

file ramdisk-recovery

预期输出:

ramdisk-recovery: ASCII cpio archive (SVR4 with no CRC)

5.3 提取cpio归档内容

创建一个目录用于存放提取的文件,然后使用cpio解包:

mkdir recovery cd recovery cpio -idv < ../ramdisk-recovery

成功执行后,当前目录会包含recovery环境的完整文件结构。

5.4 验证提取结果

检查提取的文件是否完整:

ls -l

应该能看到典型的Android根目录结构,包括:

  • init:初始化程序
  • init.rc:初始化脚本
  • sbin/:系统工具目录
  • etc/:配置文件目录
  • system/:系统目录链接

6. 常见问题与解决方案

在实际操作过程中,可能会遇到各种问题。以下是几个常见问题及其解决方法:

6.1 提取的ramdisk无法解压

症状:执行gunzip时报告"not in gzip format"错误。

可能原因

  1. 偏移计算错误,提取的内容不正确
  2. 设备使用了非标准的boot.img格式

解决方案

  1. 重新验证偏移计算
  2. 尝试不同的偏移量(前后调整几页)
  3. 检查设备是否使用了特殊的压缩或加密方式

6.2 cpio提取失败

症状:cpio命令报告"no cpio magic"或类似错误。

可能原因

  1. ramdisk可能使用了其他归档格式(如Android的new format)
  2. 文件在解压过程中损坏

解决方案

  1. 尝试使用file命令确认实际格式
  2. 对于Android新的格式,可能需要使用magiskboot工具处理
  3. 重新从原始boot.img开始流程

6.3 提取的文件不完整

症状:提取后的文件系统中缺少关键组件(如init二进制)。

可能原因

  1. 设备使用了双层ramdisk结构
  2. 某些文件被压缩或打包在子归档中

解决方案

  1. 检查init是否可能是符号链接
  2. 查找是否有额外的cpio或压缩文件需要处理
  3. 参考设备特定的文档或开发者资源

7. 自动化脚本实现

为了提高效率,可以将上述流程编写为自动化脚本。以下是一个示例脚本:

#!/bin/bash # 参数检查 if [ $# -ne 1 ]; then echo "Usage: $0 <boot.img>" exit 1 fi BOOTIMG=$1 OUTDIR=recovery_extracted # 解析boot.img头部 HEADER=$(hexdump -n 32 -e '8/1 "%02X ""\n"' $BOOTIMG) MAGIC=$(echo $HEADER | awk '{print $1$2$3$4$5$6$7$8}') if [ "$MAGIC" != "414E44524F494421" ]; then echo "Invalid boot.img magic: $MAGIC" exit 1 fi # 提取内核和ramdisk大小 KERNEL_SIZE=$((0x$(echo $HEADER | awk '{print $9$10$11$12}' | sed 's/\(..\)\(..\)\(..\)\(..\)/\4\3\2\1/'))) RAMDISK_SIZE=$((0x$(echo $HEADER | awk '{print $13$14$15$16}' | sed 's/\(..\)\(..\)\(..\)\(..\)/\4\3\2\1/'))) # 计算ramdisk偏移 PAGE_SIZE=4096 HEADER_PAGES=1 KERNEL_PAGES=$(( ($KERNEL_SIZE + $PAGE_SIZE - 1) / $PAGE_SIZE )) RAMDISK_OFFSET=$(( ($HEADER_PAGES + $KERNEL_PAGES) * $PAGE_SIZE )) # 提取ramdisk echo "Extracting ramdisk from offset $RAMDISK_OFFSET..." dd if=$BOOTIMG of=ramdisk-recovery.img bs=$PAGE_SIZE skip=$((RAMDISK_OFFSET/PAGE_SIZE)) 2>/dev/null # 解压处理 echo "Processing ramdisk..." mkdir -p $OUTDIR ( mv ramdisk-recovery.img ramdisk-recovery.gz gunzip ramdisk-recovery.gz || { echo "gunzip failed, trying alternative methods..."; exit 1; } cd $OUTDIR cpio -idv < ../ramdisk-recovery 2>/dev/null ) echo "Recovery files extracted to $OUTDIR/"

使用方式:

chmod +x extract_recovery.sh ./extract_recovery.sh boot.img

8. 进阶技巧与优化

对于需要频繁进行此类操作的高级用户,可以考虑以下优化:

8.1 使用更专业的工具链

除了基本的dd和cpio,还可以使用专门为Android开发的工具:

  • magiskboot:来自Magisk项目的多功能boot.img处理工具
  • abootimg:专门用于处理Android boot镜像的工具集
  • unpackbootimg:传统的boot.img解包工具

例如,使用magiskboot可以简化流程:

magiskboot unpack boot.img magiskboot cpio ramdisk.cpio extract

8.2 处理特殊压缩格式

某些设备可能使用非标准的压缩方式:

  • LZ4压缩:需要使用lz4工具解压
  • XZ压缩:需要使用xz工具解压
  • 多层压缩:可能需要多次解压

8.3 验证提取的完整性

为确保提取的内容完整,可以进行以下验证:

  1. 检查init二进制是否存在且可执行
  2. 验证关键目录结构(如/sbin、/etc)
  3. 检查设备特定的配置文件(如fstab.*)

8.4 重新打包ramdisk

在某些情况下,可能需要修改后重新打包ramdisk:

cd recovery find . | cpio -o -H newc > ../new-ramdisk.cpio cd .. gzip new-ramdisk.cpio mv new-ramdisk.cpio.gz new-ramdisk.img

9. 实际应用场景

成功提取recovery.img后,可以应用于多种场景:

9.1 提取专有驱动文件

如文章开头提到的场景,LineageOS编译需要从recovery中提取专有驱动文件:

./extract-files.sh --from-recovery

9.2 定制recovery环境

可以修改提取的文件,创建自定义的recovery环境:

  1. 添加自定义工具到/sbin
  2. 修改init.rc脚本
  3. 替换或修改恢复界面

9.3 调试和分析

提取的recovery环境可用于:

  1. 分析厂商的恢复实现
  2. 调试硬件初始化问题
  3. 研究系统启动流程

10. 安全注意事项

在进行这些操作时,需要注意以下安全事项:

  1. 始终备份原始boot.img
  2. 在修改前验证所有更改
  3. 避免在生产设备上实验未经验证的修改
  4. 注意文件权限和SELinux上下文
  5. 确保任何定制不会破坏系统完整性验证

对于采用verified boot的设备,修改boot.img可能需要处理签名验证:

# 禁用验证(仅限调试) fastboot flashing unlock fastboot boot modified_boot.img
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/20 15:48:33

3分钟快速上手:ModTheSpire终极模组加载器完全指南

3分钟快速上手&#xff1a;ModTheSpire终极模组加载器完全指南 【免费下载链接】ModTheSpire External mod loader for Slay The Spire 项目地址: https://gitcode.com/gh_mirrors/mo/ModTheSpire ModTheSpire是Slay The Spire游戏的专业模组加载器&#xff0c;让你无需…

作者头像 李华
网站建设 2026/4/20 15:47:22

GBase 8s ER 影子列解析

影子列是复制的表上被隐藏的列&#xff0c;其包含由数据库服务器提供的值。数据库服务器使用影子列来执行内部操作。可以 CREATE TABLE 或 ALTER TABLE 语句来将影子列添加至复制的表。 要查看影子列的内容&#xff0c;必须在 SELECT 语句的投影列表中显式地指定该列&#xff1…

作者头像 李华
网站建设 2026/4/20 15:46:19

为什么企业都爱用 MySQL?从网络安全工程师视角,把它的来龙去脉讲透

如果你现在去看企业里的 Web 系统、后台管理系统、电商项目、内容平台、用户中心,十有八九能碰到一个名字:MySQL。 很多初学者会觉得: “数据库不就是 MySQL 吗?” 其实不是。数据库有很多种,关系型数据库里也有 Oracle、DB2、SQL Server 等重量级选手。但为什么偏偏 My…

作者头像 李华
网站建设 2026/4/20 15:43:13

深度解析FanControl:Windows平台风扇控制全面指南

深度解析FanControl&#xff1a;Windows平台风扇控制全面指南 【免费下载链接】FanControl.Releases This is the release repository for Fan Control, a highly customizable fan controlling software for Windows. 项目地址: https://gitcode.com/GitHub_Trending/fa/Fan…

作者头像 李华