Guides · Updated 27 July 2026

What is actually inside a consumer router firmware image

We ran two shipping consumer routers through our binary analysis engine and published the whole result: the container formats, the component lists with versions, and the four ways the scan was wrong before we fixed it.

The short answer

A consumer router firmware image is a vendor container wrapping a compressed kernel and a squashfs root filesystem holding one to three thousand files. Two stock images we scanned on 27 July 2026, a Netgear R7000 running V1.0.11.136_10.2.120 and a TP-Link Archer C7 v5 running 1.2.1 Build 20220715, contained 1,564 and 2,225 files respectively. Between them the scan identified BusyBox, OpenSSL, OpenVPN, dnsmasq, hostapd, wpa_supplicant, curl, libpcap, miniupnpd, MiniDLNA, ProFTPD, Lua, xl2tpd, bzip2, wget, mbed TLS, libupnp, zlib and two Linux kernels, one from the 2.6.36 series and one from 3.3.8.

Why we did this

Our engine had never seen a real vendor firmware image. Every test fixture we had was synthetic, which means every fixture unpacked cleanly, because we built it to. That is not a test. So we downloaded two consumer routers from the manufacturers’ own download pages and ran them through the product exactly as a customer would, through the API, and published what came back.

It found four bugs in our own engine. Those are in this guide too, because they are the interesting part: they are the failure modes any binary analysis tool has, and three of the four returned a clean-looking answer rather than an error.

The two images

Netgear Nighthawk R7000, firmware V1.0.11.136_10.2.120, downloaded from downloads.netgear.com. The file is R7000-V1.0.11.136_10.2.120.chk, 31,735,866 bytes.

TP-Link Archer C7 v5, firmware 1.2.1 Build 20220715, downloaded from static.tp-link.com. The file is 15,727,744 bytes.

Both files come from the manufacturers’ own download pages, and the TP-Link one is the newest release TP-Link lists for that hardware version. Both are dated 2022.

The container: the partition table is in the header, and you have to read it

The Netgear file starts with the four bytes *#$^, which is Netgear’s CHK wrapper. Its header is 58 bytes long and carries a board id, U12H270T00_NETGEAR. Behind that sits a TRX image, the Broadcom reference-design container, whose magic is HDR0. The TRX header declares where each partition begins. In this image there are two: an LZMA-compressed kernel at offset 28, and a squashfs root filesystem at offset 2,221,628.

That layout is the first trap. An LZMA stream has no length field. A tool that scans for magic bytes and then works out where each chunk ends has to decide for itself where the kernel stops and the filesystem starts. Ours got it wrong and carved the kernel only. The scan came back with two components, no warnings, and a green tick.

The fix is not cleverer carving. The answer is in the file: the TRX header says where the partitions are. Read the header, split on its own offsets, hand the unpacker one bounded image at a time. After that change the same file yielded 1,564 files instead of seven.

Two components and no error is what an unread router looks like. It is also what a very clean router would look like. If your scanning tool cannot tell you which one you got, its silence is worth nothing.

The filesystem: squashfs, and one flag that decides whether you see anything

Both root filesystems are squashfs 4.0 compressed with XZ. The Netgear image uses 128 KiB blocks and holds 1,912 inodes. The TP-Link image uses 64 KiB blocks and holds 2,603 inodes. Nothing about either is exotic. Mainline unsquashfs reads both.

It still failed on one of them, and the reason is worth knowing if you ever unpack firmware in a container or a CI job. The Netgear root filesystem carries extended attributes. Restoring those needs a capability our container does not have, so unsquashfs hit "Operation not permitted" on the first symlink it wrote and abandoned a 1,912-inode filesystem having written nothing. Passing -no-xattrs and -ignore-errors recovers all of it. We are reading the image, not restoring it, so the attributes were never any use to us, and refusing to drop them cost the entire component list.

What was in the Netgear R7000

Fifteen components, each with the file the evidence came from. This is the scan output, not a summary of it.

busybox        1.22.1            bin/busybox
bzip2          1.0.6             usr/sbin/bzip2
curl           7.36.0            sbin/curl
dnsmasq        version unknown   sbin/acos_service
dropbear       version unknown   opt/bit/bitdefender.tar
iptables       version unknown   (kernel image)
libpcap        1.4.0             usr/bin/tcpdump
libupnp        1.8.4             opt/bit/bitdefender.tar
linux_kernel   2.6.36.4brcmarm   (kernel image)
mbedtls        2.16.0            usr/sbin/ookla
openssh        7.6p1             opt/bit/bitdefender.tar
openssl        1.0.2h            lib/libssl.so.1.0.0
openvpn        2.3.1             usr/local/sbin/openvpn
wget           1.14              share/man/man1/wget.1
zlib           1.2.8             lib/modules/tdts.ko

Three of those rows point at opt/bit/bitdefender.tar. That is a Bitdefender agent shipped inside the router, a second userland the manufacturer did not write and is still answerable for under the CRA. Another row points at usr/sbin/ookla, the Ookla speedtest client, which brings its own mbed TLS. A third points at lib/modules/tdts.ko, a Trend Micro kernel module carrying its own zlib.

A single consumer router therefore ships at least four separate organisations’ code. A manufacturer who lists only what their own build produced has an SBOM that is missing most of the attack surface.

What was in the TP-Link Archer C7 v5

