Skip to content
All tools
ForensicsRuns locallyNo account

Firmware unpacker: entropy, filesystems and partition tables

Work out what a router or IoT firmware image is made of - entropy profile, SquashFS/CramFS/JFFS2/UBI headers, U-Boot and partition tables - and unpack what can be unpacked in the browser.

Open in ctfpal

A firmware image is a stack of things pretending to be one file: a bootloader, a kernel, a compressed root filesystem, sometimes a partition table describing where each begins. The magic-byte scan tells you where the pieces start. What decides how long you spend on the image is the entropy.

Read the entropy before anything else

Entropy is bits of information per byte, from 0 to 8. Padding sits at 0. Code and structured data sit around 4 to 6. Compressed and encrypted data both sit above 7.9, and that is the whole difficulty: they look identical on the plot. What separates them is what is at the start of the high-entropy run.

  • High entropy that begins at a known signature - a SquashFS superblock, a gzip header, an LZMA properties byte - is compressed. There is something in there and a tool will open it.
  • High entropy that begins at nothing is the tell for encryption. Vendors encrypt firmware images with a key in the bootloader, and no amount of unpacking will help until you have it.
  • No high-entropy region at all means the image is uncompressed code. Strings and a disassembler will get further than any unpacker, and this is common in small MCU dumps.

The unpacker here draws that distinction explicitly rather than making you read it off a chart, because it is the one judgement that decides everything after it.

The headers that say what to do next

"SquashFS at 0x180000" is a start. What you need is the version and the compressor: a SquashFS 4.0 superblock states whether its blocks are gzip, lzma, lzo, xz, lz4 or zstd, and handing unsquashfs an image whose compressor it was not built with is the most common way to get a confusing failure. A U-Boot legacy header is even more direct - it states the payload's compression, architecture, load address and entry point, so its payload can be extracted and decompressed without sniffing anything.

Partition tables are worth checking too. MBR and GPT both turn up in embedded images, and a table is a map of the whole file that beats guessing at boundaries. One thing to watch: a GPT-partitioned image also carries a protective MBR describing one enormous partition, and reading that instead of the real table is the classic wrong answer.

What runs here and what does not

gzip, zlib and bzip2 decompress in the browser, and the unpack is recursive - a gzip inside a uImage inside a TRX is the normal shape, and each level is re-scanned. LZMA, XZ, LZ4 and Zstd have no decompressor in this project, and SquashFS, CramFS, JFFS2 and UBI are filesystems rather than streams. Those nodes are reported with the tool that reads them named - unsquashfs, jefferson, ubireader - rather than being skipped, because "nothing further here" and "I cannot read this format" point in opposite directions and only one of them is true.