This toolkit automates an end-to-end workflow for discovering and validating vulnerabilities in re-hosted IoT firmware:
-
Fuzzing-Driven Information Collection
-
Extract all target URIs and input parameters.
-
Locate sink functions and compute distances from basic blocks to sinks.
-
Produce mutation dictionaries for the fuzzer.
-
Instrumented QEMU binaries collect control-flow data during fuzzing.
-
Optional: Build emulation images directly with a helper script.
-
Generic, device-aware API fuzzer with configurable
Hostheader. -
Uses the pre-fuzzing artifacts to generate requests and mutations.
-
-
Taint-to-PoC Agent
- LLM-assisted taint analysis to findings vulnerabilities and produce PoCs.
-
Python: 3.8+ Install Python dependencies:
pip install -r requirements.txt -
Emulation: Greenhouse (Details can be found in https://github.com/sefcom/greenhouse.git)
- We ship instrumented binaries under
FirmAgent/FuzzingRecord/gh3fuzz/fuzz_bins/qemu/. - Control-flow collector:
libibresolver.so.
- We ship instrumented binaries under
-
Target firmware: Obtain and re-host the firmware image
-
Setting Environment Variables:
export IDAT_BIN=/path/to/idat64
export Private_API_KEY={API_KEY}
- Run pre-fuzzing (single command):
Simply run the automated pipeline script:
./Pre_Fuzzing/run.sh <target_binary>
This single command will:
- Export decompiled code and strings from IDA Pro
- Extract API endpoints and parameters using LLM
- Identify sink function address ranges
- Compute distances from basic blocks to sinks
Outputs (in the same directory as <target_binary>):
Pre_fuzzing.json- Complete fuzzing input schema with APIs and parameterssink_scope_addr.txt- Sink function address rangessink_distance_scores.json/sink_distance_scores.csv- Distance scores for prioritizationexport-for-ai-<binary_name>/- Decompiled evidence directory
- Build an emulated image (optional, automated wiring):
python build_fuzz_img.py
This integrates the instrumentation and supporting binaries into the emulation image.
- Start fuzzing (from the host):
FuzzingRecord/Fuzzer.py to match your target device's API format (e.g., request headers, authentication, payload structure).
python FuzzingRecord/Fuzzer.py \
--json-file Pre_fuzzing.json \
--delay 0.5 \
--host {target_ip_or_domain}
Fuzzing outputs:
result.json- Request packets and responses produced byFuzzingRecord/Fuzzer.pyfuzzing_results.log- Detailed fuzzing logsSource.json/source.json- Runtime-monitoring artifact containing source addresses and reachable test cases; this file is consumed byLLMATaint.pybut is not generated by the currentFuzzingRecord/Fuzzer.pyalone
- Run LLM-assisted taint analysis to generate PoCs:
Place the runtime-monitoring artifact (Source.json or source.json) in the same folder as the target binary, then run:
python LLMATaint.py \
-b {path_to_binary} \
-p {True|False} \
-t {vuln_type} \
-o {path_to_resultfolder} \
-m {model}
Supported model values currently used by the code include: R1_official, V3_official for the current LLMAPIThree path used by LLMATaint.py
The taint analysis agent will:
- Analyze the taint flow from source points identified during fuzzing
- Use the reachable test cases as base request packets during later validation / PoC generation
- First determine whether an alert exists, then validate it and generate concrete PoCs
Goal: Collect everything needed to drive effective mutations and triage.
The pre-fuzzing pipeline (Pre_Fuzzing/run.sh) automates the following steps:
-
Decompilation export (
Pre_Fuzzing/decompile.py)- Exports decompiled functions, strings, memory dumps, imports/exports
- Creates
export-for-ai-<binary_name>/directory with all evidence
-
API & parameter extraction (
Pre_Fuzzing/llm_extract_api_params.py)- Uses LLM to analyze decompiled code and extract:
- API endpoints (goform handlers, URL routes, etc.)
- Parameter names and types from function callsites
- Generates
Pre_fuzzing.jsonwith complete fuzzing schema
- Uses LLM to analyze decompiled code and extract:
-
Sink scope extraction (
Pre_Fuzzing/Get_SinkFunc.py)- Identifies dangerous sink functions (system calls, strcpy, etc.)
- Traces backward from sinks to find all reachable functions
- Outputs
sink_scope_addr.txtwith address ranges
-
Distance calculation (
Pre_Fuzzing/Distance.py)- Computes shortest path distances from basic blocks to sinks
- Prioritizes fuzzing targets closer to dangerous sinks
- Outputs
sink_distance_scores.json/sink_distance_scores.csv
Outputs:
Pre_fuzzing.json- Complete fuzzing input schema (APIs + parameters)sink_scope_addr.txt- Sink function address rangessink_distance_scores.json/sink_distance_scores.csv- Distance scores for prioritization
We provide an instrumented QEMU stack and a control-flow recording library:
- Location:
FirmAgent/FuzzingRecord/gh3fuzz/fuzz_bins/qemu/libibresolver.socollects control-flow events.
- Recommended path:
- Use
build_fuzz_img.pyto assemble the emulation image. - Instrumentation and collectors are automatically integrated.
- Use
- Customization:
You can extend
qemuaflto log additional runtime signals. We provide our Fuzzing-SA source for reference (see repo).
FuzzingRecord/Fuzzer.py to match your target device's API format:
- Update request headers (authentication tokens, cookies, etc.)
- Adjust payload structure if your device uses non-standard formats
- Configure device-specific error detection patterns
Run the generic fuzzer against your re-hosted device:
python FuzzingRecord/Fuzzer.py \
--json-file Pre_fuzzing.json \
--delay 0.5 \
--host {target_ip_or_domain}
--json-file— Pre-fuzzing output containing API definitions.--delay— Inter-request sleep to avoid rate-limits / WAFs.--host— Value for theHostheader (supports SNI / vhosts).
What happens:
- The fuzzer parses
Pre_fuzzing.jsonand parameter templates. - For each endpoint & parameter, it generates mutations.
- Requests and responses are logged to
fuzzing_results.log. - Structured request/response packets are written to
result.jsonnext to the input JSON. - Potential vulnerabilities are flagged based on error codes, timing, and content indicators.
- Source-address artifacts used by
LLMATaint.pymust be supplied separately by the runtime monitoring / instrumentation component asSource.jsonorsource.json.
Fuzzing outputs:
result.json- Structured request packets and responses written byFuzzingRecord/Fuzzer.pySource.json/source.json- Source addresses and their corresponding reachable test cases (required for taint analysis; produced by runtime monitoring rather than by the current fuzzer script alone)fuzzing_results.log- Human-readable progress and warnings
Once fuzzing identifies source points and generates test cases, use LLM-assisted taint analysis to validate and transform findings into concrete PoCs:
python LLMATaint.py \
-b {path_to_binary} \
-p {True|False} \
-t {vuln_type} \
-o {path_to_resultfolder} \
-m {model}
Supported model values currently used by the code include R1_official and V3_official for the default LLM_analysis() path.
Prerequisites:
- Place
Source.jsonorsource.json(contains source addresses and reachable test cases) in the same directory as the target binary - If available, place
Indirect_call.jsonorindirect_data.json(caller-callee pairs) in the same directory
What the agent does:
- Analyzes taint flow from source points identified during fuzzing
- Uses reachable test cases from
Source.json/source.jsonas base packets during later PoC generation - Traces data flow through decompiled code to identify vulnerable paths
- Uses a two-stage process: first taint/alert judgment, then validation + PoC generation
- Produces validated exploit code with proper input formatting
Preferred format used by the current FuzzingRecord/Fuzzer.py:
{
"api_endpoints": [
"/nitro/v1/config/example_endpoint",
"/apply.cgi"
],
"para": [
"username",
"cmd",
"action"
]
}
Legacy array-of-objects input is still partially supported for endpoint extraction, but the current fuzzer primarily expects:
api_endpoints: Relative API paths.para: Parameter names used to build per-parameter taint payloads.
fuzzing_results.log— Human-readable progress and warnings.result.json— Structured request packets and responses emitted byFuzzingRecord/Fuzzer.py.Source.json/source.json— Source addresses and their corresponding reachable test cases (required for taint analysis; produced by runtime monitoring / instrumentation).Indirect_call.json/indirect_data.json(optional) — Caller-callee pairs for indirect call resolution.
Run pre-fuzzing (single command):
./Pre_Fuzzing/run.sh /path/to/httpd
This generates Pre_fuzzing.json, sink_scope_addr.txt, and distance scores in the same directory as the binary.
Run fuzzing (host):
FuzzingRecord/Fuzzer.py request templates first!
python FuzzingRecord/Fuzzer.py \
--json-file Pre_fuzzing.json \
--delay 0.5 \
--host 192.168.0.1
This generates result.json (request packets and responses). A separate runtime-monitoring step should generate Source.json / source.json for taint analysis.
Run taint-to-PoC agent:
Place Source.json or source.json in the same directory as the binary, then:
python LLMATaint.py \
-b ./bin/httpd \
-p False \
-t ci \
-o ./results/ASUS \
-m R1_official
Reference
If you use or cite this work, please reference:
@inproceedings{Ji2026FirmAgent,
author = {Ji, Jiangan and Zhang, Chao and Gan, Shuitao and Lin, Jian and Liu, Hangtian and Liu, Tieming and Zheng, Lei and Jia, Zhipeng},
title = {FirmAgent: Leveraging Fuzzing to Assist LLM Agents with IoT Firmware Vulnerability Discovery},
booktitle = {Network and Distributed System Security (NDSS) Symposium},
year = {2026},
month = {February},
pages = {1--16},
address = {San Diego, CA, USA},
doi = {10.14722/ndss.2026.231943},
isbn = {979-8-9919276-8-0},
url = {https://dx.doi.org/10.14722/ndss.2026.231943},
}