Firmware audit methodology

Firmware audit methodology

TL;DR: Firmware audit is binary audit + filesystem archaeology. Pull the image, identify the boot loader and the OS, extract the squashfs/jffs2/cramfs, walk init scripts and daemons, audit the network services and the management plane, and look for hardcoded credentials. The exploitable bugs are usually in the management web UI, the upgrade pipeline, or a custom binary daemon. Companion to firmware-extraction and uart-jtag-debug.

Phase 0 — what you have

Acquisition Notes
Vendor download Easiest; vendor-distributed; sometimes encrypted
Dumped via SPI flash All data, including secrets, but needs hardware
UART boot log Useful for orientation; not the image itself
UPnP / TR-069 grab Some devices expose firmware via update protocols

Phase 1 — orient the image

1
2
3
4
file fw.bin
binwalk -e fw.bin                       # entropy + magic scan
strings -n 12 fw.bin | head -200
hexdump -C fw.bin | head -100

binwalk -e carves recognisable blobs (uImage, squashfs, JFFS2, CPIO, LZMA-compressed). The carved tree under _fw.bin.extracted/ is your filesystem.

If entropy is uniform high (≈ 7.9 bits/byte) the firmware is encrypted or compressed without a known header. Look at the boot loader (often present in clear) for the key-derivation path.

Phase 2 — identify the OS

Look inside the extracted rootfs:

1
2
3
4
ls _fw.bin.extracted/squashfs-root/
cat _fw.bin.extracted/squashfs-root/etc/os-release
cat _fw.bin.extracted/squashfs-root/etc/banner
cat _fw.bin.extracted/squashfs-root/etc/passwd

90% of consumer/SOHO devices run a Linux variant (OpenWrt, Buildroot, vendor-flavoured Linux). The rest: VxWorks, QNX, RTOS, eCos, bare-metal.

For Linux: identify init (often busybox or systemd-free), the package layout (busybox, BusyBox vs proper coreutils), and the kernel version.

Phase 3 — services and entry points

1
2
3
4
5
6
7
8
# Init scripts
grep -rn 'start\|exec' rootfs/etc/init.d rootfs/etc/rcS rootfs/usr/share/init
# inetd / xinetd
cat rootfs/etc/inetd.conf rootfs/etc/xinetd.d/*
# Systemd if present
ls rootfs/etc/systemd/system rootfs/lib/systemd/system
# Daemons referenced
strings rootfs/sbin/init | grep -iE 'web|cli|httpd|telnetd|sshd'

Build a service inventory: every daemon that listens, what port, started by whom, with what arguments.

Phase 4 — the management plane

The web UI / CLI is the highest-yield bug area. Devices ship custom code with weak inputs.

1
2
3
ls rootfs/www rootfs/etc/lighttpd rootfs/usr/share/web 2>/dev/null
find rootfs -name '*.cgi' -o -name '*.php' -o -name '*.lua' -o -name '*.asp'
file rootfs/sbin/httpd 2>/dev/null

For binary CGI handlers (mini_httpd / boa / uhttpd + a vendor binary), decompile in Ghidra (decompiler-driven-source-review) and audit:

  • Argument parsing.
  • Command injection via system()/popen() with attacker-controlled strings.
  • Authentication: how does the device know you’re allowed?
  • “Action” routing: a single endpoint dispatching by action= parameter.

Phase 5 — credentials and secrets

1
2
3
4
strings rootfs/etc/passwd
strings rootfs/etc/shadow
grep -rn 'admin\|password\|admin12345\|root:' rootfs/etc rootfs/usr/share
find rootfs -name '*.conf' -exec grep -l 'password' {} \;

Hardcoded admin/admin still ships in 2026. Check:

  • Recovery accounts in /etc/passwd.
  • Backdoor passwords compared in binaries (strings | grep -i magic).
  • Pre-shared keys in config files.
  • TLS certificates with private keys bundled.

Phase 6 — upgrade pipeline

The firmware upgrade endpoint is itself an attack surface:

  • Image signature verified? (look for openssl dgst, RSA_verify, ed25519).
  • Image encrypted in transit? In storage?
  • Atomic apply, or write-as-you-go?
  • “Recovery” image that’s signed weaker than the main image?

A weak upgrade verification is the path to permanent device takeover.

Phase 7 — kernel modules and drivers

1
2
find rootfs -name '*.ko'
modinfo *.ko 2>/dev/null | head

Custom drivers are often the buggiest part of the stack. IOCTL handlers (see windows-driver-ioctl-audit for the analogous Windows pattern, kernel-syscall-source-review for Linux kernel review) frequently take attacker-controlled lengths into copy_from_user calls without bounds.

Phase 8 — secondary processors

Some devices have an applications processor (Linux) and a secondary microcontroller for radio, BMC, etc. The secondary often runs RTOS code with even weaker security. Look for separate firmware blobs (*.fw files in /lib/firmware/).

Common bug patterns by class

  • Routers: WAN-side command injection via DDNS, UPnP, TR-069.
  • Cameras: ONVIF/RTSP handlers with stack BOFs; default creds.
  • IP phones: SIP parsing bugs; provisioning endpoints with hardcoded URLs.
  • Printers: PJL command injection; SNMP info disclosure.
  • Industrial gateways: Modbus / DNP3 unauth control; OPC-UA cert misuse.
  • Consumer IoT: MQTT brokers exposed with default creds; OTA update without signing.

Static + dynamic loop

Static analysis points at a function; dynamic confirms.

For Linux-userland binaries, use qemu-user to run binaries on your Kali:

1
2
3
4
sudo apt install qemu-user-static
cp $(which qemu-mipsel-static) rootfs/usr/bin/
chroot rootfs /sbin/httpd -p 8080
# now you can fuzz/repro from outside, with the device's daemon running on your host

For full-system emulation, see Firmadyne, FirmAE, and Avatar2.

Reporting

For a vendor report you want:

  • Acquired firmware version (filename + sha256).
  • Affected component (binary path inside the image).
  • Reproducer (Python script or curl one-liner) that triggers the bug from network.
  • Pre-auth vs post-auth.
  • Suggested fix.

References