[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鏡像文件。
於是,解決辦法也就有了:從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 前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.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,因此可以忽略
分析:
- kernel大小爲0x00f21ca4(15867044),算上文件頭的1頁,因此kernel會佔用:
(4096 + 15867044) / 4096 = 3874.79… - 考慮頁對齊,因此會佔用3875頁,因此ramdisk內容是boot.img文件偏移0x00F23000開始;
- 而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獲取到所有需要的文件了;