Skip to content

uimage.py

ofrak.core.uimage

UImage (GenericBinary) dataclass

A UImage is an image file that has a U-Boot wrapper (installed by the mkimage utility), and contains 1 or more images for use by U-Boot during boot.

The U-Boot wrapper, or UImageHeader is a 64-byte fixed size header which contains information about the included images, such as the OS type, loader information, hardware ISA, etc. as well as CRC32 integrity checks.

The contained images, or UImageBodies, can be anything from other nested UImages, to filesystems, kernels, and scripts.

Assuming the body of a UImage is a program, the following snippet outlines a way to tag and analyze it. Note the addition of a CodeRegion view with a virtual address obtained from the UImageHeader, as well as tagging the UImageBody as a Program.

uimage = await resource.view_as(UImage)
uimage_header = await uimage.get_header()
uimage_bodies = await uimage.get_bodies()
uimage_body = uimage_bodies[0]
uimage_body.resource.add_view(
    CodeRegion(
        virtual_address=uimage_header.get_load_vaddr(),
        size=uimage_header.get_data_size(),
    )
)
uimage_body.resource.add_tag(Program)
await uimage_body.resource.save()

UImageArch (Enum)

An enumeration.

UImageCompressionType (Enum)

An enumeration.

UImageHeader (ResourceView) dataclass

UImage header.

Attributes:

Name Type Description
ih_magic int

Image Header Magic Number, 4 bytes

ih_hcrc int

Image Header CRC Checksum (when this field itself is zeroed out), 4 bytes

ih_time int

Image Creation Timestamp, 4 bytes

ih_size int

Image Data Size, 4 bytes

ih_load int

Data Load Address, 4 bytes

ih_ep int

Entry Point Address, 4 bytes

ih_dcrc int

Image Data CRC Checksum, 4 bytes

ih_os int

Operating System, 1 byte

ih_arch int

CPU architecture, 1 byte

ih_type int

Image Type, 1 byte

ih_comp int

Compression Type, 1 byte

ih_name bytes

Image Name, 32 bytes

UImageHeaderAttributesAnalyzer (Analyzer)

Parses and extracts all fields from a UImage header including magic number, header and data CRC checksums, creation timestamp, data size, load address, entry point address, target architecture (ARM, MIPS, x86, etc.), operating system (Linux, U-Boot, VxWorks, etc.), image type (kernel, ramdisk, multi, etc.), compression type (none, gzip, bzip2, lzma), and image name. Use to understand UImage metadata without fully unpacking, verify header integrity via CRCs, determine what kind of firmware component you're analyzing, or extract load addresses for further analysis.

analyze(self, resource, config=None) async

Analyze a resource for to extract specific ResourceAttributes.

Users should not call this method directly; rather, they should run Resource.run or Resource.analyze.

Parameters:

Name Type Description Default
resource Resource

The resource that is being analyzed

required
config

Optional config for analyzing. If an implementation provides a default, this default will always be used when config would otherwise be None. Note that a copy of the default config will be passed, so the default config values cannot be modified persistently by a component run.

None

Returns:

Type Description
UImageHeader

The analysis results

Source code in ofrak/core/uimage.py
async def analyze(self, resource: Resource, config=None) -> UImageHeader:
    tmp = await resource.get_data()
    deserializer = BinaryDeserializer(
        io.BytesIO(tmp),
        endianness=Endianness.BIG_ENDIAN,
        word_size=4,
    )

    deserialized = deserializer.unpack_multiple(f"IIIIIIIBBBB{UIMAGE_NAME_LEN}s")
    (
        ih_magic,
        ih_hcrc,
        ih_time,
        ih_size,
        ih_load,
        ih_ep,
        ih_dcrc,
        ih_os,
        ih_arch,
        ih_type,
        ih_comp,
        ih_name,
    ) = deserialized

    assert ih_magic == UIMAGE_MAGIC

    return UImageHeader(
        ih_magic,
        ih_hcrc,
        ih_time,
        ih_size,
        ih_load,
        ih_ep,
        ih_dcrc,
        ih_os,
        ih_arch,
        ih_type,
        ih_comp,
        ih_name,
    )

UImageHeaderModifier (Modifier)

Modifies UImage header fields such as load address, entry point, data size, compression type, OS type, architecture, or image name, then automatically recalculates and updates the header CRC32 checksum to maintain validity. Bootloaders verify this CRC before loading. Use for changing boot addresses, updating entry points after code modification, switching compression methods, adjusting image metadata, or fixing corrupted headers. Critical for ensuring modified UImage files are accepted by U-Boot bootloaders.

