Skip to content

Back to the catalog

FirmAgent: Fuzzing + LLM Agents for IoT Firmware Vulnerability Discovery (NDSS 2026)
github.comrepo

FirmAgent: Fuzzing + LLM Agents for IoT Firmware Vulnerability Discovery (NDSS 2026)

FirmAgent is an academic research toolkit (NDSS 2026) for finding vulnerabilities in re-hosted IoT firmware web services by combining IDA-based pre-fuzzing (CFG/sink distance scoring), QEMU instrumented tracing, CFG-guided API fuzzing, and an optional LLM agent (LLMATaint.py) that consumes runtime taint traces to reason about and produce PoCs for bug classes like command injection. The pipeline is explicit about scope: it is meant for authorized, owned or re-hosted firmware (e.g. Greenhouse-style containers), and stitches together sink-scope extraction, feedback-driven fuzzing, and request-to-trace attribution before handing traces to an LLM for taint-to-PoC analysis.

TL;DR Fuzzing + LLM-agent pipeline (NDSS 2026) for taint-guided IoT firmware vulnerability discovery, from IDA sink analysis to QEMU-traced fuzzing to LLM-driven PoC generation.
Document

What it is

FirmAgent's repo README, giving the exact command sequence to run the pipeline the summary describes. The summary tells you the pipeline exists; the README tells you how to invoke each stage, in what order, and what artifacts each produces. Requires Python 3.8+, IDA Pro (idat/idat64), Docker with a Greenhouse/re-hosting image, an instrumented QEMU binary with a shared host trace directory, and an LLM endpoint only for API extraction and LLMATaint.py.

What to do

Configure via environment (do not commit keys):

export IDAT_BIN=/path/to/idat64
export Private_API_KEY=...
export LLM_BASE_URL=https://your-endpoint
export LLM_MODEL=your-model

Stage 1 — pre-fuzzing writes sink_scope_addr.txt, sink_cfg.json, sink_distance_scores.json/.csv, and IDA evidence next to the binary:

Pre_Fuzzing/run.sh /path/to/httpd

API/parameter extraction is a separate, commented-out LLM step — run it explicitly to produce Pre_fuzzing.json (must contain api_endpoints and para; may add a mutation dictionary):

python3 Pre_Fuzzing/llm_extract_api_params.py --input /path/to/export-for-ai-httpd --output /path/to/Pre_fuzzing.json

Stage 2 — start the instrumented container with tracing restricted to the sink scope:

FuzzingRecord/rehost_session.sh start --container <container> \
  --dfilter "$(python3 FuzzingRecord/make_dfilter.py /path/to/sink_scope_addr.txt)"

This sets QEMU_DFILTER, QEMU_LOG=exec,nochain, QEMU_LOG_FILENAME=/scratch/qemu_trace.log. Stop with rehost_session.sh stop --container <container> — this resets the trace log.

The fuzzer has two modes that the summary collapses into one. Legacy (no feedback) needs only --json-file. CFG-guided feedback mode additionally requires both --distance-file and --qemu-log:

python3 FuzzingRecord/Fuzzer.py --json-file /path/to/Pre_fuzzing.json \
  --distance-file /path/to/sink_distance_scores.json \
  --qemu-log /host/shared/qemu_trace.log --host <container-ip> \
  --feedback-settle 0.3 --iterations 1000 --random-seed 1 --tag-mode unique

Feedback mode fuses dictionary mutations with runtime block/edge coverage and shortest distance to a sink. Outputs result.json, result_stream.ndjson, fuzzing_summary.json.

Stage 3 — for exact request-to-trace attribution, run the sidecar before fuzzing with --tag-mode unique, then build Source.json:

python3 FuzzingRecord/taint_monitor.py --stream /path/to/result_stream.ndjson \
  --qemu-log /host/shared/qemu_trace.log --out /path/to/trace_windows.jsonl --settle 0.3
python3 FuzzingRecord/build_source_json.py --qemu-log /host/shared/qemu_trace.log \
  --windows /path/to/trace_windows.jsonl --result-stream /path/to/result_stream.ndjson \
  --output /path/to/Source.json

Stage 4 — LLM taint-to-PoC. Source.json (or source.json) must sit in the same directory as the target binary; Indirect_call.json/indirect_data.json may be supplied when available. The -t flag names the bug class (ci/command_injection), -m selects the model:

python LLMATaint.py -b ./bin/httpd -p False -t ci -o ./results/ASUS -m R1_official

Caveats

  • Ordering matters: the taint sidecar must run before fuzzing and depends on unique tags for attribution.
  • If the trace file is missing, rotated, or truncated, that iteration is marked invalid and the loop continues — so a misconfigured shared directory silently degrades feedback mode to noise.
  • README states scope explicitly: only firmware/services you own or are explicitly authorized to test.

For citation: NDSS 2026, doi 10.14722/ndss.2026.231943.

Technologies named here

  • IoT firmware httpd binaries
  • FirmAgent
  • IDA Pro
  • Pre_Fuzzing/run.sh
  • Pre_Fuzzing/llm_extract_api_params.py
  • Docker / Greenhouse re-hosting image
  • Instrumented QEMU
  • FuzzingRecord/rehost_session.sh
  • FuzzingRecord/make_dfilter.py
  • FuzzingRecord/Fuzzer.py
  • FuzzingRecord/taint_monitor.py
  • FuzzingRecord/build_source_json.py
  • LLMATaint.py
  • LLM endpoint
  • QEMU
  • Docker
  • Python
  • LLM

Techniques named here

  • IDA-based pre-fuzzing
  • QEMU dynamic instrumentation / tracing
  • CFG-guided feedback fuzzing
  • Legacy taint traversal fuzzing
  • Request-to-trace attribution
  • LLM-assisted taint analysis / PoC generation
  • Command injection