openwrt.py
ofrak_components.openwrt
OpenWrtIdentifier (Identifier)
identify(self, resource, config=None)
async
Perform identification on the given resource.
Users should not call this method directly; rather, they should run Resource.identify.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
resource |
Resource |
required | |
config |
Optional config for identifying. 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/openwrt.py
async def identify(self, resource: Resource, config=None) -> None:
trx_magic = await resource.get_data(range=Range(0, 4))
if trx_magic == OPENWRT_TRX_MAGIC_BYTES:
resource.add_tag(OpenWrtTrx)
OpenWrtTrx (GenericBinary)
dataclass
OpenWrtTrx is a TRX binary update for OpenWrt firmware.
TRX binaries are used for upgrading the firmware of devices already running OpenWrt firmware
The OpenWrtTrx consists of 1 OpenWrtTrxHeader and 3 or 4 partition(s): - OpenWrtTrxLzmaLoader - OpenWrtTrxKernel - OpenWrtTrxRootfs - OpenWrtTrxBinheader (in TRX v2)
OpenWrtTrxHeader (ResourceView)
dataclass
OpenWrt trx header Information from https://openwrt.org/docs/techref/headers
TRX v1
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+---------------------------------------------------------------+
| magic number ('HDR0') |
+---------------------------------------------------------------+
| length (header size + data) |
+---------------+---------------+-------------------------------+
| 32-bit CRC value |
+---------------+---------------+-------------------------------+
| TRX flags | TRX version |
+-------------------------------+-------------------------------+
| Partition offset[0] |
+---------------------------------------------------------------+
| Partition offset[1] |
+---------------------------------------------------------------+
| Partition offset[2] |
+---------------------------------------------------------------+
offset[0] = lzma-loader
offset[1] = Linux-Kernel
offset[2] = rootfs
TRX v2
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+---------------------------------------------------------------+
| magic number ('HDR0') |
+---------------------------------------------------------------+
| length (header size + data) |
+---------------+---------------+-------------------------------+
| 32-bit CRC value |
+---------------+---------------+-------------------------------+
| TRX flags | TRX version |
+-------------------------------+-------------------------------+
| Partition offset[0] |
+---------------------------------------------------------------+
| Partition offset[1] |
+---------------------------------------------------------------+
| Partition offset[2] |
+---------------------------------------------------------------+
| Partition offset[3] |
+---------------------------------------------------------------+
offset[0] = lzma-loader
offset[1] = Linux-Kernel
offset[2] = rootfs
offset[3] = bin-Header
OpenWrtTrxHeaderAttributesAnalyzer (Analyzer)
Analyze the OpenWrtTrxHeader of a OpenWrtTrx firmware file.
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 |
---|---|
OpenWrtTrxHeader |
The analysis results |
Source code in ofrak_components/openwrt.py
async def analyze(self, resource: Resource, config=None) -> OpenWrtTrxHeader:
tmp = await resource.get_data()
deserializer = BinaryDeserializer(
io.BytesIO(tmp),
endianness=Endianness.LITTLE_ENDIAN,
word_size=4,
)
deserialized = deserializer.unpack_multiple("IIIHH")
(
trx_magic,
trx_length,
trx_crc,
trx_flags,
trx_version,
) = deserialized
assert trx_magic == OPENWRT_TRX_MAGIC_START
if trx_version == OpenWrtTrxVersion.VERSION1.value:
deserialized = deserializer.unpack_multiple("III")
(
trx_loader_offset,
trx_kernel_offset,
trx_rootfs_offset,
) = deserialized
header = OpenWrtTrxHeader(
trx_magic,
trx_length,
trx_crc,
trx_flags,
trx_version,
trx_loader_offset,
trx_kernel_offset,
trx_rootfs_offset,
None,
)
elif trx_version == OpenWrtTrxVersion.VERSION2.value:
deserialized = deserializer.unpack_multiple("IIII")
(
trx_loader_offset,
trx_kernel_offset,
trx_rootfs_offset,
trx_binheader_offset,
) = deserialized
header = OpenWrtTrxHeader(
trx_magic,
trx_length,
trx_crc,
trx_flags,
trx_version,
trx_loader_offset,
trx_kernel_offset,
trx_rootfs_offset,
trx_binheader_offset,
)
else:
raise ValueError(f"Unknown OpenWrt TRX version: {trx_version}")
return header
OpenWrtTrxHeaderModifier (Modifier)
Modify a OpenWrtTrxHeader 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 |
OpenWrtTrxHeaderModifierConfig |
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/openwrt.py
async def modify(self, resource: Resource, config: OpenWrtTrxHeaderModifierConfig) -> None:
original_attributes = await resource.analyze(OpenWrtTrxHeader.attributes_type)
new_attributes = ResourceAttributes.replace_updated(original_attributes, config)
serialized_header = await OpenWrtTrxHeaderModifier.serialize(new_attributes)
header_v = await resource.view_as(OpenWrtTrxHeader)
resource.queue_patch(
Range.from_size(0, header_v.get_header_length()),
serialized_header,
)
resource.add_attributes(new_attributes)
serialize(updated_attributes)
async
staticmethod
Serialize updated_attributes
into bytes. This method doesn't perform any check or compute
any CRC.
Source code in ofrak_components/openwrt.py
@staticmethod
async def serialize(
updated_attributes: OpenWrtTrxHeader.attributes_type, # type: ignore
) -> bytes:
"""
Serialize `updated_attributes` into bytes. This method doesn't perform any check or compute
any CRC.
"""
if updated_attributes.trx_version == OpenWrtTrxVersion.VERSION1.value:
return struct.pack(
"<IIIHHIII",
OPENWRT_TRX_MAGIC_START,
updated_attributes.trx_length,
updated_attributes.trx_crc,
updated_attributes.trx_flags,
updated_attributes.trx_version,
updated_attributes.trx_loader_offset,
updated_attributes.trx_kernel_offset,
updated_attributes.trx_rootfs_offset,
)
elif updated_attributes.trx_version == OpenWrtTrxVersion.VERSION2.value:
return struct.pack(
"<IIIHHIIII",
OPENWRT_TRX_MAGIC_START,
updated_attributes.trx_length,
updated_attributes.trx_crc,
updated_attributes.trx_flags,
updated_attributes.trx_version,
updated_attributes.trx_loader_offset,
updated_attributes.trx_kernel_offset,
updated_attributes.trx_rootfs_offset,
updated_attributes.trx_binheader_offset,
)
else:
raise ValueError(f"Unknown OpenWrt TRX version: {updated_attributes.trx_version}")
OpenWrtTrxHeaderModifierConfig (ComponentConfig)
dataclass
Modifier config for a OpenWrtTrxHeader.
The following field is not modifiable:
- Image Header Magic Number (a constant): trx_magic
The following fields are modifiable, but will be overwritten by the packer:
- Image Data Size: trx_length
- Image CRC Checksum: trx_crc
- LZMA Loader offset: trx_loader_offset
- Kernel offset: trx_kernel_offset
- Filesystem offset: trx_rootfs_offset
- BinHeader offset, applicable for TRX Version 2: trx_binheader_offset
OpenWrtTrxPacker (Packer)
Pack an OpenWrtTrx firmware file.
It consolidates the OpenWrtTrxHeader and all partition instances into a single binary, updating the CRC checksum, data size, and partition offsets in the header.
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/openwrt.py
async def pack(self, resource: Resource, config=None):
openwrt_v = await resource.view_as(OpenWrtTrx)
header = await openwrt_v.get_header()
lzma_loader = await resource.get_only_child_as_view(
OpenWrtTrxLzmaLoader, ResourceFilter.with_tags(OpenWrtTrxLzmaLoader)
)
kernel = await resource.get_only_child_as_view(
OpenWrtTrxKernel, ResourceFilter.with_tags(OpenWrtTrxKernel)
)
rootfs = await resource.get_only_child_as_view(
OpenWrtTrxRootfs, ResourceFilter.with_tags(OpenWrtTrxRootfs)
)
if header.get_version() == OpenWrtTrxVersion.VERSION2:
binheader = await resource.get_only_child_as_view(
OpenWrtTrxBinheader, ResourceFilter.with_tags(OpenWrtTrxBinheader)
)
repacked_data_l = []
trx_loader_offset = header.get_header_length()
repacked_data_l.append(await lzma_loader.resource.get_data())
trx_kernel_offset = trx_loader_offset + len(repacked_data_l[-1])
repacked_data_l.append(await kernel.resource.get_data())
trx_rootfs_offset = trx_kernel_offset + len(repacked_data_l[-1])
repacked_data_l.append(await rootfs.resource.get_data())
if header.get_version() == OpenWrtTrxVersion.VERSION2:
trx_binheader_offset = trx_rootfs_offset + len(repacked_data_l[-1])
repacked_data_l.append(await binheader.resource.get_data())
repacked_data_b = b"".join(repacked_data_l)
trx_length = header.get_header_length() + len(repacked_data_b)
data_to_crc = struct.pack("<H", header.trx_flags)
data_to_crc += struct.pack("<H", header.trx_version)
data_to_crc += struct.pack("<I", trx_loader_offset)
data_to_crc += struct.pack("<I", trx_kernel_offset)
data_to_crc += struct.pack("<I", trx_rootfs_offset)
if header.get_version() == OpenWrtTrxVersion.VERSION2:
data_to_crc += struct.pack("<I", trx_binheader_offset)
data_to_crc += repacked_data_b
if header.get_version() == OpenWrtTrxVersion.VERSION1:
header_config = OpenWrtTrxHeaderModifierConfig(
trx_length=trx_length,
trx_crc=openwrt_crc32(data_to_crc),
trx_loader_offset=trx_loader_offset,
trx_kernel_offset=trx_kernel_offset,
trx_rootfs_offset=trx_rootfs_offset,
)
else:
header_config = OpenWrtTrxHeaderModifierConfig(
trx_length=trx_length,
trx_crc=openwrt_crc32(data_to_crc),
trx_loader_offset=trx_loader_offset,
trx_kernel_offset=trx_kernel_offset,
trx_rootfs_offset=trx_rootfs_offset,
trx_binheader_offset=trx_binheader_offset,
)
await header.resource.run(OpenWrtTrxHeaderModifier, header_config)
original_size = await resource.get_data_length()
resource.queue_patch(Range(header.get_header_length(), original_size), repacked_data_b)
OpenWrtTrxUnpacker (Unpacker)
Unpack an OpenWrtTrx firmware file into: - OpenWrtTrxHeader - OpenWrtTrxLzmaLoader - OpenWrtTrxKernel - OpenWrtTrxRootfs - OpenWrtTrxBinheader (if TRX v2)
The header has a mapped data range, whereas all partitions are unmapped data.
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/openwrt.py
async def unpack(self, resource: Resource, config=None):
data = await resource.get_data()
# Peek into TRX version to know how big the header is
trx_version = OpenWrtTrxVersion(struct.unpack("<H", data[14:16])[0])
if trx_version == OpenWrtTrxVersion.VERSION1:
trx_header_r = await resource.create_child(
tags=(OpenWrtTrxHeader,), data_range=Range(0, OPENWRT_TRXV1_HEADER_LEN)
)
elif trx_version == OpenWrtTrxVersion.VERSION2:
trx_header_r = await resource.create_child(
tags=(OpenWrtTrxHeader,), data_range=Range(0, OPENWRT_TRXV2_HEADER_LEN)
)
else:
raise UnpackerError(f"Unknown OpenWrt TRX version: {trx_version}")
trx_header = await trx_header_r.view_as(OpenWrtTrxHeader)
# Create lzma loader child
await resource.create_child(
tags=(OpenWrtTrxLzmaLoader,),
data=data[trx_header.trx_loader_offset : trx_header.trx_kernel_offset],
)
# Create kernel child
await resource.create_child(
tags=(OpenWrtTrxKernel,),
data=data[trx_header.trx_kernel_offset : trx_header.trx_rootfs_offset],
)
if trx_version == OpenWrtTrxVersion.VERSION1:
# Create rootfs child
await resource.create_child(
tags=(OpenWrtTrxRootfs,),
data=data[trx_header.trx_rootfs_offset :],
)
else: # TRX Version 2
# Create rootfs child
await resource.create_child(
tags=(OpenWrtTrxRootfs,),
data=data[trx_header.trx_rootfs_offset : trx_header.trx_binheader_offset],
)
# Create binHeader child
await resource.create_child(
tags=(OpenWrtTrxBinheader,),
data=data[trx_header.trx_binheader_offset :],
)
OpenWrtTrxVersion (Enum)
An enumeration.
openwrt_crc32(data)
Calculate CRC32 a-la OpenWrt. Original implementation: https://git.archive.openwrt.org/?p=14.07/openwrt.git;a=blob;f=tools/firmware-utils/src/trx.c
Implements CRC-32 Ethernet which requires XOR'ing the zlib.crc32 result with 0xFFFFFFFF
Source code in ofrak_components/openwrt.py
def openwrt_crc32(data: bytes) -> int:
"""
Calculate CRC32 a-la OpenWrt. Original implementation:
<https://git.archive.openwrt.org/?p=14.07/openwrt.git;a=blob;f=tools/firmware-utils/src/trx.c>
Implements CRC-32 Ethernet which requires XOR'ing the zlib.crc32 result with 0xFFFFFFFF
"""
return (zlib.crc32(data) & 0xFFFFFFFF) ^ 0xFFFFFFFF