modify(self, resource, config) async

Modify the given resource.

Users should not call this method directly; rather, they should run Resource.run.

Parameters:

Name Type Description Default
resource Resource required
config UImageHeaderModifierConfig

Optional config for modification. If an implementation provides a default, this default will always be used when config would otherwise be None. Note that a copy of the default config will be passed, so the default config values cannot be modified persistently by a component run.

required
Source code in ofrak/core/uimage.py
async def modify(self, resource: Resource, config: UImageHeaderModifierConfig) -> None:
    original_attributes = await resource.analyze(AttributesType[UImageHeader])
    # First serialize the header with the ih_hcrc field set to 0, to compute this CRC later
    new_attributes = ResourceAttributes.replace_updated(original_attributes, config)
    tmp_serialized_header = await UImageHeaderModifier.serialize(new_attributes, ih_hcrc=0)
    # Patch this header with its CRC32 in the ih_hcrc field
    ih_hcrc = zlib.crc32(tmp_serialized_header)
    serialized_header = await UImageHeaderModifier.serialize(new_attributes, ih_hcrc=ih_hcrc)
    resource.queue_patch(Range.from_size(0, UIMAGE_HEADER_LEN), serialized_header)
    new_attributes = ResourceAttributes.replace_updated(
        new_attributes, UImageHeaderModifierConfig(ih_hcrc=ih_hcrc)
    )
    resource.add_attributes(new_attributes)

serialize(updated_attributes, ih_hcrc=0) async staticmethod

Serialize updated_attributes into bytes, using ih_hcrc for the eponymous field. This method doesn't perform any check or compute any CRC.

Source code in ofrak/core/uimage.py
@staticmethod
async def serialize(
    updated_attributes: AttributesType[UImageHeader], ih_hcrc: int = 0
) -> bytes:
    """
    Serialize `updated_attributes` into bytes, using `ih_hcrc` for the eponymous field.
    This method doesn't perform any check or compute any CRC.
    """
    return struct.pack(
        f"!IIIIIIIBBBB{UIMAGE_NAME_LEN}s",
        UIMAGE_MAGIC,
        ih_hcrc,
        updated_attributes.ih_time,
        updated_attributes.ih_size,
        updated_attributes.ih_load,
        updated_attributes.ih_ep,
        updated_attributes.ih_dcrc,
        updated_attributes.ih_os,
        updated_attributes.ih_arch,
        updated_attributes.ih_type,
        updated_attributes.ih_comp,
        updated_attributes.ih_name,
    )

UImageHeaderModifierConfig (ComponentConfig) dataclass

Modifier config for a UImageHeader.

The following field is not modifiable: - Image Header Magic Number (a constant)

The following field is modifiable, but will be overwritten by the modifier: - Image Header CRC Checksum (ih_hcrc)

The following fields are modifiable, but will be overwritten by the packer: - Image Data Size (ih_size) - Image Data CRC Checksum (ih_dcrc)

UImageMultiHeader (ResourceView) dataclass

UImage MULTI-type header holding sizes of bodies contained in the parent uimage The header size is unknown, and is calculated by the uboot loader by reading past the UImageHeader until it reaches a null dword ().

UImageMultiHeaderAttributesAnalyzer (Analyzer)

Parses the UImageMultiHeader present in multi-file UImage images (UImageType.MULTI), extracting the sizes of each contained image component. Multi-file UImages package multiple components (like kernel + ramdisk) together. Use when analyzing multi-component UImage files to understand how many images are packaged together, determine individual image sizes for extraction, or prepare to unpack specific components. Each size field indicates the length of one packaged image.

analyze(self, resource, config=None) async

Analyze a resource for to extract specific ResourceAttributes.

Users should not call this method directly; rather, they should run Resource.run or Resource.analyze.

Parameters:

Name Type Description Default
resource Resource

The resource that is being analyzed

required
config

Optional config for analyzing. If an implementation provides a default, this default will always be used when config would otherwise be None. Note that a copy of the default config will be passed, so the default config values cannot be modified persistently by a component run.

None

Returns:

Type Description
UImageMultiHeader

The analysis results

