Skip to content

Instantly share code, notes, and snippets.

@R0rt1z2
Created November 24, 2025 14:23
Show Gist options
  • Select an option

  • Save R0rt1z2/72a5217af4a43a552609d3bbd7e556f1 to your computer and use it in GitHub Desktop.

Select an option

Save R0rt1z2/72a5217af4a43a552609d3bbd7e556f1 to your computer and use it in GitHub Desktop.
#!/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