[EXPTECH]__Extract_Image_from_bootimg

之前写了如何从kernel Image文件中静态提取符号地址,后来就有就有朋友问道image文件怎么得到。其实这个已经有很多前辈总结过了。就简单说先

获取boot.img

boot.img 一般有两种方法拿到:

  • 1.从厂商提供的刷机包中提取
    这种方法很直观,介绍略
  • 2.从手机中提取
    首先在手机中找到boot对应的分区

    1
    ls /dev/block/platform/<soc>/by-name |grep boot

    然后用dd命令将对应分区dump出来即可
    可以参考从Android设备中提取内核和逆向分析

解包boot.img

在Android源码中已经给出了boot.img的结构
代码位置: /system/core/mkbootimg/bootimg.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
struct boot_img_hdr
{
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 */
uint32_t unused; /* reserved for future expansion: MUST be 0 */
/* 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));
/*
** +-----------------+
** | 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
*/

所以,首先解析boot header

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def getimghead(self):
self.imghead.MAGICNUM = self.imgdata[0:8]
self.imghead.kernel_size,\
self.imghead.kernel_addr,\
self.imghead.ramdisk_size,\
self.imghead.ramdisk_addr,\
self.imghead.second_size,\
self.imghead.ramdisk_addr,\
self.imghead.tags_addr,\
self.imghead.page_size,\
self.imghead.unused,\
self.imghead.os_version = struct.unpack_from('10I', self.imgdata, 8)
self.imghead.name,self.imghead.cmdline = struct.unpack_from('16s512s',self.imgdata,48)
self.imghead.id = struct.unpack_from("32s",self.imgdata,576)[0].encode('hex')[:40]
self.imghead.extra_cmdline = struct.unpack_from('1024s',self.imgdata,608)

取得page_size,从第二个page开始读取kernel_size就可以获取到kernel文件了,但是取得的kernel有可能是压缩过的。还需要进一步处理

这里大致介绍一下kernel image文件的类型,首先编译出来的kernel是vmlinux,这是一个elf文件,但是由于kernel加载进内存执行的时候系统几乎还是一片空白的状态,是不能解析elf的。所以通常都用objcopy将vmlinux转换成bin文件,后面考虑到size问题,有可能还需要进行压缩。通常Android的kernel有这几种:

  • Image -> vmlinux objcopy之后的bin文件
  • Image.gz -> vmlinux objcopy之后的bin文件 再进行gzip压缩 (在bootloader阶段解压)
  • zImage -> vmlinux objcopy之后的bin文件 再进行gzip压缩 再在前面部署自解压代码 (bootloader调用自解压代码解压)
  • XXX-dtb -> 带有dtb后缀的kernel文件,是在后面追加了device tree数据的kernel

Image.gz实际上就是一个gzip文件,以\1f\8b\08开头
zImage 以8个\00\00\a0\e1开头,前面是自解压代码,image.gz文件接在后面,可以用\1f\8b\08定位
(PS:dtb数据的magic num是 \d0\0d\fe\ed)

定位到压缩后的kernel后 提取出来解压就得到Image文件了。


code:ImgExtractor

同时把ramdisk.img也提取了,用cpio处理即可

kernel文件静态分析的部分,就暂时告一段落,后面开始写bypass技术。