Created
November 24, 2025 14:23
-
-
Save R0rt1z2/72a5217af4a43a552609d3bbd7e556f1 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env python3 | |
| import struct | |
| import sys | |
| """ | |
| 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; | |
| 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 */ | |
| uint8_t extra_cmdline[BOOT_EXTRA_ARGS_SIZE]; | |
| }; | |
| """ | |
| HEADER_SIZE = 0x800 | |
| BOOT_MAGIC = b'ANDROID!' | |
| # Source - https://stackoverflow.com/a | |
| # Posted by rhoitjadhav, modified by community. See post 'Timeline' for change history | |
| # Retrieved 2025-11-24, License - CC BY-SA 4.0 | |
| def convert_bytes(size): | |
| for x in ['bytes', 'KB', 'MB', 'GB', 'TB']: | |
| if size < 1024.0: | |
| return '%3.1f %s' % (size, x) | |
| size /= 1024.0 | |
| return size | |
| def parse_os_data(data): | |
| """ | |
| We're dealing with a 32 bit integer, which gets divided into | |
| two different part(s), to handle both os_version and os_patch. | |
| The upper (first) 21 bits are used to store the Android version. | |
| The lower (last) 11 bits are used to store the Security Patch. | |
| """ | |
| if data != 0: | |
| os_version = data >> 11 | |
| os_patch = data & 0x7FF | |
| # The upper 21 bits are divided into three parts: | |
| # Major (bits 31-14), Minor (bits 13-7), Patch (bits 6-0) | |
| major = (os_version >> 14) & 0x7F | |
| minor = (os_version >> 7) & 0x7F | |
| patch = os_version & 0x7F | |
| # The lower 11 bits are divided into two parts: | |
| # Year (+2000), Month | |
| year = (os_patch >> 4) + 2000 | |
| month = os_patch & 0xF | |
| return ('%d-%02d' % (year, month)), ('%d.%d.%d' % (major, minor, patch)) | |
| else: | |
| return 'N/A', 'N/A' | |
| def main(): | |
| if len(sys.argv) != 2: | |
| exit('USAGE: %s <boot.img>' % sys.argv[0]) | |
| with open(sys.argv[1], 'rb') as f: | |
| f.seek(0x4040 if f.read(4) == b'BFBF' else 0) | |
| header = f.read(HEADER_SIZE) | |
| if header[0:8] != BOOT_MAGIC: | |
| exit('Not a valid Android boot image') | |
| magic = struct.unpack('8s', header[0:8])[0] | |
| kernel_size = struct.unpack('<I', header[8:12])[0] | |
| kernel_address = struct.unpack('<I', header[12:16])[0] | |
| # This is required to calculate other addresses since | |
| # they are all relative to what gets loaded first, which | |
| # is always the kernel. | |
| base = kernel_address - 0x00008000 | |
| ramdisk_size = struct.unpack('<I', header[16:20])[0] | |
| ramdisk_offset = struct.unpack('<I', header[20:24])[0] | |
| # This is mostly unused on MediaTek devices. The only device that | |
| # I have seen that uses this, is the Amazon Fire TV 2 (sloane). | |
| second_size = struct.unpack('<I', header[24:28])[0] | |
| second_offset = ( | |
| struct.unpack('<I', header[28:32])[0] - base if second_size != 0 else 0 | |
| ) | |
| tags_offset = struct.unpack('<I', header[32:36])[0] - base | |
| page_size = struct.unpack('<I', header[36:40])[0] | |
| unused = struct.unpack('<I', header[40:44])[0] | |
| os_version, os_patch = parse_os_data(struct.unpack('<I', header[44:48])[0]) | |
| board_name = struct.unpack('16s', header[48:64])[0].rstrip(b'\x00').decode() | |
| # The extra cmdline is the actual last entry of the Boot Image Header, however, | |
| # I placed it here so it's grouped with the regular cmdline. | |
| cmdline = struct.unpack('512s', header[64:576])[0].rstrip(b'\x00').decode() | |
| extra_cmdline = ( | |
| struct.unpack('1024s', header[608:1632])[0].rstrip(b'\x00').decode() | |
| ) | |
| # Android Boot Image contains an 32 bytes Id. The specification does not actually | |
| # mandates any specific implementation of this Id (it can be a timestamp, a CRC | |
| # checksum, a SHA hash, ...). | |
| ids = struct.unpack('<IIIIIIII', header[576:608]) | |
| print('Boot Magic:', magic.decode()) | |
| print('Boot Base Address: 0x{:08X}'.format(base)) | |
| print('Kernel Size:', convert_bytes(kernel_size)) | |
| print('Kernel Address: 0x{:08X}'.format(kernel_address)) | |
| print('Ramdisk Size:', convert_bytes(ramdisk_size)) | |
| print('Ramdisk Address: 0x{:08X}'.format(ramdisk_offset)) | |
| # The size being zero indicates that there is no second image | |
| print('Second Size:', convert_bytes(second_size)) | |
| print('Second Address: 0x{:08X}'.format(second_offset)) | |
| # This equals the load address of the DTB | |
| print('Tags Address: 0x{:08X}'.format(tags_offset)) | |
| print('Kernel Page Size:', page_size) | |
| # Like the name states, this is mostly unused. Only certain OEMs | |
| # (e.g. Samsung) store stuff here, for proprietary specific purposes. | |
| if unused != 0: | |
| print('Unused:', unused) | |
| print('OS version:', os_version) | |
| print('OS security patch:', os_patch) | |
| if board_name: | |
| print('Board Name:', board_name) | |
| if cmdline: | |
| print('Kernel Command Line:', cmdline) | |
| if extra_cmdline: | |
| print('Extra Kernel Command Line:', extra_cmdline) | |
| print('Identifier:', end='') | |
| for idx in ids: | |
| print(' 0x{:08X}'.format(idx), end='') | |
| if __name__ == '__main__': | |
| main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment