[Android][boot]从支持A/B FOTA的设备中提取recovery.img

问题由来

五一节期间,不太想做和工作强相关的内容,但是无奈手痒痒,于是想将手上的一台Essential PH-1的系统更换为lineageOS 16。

参考:官方WIKI

在搭建编译环境时卡在了Extract proprietary blobs这一步,具体原因是因为extract-files.sh脚本需要提取的定义在proprietary-files-recovery.txt中的文件需要提取recovery下的源文件,而当前的软件中并没有将recovery挂载在系统上。

因此我选择采用wiki中介绍的第二种方式获取这部分文件:Extracting proprietary blobs from installable zip.,我按照步骤下载了nightly build的zip包,并按照步骤进行了拆包与挂载,然而依旧没有recovery的信息。

由于Essential PH-1采用了A/B OTA机制,因此按照Google设计,可以将recovery.img打入boot.img的ramdisk部分,因此无法直接从zip安装包中解析出单独的recovery.img镜像文件。

分区和镜像对应关系官网介绍
A/B OTA官网介绍

于是,解决办法也就有了:从boot.img中提取recovery.img镜像,并将其拆开,获取需要的文件;

第一步 获取boot.img

这一步其实很简单,根据官方WIKI一步一步走就可以了,Essential PH-1的nightly build打包方式是Payload-based OTA,因此参考Extracting proprietary blobs from payload-based OTAs即可。

需要注意的是,拆分payload.bin时需要python运行环境,且根据每个人不同,需要事先安装如下依赖:

pip install google-cloud
pip install protobuf
pip install google
pip install backports.lzma

提取完成后可以看到zip包等效的各个分区镜像,当然也包括boot.img;

第二步 提取recovery.img

这一步需要一定的内核知识,以及二进制文件查看的工具(UltraEdit或其他类似工具);

首先我们查看代码中打包boot.img是参照的文件结构:

代码路径:system/core/mkbootimg/include/bootimg/bootimg.h

#define BOOT_MAGIC "ANDROID!"
#define BOOT_MAGIC_SIZE 8
#define BOOT_NAME_SIZE 16
#define BOOT_ARGS_SIZE 512
#define BOOT_EXTRA_ARGS_SIZE 1024

#define BOOT_HEADER_VERSION_ZERO 0
/*
 *  Bootloader expects the structure of boot_img_hdr with header version
 *  BOOT_HEADER_VERSION_ZERO to be as follows:
 */
struct boot_img_hdr_v0 {
    uint8_t magic[BOOT_MAGIC_SIZE];

    uint32_t kernel_size; /* size in bytes */
    uint32_t kernel_addr; /* physical load addr */

    uint32_t ramdisk_size; /* size in bytes */
    uint32_t ramdisk_addr; /* physical load addr */

    uint32_t second_size; /* size in bytes */
    uint32_t second_addr; /* physical load addr */

    uint32_t tags_addr; /* physical addr for kernel tags */
    uint32_t page_size; /* flash page size we assume */
    /*
     * version for the boot image header.
     */
    uint32_t header_version;

    /* operating system version and security patch level; for
     * version "A.B.C" and patch level "Y-M-D":
     * ver = A << 14 | B << 7 | C         (7 bits for each of A, B, C)
     * lvl = ((Y - 2000) & 127) << 4 | M  (7 bits for Y, 4 bits for M)
     * os_version = ver << 11 | lvl */
    uint32_t os_version;

    uint8_t name[BOOT_NAME_SIZE]; /* asciiz product name */

    uint8_t cmdline[BOOT_ARGS_SIZE];

    uint32_t id[8]; /* timestamp / checksum / sha1 / etc */

    /* Supplemental command line data; kept here to maintain
     * binary compatibility with older versions of mkbootimg */
    uint8_t extra_cmdline[BOOT_EXTRA_ARGS_SIZE];
} __attribute__((packed));

/*
 * It is expected that callers would explicitly specify which version of the
 * boot image header they need to use.
 */
typedef struct boot_img_hdr_v0 boot_img_hdr;

/* When a boot header is of version BOOT_HEADER_VERSION_ZERO, the structure of boot image is as
 * follows:
 *
 * +-----------------+
 * | boot header     | 1 page
 * +-----------------+
 * | kernel          | n pages
 * +-----------------+
 * | ramdisk         | m pages
 * +-----------------+
 * | second stage    | o pages
 * +-----------------+
 *
 * n = (kernel_size + page_size - 1) / page_size
 * m = (ramdisk_size + page_size - 1) / page_size
 * o = (second_size + page_size - 1) / page_size
 *
 * 0. all entities are page_size aligned in flash
 * 1. kernel and ramdisk are required (size != 0)
 * 2. second is optional (second_size == 0 -> no second)
 * 3. load each element (kernel, ramdisk, second) at
 *    the specified physical address (kernel_addr, etc)
 * 4. prepare tags at tag_addr.  kernel_args[] is
 *    appended to the kernel commandline in the tags.
 * 5. r0 = 0, r1 = MACHINE_TYPE, r2 = tags_addr
 * 6. if second_size != 0: jump to second_addr
 *    else: jump to kernel_addr
 */
 
#define BOOT_HEADER_VERSION_ONE 1

