Inspect boot images without flashing

Map headers, ramdisks, device trees, and verification metadata offline.

10 min readChapter 18 of 50Intermediate
In this chapter

This chapter turns the role map in Boot and partitions into a source/spec reading exercise. It does not teach flashing, image patching, or recovery procedures. A boot image is a structured format whose fields depend on a header version; reading one safely means matching the parser/spec version and treating every conclusion as evidence-limited.

Before you start#

Read Bytes, Hex, and Trust, Boot and partitions, and Programs, Memory & System Calls. You should know hex is a notation for bytes and understand that a signature/verified chain establishes integrity relative to a trust root, not software correctness. No image modification or device access is required.

Learning goals#

You will distinguish a partition name from the image format stored there; explain why header version must be identified before parsing fields; interpret a versioned component map; distinguish Android release from device launch/layout; and create an offline inventory without writing to a device or extracting private data.

Bytes, headers, and conditional layouts#

A byte is an 8-bit unit, commonly displayed in hexadecimal (two hex digits per byte). A field is a byte range interpreted according to a format definition. A parser does not know a field's meaning from its value alone: it needs the correct format and version. A magic value is a fixed byte sequence used by some formats to help identify them. Finding a magic value is a clue, not proof the whole file is valid or authentic. A hash summarizes bytes; a signature is checked using a trust key and verification rules. Neither tells you the code is safe. Android Verified Boot validates images/metadata according to the configured chain, rollback indices, and device state.

Versioned image map

The following is a learning comparison, not a complete compatibility matrix. Use the linked AOSP table for all fields and supported version behavior.

Layout family (examples)bootinit_bootvendor_bootOther evidence to inspect
Older boot-ramdisk designsKernel plus ramdisk may be packaged together.Typically absent in this older design.Layout-specific vendor data may be elsewhere or absent.Header version, recovery design, fstab, AVB descriptors.
Android 10/11 first-stage-init transitionBoot image commonly contains kernel and first-stage ramdisk in documented layouts.Not the generic modern split role in these historical docs.Vendor ramdisk details depend on design. Ramdisk partitions page explicitly applies to Android 11 and lower; system-as-root/recovery arrangement matters.
Android 12+ GKI-family designs Commonly carries GKI kernel; boot header version and board design affect content (header versions).Generic ramdisk partition on designs using the generic boot partition scheme; Android 13+ devices commonly use this split.Vendor-specific ramdisk fragments and related vendor boot content in supported formats.Boot header version, generic/vendor ramdisk table, DTB/bootconfig placement, launch level, build config.
Product-specific variationsKernel and associated payload according to actual design.Optional.May combine fragments/configuration differently.dtbo, recovery, vbmeta, chained metadata, slots, dynamic partitions, fstab and vendor data.

Do not turn the table into rules like “Android 13 means init_boot” or “Android 14 has vendor_boot.” Android version and device launch version are related but not interchangeable. A later OS update can retain a device's original partition architecture. boot, init_boot, and vendor_boot are names/roles; actual payload is established from the matching header and build configuration.

Fields and neighboring images

Same offset, different field
Offset 0, 8 bytes
Boot magic ANDROID! is a format clue, not authentication.
Offset 40, u32
header_version is at this offset for boot header versions 0–4.
Offset 12, version 0
kernel_addr: a field with a different meaning from the v3 field at the same position.
Offset 12, version 3
ramdisk_size. In v3, kernel_size is at offset 8; v0 offsets must not be reused.
Source map. Selected AOSP boot header offsets, not a complete structure or a validation procedure. All numbers are byte offsets from the header start; use matching definitions.

The Android 14 AOSP header definitions are in include/bootimg/bootimg.h. Its boot_img_hdr_v3 lays out magic[8], kernel_size, ramdisk_size, os_version, header_size, reserved words, header_version, and command-line bytes; boot_img_hdr_v4 extends v3 with signature_size. The AOSP header guide documents the associated format and alignment rules. In the boot header, header_version stays at byte offset 40 across versions 0–4, so a parser can read it first; the neighboring fields change meaning, e.g. offset 12 is kernel_addr in v0 but ramdisk_size in v3 (boot image header guide). v3/v4 boot header pages are 4096 bytes; v3 header_size describes the header structure, not the full page. Vendor-boot v4 has separate fields including vendor ramdisk table sizes/count and bootconfig size. Do not reuse a v0/v1 offset to parse v3/v4. A real parser must validate the magic, version, header structure size, and all declared section lengths against the actual buffer before trusting any field.

