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