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