Related components are not all fields in one file:

  • DTB/DTBO: base device-tree blob and optional overlays may be in separate images/partitions or packaged according to platform design (DTB images, overlays). Their job is hardware description, not boot policy.
  • Bootconfig/kernel command line: parameters passed to early kernel/userspace (bootconfig extends the command line); command-line inspection does not show every bootconfig value or prove image integrity.
  • Recovery: may be a separate image/partition or share a boot/ramdisk strategy. The device's recovery design must be established from evidence.
  • vbmeta: AVB metadata contains descriptors and relationships for verified images, potentially using chained partitions (Verified Boot). It is not the kernel/ramdisk payload itself. Verification details depend on chain and device lock state.
  • super and slots: dynamic logical partitions may live in super; A/B metadata selects slot-aware images. Neither mechanism changes the meaning of a boot-header field. Slot naming is not a cryptographic validation result.

An image's location also does not establish its contents. A file called boot.img could be a factory archive's image, a build artifact, a repacked image, or a file from another device. Record provenance, hash, source URL/build fingerprint, target product, and parser version. Never upload private device images or keys to an untrusted site.

Practice#

Prerequisites: workstation shell with Python 3 (standard library only) and browser access to AOSP documentation. No device, root, JDK, downloaded image, or third-party parser is needed. This lab constructs a synthetic teaching record in memory. It is not a bootable image, has no real kernel or ramdisk, and must never be flashed or passed to a boot tool.

Browser-only field card

Header, payload, and page gaps

Synthetic teaching data, not a device capture.

Offsets 0–4095: header page
Selected v3 header fields; header_size is 1580, inside a 4096-byte page.
Offsets 4096–8191: kernel page
4 bytes of K at 4096–4099, followed by 4092 padding bytes.
Offsets 8192–8194: ramdisk filler
3 bytes of R; the provided buffer ends here.
Synthetic teaching data. Not to scale. The 8,195-byte fixture is deliberately non-bootable. K and R are filler, not a real kernel or ramdisk. Header structure size is not page size.

If you cannot run Python, use this provided synthetic metadata card. It represents only selected boot_img_hdr_v3 fields, not a complete header or valid image:

text
format: synthetic Android-style boot header training fixture
magic bytes [0..7]: 41 4e 44 52 4f 49 44 21     ASCII "ANDROID!"
kernel_size u32 little-endian at offset 8: 4
ramdisk_size u32 little-endian at offset 12: 3
header_size u32 little-endian at offset 20: 1580
header_version u32 little-endian at offset 40: 3
buffer length: 8195 bytes (header page + 4096-byte kernel page + 3 filler bytes)
kernel-page filler at offset 4096: 4 bytes 0x4b; ramdisk-page filler at offset 8192: 3 bytes 0x52

The offsets and field names are based on Android 14's tagged boot_img_hdr_v3 definition. The chosen values and filler are synthetic. They do not form a valid signed, bootable, or device-compatible boot image.

Run the in-memory parser

This complete program uses BytesIO and struct; it writes no files. It parses only the selected v3 header fields and checks header/page/payload bounds. It is a teaching parser, not AOSP mkbootimg or a production-robust Android image parser.

sh
# HOST workstation shell
python3 - <<'PY'
from io import BytesIO
import struct

PAGE = 4096
HEADER_SIZE_V3 = 1580  # sizeof(boot_img_hdr_v3) in the tagged AOSP header

def make_fixture():
    image = bytearray(PAGE * 2 + 3)
    image[0:8] = b"ANDROID!"
    struct.pack_into("<I", image, 8, 4)       # kernel_size
    struct.pack_into("<I", image, 12, 3)      # ramdisk_size
    struct.pack_into("<I", image, 20, HEADER_SIZE_V3)
    struct.pack_into("<I", image, 40, 3)      # header_version
    image[PAGE:PAGE + 4] = b"K" * 4          # filler, NOT a kernel
    image[PAGE * 2:PAGE * 2 + 3] = b"R" * 3 # filler, NOT a ramdisk
    return bytes(image)

