Skip to content

uimage.py

ofrak_components.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)

Analyze the UImageHeader of a UImage

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_components/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)

Modify a UImageHeader according to a given modifier config. Updates the header CRC field (ih_hcrc).

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_components/uimage.py
async def modify(self, resource: Resource, config: UImageHeaderModifierConfig) -> None:
    original_attributes = await resource.analyze(UImageHeader.attributes_type)
    # 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_components/uimage.py
@staticmethod
async def serialize(
    updated_attributes: UImageHeader.attributes_type, ih_hcrc: int = 0  # type: ignore
) -> 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)

Analyze the UImageMultiHeader of a UImageType.MULTI UImage

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_components/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)

Modify a UImageMultiHeader according to a given modifier config.

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_components/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(UImageMultiHeader.attributes_type)
    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_components/uimage.py
@staticmethod
async def serialize(
    updated_attributes: UImageMultiHeader.attributes_type,  # type: ignore
) -> 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_components/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 resource.get_only_child_as_view(
            UImageMultiHeader, ResourceFilter.with_tags(UImageMultiHeader)
        )
        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)

Analyze the ProgramAttributes of a UImage from its header

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_components/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)

UImage unpacker.

It unpacks the UImage resource into: - A 64-byte UImageHeader - [Optional] UImageMultiHeader (for UImageTypes.MULTI images) - A list of UImageBody instances (1 or many) - [Optional] UImageTrailingBytes (if any)

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_components/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:],
        )