Source code in ofrak/core/uimage.py
async def analyze(self, resource: Resource, config=None) -> UImageMultiHeader:
    resource_data = await resource.get_data()
    deserializer = BinaryDeserializer(
        io.BytesIO(resource_data),
        endianness=Endianness.BIG_ENDIAN,
        word_size=4,
    )
    uimage_multi_header_size = (len(resource_data) - 4) // 4  # Remove trailing null dword
    deserialized = deserializer.unpack_multiple(f"{uimage_multi_header_size}I")
    return UImageMultiHeader(deserialized)

UImageMultiHeaderModifier (Modifier)

Modifies the multi-header structure in UImageType.MULTI images, which contains size fields for each packaged component (like kernel + ramdisk combinations). Changes allow adding, removing, or resizing packaged images. Use when modifying multi-component UImage files, changing image sizes after compression or decompression, adding or removing packaged components, or reorganizing multi-file firmware. Must ensure size fields accurately reflect component sizes for proper unpacking by bootloaders.

modify(self, resource, config) async

Modify the given resource.

Users should not call this method directly; rather, they should run Resource.run.

Parameters:

Name Type Description Default
resource Resource required
config UImageMultiHeaderModifierConfig

Optional config for modification. If an implementation provides a default, this default will always be used when config would otherwise be None. Note that a copy of the default config will be passed, so the default config values cannot be modified persistently by a component run.

required
Source code in ofrak/core/uimage.py
async def modify(self, resource: Resource, config: UImageMultiHeaderModifierConfig) -> None:
    # # First serialize the header with the ih_hcrc field set to 0, to compute this CRC later
    original_attributes = await resource.analyze(AttributesType[UImageMultiHeader])
    new_attributes = ResourceAttributes.replace_updated(original_attributes, config)
    serialized_multiheader = await UImageMultiHeaderModifier.serialize(new_attributes)
    uimage_multi_header = await resource.view_as(UImageMultiHeader)
    resource.queue_patch(
        Range(0, uimage_multi_header.get_multi_header_size()),
        serialized_multiheader,
    )
    resource.add_attributes(new_attributes)

serialize(updated_attributes) async staticmethod

Serialize updated_attributes into bytes

Source code in ofrak/core/uimage.py
@staticmethod
async def serialize(
    updated_attributes: AttributesType[UImageMultiHeader],
) -> bytes:
    """
    Serialize `updated_attributes` into bytes
    """
    serialized = b""
    for image_size in updated_attributes.image_sizes:
        serialized += struct.pack("!I", image_size)
    serialized += b"\x00" * 4
    return serialized

UImageMultiHeaderModifierConfig (ComponentConfig) dataclass

Modifier config for a UImageMultiHeader.

UImageOperatingSystem (Enum)

An enumeration.

UImagePacker (Packer)

UImage packer.

It patches the resource's UImageHeader and UImageBody instances into a single binary, updating the CRC checksums and image data size in the UImageHeader. Also handles the UImageMultiHeader in the case of UImageTypes.MULTI UImages.

pack(self, resource, config=None) async

Pack the given resource.

Users should not call this method directly; rather, they should run Resource.run or Resource.pack.

Parameters:

Name Type Description Default
resource Resource required
config

Optional config for packing. If an implementation provides a default, this default will always be used when config would otherwise be None. Note that a copy of the default config will be passed, so the default config values cannot be modified persistently by a component run.

None
Source code in ofrak/core/uimage.py
async def pack(self, resource: Resource, config=None):
    repacked_body_data = b""
    uimage_view = await resource.view_as(UImage)
    header = await uimage_view.get_header()
    if header.get_type() == UImageType.MULTI:
        image_sizes = []
        for uimage_body in await uimage_view.get_bodies():
            image_sizes.append(await uimage_body.resource.get_data_length())
        multi_header = await uimage_view.get_multi_header()
        multiheader_modifier_config = UImageMultiHeaderModifierConfig(image_sizes=image_sizes)
        await multi_header.resource.run(UImageMultiHeaderModifier, multiheader_modifier_config)
        repacked_body_data += await multi_header.resource.get_data()
    for uimage_body in await uimage_view.get_bodies():
        repacked_body_data += await uimage_body.resource.get_data()

    # If there are UImageTrailingBytes, get them as well.
    resource_children = await resource.get_children()
    if any([child.has_tag(UImageTrailingBytes) for child in resource_children]):
        trailing_bytes_r = await resource.get_only_child_as_view(
            UImageTrailingBytes, ResourceFilter.with_tags(UImageTrailingBytes)
        )
        repacked_body_data += await trailing_bytes_r.resource.get_data()
    ih_size = len(repacked_body_data)
    ih_dcrc = zlib.crc32(repacked_body_data)
    header_modifier_config = UImageHeaderModifierConfig(ih_size=ih_size, ih_dcrc=ih_dcrc)
    await header.resource.run(UImageHeaderModifier, header_modifier_config)
    # Patch UImageHeader data
    header_data = await header.resource.get_data()
    # Patch all other data
    original_size = await resource.get_data_length()
    resource.queue_patch(Range(0, original_size), header_data + repacked_body_data)