def parse_v3(data):
    stream = BytesIO(data)
    if len(data) < PAGE:
        raise ValueError("truncated: missing 4096-byte v3 header page")
    if stream.read(8) != b"ANDROID!":
        raise ValueError("bad boot magic")
    kernel_size, ramdisk_size = struct.unpack_from("<II", data, 8)
    header_size = struct.unpack_from("<I", data, 20)[0]
    header_version = struct.unpack_from("<I", data, 40)[0]
    if header_version != 3:
        raise ValueError(f"unsupported fixture parser version: {header_version}")
    if header_size != HEADER_SIZE_V3 or header_size > PAGE:
        raise ValueError(f"invalid v3 header_size: {header_size}")
    if kernel_size == 0 or ramdisk_size == 0:
        raise ValueError("v3 fixture requires nonempty kernel and ramdisk sections")
    ramdisk_offset = PAGE + ((kernel_size + PAGE - 1) // PAGE) * PAGE
    if ramdisk_offset + ramdisk_size > len(data):
        raise ValueError("declared payload sizes exceed available bytes")
    return "\n".join((
        "magic: ANDROID!",
        f"header_version: {header_version}",
        f"header_size: {header_size}",
        f"kernel_size: {kernel_size}",
        f"ramdisk_size: {ramdisk_size}",
        f"buffer_length: {len(data)}",
        "classification: synthetic/non-bootable fixture",
    ))

fixture = make_fixture()
print(parse_v3(fixture))
print("truncated case:")
try:
    parse_v3(fixture[:100])
except ValueError as exc:
    print(type(exc).__name__ + ":", exc)
print("oversized declaration case:")
bad = bytearray(fixture)
struct.pack_into("<I", bad, 8, 9999)
try:
    parse_v3(bytes(bad))
except ValueError as exc:
    print(type(exc).__name__ + ":", exc)
PY

Expected output:

text
magic: ANDROID!
header_version: 3
header_size: 1580
kernel_size: 4
ramdisk_size: 3
buffer_length: 8195
classification: synthetic/non-bootable fixture
truncated case:
ValueError: truncated: missing 4096-byte v3 header page
oversized declaration case:
ValueError: declared payload sizes exceed available bytes

This is a source-offset exercise: it demonstrates that values at selected offsets can be decoded under a declared format. It does not validate every boot_img_hdr_v3 field, signatures, AVB descriptors, vendor_boot, or device compatibility. The 4 K bytes and 3 R bytes are filler only, not actual payloads. A real image has format-specific alignment and integrity requirements. Do not save or rename this fixture as boot.img.

Expected failure interpretation: short input is truncated; wrong magic should fail; header_version other than 3 is unsupported by this deliberately narrow parser; wrong header_size is rejected; declared section sizes beyond available bytes are rejected. A parser error is not a reason to patch bytes or try a different header offset blindly. After the code, compare field names/offsets against the tagged header and AOSP format document. For v4, notice the inherited v3 header plus signature_size; this exercise intentionally refuses v4 rather than pretending to parse it.

Knowledge check#

  1. Why must a parser identify the boot header version before interpreting offsets?
  2. Does a valid boot image imply that the device has no separate init_boot?
  3. What do DTB and DTBO contribute, and are they always in boot?
  4. What does vbmeta do, and why is it not “the boot image checksum”?
  5. If an Android 14 device lacks a vendor_boot partition name in a shell listing, what can you conclude?

Answers#

  1. Fields, offsets, and payload organization vary by version; the wrong parser can misread bytes or accept malformed data.
  2. No. The images have different roles and may coexist in a supported split layout; exact design is device-specific.
  3. DTB supplies base hardware description; DTBO can overlay it. Packaging varies, so neither is guaranteed to be in boot.
  4. AVB metadata describes integrity relationships/policy for images and may chain to other metadata; it is not simply a checksum filename or all payload data.
  5. Only that this particular listing/build did not expose that name. It does not establish the full image layout or content.

Checkpoint#

Submit the parser's expected output (or hand-annotated field card), its two failure-case outputs, and a versioned image map with source links. Include an inventory template with artifact provenance, hash, header version, interpreted fields, layout-dependent items, and unknowns. Pass if you correctly identify the v3 selected offsets, reject truncated/oversized data, and label the fixture non-bootable. Remediation: compare offsets against the pinned bootimg.h, recount bytes from offset zero, and rerun the in-memory parser; do not proceed to a real image until each field is explained.

Sources#

A little further beneath the surface.

Check your understanding with the checkpoint. Marking a chapter as read saves a bookmark, not a proficiency assessment.