由上方代码可以获取两个信息:

  1. 文件头结构;
    1.1 前8个字节固定为ANDROID! (41 4E 44 52 4F 49 44 21)
    1.2 9-12个字节对应kernel的大小(ARM架构为小端显示,下同)
    1.3 13-16个字节对应加载kernel的物理地址(默认情况下都为00 80 00 00,同样为小端显示,即0x00008000)
    1.4 17-20个字节对应ramdisk的大小;
    1.5 21-24个字节对应加载ramdisk的物理地址;
  2. 偏移地址的定义;
    2.1 文件内容页对其,当前系统默认4K为一页,因此size不足4K的,会用0补齐;
以我的这颗boot.img为例,文件头信息如下:
41 4e 44 52 4f 49 44 21 //固定头,ANDROID!
a4 1c f2 00 00 80 00 00 //kernel大小为0x00f21ca4,加载到物理内存的0x00008000
1a 1c cc 00 00 00 20 02 //ramdisk大小为0x00cc1c1a,加载到物理内存的0x02200000
00 00 00 00 00 00 f0 00 //second stage大小为0,因此可以忽略

分析:

  1. kernel大小为0x00f21ca4(15867044),算上文件头的1页,因此kernel会占用:
    (4096 + 15867044) / 4096 = 3874.79…
  2. 考虑页对齐,因此会占用3875页,因此ramdisk内容是boot.img文件偏移0x00F23000开始;
  3. 而second stage为0,即不存在,因此ramdisk应该是从0x00F23000开始到整个文件结尾,于是使用指令:
dd if=boot.img of=ramdisk-recovery.img bs=4096 skip=3875

将boot.img中0x00F23000到文件结尾的部分截取出来,命名为ramdisk-recovery.img;

第三步 从recovery.img中提取文件

首先我们确认下这个ramdisk-recovery.img的格式:

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

显然是个gz压缩后的文件,因此我们使用gunzip解压:

$ move ramdisk-recovery.img ramdisk-recovery.gz
$ gunzip -v ramdisk-recovery.gz

完成后可以得到ramdisk-recovery,再对其执行file获取其文件格式:

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

此时我们就可以使用cpio将该文件中的内容提取出来了:

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

total 3264
drwxrwxr-x 23 user user    4096 May  3 14:46 ./
drwxrwxr-x  5 user user    4096 May  3 17:55 ../
drwxr-xr-x  2 user user    4096 May  3 14:46 acct/
lrwxrwxrwx  1 user user      11 May  3 14:46 bin -> /system/bin
drwxr-xr-x  2 user user    4096 May  3 14:46 bt_firmware/
lrwxrwxrwx  1 user user      50 May  3 14:46 bugreports -> /data/user_de/0/com.android.shell/files/bugreports
dr-xr-xr-x  2 user user    4096 May  3 14:46 config/
lrwxrwxrwx  1 user user      17 May  3 14:46 d -> /sys/kernel/debug/
drwxrwx--x  2 user user    4096 May  3 14:46 data/
lrwxrwxrwx  1 user user      12 May  3 14:46 default.prop -> prop.default
drwxr-xr-x  2 user user    4096 May  3 14:46 dev/
drwxr-xr-x  2 user user    4096 May  3 14:46 dsp/
drwxr-xr-x  4 user user    4096 May  3 14:46 etc/
drwxr-xr-x  2 user user    4096 May  3 14:46 firmware/
-rw-r-----  1 user user     353 May  3 14:46 fstab.recovery.mata
-rwxr-x---  1 user user 2216816 May  3 14:46 init*
-rwxr-x---  1 user user    4371 May  3 14:46 init.rc*
-rwxr-x---  1 user user     696 May  3 14:46 init.recovery.mata.rc*
drwxr-xr-x  2 user user    4096 May  3 14:46 mnt/
drwxr-xr-x  2 user user    4096 May  3 14:46 odm/
drwxr-xr-x  2 user user    4096 May  3 14:46 oem/
drwxr-xr-x  2 user user    4096 May  3 14:46 persist/
-rw-r--r--  1 user user   26662 May  3 14:46 plat_file_contexts
-rw-r--r--  1 user user   31900 May  3 14:46 plat_property_contexts
drwxr-xr-x  2 user user    4096 May  3 14:46 postinstall/
drwxr-xr-x  2 user user    4096 May  3 14:46 proc/
lrwxrwxrwx  1 user user      15 May  3 14:46 product -> /system/product
-rw-r--r--  1 user user    7812 May  3 14:46 prop.default
drwxr-xr-x  3 user user    4096 May  3 14:46 res/
drwxr-x---  2 user user    4096 May  3 14:46 sbin/
drwxrwxrwx  2 user user    4096 May  3 14:46 sdcard/
-rw-r--r--  1 user user  831792 May  3 14:46 sepolicy
drwxr-x--x  2 user user    4096 May  3 14:46 storage/
drwxr-xr-x  2 user user    4096 May  3 14:46 sys/
lrwxrwxrwx  1 user user      19 May  3 14:46 system -> /system_root/system
drwxr-xr-x  2 user user    4096 May  3 14:46 system_root/
drwxr-xr-x  2 user user    4096 May  3 14:46 tmp/
-rw-r--r--  1 user user    5359 May  3 14:46 ueventd.rc
-rw-r--r--  1 user user   68187 May  3 14:46 vendor_file_contexts
-rw-r--r--  1 user user   22488 May  3 14:46 vendor_property_contexts
-rw-r--r--  1 user user     524 May  3 14:46 verity_key

至此,recovery中的文件提取完毕;
结合之前挂载的system与vendor,可以通过./extract-files.sh获取到所有需要的文件了;

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值