The legacy U-Boot image format (uImage), as created by mkimage: a 64-byte
header followed by the image data (e.g. a Linux kernel, a ramdisk or a
script). The header describes the data (OS, CPU architecture, image type,
compression, load and entry point addresses, name) and protects both the
header and the data with CRC-32 checksums.
U-Boot's newer FIT (Flattened Image Tree) format is not covered by this spec.
This page hosts a formal specification of U-Boot Image wrapper using Kaitai Struct. This specification can be automatically translated into a variety of programming languages to get a parsing library.
import kaitai_struct_nim_runtime
import options
type
Uimage* = ref object of KaitaiStruct
`header`*: Uimage_Uheader
`data`*: seq[byte]
`parent`*: KaitaiStruct
Uimage_MagicTypes* = enum
allnet_all_sg8316m = 5
allnet_all_sg8208m = 6
allnet_all_sg8324m = 22
datto_l8 = 7353344
allnet_all_wapc0450c = 9453568
engenius_ews2910p_v3 = 50398464
engenius_ews1200_28tfp = 50402048
engenius_ews7926efp = 50402050
engenius_ews7952p = 50406401
engenius_ews2908p = 50409728
engenius_ews7928p_v2 = 51450112
engenius_ews1200_28tfp_v2 = 51450624
engenius_ews2910p = 58730768
engenius_ews5912fp = 58743058
engenius_ews7928p = 58751272
engenius_ews1200d_10t = 59187216
engenius_ews1200_28t = 59187240
engenius_ews7928fp = 59216527
engenius_ews1200_52t = 60235858
engenius_ews7952fp = 60265775
youku_yk_l1 = 304680960
realtek_sdk = 305418240
netgear_gs750e = 391006017
netgear_gs728tp_v2 = 391006018
bolt_bl100 = 638656533
uimage = 654645590
netgear_wnce4004 = 654645607
netgear_wnr612_v2_early = 808857906
netgear_wnr1000_v2_vc = 825241648
netgear_wnr1000_v2 = 825241649
netgear_wnr1000_v2_vm = 825241650
netgear_wnr1000_v4 = 825241652
netgear_wpn824n = 825307184
zyxel_uag50 = 827343191
netgear_wnr2000 = 842018865
netgear_wnr2000_v3 = 842018867
netgear_wnr2000_v4 = 842018868
netgear_wnr612_v2 = 842020401
netgear_wnr2200 = 842149936
netgear_wndr3700 = 859254832
netgear_wndr3700_v2 = 859254833
netgear_wndr3700_v4 = 859254835
netgear_r6100 = 909127728
netgear_wnr612 = 909193778
mitrastar_comb = 1129270594
mitrastar_comc = 1129270595
netgear_gs308t = 1313293109
netgear_nge = 1313293600
netgear_ngf = 1313293856
netgear_ngg = 1313294112
netgear_ngh = 1313294368
netgear_ngi = 1313294624
netgear_ngp = 1313296416
openwrt_okli = 1330334793
openwrt_dlink_covr = 1752396147
openwrt_senao_okli = 1936805707
engenius_ecs1112fp = 2155872258
engenius_fitswitch = 2155872259
engenius_ews1200_28tfp_old_fw = 2197885696
engenius_ews7926efp_old_fw = 2197885698
engenius_ews7952p_old_fw = 2197890049
realtek_rtl838x = 2206203904
iodata_bsh_g08m = 2206203905
iodata_bsh_gp08 = 2206203906
iodata_bsh_g16m = 2206203907
iodata_bsh_g24m = 2206203908
iodata_bsh_g08mb = 2206203920
iodata_bsh_gp08mb = 2206203921
iodata_bsh_g16mb = 2206203922
iodata_bsh_g24mb = 2206203923
trendnet_tpe_3012ls = 2206210573
trendnet_tpe_3018ls = 2206210574
engenius_egs2108p_old_fw = 2206212360
engenius_egs2110p_old_fw = 2206212368
engenius_ews2910p_old_fw = 2206214416
engenius_egs5110p_old_fw = 2206224656
engenius_egs5212fp_old_fw = 2206224914
engenius_ews5912fp_old_fw = 2206226706
engenius_ews7928p_old_fw = 2206234920
engenius_ews1200d_10t_old_fw = 2206670864
engenius_ews1200_28t_old_fw = 2206670888
engenius_egs7228fp_old_fw = 2206671503
engenius_ews7928fp_old_fw = 2206700175
realtek_rtl839x = 2207252480
realtek_rtl8396 = 2207645696
engenius_ews1200_52t_old_fw = 2207719506
engenius_egs7252fp_old_fw = 2207720751
engenius_ews7952fp_old_fw = 2207749423
realtek_rtl930x = 2466250752
zyxel_xgs1010_12 = 2466254864
zyxel_xgs1210_12 = 2466255376
zyxel_xgs1250_12 = 2466255440
nicgiga_s100_0800s_m = 2466447360
plasmacloud_psx28 = 2467299328
Uimage_UimageArch* = enum
invalid = 0
alpha = 1
arm = 2
i386 = 3
ia64 = 4
mips = 5
mips64 = 6
ppc = 7
s390 = 8
sh = 9
sparc = 10
sparc64 = 11
m68k = 12
nios = 13
microblaze = 14
nios2 = 15
blackfin = 16
avr32 = 17
st200 = 18
sandbox = 19
nds32 = 20
openrisc = 21
arm64 = 22
arc = 23
x86_64 = 24
xtensa = 25
riscv = 26
Uimage_UimageComp* = enum
none = 0
gzip = 1
bzip2 = 2
lzma = 3
lzo = 4
lz4 = 5
zstd = 6
mz = 9
xip = 10
Uimage_UimageOs* = enum
invalid = 0
openbsd = 1
netbsd = 2
freebsd = 3
bsd4_4 = 4
linux = 5
svr4 = 6
esix = 7
solaris = 8
irix = 9
sco = 10
dell = 11
ncr = 12
lynxos = 13
vxworks = 14
psos = 15
qnx = 16
u_boot = 17
rtems = 18
artos = 19
unity = 20
integrity = 21
ose = 22
plan9 = 23
openrtos = 24
arm_trusted_firmware = 25
tee = 26
opensbi = 27
efi = 28
elf = 29
Uimage_UimageType* = enum
invalid = 0
standalone = 1
kernel = 2
ramdisk = 3
multi = 4
firmware = 5
script = 6
filesystem = 7
flatdt = 8
kwbimage = 9
imximage = 10
ublimage = 11
omapimage = 12
aisimage = 13
kernel_noload = 14
pblimage = 15
mxsimage = 16
gpimage = 17
atmelimage = 18
socfpgaimage = 19
x86_setup = 20
lpc32xximage = 21
loadable = 22
rkimage = 23
rksd = 24
rkspi = 25
zynqimage = 26
zynqmpimage = 27
zynqmpbif = 28
fpga = 29
vybridimage = 30
tee = 31
firmware_ivt = 32
pmmc = 33
stm32image = 34
socfpgaimage_v1 = 35
mtkimage = 36
imx8mimage = 37
imx8image = 38
copro = 39
sunxi_egon = 40
sunxi_toc0 = 41
fdt_legacy = 42
renesas_spkg = 43
starfive_spl = 44
tfa_bl31 = 45
stm32image_v2 = 46
amlimage = 47
Uimage_AsusFirmwareInformation* = ref object of KaitaiStruct
`kernelVersion`*: Uimage_Version
`fsVersion`*: Uimage_Version
`productId`*: string
`extraInfo`*: seq[byte]
`parent`*: Uimage_NameOrAsusInfo
Uimage_NameOrAsusInfo* = ref object of KaitaiStruct
`name`*: string
`asusInfo`*: Uimage_AsusFirmwareInformation
`parent`*: Uimage_Uheader
`byte0Inst`: uint8
`byte0InstFlag`: bool
`byte4Inst`: uint8
`byte4InstFlag`: bool
`hasAsusInfoInst`: bool
`hasAsusInfoInstFlag`: bool
Uimage_Uheader* = ref object of KaitaiStruct
`magic`*: Uimage_MagicTypes
`headerCrc`*: uint32
`timestamp`*: uint32
`lenImage`*: uint32
`loadAddress`*: uint32
`entryAddress`*: uint32
`dataCrc`*: uint32
`osType`*: Uimage_UimageOs
`architecture`*: Uimage_UimageArch
`imageType`*: Uimage_UimageType
`compressionType`*: Uimage_UimageComp
`nameOrAsusInfo`*: Uimage_NameOrAsusInfo
`parent`*: Uimage
`rawNameOrAsusInfo`*: seq[byte]
Uimage_Version* = ref object of KaitaiStruct
`major`*: uint8
`minor`*: uint8
`parent`*: Uimage_AsusFirmwareInformation
proc read*(_: typedesc[Uimage], io: KaitaiStream, root: KaitaiStruct, parent: KaitaiStruct): Uimage
proc read*(_: typedesc[Uimage_AsusFirmwareInformation], io: KaitaiStream, root: KaitaiStruct, parent: Uimage_NameOrAsusInfo): Uimage_AsusFirmwareInformation
proc read*(_: typedesc[Uimage_NameOrAsusInfo], io: KaitaiStream, root: KaitaiStruct, parent: Uimage_Uheader): Uimage_NameOrAsusInfo
proc read*(_: typedesc[Uimage_Uheader], io: KaitaiStream, root: KaitaiStruct, parent: Uimage): Uimage_Uheader
proc read*(_: typedesc[Uimage_Version], io: KaitaiStream, root: KaitaiStruct, parent: Uimage_AsusFirmwareInformation): Uimage_Version
proc byte0*(this: Uimage_NameOrAsusInfo): uint8
proc byte4*(this: Uimage_NameOrAsusInfo): uint8
proc hasAsusInfo*(this: Uimage_NameOrAsusInfo): bool
##[
The legacy U-Boot image format (uImage), as created by `mkimage`: a 64-byte
header followed by the image data (e.g. a Linux kernel, a ramdisk or a
script). The header describes the data (OS, CPU architecture, image type,
compression, load and entry point addresses, name) and protects both the
header and the data with CRC-32 checksums.
U-Boot's newer FIT (Flattened Image Tree) format is not covered by this spec.
@see <a href="https://github.com/u-boot/u-boot/blob/ece349ade2973e220f524ce59e59711cc919263f/include/image.h">Git tag "v2026.07"</a>
]##
proc read*(_: typedesc[Uimage], io: KaitaiStream, root: KaitaiStruct, parent: KaitaiStruct): Uimage =
template this: untyped = result
this = new(Uimage)
let root = if root == nil: cast[Uimage](this) else: cast[Uimage](root)
this.io = io
this.root = root
this.parent = parent
let headerExpr = Uimage_Uheader.read(this.io, this.root, this)
this.header = headerExpr
let dataExpr = this.io.readBytes(int(this.header.lenImage))
this.data = dataExpr
proc fromFile*(_: typedesc[Uimage], filename: string): Uimage =
Uimage.read(newKaitaiFileStream(filename), nil, nil)
##[
ASUS firmware images [overlay the 32-byte image
name](https://github.com/drag0njoe/RT-AC55U/blob/cf5874684ab6995ee07e91a52dfdd0562fcc2655/release/src/asustools/mkimage.src/include/image.h#L185-L188)
with a [`TAIL`
structure](https://github.com/drag0njoe/RT-AC55U/blob/cf5874684ab6995ee07e91a52dfdd0562fcc2655/release/src/asustools/mkimage.src/include/image.h#L151-L171)
that contains version information and the product ID (model name).
The first 16 bytes have the same structure in all three known variants of
the `TAIL` structure. The meaning of the last 16 bytes differs across the
variants - therefore, for simplicity, this Kaitai Struct implementation
treats them as an opaque byte array named `extra_info`. For an overview of
the variants, see the documentation for `extra_info`.
]##
proc read*(_: typedesc[Uimage_AsusFirmwareInformation], io: KaitaiStream, root: KaitaiStruct, parent: Uimage_NameOrAsusInfo): Uimage_AsusFirmwareInformation =
template this: untyped = result
this = new(Uimage_AsusFirmwareInformation)
let root = if root == nil: cast[Uimage](this) else: cast[Uimage](root)
this.io = io
this.root = root
this.parent = parent
let kernelVersionExpr = Uimage_Version.read(this.io, this.root, this)
this.kernelVersion = kernelVersionExpr
let fsVersionExpr = Uimage_Version.read(this.io, this.root, this)
this.fsVersion = fsVersionExpr
##[
In the Ralink SDK variant (see `extra_info`), the product ID field is
actually 23 bytes long, but no known product ID is longer than 12
bytes.
]##
let productIdExpr = encode(this.io.readBytes(int(12)).bytesTerminate(0, false), "UTF-8")
this.productId = productIdExpr
##[
The structure of these bytes varies among the three known variants of
the `TAIL` structure, and the header doesn't say which one is used:
* Plain: 8 hardware versions (using the `version` type) - see
<https://github.com/drag0njoe/RT-AC55U/blob/cf5874684ab6995ee07e91a52dfdd0562fcc2655/release/src/asustools/mkimage.src/include/image.h#L170>
Sample file: `RT-AC51U_3.0.0.4_380_8591-ga8dd632.trx` from
<https://dlcdnets.asus.com/pub/ASUS/wireless/RT-AC51U/FW_RT_AC51U_30043808591.zip>,
released on 2020-09-22 and listed on
<https://www.asus.com/supportonly/rt-ac51u/helpdesk_bios/>.
Source code links:
- [ASUS GPL source, RT-AC55UHP
3.0.0.4.382.51915](https://dlcdnets.asus.com/pub/ASUS/wireless/RT-AC55UHP/GPL_RT_AC55UHP_300438251915.zip)
(`asuswrt/release/src/asustools/mkimage.src/` in
`GPL_RT-AC55UHP_3.0.0.4.382.51915-g4086e57.tgz`)
- [ASUS GPL source, RT-AC55U 3.0.0.4.382.50702 (unofficial
mirror)](https://github.com/drag0njoe/RT-AC55U/tree/cf5874684ab6995ee07e91a52dfdd0562fcc2655/release/src/asustools/mkimage.src)
* `TRX_NEW` (a macro defined when building `mkimage`): the
build number and extended build number (`u2le` each), two key bytes
and 5 hardware versions - see
<https://github.com/drag0njoe/RT-AC55U/blob/cf5874684ab6995ee07e91a52dfdd0562fcc2655/release/src-qca/asustools/mkimage.src/include/image.h#L174-L180>
`mkimage` [overwrites the first 3 hardware
versions](https://github.com/drag0njoe/RT-AC55U/blob/cf5874684ab6995ee07e91a52dfdd0562fcc2655/release/src-qca/asustools/mkimage.src/mkimage.c#L546-L569)
with values derived from the key bytes.
Sample file: `RT-AC55UHP_3.0.0.4_382_52236-ga0f880d.trx` (build 382,
extended build 52236) from
<https://dlcdnets.asus.com/pub/ASUS/wireless/RT-AC55UHP/FW_RT_AC55UHP_300438252236.zip>,
released on 2020-06-04 and listed on
<https://www.asus.com/supportonly/rt-ac55uhp/helpdesk_bios/>.
Source code links:
- [ASUS GPL source, RT-AC55UHP
3.0.0.4.382.51915](https://dlcdnets.asus.com/pub/ASUS/wireless/RT-AC55UHP/GPL_RT_AC55UHP_300438251915.zip)
(`asuswrt/release/src-qca/asustools/mkimage.src/` in
`GPL_RT-AC55UHP_3.0.0.4.382.51915-g4086e57.tgz`)
- [ASUS GPL source, RT-AC55U 3.0.0.4.382.50702 (unofficial
mirror)](https://github.com/drag0njoe/RT-AC55U/tree/cf5874684ab6995ee07e91a52dfdd0562fcc2655/release/src-qca/asustools/mkimage.src)
* Ralink SDK: the rest of a 23-byte product ID (see `product_id`),
a version suffix letter and the kernel part size (called `ih_ksz`) -
see <https://github.com/andy-padavan/rt-n56u/blob/32a93db4026cc2cff585d7008373432d888fc1aa/trunk/tools/mkimage/include/image.h#L164-L165>
Sample file: `RP-N12_1.0.1.1f.trx` from
<https://dlcdnets.asus.com/pub/ASUS/wireless/RP-N12/FW_RP-N12_1.0.1.1f.zip>,
released on 2019-11-12 and listed on
<https://www.asus.com/supportonly/rp-n12/helpdesk_bios/>.
Source code links:
- [ASUS GPL source, RP-N12
1.0.1.1f](https://dlcdnets.asus.com/pub/ASUS/wireless/RP-N12/GPL_RP_N12_1011f.zip)
(`GPL_RP-N12_source.1011f/user/mkimage/include/image.h` in
`GPL_RP-N12_source.1011f.tar.bz2`)
- [rt-n56u custom firmware by Andy
Padavan](https://github.com/andy-padavan/rt-n56u/blob/32a93db4026cc2cff585d7008373432d888fc1aa/trunk/tools/mkimage/include/image.h)
In the plain and `TRX_NEW` variants, the last 4 bytes may instead
contain the offset of the root file system, which is [written by
`mkimage -r`](https://github.com/drag0njoe/RT-AC55U/blob/cf5874684ab6995ee07e91a52dfdd0562fcc2655/release/src/asustools/mkimage.src/mkimage.c#L527-L542)
as the magic byte 0xA9
([`ROOTFS_OFFSET_MAGIC`](https://github.com/drag0njoe/RT-AC55U/blob/cf5874684ab6995ee07e91a52dfdd0562fcc2655/release/src/asustools/mkimage.src/include/image.h#L154-L159))
followed by the offset as a 24-bit big-endian integer.
]##
let extraInfoExpr = this.io.readBytes(int(16))
this.extraInfo = extraInfoExpr
proc fromFile*(_: typedesc[Uimage_AsusFirmwareInformation], filename: string): Uimage_AsusFirmwareInformation =
Uimage_AsusFirmwareInformation.read(newKaitaiFileStream(filename), nil, nil)
proc read*(_: typedesc[Uimage_NameOrAsusInfo], io: KaitaiStream, root: KaitaiStruct, parent: Uimage_Uheader): Uimage_NameOrAsusInfo =
template this: untyped = result
this = new(Uimage_NameOrAsusInfo)
let root = if root == nil: cast[Uimage](this) else: cast[Uimage](root)
this.io = io
this.root = root
this.parent = parent
if not(this.hasAsusInfo):
let nameExpr = encode(this.io.readBytesTerm(0, false, true, false), "UTF-8")
this.name = nameExpr
if this.hasAsusInfo:
let asusInfoExpr = Uimage_AsusFirmwareInformation.read(this.io, this.root, this)
this.asusInfo = asusInfoExpr
proc byte0(this: Uimage_NameOrAsusInfo): uint8 =
if this.byte0InstFlag:
return this.byte0Inst
let pos = this.io.pos()
this.io.seek(int(0))
let byte0InstExpr = this.io.readU1()
this.byte0Inst = byte0InstExpr
this.io.seek(pos)
this.byte0InstFlag = true
return this.byte0Inst
proc byte4(this: Uimage_NameOrAsusInfo): uint8 =
if this.byte4InstFlag:
return this.byte4Inst
let pos = this.io.pos()
this.io.seek(int(4))
let byte4InstExpr = this.io.readU1()
this.byte4Inst = byte4InstExpr
this.io.seek(pos)
this.byte4InstFlag = true
return this.byte4Inst
proc hasAsusInfo(this: Uimage_NameOrAsusInfo): bool =
##[
Whether this type stores ASUS firmware information (`asus_info`)
rather than an image name (`name`).
The header doesn't say which one it is, so we infer it using a
heuristic based on the layout of the `asus_firmware_information` type.
If ASUS firmware info is present, the first byte (the major kernel
version number) is a small integer (i.e. not a printable character),
and byte 4 (first byte of the product ID/model name) is a printable
character. An empty name is not mistaken for ASUS information, because
its byte 4 is a null byte.
The highest known major kernel version number is 9 (e.g. in
`DSL-N55U_9.0.0.4_380_3925-gad8f412_Annex_A.trx` from
<https://dlcdnets.asus.com/pub/ASUS/wireless/DSL-N55U/FW_DSL_N55U_90043803925.zip>,
released on 2016-08-04 and listed on
<https://www.asus.com/supportonly/dsl-n55u/helpdesk_bios/>). ASUS
seems to use such high major versions (9, sometimes 7) for beta
releases, whereas regular releases use 3 (or 1 in older firmware).
It can also be 0, in particular due to a bug in the MediaTek MT798X
build of ASUS's `mkimage` (it reads the `-V` values from fixed `argv`
positions, even though it parses the options using `getopt()` - see
<https://github.com/blocktrron/tuf-ax4200-gpl/blob/a4fe37f4d78473f97c6d47cf93d97c3811af90cc/release/src-mtk-MT798X/Uboot-upstream/tools/mkimage.c#L317-L333>).
For example, this is the case in
`RT-AX52_3.0.0.4_388_34015-g9488655.trx` from
<https://dlcdnets.asus.com/pub/ASUS/wireless/RT-AX52/FW_RT-AX52_300438834015.zip>,
released on 2026-03-20 and listed on
<https://www.asus.com/supportonly/rt-ax52/helpdesk_bios?model2Name=RT-AX52>.
]##
if this.hasAsusInfoInstFlag:
return this.hasAsusInfoInst
let hasAsusInfoInstExpr = bool( ((this.byte0 < 32) and (this.byte4 >= 32) and (this.byte4 < 127)) )
this.hasAsusInfoInst = hasAsusInfoInstExpr
this.hasAsusInfoInstFlag = true
return this.hasAsusInfoInst
proc fromFile*(_: typedesc[Uimage_NameOrAsusInfo], filename: string): Uimage_NameOrAsusInfo =
Uimage_NameOrAsusInfo.read(newKaitaiFileStream(filename), nil, nil)
proc read*(_: typedesc[Uimage_Uheader], io: KaitaiStream, root: KaitaiStruct, parent: Uimage): Uimage_Uheader =
template this: untyped = result
this = new(Uimage_Uheader)
let root = if root == nil: cast[Uimage](this) else: cast[Uimage](root)
this.io = io
this.root = root
this.parent = parent
##[
Besides the standard U-Boot magic, some vendors' bootloaders expect
their own value (often a different one for each device model), with
the rest of the header unchanged. `magic_types` lists the values
known so far; images with any other magic are rejected.
]##
let magicExpr = Uimage_MagicTypes(this.io.readU4be())
this.magic = magicExpr
let headerCrcExpr = this.io.readU4be()
this.headerCrc = headerCrcExpr
let timestampExpr = this.io.readU4be()
this.timestamp = timestampExpr
let lenImageExpr = this.io.readU4be()
this.lenImage = lenImageExpr
let loadAddressExpr = this.io.readU4be()
this.loadAddress = loadAddressExpr
let entryAddressExpr = this.io.readU4be()
this.entryAddress = entryAddressExpr
let dataCrcExpr = this.io.readU4be()
this.dataCrc = dataCrcExpr
let osTypeExpr = Uimage_UimageOs(this.io.readU1())
this.osType = osTypeExpr
let architectureExpr = Uimage_UimageArch(this.io.readU1())
this.architecture = architectureExpr
let imageTypeExpr = Uimage_UimageType(this.io.readU1())
this.imageType = imageTypeExpr
let compressionTypeExpr = Uimage_UimageComp(this.io.readU1())
this.compressionType = compressionTypeExpr
let rawNameOrAsusInfoExpr = this.io.readBytes(int(32))
this.rawNameOrAsusInfo = rawNameOrAsusInfoExpr
let rawNameOrAsusInfoIo = newKaitaiStream(rawNameOrAsusInfoExpr)
let nameOrAsusInfoExpr = Uimage_NameOrAsusInfo.read(rawNameOrAsusInfoIo, this.root, this)
this.nameOrAsusInfo = nameOrAsusInfoExpr
proc fromFile*(_: typedesc[Uimage_Uheader], filename: string): Uimage_Uheader =
Uimage_Uheader.read(newKaitaiFileStream(filename), nil, nil)
proc read*(_: typedesc[Uimage_Version], io: KaitaiStream, root: KaitaiStruct, parent: Uimage_AsusFirmwareInformation): Uimage_Version =
template this: untyped = result
this = new(Uimage_Version)
let root = if root == nil: cast[Uimage](this) else: cast[Uimage](root)
this.io = io
this.root = root
this.parent = parent
let majorExpr = this.io.readU1()
this.major = majorExpr
let minorExpr = this.io.readU1()
this.minor = minorExpr
proc fromFile*(_: typedesc[Uimage_Version], filename: string): Uimage_Version =
Uimage_Version.read(newKaitaiFileStream(filename), nil, nil)