1#!/usr/bin/env python 2# Copyright 2018, The Android Open Source Project 3# 4# Licensed under the Apache License, Version 2.0 (the "License"); 5# you may not use this file except in compliance with the License. 6# You may obtain a copy of the License at 7# 8# http://www.apache.org/licenses/LICENSE-2.0 9# 10# Unless required by applicable law or agreed to in writing, software 11# distributed under the License is distributed on an "AS IS" BASIS, 12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13# See the License for the specific language governing permissions and 14# limitations under the License. 15 16"""unpacks the bootimage. 17 18Extracts the kernel, ramdisk, second bootloader, dtb and recovery dtbo images. 19""" 20 21from __future__ import print_function 22from argparse import ArgumentParser, FileType 23from struct import unpack 24import os 25 26BOOT_IMAGE_HEADER_V3_PAGESIZE = 4096 27VENDOR_BOOT_IMAGE_HEADER_V3_SIZE = 2112 28 29def create_out_dir(dir_path): 30 """creates a directory 'dir_path' if it does not exist""" 31 if not os.path.exists(dir_path): 32 os.makedirs(dir_path) 33 34 35def extract_image(offset, size, bootimage, extracted_image_name): 36 """extracts an image from the bootimage""" 37 bootimage.seek(offset) 38 with open(extracted_image_name, 'wb') as file_out: 39 file_out.write(bootimage.read(size)) 40 41 42def get_number_of_pages(image_size, page_size): 43 """calculates the number of pages required for the image""" 44 return (image_size + page_size - 1) // page_size 45 46 47def cstr(s): 48 """Remove first NULL character and any character beyond.""" 49 return s.split('\0', 1)[0] 50 51 52def format_os_version(os_version): 53 a = os_version >> 14 54 b = os_version >> 7 & ((1<<7) - 1) 55 c = os_version & ((1<<7) - 1) 56 return '{}.{}.{}'.format(a, b, c) 57 58 59def format_os_patch_level(os_patch_level): 60 y = os_patch_level >> 4 61 y += 2000 62 m = os_patch_level & ((1<<4) - 1) 63 return '{:04d}-{:02d}'.format(y, m) 64 65 66def print_os_version_patch_level(value): 67 os_version = value >> 11 68 os_patch_level = value & ((1<<11) - 1) 69 print('os version: %s' % format_os_version(os_version)) 70 print('os patch level: %s' % format_os_patch_level(os_patch_level)) 71 72 73def unpack_bootimage(args): 74 """extracts kernel, ramdisk, second bootloader and recovery dtbo""" 75 kernel_ramdisk_second_info = unpack('9I', args.boot_img.read(9 * 4)) 76 version = kernel_ramdisk_second_info[8] 77 if version < 3: 78 print('kernel_size: %s' % kernel_ramdisk_second_info[0]) 79 print('kernel load address: %#x' % kernel_ramdisk_second_info[1]) 80 print('ramdisk size: %s' % kernel_ramdisk_second_info[2]) 81 print('ramdisk load address: %#x' % kernel_ramdisk_second_info[3]) 82 print('second bootloader size: %s' % kernel_ramdisk_second_info[4]) 83 print('second bootloader load address: %#x' % kernel_ramdisk_second_info[5]) 84 print('kernel tags load address: %#x' % kernel_ramdisk_second_info[6]) 85 print('page size: %s' % kernel_ramdisk_second_info[7]) 86 print_os_version_patch_level(unpack('I', args.boot_img.read(1 * 4))[0]) 87 else: 88 print('kernel_size: %s' % kernel_ramdisk_second_info[0]) 89 print('ramdisk size: %s' % kernel_ramdisk_second_info[1]) 90 print_os_version_patch_level(kernel_ramdisk_second_info[2]) 91 92 print('boot image header version: %s' % version) 93 94 if version < 3: 95 product_name = cstr(unpack('16s', args.boot_img.read(16))[0].decode()) 96 print('product name: %s' % product_name) 97 cmdline = cstr(unpack('512s', args.boot_img.read(512))[0].decode()) 98 print('command line args: %s' % cmdline) 99 else: 100 cmdline = cstr(unpack('1536s', args.boot_img.read(1536))[0].decode()) 101 print('command line args: %s' % cmdline) 102 103 if version < 3: 104 args.boot_img.read(32) # ignore SHA 105 106 if version < 3: 107 extra_cmdline = cstr(unpack('1024s', 108 args.boot_img.read(1024))[0].decode()) 109 print('additional command line args: %s' % extra_cmdline) 110 111 if version < 3: 112 kernel_size = kernel_ramdisk_second_info[0] 113 ramdisk_size = kernel_ramdisk_second_info[2] 114 second_size = kernel_ramdisk_second_info[4] 115 page_size = kernel_ramdisk_second_info[7] 116 else: 117 kernel_size = kernel_ramdisk_second_info[0] 118 ramdisk_size = kernel_ramdisk_second_info[1] 119 second_size = 0 120 page_size = BOOT_IMAGE_HEADER_V3_PAGESIZE 121 122 if 0 < version < 3: 123 recovery_dtbo_size = unpack('I', args.boot_img.read(1 * 4))[0] 124 print('recovery dtbo size: %s' % recovery_dtbo_size) 125 recovery_dtbo_offset = unpack('Q', args.boot_img.read(8))[0] 126 print('recovery dtbo offset: %#x' % recovery_dtbo_offset) 127 boot_header_size = unpack('I', args.boot_img.read(4))[0] 128 print('boot header size: %s' % boot_header_size) 129 else: 130 recovery_dtbo_size = 0 131 if 1 < version < 3: 132 dtb_size = unpack('I', args.boot_img.read(4))[0] 133 print('dtb size: %s' % dtb_size) 134 dtb_load_address = unpack('Q', args.boot_img.read(8))[0] 135 print('dtb address: %#x' % dtb_load_address) 136 else: 137 dtb_size = 0 138 139 140 # The first page contains the boot header 141 num_header_pages = 1 142 143 num_kernel_pages = get_number_of_pages(kernel_size, page_size) 144 kernel_offset = page_size * num_header_pages # header occupies a page 145 image_info_list = [(kernel_offset, kernel_size, 'kernel')] 146 147 num_ramdisk_pages = get_number_of_pages(ramdisk_size, page_size) 148 ramdisk_offset = page_size * (num_header_pages + num_kernel_pages 149 ) # header + kernel 150 image_info_list.append((ramdisk_offset, ramdisk_size, 'ramdisk')) 151 152 if second_size > 0: 153 second_offset = page_size * ( 154 num_header_pages + num_kernel_pages + num_ramdisk_pages 155 ) # header + kernel + ramdisk 156 image_info_list.append((second_offset, second_size, 'second')) 157 158 if recovery_dtbo_size > 0: 159 image_info_list.append((recovery_dtbo_offset, recovery_dtbo_size, 160 'recovery_dtbo')) 161 if dtb_size > 0: 162 num_second_pages = get_number_of_pages(second_size, page_size) 163 num_recovery_dtbo_pages = get_number_of_pages(recovery_dtbo_size, page_size) 164 dtb_offset = page_size * ( 165 num_header_pages + num_kernel_pages + num_ramdisk_pages + num_second_pages + 166 num_recovery_dtbo_pages 167 ) 168 169 image_info_list.append((dtb_offset, dtb_size, 'dtb')) 170 171 for image_info in image_info_list: 172 extract_image(image_info[0], image_info[1], args.boot_img, 173 os.path.join(args.out, image_info[2])) 174 175 176def unpack_vendor_bootimage(args): 177 kernel_ramdisk_info = unpack('5I', args.boot_img.read(5 * 4)) 178 print('vendor boot image header version: %s' % kernel_ramdisk_info[0]) 179 print('kernel load address: %#x' % kernel_ramdisk_info[2]) 180 print('ramdisk load address: %#x' % kernel_ramdisk_info[3]) 181 print('vendor ramdisk size: %s' % kernel_ramdisk_info[4]) 182 183 cmdline = cstr(unpack('2048s', args.boot_img.read(2048))[0].decode()) 184 print('vendor command line args: %s' % cmdline) 185 186 tags_load_address = unpack('I', args.boot_img.read(1 * 4))[0] 187 print('kernel tags load address: %#x' % tags_load_address) 188 189 product_name = cstr(unpack('16s', args.boot_img.read(16))[0].decode()) 190 print('product name: %s' % product_name) 191 192 dtb_size = unpack('2I', args.boot_img.read(2 * 4))[1] 193 print('dtb size: %s' % dtb_size) 194 dtb_load_address = unpack('Q', args.boot_img.read(8))[0] 195 print('dtb address: %#x' % dtb_load_address) 196 197 ramdisk_size = kernel_ramdisk_info[4] 198 page_size = kernel_ramdisk_info[1] 199 200 # The first pages contain the boot header 201 num_boot_header_pages = get_number_of_pages(VENDOR_BOOT_IMAGE_HEADER_V3_SIZE, page_size) 202 num_boot_ramdisk_pages = get_number_of_pages(ramdisk_size, page_size) 203 ramdisk_offset = page_size * num_boot_header_pages 204 image_info_list = [(ramdisk_offset, ramdisk_size, 'vendor_ramdisk')] 205 206 dtb_offset = page_size * (num_boot_header_pages + num_boot_ramdisk_pages 207 ) # header + vendor_ramdisk 208 image_info_list.append((dtb_offset, dtb_size, 'dtb')) 209 210 for image_info in image_info_list: 211 extract_image(image_info[0], image_info[1], args.boot_img, 212 os.path.join(args.out, image_info[2])) 213 214 215def unpack_image(args): 216 boot_magic = unpack('8s', args.boot_img.read(8))[0].decode() 217 print('boot_magic: %s' % boot_magic) 218 if boot_magic == "ANDROID!": 219 unpack_bootimage(args) 220 elif boot_magic == "VNDRBOOT": 221 unpack_vendor_bootimage(args) 222 223 224def parse_cmdline(): 225 """parse command line arguments""" 226 parser = ArgumentParser( 227 description='Unpacks boot.img/recovery.img, extracts the kernel,' 228 'ramdisk, second bootloader, recovery dtbo and dtb') 229 parser.add_argument( 230 '--boot_img', 231 help='path to boot image', 232 type=FileType('rb'), 233 required=True) 234 parser.add_argument('--out', help='path to out binaries', default='out') 235 return parser.parse_args() 236 237 238def main(): 239 """parse arguments and unpack boot image""" 240 args = parse_cmdline() 241 create_out_dir(args.out) 242 unpack_image(args) 243 244 245if __name__ == '__main__': 246 main() 247