Skip to content
All posts
revrevised March 20, 20265 min read

Firmware challenges: the filesystem hiding inside the blob

A firmware image is a whole embedded Linux system packed into one file. How to pull the filesystem out with binwalk, find the hardcoded secrets and backdoors CTF authors plant, and recognise the bootloader and flash layout around it.

A firmware challenge hands you a single binary blob - a .bin dumped from a router, a camera, a smart plug - and the flag is somewhere inside a complete embedded Linux system that has been compressed and concatenated into that one file. It looks impenetrable. It almost never is, because embedded firmware is built from a small number of standard, well-understood pieces, and the tooling to take them apart is mature.

The mental model that makes firmware tractable: the blob is a stack of regions laid end to end. When the blob was read off the board rather than downloaded, the capture itself is the challenge - see hardware and signal challenges. A bootloader, a kernel, a root filesystem, maybe a second filesystem for configuration. Your job is to find where each region starts, decompress the one that holds files, and then treat it as an ordinary Linux box to enumerate.

Step one: let binwalk read the layout

binwalk scans the blob for the magic signatures of known formats and prints a map of what is where - and if the interesting region turns out to be an archive, archive attacks picks up from there. This one command usually tells you the whole structure.

binwalk firmware.bin
# DECIMAL     HEX          DESCRIPTION
# 0           0x0          uImage header, kernel, ...
# 64          0x40         LZMA compressed data
# 1310720     0x140000     Squashfs filesystem, little endian, version 4.0
Each row is a region. The Squashfs (or JFFS2, CramFS, UBI, ext) entry is the root filesystem - that is where the files, and the flag, live.

The signature types you will see over and over: a uImage or bootloader header at the front, an LZMA/gzip/xz blob (the compressed kernel), and a filesystem - Squashfs most often, sometimes JFFS2, CramFS, UBIFS, or a plain ext. Recognising these by name tells you which extraction tool each region needs.

Step two: extract the filesystem

binwalk's -e flag carves and decompresses everything it recognised, unpacking the filesystem into a directory tree you can walk.

binwalk -e firmware.bin              # extract known regions
# newer binwalk: use -Me to recurse into nested archives automatically

# The result lands in _firmware.bin.extracted/
ls _firmware.bin.extracted/
cd _firmware.bin.extracted/squashfs-root/   # a full root filesystem
If binwalk's built-in extractor stalls on the filesystem, unsquashfs (for Squashfs) or the format-specific tool finishes the job. sasquatch handles the vendor-modified Squashfs variants binwalk chokes on.

Step three: enumerate it like a Linux box

You now have a root filesystem. Everything from the Linux side of CTF applies, but firmware has its own reliable hiding places because embedded developers cut corners the same way every time.

  • Hardcoded credentials. /etc/passwd and /etc/shadow - crack the hashes; embedded devices reuse weak root passwords. grep -rniE 'password|passwd|secret|key|token' etc/ finds the ones left in config files and init scripts.
  • Backdoors in startup. /etc/init.d/, /etc/rc.local, and the busybox startup scripts. CTF firmware often plants a listener or a magic-string check here - read what runs at boot.
  • Keys and certificates. find . -name '*.pem' -o -name '*.key' -o -name '*.crt'. Private keys baked into firmware are a classic finding and a common flag.
  • The web interface. Most devices ship a web admin. Find the CGI binaries or PHP under /www, /web, or /htdocs and read them - command injection in a router's CGI is the archetypal IoT bug.
  • Custom binaries. The vendor's own daemons in /bin, /sbin, /usr/bin. A binary that is not part of standard busybox is where the intended vulnerability usually lives; pull it out and reverse it.
# The fast sweep across the whole extracted tree:
grep -rniE 'flag|ctf\{|backdoor|admin|root' . 2>/dev/null | head
strings -n 8 bin/the-vendor-daemon | grep -iE 'password|/bin/sh|system|flag'
strings on the vendor daemon frequently reveals a hardcoded comparison or a system() call with attacker-influenced input.

The hardware layer, briefly

Some challenges do not hand you the blob - they hand you a photo of a board, or a memory dump taken over a hardware interface, and expect you to know where firmware comes from. The vocabulary worth having: UART is the serial console, often a boot log and sometimes a root shell on four unlabelled pads; SPI flash is the chip that stores the firmware, dumpable with a clip and a cheap programmer into exactly the kind of blob above; JTAG is the debug interface that can halt the CPU and read memory. If a challenge mentions pin headers, a flash chip part number, or a flashrom dump, it is telling you the firmware was read off the hardware and the analysis is the same from there.

The workflow

  1. binwalk the blob to map its regions; identify the filesystem type.
  2. binwalk -e (or unsquashfs/sasquatch) to extract the root filesystem.
  3. If extraction fails, check entropy for encryption or single-byte obfuscation.
  4. Enumerate the filesystem: credentials, boot scripts, keys, the web interface, custom binaries.
  5. Reverse the vendor's own daemon or CGI - that is where the planted bug lives.