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:
- High execution overhead and slow feedback loops.
- Architecture incompatibilities: custom NVRAM, vendor peripherals and proprietary SoC hardware are exactly where generic emulators tend to break.
- 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/shadowor 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_STACKflags for an executable stack. A missingPT_GNU_STACKheader is also treated as an executable stack, since that is the default on several architectures common in firmware (ARM, MIPS). - PIE / ASLR:
ET_DYNalone is not enough, because shared libraries areET_DYNtoo. The engine separates PIE executables from.sofiles by checking forPT_INTERPand theDF_1_PIEflag inDT_FLAGS_1.ET_EXECmeans a fixed load address. - Stack canaries: looks for
__stack_chk_failin.dynsymand.symtab. - RELRO: checks
PT_GNU_RELROfor Partial RELRO, and for Full RELRO also requires immediate binding (DT_BIND_NOW,DF_BIND_NOWinDT_FLAGS, orDF_1_NOWinDT_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.
