Many of the newer inMusic devices have switched from using unsigned firmware updates to using signed firmware updates. This prevents modifications to the firmware updates unfortunately (although you can likely still modify the OS on the device and boot into the modifications). Whether it will boot using a modified rootfs is untested.
Extracting these images is a solved problem — every partition, rootfs included, can be pulled out directly (see Extracting). What remains blocked is rebuilding an installable update, because of the signature block described in Signature.
Header
AZ0x is a standalone container format, not a header bolted onto a flattened device tree. 0xD00DFEED does appear inside these files, but as the payload of the BOOT partitions — each of which is its own FIT image. The rootfs is a separate, xz-compressed partition and is not reachable by parsing that device tree.
41 5A 30 78 01 00 00 00 00 00 00 00 38 00 00 00 38 00 00 00 98 00 00 00 A0 00 00 00 E0 01 00 00 60 00 00 00 01 00 05 00 02 00 00 00 E0 01 00 00 01 00 00 00 0A 00 00 00 00 53 4E 41 50 53 48 4F 54 00 48 47 30 36 20 75 70 67 72 61 64 65 20 69 6D 61 67 65 00 48 47 30 36 00 73 70 6C 61 73 68 00 75 70 64 61 74 65 73 70 6C 61 73 68 00 6B 65 72 6E 65 6C 00 72 6F 6F 74 66 73 00 74 65 73 74 2D 68 65 61 64 72 75 73 68 00 68 65 61 64 72 75 73 68 2D 31 00 00 00 00 1B 30 63 07 1D 00 00 00 42 4F 4F 54 00 00 00 00 F0 03 00 00 00 00 00 00 BC D5 08 00 00 00 00 00 00 00 00 00 00 00 00 00 37 C2 75 FC 74 8B 58 E0 A7 83 C3 7F 70 D8 E5 E4 CF 48 8F EA 8A 2E F3 80 03 DE EA D7 56 5B C7 0B 50 41 52 54 01 00 00 00 B0 D9 08 00 00 00 00 00 5C 21 04 00 00 00 00 00 22 00 00 00 00 00 00 00 E5 C0 2B BF 72 A7 47 79 5E BD ED ED FE AA C7 63 5B CC E9 BA 7D 59 B1 58 62 77 0C 83 DD 85 9B DB 50 41 52 54 01 00 00 00 10 FB 0C 00 00 00 00 00 60 28 04 00 00 00 00 00 29 00 00 00 00 00 00 00 CC 87 F9 19 AC 6F 04 17 DC 7F AA 8A 41 CA D0 29 EE 4E 5E 7C F4 0E EC D8 54 06 A4 C7 84 B1 4F C8 50 41 52 54 01 00 00 00 70 23 11 00 00 00 00 00 20 FE 5A 00 00 00 00 00 36 00 00 00 00 00 00 00 57 23 F5 34 71 DA 67 B1 FD DE 39 2F EE 1D 42 62 0B CC 44 9E A1 16 81 E2 A6 39 CF F2 4B 22 22 68 50 41 52 54 01 00 00 00 90 21 6C 00 00 00 00 00 F8 66 51 09 00 00 00 00 3D 00 00 00 00 00 00 00 CC 1B 1E AC 57 E1 44 FC 49 59 41 DB 00 B2 76 8D C4 69 2E 92 F4 2B C8 60 4B 6F 6B CC A8 E9 B4 CA 44 00 00 00 00 00 00 00 0B 49 0A 9D AB 98 E7 F1 30 74 23 79 4D EB F5 D3 DB D9 80 67 1B 4D 78 6F 1B EA 9A 8E D0 54 DE 7F 01 4E 38 8A 3C 27 67 C4 EB 49 EE EE 20 5A 7C 8D 1C 47 AB C3 53 94 48 50 2E 68 02 B0 09 7A C3 C0 D3 7F C3 DD 6B 42 4F EA 4E 3D 12 32 9B AA 6B CB 3F 47 F0 AB 48 2D 0E F0 E8 20 80 8A 94 41 6C F8 32 E9 F3 0A 82 15 B0 C1 70 A8 FE 65 95 C8 55 47 23 8A 79 B5 E9 D2 5C 46 53 BC 09 CC 88 AA 7C 2A 3D F2 D3 49 11 3F CC BD 36 70 14 B8 85 E5 AA 9E 49 A5 4A 00 16 FF 4C 25 CF 19 A6 F7 1A E9 7C A6 2F A5 0E 59 9C 9F CF 24 4F 01 4A 13 B3 06 A2 2E 22 35 B8 E2 92 46 5F 5B 51 AD 43 09 11 C0 37 BA A4 36 35 03 A1 65 D0 99 EB F6 76 C2 0B CD 43 19 8C C5 FD 8B 74 9C 62 96 61 6E 59 FE B5 22 2B 1E B9 0A FB B5 FA 43 CC 92 49 99 90 B4 8B F8 AB FF 00 37 C9 15 BF 6D FC 2B 5F 65 8C C6 24 38 7E FF 52 00 00 00 00 00 00 00 20 3D 58 F4 8B 42 83 81 58 B0 71 A2 0A 43 2B 64 2F DB 9D A6 65 93 8A 43 36 3D 25 F7 99 6C 11 71 7B 43 08 6C 70 B8 0C DD 3F 50 2E 5B 16 10 80 61 F3 4E D1 14 59 3D 97 36 37 0C ED 11 A7 D3 EB CE 17 A0 88 1D BD 53 37 3C 39 4D F2 8E E3 74 72 B9 F3 A8 2D 74 7C A3 25 FD DF 28 9E 09 0D 35 65 BB E3 F4 83 FF 1C 2A 0C FF 11 8B C5 CA 34 3E 1D C6 4C 3A 94 CF CF DB A6 4A 8C 56 8A 8D 26 7A DC BE 8A F1 50 BC AB ED C9 18 46 E8 E1 29 D6 64 E0 18 FD B1 C9 07 6B 52 E4 62 BC 52 B7 B6 C8 CE 6B 58 F7 41 AE D2 15 F4 A4 92 1B EF 10 58 4F 87 8E 46 E7 41 EC 0A 74 9C 44 10 81 55 DE DA BD 74 B9 0C A5 A5 3A 7C 5F 70 9D B9 FA 25 BE 21 CF 49 18 B3 48 F8 2C 35 A4 D8 83 A2 21 9D 2D 73 7B 48 0A BD E5 A7 25 97 43 B4 5D 80 65 20 30 20 7F 57 84 91 27 82 E5 9D 2F 04 44 EC B7 A0 61 7B B9 88 C5 7E
Header Value Descriptions
All of these start with 41 5A 30 78, which decodes to AZ0x. This is a fixed literal — it is identical across every AZ0x image I have checked, and the x is not a wildcard standing in for a board revision. The 01 00 00 00 that follows is a separate field: the container's format version, which so far is always 1.
The header is a fixed 0x38 bytes, immediately followed by a string table. Every string in the file — version, image name, device names, partition names — lives in that table and is referenced by an offset relative to its start. Offset 0 in the table is a NUL, meaning "no string".
Offset
Size
Description
0x00
4
Magic, AZ0x
0x04
4
Format version (always 1 so far)
0x10
4
String table offset (0x38)
0x14
4
Device table offset
0x18
4
Partition table offset
0x1c
4
Partition table end offset
0x20
4
String table size
0x24
2
Number of supported devices
0x26
2
Number of partitions
0x34
4
Offset (into the string table) of the update image name
Reading via these pointers is more robust than scanning for NULs sequentially, though both work in practice.
Offset 0x39 up to the byte 0x00 contains the firmware version. (This is the string table at 0x38, plus 1 to skip its leading NUL.)
On Denon devices, I've seen 4.1.0, these likely have the software version for all updates.
On HeadRush devices, I've seen SNAPSHOT. This is likely their SVN's branch the build is produced from (not the software version).
Following the version (up to the next 0x00), we get the name of the update image:
HG06 upgrade image
HV01 upgrade image
EngineOS upgrade image
Following this, we get the model IDs of the devices this firmware update supports. This can be a number of strings (the number of these strings can be found at offset 0x24).
There is also a device table at the offset stored in 0x14, holding one 8-byte entry per device: a u32 numeric device ID followed by a u32 offset of that device's name in the string table. This is where the numeric IDs come from:
Device
ID
JC11S
0x15e4d007
JP11S
0x15e4c00c
JP20
0x15e4d011
JP21
0x15e4d012
NH08S
0x15e4303f
NH10
0x15e42059
HG06
0x0763301b
HV01
0x07633019
The order of this table matters: a device's index here is its bit position in the partition device mask described below.
Partitions
Before retrieving the partitions, we need to look at a couple values at different offsets to determine the partition metadata layout in the firmware.
Offset
Description
0x18
The beginning address of the partitions
0x26
The number of partitions in the image
For example, PRIME4PLUS-3.1.0-Update.img contains:
Offset
Value
0x18
0xa8
0x26
0x09
This tells us that we need to go to 0xa8 to get the first partition's metadata. Partition's metadata is 0x40 in length, so we can read this into a buffer.
Since we know we have 9 partitions, we read this 9 times before we reach the end of all partitions' metadata.
The partition metadata that I have figured out the meaning of is as follows (offset is the offset from the beginning of the partition's metadata):
Offset
Description
0x0-0x3
BOOT, PART or PARR — the partition kind. PARR is a recovery partition (e.g. AC50)
0x8-0xf
Offset of this partition's own data in the file (little endian)
0x10-0x17
The size of the partition data
0x18-0x1b
Offset of the partition's name in the string table (0 = unnamed, as on BOOT)
0x1c-0x1f
Device bitmask — bit N is set for device N of the device table
0x20-0x3f
SHA-256 of the partition data — confirmed, verified byte-for-byte
Bytes 0x4-0x7 are a flags word, not a type enum — the marker at 0x0 already says what kind of partition this is. Across 1,375 partition entries only two bits are ever set:
Bit
Meaning
0x00000001
Payload is xz-compressed. Clear means stored raw
0x80000000
Entry is device-specific, i.e. the device mask at 0x1c is non-zero
Every other bit was zero in every image checked.
The device mask
The value at 0x1c is a bitmask over the device table, not a partition number. The SCLIVE2 4.1.0 header above demonstrates this plainly. Its device table is JC11S, JP11S, JP20, JP21, and its entries carry:
Entry
Mask
Meaning
BOOT ×4
0x01, 0x02, 0x04, 0x08
one bootloader per device
splash
0x01
JC11S only
splash
0x0E
JP11S + JP20 + JP21
kernel, rootfs
0x00
shared by every device
A mask of 0x0E could not be a partition number — it is 0x02 | 0x04 | 0x08. This also means partition names are not unique: a multi-device image legitimately contains two partitions both called splash, separated only by mask. Any extractor has to disambiguate them or it will silently overwrite one with the other.
Compression
Compression is given by bit 0 of the flags word at 0x4. Sniffing the payload magic works equally well and agreed with the flag on all 1,375 entries checked, so either can be used:
Marker
Flag bit 0
Payload
Count
BOOT
clear
Uncompressed FIT (0xD00DFEED)
424
PART
set
xz (FD 37 7A 58 5A 00)
904
PART
clear
Raw — only ever splash/updatesplash
30
PARR
set
xz
17
Note that a raw splash is not unusual enough to ignore: 30 of them appear across the collection, so an extractor that assumes every PART is xz will fail on those.
Signature
Between the end of the partition table (0x1c) and the first partition's data sits a signature block: a u64 length, followed by that many bytes of high-entropy data.
The length is variable — across 207 images it ranges from 59 to 104 bytes, most commonly 68, 86, 75 and 80. That variability is itself the clue: a fixed-size signature would be a constant length, whereas DER encoding of an ECDSA signature varies by a few bytes depending on how the two integers encode. Combined with being far too short for RSA-2048, this points to DER-encoded ECDSA rather than a raw fixed-width signature.
This is what actually blocks rebuilding an update. The partition table itself is trivial to regenerate, but without the vendor's signing key the device rejects the result, so my extraction utility refuses to repack AZ0x images rather than emit a file that cannot install. Note this signature is separate from the RSA verification found in the updater ramfs on 5.0.4, which is inactive because the shipped images carry no such signature and the ramfs ships no keys.
Extracting
The rootfs — and every other partition — can now be extracted directly:
python main.py -f <firmware.img> --ssh
Partitions land in firmwares/generated/ as <partition>_<firmware>.img, with per-device duplicates disambiguated (splash-jc11s, splash-jp11s-jp20-jp21). The extracted rootfs is a bare ext2/ext4 filesystem, so it mounts directly:
sudo mount -o loop ./firmwares/generated/rootfs_<firmware>.img ./firmwares/mnt
This has been validated across every AZ0x image I have (207 of them), plus the older FDT and AZ01 containers.
Data (legacy notes)
After removing the header from the file, dumpimage -l <image> on SCLIVE2-4.1.0-Update.img outputs: