[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獲取到所有需要的文件了;

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章