Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
130 changes: 124 additions & 6 deletions src/AI/AI-Assisted-Fuzzing-and-Vulnerability-Discovery.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,121 @@ Key points:

---

## 3. Agent-Based PoV (Exploit) Generation
## 3. Local-LLM libFuzzer Harness Engineering

For source that cannot leave an analyst workstation, a local code model can draft the small adapter between fuzzer bytes and a C/C++ API. The useful security boundary is explicit: **the model writes and revises test infrastructure; coverage-guided mutation explores inputs; sanitizers report invalid operations; a human reviews the harness and triages findings**.<sup>[[9]](#references)</sup>

For engine-level corpus, feedback, snapshot, and stateful-fuzzing techniques that do not depend on an LLM, see the general [Fuzzing Methodology](../generic-methodologies-and-resources/fuzzing.md).

Start with the exact declaration copied from the target header and constrain the output format. A narrow prompt reduces invented APIs and makes the result easy to review:<sup>[[8]](#references)[[9]](#references)</sup>

```prompt
Write a libFuzzer target for this exact declaration:
int parse_records(const uint8_t *data, size_t size);

Output only C code. Implement LLVMFuzzerTestOneInput, include the
declaration, pass data and size unchanged, and do not use files,
network access, randomness, clocks, threads, or persistent state.
```

For a raw byte parser, the expected result should be no more complicated than:<sup>[[6]](#references)[[9]](#references)</sup>

```c
#include <stddef.h>
#include <stdint.h>

extern int parse_records(const uint8_t *, size_t);

int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
(void)parse_records(data, size);
return 0;
}
```

Compilation is only the first validation gate. Review every generated target against these invariants before spending CPU time on it:<sup>[[6]](#references)[[8]](#references)[[9]](#references)</sup>

- **Exact API:** compare types, ownership rules, initialization, teardown, and calling convention with the real header and existing callers.
- **Harness safety:** guard every read from `data`; sanitizer findings must originate in the target, not generated glue.
- **Repeatability:** make the result a function of `data` and `size`; remove external I/O, time, uncontrolled randomness, unjoined threads, and state that survives between calls.
- **Throughput:** initialize expensive immutable state once only when this cannot affect behavior, bound input size, suppress logging, and free per-input allocations.
- **Reachability:** verify that the intended functions and branches execute. A target that compiles but only exercises rejection paths is a silent failure.

### Structure-aware targets: preserve invariants without hiding bugs

If most raw inputs fail a magic, length, version, or checksum gate, use the fuzz bytes as **typed field material** and rebuild a valid object. Fix format constants, leave semantic fields and payload bytes mutable, and derive integrity metadata from the reconstructed payload. `FuzzedDataProvider` is useful for consuming bounded typed values, while the adapter remains responsible for limiting any generated allocation and conversion.<sup>[[9]](#references)</sup>

<details>
<summary>Example structure-aware C++ target</summary>

```cpp
#include <fuzzer/FuzzedDataProvider.h>
#include <stddef.h>
#include <stdint.h>
#include <vector>

extern "C" int parse_message(const uint8_t *, size_t);

extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
FuzzedDataProvider fdp(data, size);
uint8_t version = fdp.ConsumeIntegral<uint8_t>();
auto body = fdp.ConsumeRemainingBytes<uint8_t>();
if (body.size() > UINT16_MAX) body.resize(UINT16_MAX);
uint16_t len = static_cast<uint16_t>(body.size());
std::vector<uint8_t> msg = {0x8b, 'M', 'S', 'G', version,
static_cast<uint8_t>(len >> 8),
static_cast<uint8_t>(len)};
msg.insert(msg.end(), body.begin(), body.end());
(void)parse_message(msg.data(), msg.size());
return 0;
}
```

</details>

Do not use one structure-repairing target for every question. Maintain one target that derives lengths/checksums to explore deep valid states, and another that keeps the **declared** metadata fuzzer-controlled while bounding only the harness' own reads and allocations. The second target preserves mismatched-length and bad-integrity attack surfaces that automatic repair would otherwise erase.<sup>[[9]](#references)</sup>

### Seeds, dictionaries, sanitizers, and coverage feedback

Ask the model to generate a script that writes binary seeds rather than asking it to print binary directly. Seed empty, minimal, typical, and boundary-valid objects; then extract magic values, tags, enum encodings, and parser keywords into a reviewed dictionary. libFuzzer accepts that dictionary with `-dict=`, and keeps mutations that expose new coverage in the corpus.<sup>[[6]](#references)[[9]](#references)</sup>

```text
magic="\x8bMSG"
version_min="\x00"
version_max="\xff"
config_tag="CONFIG"
```

Build the same target with complementary instrumentation. ASan and UBSan can share a fuzzing binary; use a separate MSan build and instrument its dependencies to avoid reports caused by uninstrumented code.<sup>[[6]](#references)[[9]](#references)</sup>

```bash
clang++ -g -O1 -fsanitize=fuzzer,address,undefined \
-fno-sanitize-recover=undefined target.cc harness.cc -o fuzz_target
./fuzz_target corpus -dict=msg.dict -max_len=65535

clang++ -g -O1 -fsanitize=fuzzer,memory \
target.cc harness.cc -o fuzz_target_msan
```

Treat coverage as the evaluator for each model revision. In libFuzzer output, early-flat `cov`/`ft` and a static corpus usually mean bad framing, an overly restrictive early return, or the wrong call; low `exec/s` points to expensive setup, I/O, allocation, or leaked state. `NEW` only proves that a coverage feature changed, not that the intended parser was reached.<sup>[[6]](#references)[[9]](#references)</sup>

For source-level feedback, make a coverage build, fuzz it, and give the model only the uncovered functions plus their guarding conditions. Verify the resulting harness/seed/dictionary diff and measure again:<sup>[[7]](#references)[[8]](#references)[[9]](#references)</sup>

```bash
clang++ -g -O1 -fsanitize=fuzzer \
-fprofile-instr-generate -fcoverage-mapping target.cc harness.cc -o fuzz_cov
mkdir -p profiles
LLVM_PROFILE_FILE='profiles/%p.profraw' ./fuzz_cov -max_total_time=300 corpus
llvm-profdata merge -sparse profiles/*.profraw -o fuzz.profdata
llvm-cov report ./fuzz_cov -instr-profile=fuzz.profdata
llvm-cov show ./fuzz_cov -instr-profile=fuzz.profdata \
--show-branches=count --region-coverage-lt=1 target.cc
```

The scalable loop is therefore **generate → compile/link → smoke-test under sanitizers → measure target coverage → provide the smallest relevant coverage/source context → revise → re-measure**. OSS-Fuzz-Gen applies the same principle by evaluating generated targets for compilability, runtime failures, coverage, and coverage improvement over existing targets instead of accepting plausible code at face value.<sup>[[7]](#references)[[8]](#references)</sup>

---

## 4. Agent-Based PoV (Exploit) Generation

After a crash is found you still need a **proof-of-vulnerability (PoV)** that deterministically triggers it.

Expand All @@ -100,7 +214,7 @@ Advantages:

---

## 4. Directed Fuzzing with Fine-Tuned Code Models
## 5. Directed Fuzzing with Fine-Tuned Code Models

Fine-tune an open-weight model (e.g. Llama-7B) on C/C++ source labelled with vulnerability patterns (integer overflow, buffer copy, format string). Then:

Expand All @@ -118,22 +232,22 @@ Empirically this shrinks time-to-crash by >2× on real targets.

---

## 5. AI-Guided Patching Strategies
## 6. AI-Guided Patching Strategies

### 5.1 Super Patches
### 6.1 Super Patches
Ask the model to *cluster* crash signatures and propose a **single patch** that removes the common root cause. Submit once, fix several bugs → fewer accuracy penalties in environments where each wrong patch costs points.

Prompt outline:
```
Here are 10 stack traces + file snippets. Identify the shared mistake and generate a unified diff fixing all occurrences.
```

### 5.2 Speculative Patch Ratio
### 6.2 Speculative Patch Ratio
Implement a queue where confirmed PoV-validated patches and *speculative* patches (no PoV) are interleaved at a 1:​N ratio tuned to scoring rules (e.g. 2 speculative : 1 confirmed). A cost model monitors penalties vs. points and self-adjusts N.

---

## 6. Deterministic File-by-File AI Code Review
## 7. Deterministic File-by-File AI Code Review

A frequent failure mode in AI-assisted review is asking one agent to inspect a whole repository and hoping it chooses the right files and grep terms. A more reliable pattern is to **force repository coverage**:<sup>[[1]](#references)</sup>

Expand Down Expand Up @@ -189,5 +303,9 @@ graph TD
- [3] [GitHub Copilot community security-review skill](https://github.com/github/awesome-copilot/blob/main/skills/security-review/SKILL.md)
- [4] [Trail of Bits – AIxCC finals: Tale of the tape](https://blog.trailofbits.com/2025/08/07/aixcc-finals-tale-of-the-tape/)
- [5] [CTF Radiooo AIxCC finalist interviews](https://www.youtube.com/@ctfradiooo)
- [6] [LLVM — libFuzzer: a library for coverage-guided fuzz testing](https://llvm.org/docs/LibFuzzer.html)
- [7] [Google — OSS-Fuzz-Gen](https://github.com/google/oss-fuzz-gen)
- [8] [Google Online Security Blog — Leveling Up Fuzzing: Finding More Vulnerabilities with AI](https://security.googleblog.com/2024/11/leveling-up-fuzzing-finding-more.html)
- [9] [8kSec — AI-Assisted Fuzzing Harness Generation with a Local LLM](https://8ksec.io/ai-assisted-fuzzing-harness-local-llm)

{{#include ../banners/hacktricks-training.md}}