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.
Top comments (0)