busybox        1.19.4            bin/busybox
curl           7.79.1            usr/lib/libcurl.so.4.7.0
dnsmasq        2.83              usr/sbin/dnsmasq
dropbear       version unknown   lib/upgrade/common.sh
hostapd        2.0               usr/sbin/hostapd
iptables       version unknown   (kernel image)
libpcap        1.1.1             usr/lib/libpcap.so.1.1.1
linux_kernel   3.3.8             (kernel image)
lua            5.1.4             usr/lib/liblua.so.5.1.4
minidlna       1.1.2             usr/sbin/minidlnad
miniupnpd      1.8               usr/sbin/miniupnpd
openssl        1.0.2u            usr/lib/libssl.so.1.0.0
openvpn        2.4.11            usr/sbin/openvpn
proftpd        1.3.4             usr/sbin/proftpd
sqlite         version unknown   usr/sbin/minidlnad
u-boot         1.1.4             (bootloader, unpacked image)
wpa_supplicant 2.0               usr/sbin/wpa_supplicant
xl2tpd         1.1.12            usr/sbin/xl2tpd
zlib           1.2.7             usr/lib/libz.so.1.2.7

The full file listing is longer than the component list by a wide margin. The image also contains Samba, ntfs-3g, aria2, radvd, pptpd, conntrack, an OpenWrt userland with ubus and uci, and about two hundred vendor binaries with no upstream at all. A signature pack names what it recognises. Everything else is in the file list.

Four ways a scan lies to you

Every one of these produced a plausible answer rather than an error, which is what makes them worth writing down.

A filesystem found and never opened. The scan reports whatever the kernel image contains and stops. Two components, no warning. Our engine now records that a filesystem was seen, tries to open it itself when the unpacker gives up, and marks the scan degraded with the reason when nobody could open it.

A four-byte magic that is not a filesystem. The squashfs magic is four bytes, and four bytes turn up by coincidence somewhere in a two-thousand-file root filesystem. Handing that coincidence to an extractor is not free: neither extractor we use fails fast on a header that parses and describes nothing, and one false positive took a scan from thirteen seconds to over twenty minutes. Parse the superblock and check its fields against the file size before spawning anything.

A string that is not an installation. The TP-Link image contains etc/easy-rsa/openssl-1.0.0.cnf, whose first line reads "For use with easy-rsa version 2.0 and OpenSSL 1.0.0". We read that comment as the installed version while usr/bin/openssl sat there announcing 1.0.2u. The same image reported OpenSSH, because dropbear’s own help text says "Set option in OpenSSH-like format" and libssh names OpenSSH in its protocol strings. That router ships neither. Reporting it attaches every OpenSSH CVE to a product that has none of them.

A version that depends on directory order. Where two files disagree, something has to choose. Choosing whichever the walk reached first is not a method. We now keep every candidate version with the set of files it was found in, and report the one the most files agree on, at the strongest evidence available.

What this means for your CRA technical file

Annex I Part II point 1 requires manufacturers to identify and document vulnerabilities and components, including by drawing up an SBOM in a commonly used and machine-readable format covering at the very least the top-level dependencies. Both images above have components a build manifest would never list, because the manufacturer did not build them. The Bitdefender tarball, the Ookla client and the Trend Micro kernel module are top-level dependencies of the shipped product whoever compiled them.

Article 13(8) sets a support period of at least five years, or the expected use time where that is shorter. Both these images carry a kernel series from 2010 and 2012 and an OpenSSL 1.0.2 build, a branch that stopped receiving public updates at the end of 2019. That is not unusual for embedded products and it is not automatically a compliance failure, because Article 13(4) lets a manufacturer justify a requirement that does not apply to their product in writing. What it does mean is that the justification has to exist, in the technical documentation, naming the component.

The practical point: you cannot write that justification for a component you do not know you ship. Start from the image, not from the build.

How to do this yourself

  • Download your own shipping firmware from your own download page, not from your build server. Ship what customers get.
  • Split the vendor container on the offsets in its own header before unpacking anything.
  • Unpack squashfs with extended attributes turned off and errors ignored, or a permissions failure will lose you the whole filesystem.
  • Check every identification against the file it came from. A version with no file path behind it is an assertion.

Our free wizard needs no signup and tells you your CRA classification and conformity route. Binary firmware analysis of your own images is part of the Team plan.

Questions people actually ask

What file format is consumer router firmware?

Usually a vendor container wrapping a compressed kernel and a squashfs root filesystem. Netgear ships CHK, whose magic is *#$^, wrapping a Broadcom TRX image whose magic is HDR0. TP-Link ships its own header wrapping a uImage kernel and a squashfs. The container header declares where each partition begins, and reading it is more reliable than scanning for magic bytes.

How many components are in a consumer router?

More than a build manifest lists. Scanning stock firmware for a Netgear R7000 and a TP-Link Archer C7 v5 identified 15 and 19 named open-source components respectively, out of root filesystems holding 1,564 and 2,225 files. Both images also carry third-party userland the manufacturer did not build, including in one case a complete Bitdefender agent, a speedtest client with its own TLS library and a Trend Micro kernel module.

Why does unsquashfs fail with "Operation not permitted"?

It is trying to restore extended attributes, and setting those needs a capability most containers and CI runners do not have. unsquashfs aborts on the first failure and can write nothing at all, so a perfectly readable filesystem looks unreadable. Pass -no-xattrs and -ignore-errors. You are reading the image, not restoring it.

Can binary analysis produce a complete SBOM?

No, and a tool claiming otherwise is not measuring. Binary analysis identifies components that leave a version banner in the image. Statically linked, stripped or packed code can be invisible, and vendor binaries with no upstream have nothing to match against. What it can do is name what it found, name what it could not check and say which is which, which is more than a build manifest gives you for firmware you did not compile.

Does the CRA require an SBOM for firmware I did not write?

Yes. Annex I Part II point 1 requires an SBOM in a commonly used, machine-readable format covering at the very least the top-level dependencies. There is no exemption for a rootfs supplied by an ODM. If you place the product on the EU market you are the manufacturer for CRA purposes and the components inside it are yours to document.