UImageProgramAttributesAnalyzer (Analyzer)

Derives program architecture attributes (instruction set, bit width, endianness) from UImage header's architecture field (ih_arch). Maps UImage architecture enumerations (ARM, MIPS, x86, PowerPC, etc.) to OFRAK's architecture model. Use to identify the target platform for UImage firmware without examining the actual code, set up proper disassembly tools, verify firmware compatibility with target devices, or understand what embedded system the firmware runs on. Used to determine architecture before unpacking.

analyze(self, resource, config=None) async

Analyze a resource for to extract specific ResourceAttributes.

Users should not call this method directly; rather, they should run Resource.run or Resource.analyze.

Parameters:

Name Type Description Default
resource Resource

The resource that is being analyzed

required
config

Optional config for analyzing. If an implementation provides a default, this default will always be used when config would otherwise be None. Note that a copy of the default config will be passed, so the default config values cannot be modified persistently by a component run.

None

Returns:

Type Description
Tuple[ofrak.core.architecture.ProgramAttributes]

The analysis results

Source code in ofrak/core/uimage.py
async def analyze(self, resource: Resource, config=None) -> Tuple[ProgramAttributes]:
    uimage_view = await resource.view_as(UImage)
    uimage_header = await uimage_view.get_header()
    uimage_program_attributes = self.from_deserialized_header(uimage_header)
    return (uimage_program_attributes,)

UImageType (Enum)

An enumeration.

UImageUnpacker (Unpacker)

Extracts UImage bootloader images into their constituent parts: a 64-byte header, optional multi-header for multi-file images, one or more body sections containing the actual firmware/kernel data, and any trailing bytes. This is the standard format used by U-Boot bootloaders in embedded systems. Use when analyzing embedded device firmware that uses U-Boot, or when you need to examine or modify kernel images, ramdisks, or device tree blobs packaged in UImage format. The unpacker automatically handles different UImage types including standalone programs, kernels, ramdisks, multi-file images, and filesystems.

unpack(self, resource, config=None) async

Unpack the given resource.

Users should not call this method directly; rather, they should run Resource.run or Resource.unpack.

Parameters:

Name Type Description Default
resource Resource

The resource that is being unpacked

required
config

Optional config for unpacking. If an implementation provides a default, this default will always be used when config would otherwise be None. Note that a copy of the default config will be passed, so the default config values cannot be modified persistently by a component run.

None
Source code in ofrak/core/uimage.py
async def unpack(self, resource: Resource, config=None):
    uimage_header_r = await resource.create_child(
        tags=(UImageHeader,), data_range=Range(0, UIMAGE_HEADER_LEN)
    )
    uimage_header = await uimage_header_r.view_as(UImageHeader)
    resource_data = await resource.get_data()
    if uimage_header.get_type() == UImageType.MULTI:
        uimage_multi_size = resource_data[UIMAGE_HEADER_LEN:].find(b"\x00" * 4) + 4
        await resource.create_child(
            tags=(UImageMultiHeader,),
            data=resource_data[UIMAGE_HEADER_LEN : UIMAGE_HEADER_LEN + uimage_multi_size],
        )
        uimage_multi_header = await resource.get_only_child_as_view(
            UImageMultiHeader, ResourceFilter.with_tags(UImageMultiHeader)
        )

        image_i_start = UIMAGE_HEADER_LEN + uimage_multi_size
        for image_size in uimage_multi_header.get_image_sizes():
            await resource.create_child(
                tags=(UImageBody,),
                data=resource_data[image_i_start : image_i_start + image_size],
            )
            image_i_start += image_size

        total_len_with_bodies = (
            UIMAGE_HEADER_LEN
            + uimage_multi_header.get_multi_header_size()
            + sum(uimage_multi_header.get_image_sizes())
        )
        if total_len_with_bodies < uimage_header.ih_size:
            await resource.create_child(
                tags=(UImageTrailingBytes,), data=resource_data[total_len_with_bodies:]
            )
    else:
        await resource.create_child(
            tags=(UImageBody,),
            data=resource_data[UIMAGE_HEADER_LEN:],
        )