Security assessments for IoT devices and embedded Linux systems have long relied on two main approaches: manual static inspection or full dynamic emulation (QEMU, Unicorn, Firmadyne, FirmAE).

Dynamic analysis is invaluable for verifying vulnerabilities, but plugging emulated environments into automated CI/CD pipelines often leads to:

  1. High execution overhead and slow feedback loops.
  2. Architecture incompatibilities: custom NVRAM, vendor peripherals and proprietary SoC hardware are exactly where generic emulators tend to break.
  3. Flaky automation that stalls pull requests.

To bridge this gap I built firmware-auditor: a fast, zero-emulation static analysis framework for unpacked root filesystem images.

The Core Idea: Reproducible Static Posture

When you have an unpacked SquashFS, ext4 or UBIFS rootfs, many security posture indicators can be checked entirely offline, without executing a single target binary:

  • ELF binary hardening: are binaries built with modern mitigations, or exposed to classic exploitation techniques?
  • Filesystem hygiene and secret exposure: did the build bake private SSH keys, a readable /etc/shadow or world-writable scripts into production?
  • Actionable DevSecOps gates: can the pipeline fail (exit 1) on critical issues and emit standardized, machine-readable results?

Here is what a run looks like. This is a test rootfs with intentionally planted flaws, not a real firmware image:

rootfs : test_rootfs duration : 0.007s (workers=1) severity : >= info analyzers : binary_hardening 1.0.0 ok (0 findings, 0.004s) filesystem_hygiene 1.0.0 ok (5 findings, 0.002s) SUMMARY BY SEVERITY CRITICAL 5 #################### HIGH 0 MEDIUM 0 LOW 0 TOTAL 5 FINDINGS BY RULE (4 rule(s), 5 finding(s)) SEVERITY RULE COUNT LOCATION SAMPLE TITLE CRITICAL FWK-PERM-001 1 etc World-writable directory CRITICAL FWK-KEY-001 1 etc/id_rsa Private key material in the firmware image CRITICAL FWK-PERM-002 2 etc/id_rsa; shadow World-writable file in a sensitive tree CRITICAL FWK-PERM-004 1 etc/shadow Password hash database readable by any user 

Architecture

The framework is written in Python 3.10+ with a modular design built around isolation and reproducibility:

firmware_auditor/ ├── analyzers/ │ ├── base.py # Base analyzer & Finding contract │ ├── binary_hardening.py # ELF checks (NX, PIE, Canary, RELRO) │ └── filesystem_hygiene.py # Secrets, permissions, credentials ├── core/ │ ├── pipeline.py # Concurrent runner, fault isolation │ └── reporter.py # Console, JSON and SARIF v2.1.0 └── tests/ # pytest suite (Ubuntu & Windows) 

Each analyzer runs behind a fault-isolation boundary with a timeout guard, so one broken analyzer or a malformed binary doesn't take down the whole scan.

1. ELF Hardening Inspections

Using pyelftools, the engine parses ELF headers directly:

  • NX / DEP: checks the PT_GNU_STACK flags for an executable stack. A missing PT_GNU_STACK header is also treated as an executable stack, since that is the default on several architectures common in firmware (ARM, MIPS).
  • PIE / ASLR: ET_DYN alone is not enough, because shared libraries are ET_DYN too. The engine separates PIE executables from .so files by checking for PT_INTERP and the DF_1_PIE flag in DT_FLAGS_1. ET_EXEC means a fixed load address.
  • Stack canaries: looks for __stack_chk_fail in .dynsym and .symtab.
  • RELRO: checks PT_GNU_RELRO for Partial RELRO, and for Full RELRO also requires immediate binding (DT_BIND_NOW, DF_BIND_NOW in DT_FLAGS, or DF_1_NOW in DT_FLAGS_1).

2. Filesystem Hygiene

Instead of running arbitrary regex over the entire filesystem, the module targets specific security attributes:

  • Checks permission masks (st_mode) on sensitive Unix paths (/etc, /bin, /sbin).
  • Detects unshadowed or empty password hashes in authentication files.
  • Identifies raw private key material without leaking the secret into CI logs or reports.

Note: permission checks need an extraction that preserves Unix modes (unsquashfs, tar, WSL or Linux). A rootfs copied onto NTFS loses this information, and permission findings will not be reliable.

3. Native SARIF Support for GitHub Code Scanning

firmware-auditor outputs SARIF v2.1.0 instead of a proprietary report format, so findings can show up directly under Security > Code scanning on GitHub:

firmware-auditor /path/to/extracted/rootfs \ --format sarif \ --output report.sarif \ --fail-on high 

GitHub Actions example

name: firmware-audit on: [pull_request] permissions: contents: read security-events: write jobs: audit: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: python-version: "3.12" - name: Install firmware-auditor run: | git clone https://github.com/RichJJ98/firmware-auditor pip install ./firmware-auditor - name: Extract rootfs (preserving permissions) run: | sudo apt-get update && sudo apt-get install -y squashfs-tools sudo unsquashfs -f -d rootfs firmware.squashfs - name: Run audit run: firmware-auditor rootfs --format sarif --output report.sarif --fail-on high - name: Upload SARIF if: always() uses: github/codeql-action/upload-sarif@v3 with: sarif_file: report.sarif 

The if: always() on the upload step matters: with --fail-on high the audit step exits non-zero, and without it the findings would never reach the Code scanning tab.

Limitations (What It Does Not Cover)

Static analysis has blind spots, and it is better to say so upfront:

  • Not a replacement for dynamic analysis. It won't confirm exploitability or find runtime and logic vulnerabilities.
  • No CVE matching of bundled library versions (yet).
  • Possible false negatives on canaries: stripped or statically linked binaries may not expose __stack_chk_fail.
  • Needs an already unpacked rootfs. Extraction (binwalk, unsquashfs, etc.) is a separate step, and encrypted or proprietary containers are out of scope.
  • Permission checks depend on extraction fidelity (see the note above).

Open Source & Next Steps

firmware-auditor is released under the MIT License, and the test suite runs in CI on both Linux and Windows.

If you work with embedded devices, IoT security or DevSecOps pipelines, clone it, try it on your own firmware images and contribute new rule modules. Feedback, issues and pull requests are very welcome.