openwrt.py
ofrak.core.openwrt
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).
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] |
+---------------------------------------------------------------+
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] |
+---------------------------------------------------------------+
OpenWrtTrxHeaderAttributesAnalyzer (Analyzer)
Parses OpenWrt TRX firmware header to extract magic number (0x30524448), total firmware length, CRC32 checksum for integrity verification, firmware flags, and partition offsets pointing to the bootloader, kernel, and root filesystem locations. Use to understand TRX firmware structure, validate firmware integrity via CRC, locate specific partitions for extraction, or prepare for firmware modification. Essential for analyzing router firmware before making changes.
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/core/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 not in [OpenWrtTrxVersion.VERSION1.value, OpenWrtTrxVersion.VERSION2.value]:
raise ValueError(f"Unknown OpenWrt TRX version: {trx_version}")
max_num_partitions = 3 if trx_version == OpenWrtTrxVersion.VERSION1.value else 4
trx_partition_offsets = []
for _ in range(max_num_partitions):
offset = deserializer.unpack_uint()
if offset == 0:
break
trx_partition_offsets.append(offset)
header = OpenWrtTrxHeader(
trx_magic, trx_length, trx_crc, trx_flags, trx_version, trx_partition_offsets
)
return header
OpenWrtTrxHeaderModifier (Modifier)
Modifies OpenWrt TRX firmware header fields including firmware length, CRC32 checksum, flags, and partition offsets, then recalculates the CRC to maintain header validity. The TRX header must be valid for bootloaders to accept the firmware. Use when adjusting TRX structure after partition modifications, updating partition offsets after resizing, changing firmware configuration flags, fixing header corruption, or ensuring header integrity after modifications. Critical for creating valid modified router firmware that will boot correctly.
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/core/openwrt.py
async def modify(self, resource: Resource, config: OpenWrtTrxHeaderModifierConfig) -> None:
original_attributes = await resource.analyze(AttributesType[OpenWrtTrxHeader])
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/core/openwrt.py
@staticmethod
async def serialize(
updated_attributes: AttributesType[OpenWrtTrxHeader],
) -> bytes:
"""
Serialize `updated_attributes` into bytes. This method doesn't perform any check or compute
any CRC.
"""
output = struct.pack(
"<IIIHH",
OPENWRT_TRX_MAGIC_START,
updated_attributes.trx_length,
updated_attributes.trx_crc,
updated_attributes.trx_flags,
updated_attributes.trx_version,
)
if updated_attributes.trx_version == OpenWrtTrxVersion.VERSION1.value:
num_offsets = 3
elif updated_attributes.trx_version == OpenWrtTrxVersion.VERSION2.value:
num_offsets = 4
else:
raise ValueError()
for offset in updated_attributes.trx_partition_offsets:
output += struct.pack("<I", offset)
for i in range(num_offsets - len(updated_attributes.trx_partition_offsets)):
output += struct.pack("<I", 0)
return output
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)
Repackages OpenWrt TRX firmware partitions with an updated header containing recalculated CRC32 checksums, partition offsets, and total length. The TRX format requires accurate checksums for bootloaders to accept the firmware. Use after modifying TRX firmware partitions (kernel, filesystem) to create a valid firmware image for flashing to routers. Critical for ensuring modified router firmware will boot correctly.
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/openwrt.py
async def pack(self, resource: Resource, config=None):
openwrt_v = await resource.view_as(OpenWrtTrx)
header = await openwrt_v.get_header()
children_by_offset = sorted(
[
(await child.get_data_range_within_root(), child)
for child in await resource.get_children()
if not child.has_tag(OpenWrtTrxHeader)
],
key=lambda x: x[0].start,
)
repacked_data_l = [await child.get_data() for _, child in children_by_offset]
repacked_data_b = b"".join(repacked_data_l)
trx_length = header.get_header_length() + len(repacked_data_b)
offsets = [r.start for r, _ in children_by_offset]
header_config = OpenWrtTrxHeaderModifierConfig(
trx_length=trx_length, trx_partition_offsets=offsets
)
await header.resource.run(OpenWrtTrxHeaderModifier, header_config)
header_data = await header.resource.get_data()
data_to_crc = header_data[12:] + repacked_data_b
header_config = OpenWrtTrxHeaderModifierConfig(
trx_crc=openwrt_crc32(data_to_crc),
)
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)
Extracts firmware partitions from OpenWrt TRX (TRX firmware) format files, which package multiple firmware components (bootloader, kernel, root filesystem) with a header containing offsets and checksums. The TRX format is widely used in router firmware. Use when analyzing router firmware, examining individual partitions like the Linux kernel or SquashFS root filesystem, or preparing to modify and repackage router firmware. The header is extracted as a mapped resource while partitions are extracted as separate unmapped children.
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/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 not in [OpenWrtTrxVersion.VERSION1, OpenWrtTrxVersion.VERSION2]:
raise UnpackerError(f"Unknown OpenWrt TRX version: {trx_version}")
header_len = (
OPENWRT_TRXV1_HEADER_LEN
if trx_version == OpenWrtTrxVersion.VERSION1
else OPENWRT_TRXV2_HEADER_LEN
)
trx_header_r = await resource.create_child(
tags=(OpenWrtTrxHeader,), data_range=Range(0, header_len)
)
trx_header = await trx_header_r.view_as(OpenWrtTrxHeader)
partition_offsets = trx_header.trx_partition_offsets
for i, offset in enumerate(partition_offsets):
if offset == 0:
break
next_offset = (
partition_offsets[i + 1] if i < (len(partition_offsets) - 1) else len(data)
)
partition = data[offset:next_offset]
child = await resource.create_child(
tags=(GenericBinary,), data_range=Range(offset, next_offset)
)
if OPENWRT_TRX_MARK in partition:
partition = partition[: partition.index(OPENWRT_TRX_MARK)]
await child.create_child(
tags=(GenericBinary,), data_range=Range.from_size(0, len(partition))
)
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/core/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