You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: src/generic-methodologies-and-resources/fuzzing.md
+79-1Lines changed: 79 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -4,7 +4,7 @@
4
4
5
5
## Mutational Grammar Fuzzing: Coverage vs. Semantics
6
6
7
-
In **mutational grammar fuzzing**, inputs are mutated while staying **grammar-valid**. In coverage-guided mode, only samples that trigger **new coverage** are saved as corpus seeds. For **language targets** (parsers, interpreters, engines), this can miss bugs that require **semantic/dataflow chains** where the output of one construct becomes the input to another.
7
+
In **mutational grammar fuzzing**, inputs are mutated while staying **grammar-valid**. In coverage-guided mode, only samples that trigger **new coverage** are saved as corpus seeds. For **language targets** (parsers, interpreters, engines), this can miss bugs that require **semantic/dataflow chains** where the output of one construct becomes the input of another.
8
8
9
9
**Failure mode:** the fuzzer finds seeds that individually exercise `document()` and `generate-id()` (or similar primitives), but **does not preserve the chained dataflow**, so the “closer-to-bug” sample is dropped because it doesn’t add coverage. With **3+ dependent steps**, random recombination becomes expensive and coverage feedback does not guide search.
10
10
@@ -14,6 +14,51 @@ In **mutational grammar fuzzing**, inputs are mutated while staying **grammar-va
14
14
15
15
Coverage-guided mutation is **greedy**: a new-coverage sample is saved immediately, often retaining large unchanged regions. Over time, corpora become **near-duplicates** with low structural diversity. Aggressive minimization can remove useful context, so a practical compromise is **grammar-aware minimization** that **stops after a minimum token threshold** (reduce noise while keeping enough surrounding structure to remain mutation-friendly).
16
16
17
+
A practical corpus rule for mutational fuzzing is: **prefer a small set of structurally different seeds that maximize coverage** over a large pile of near-duplicates. In practice, this usually means:
18
+
19
+
- Start from **real-world samples** (public corpora, crawling, captured traffic, file sets from the target ecosystem).
20
+
- Distill them with **coverage-based corpus minimization** instead of keeping every valid sample.
21
+
- Keep seeds **small enough** that mutations land on meaningful fields rather than spending most cycles on irrelevant bytes.
22
+
- Re-run corpus minimization after major harness/instrumentation changes, because the “best” corpus changes when reachability changes.
23
+
24
+
## Comparison-Aware Mutation For Magic Values
25
+
26
+
A common reason fuzzers plateau is not syntax but **hard comparisons**: magic bytes, length checks, enum strings, checksums, or parser dispatch values guarded by `memcmp`, switch tables, or cascaded comparisons. Pure random mutation wastes cycles trying to guess these values byte-by-byte.
27
+
28
+
For these targets, use **comparison tracing** (for example AFL++ `CMPLOG` / Redqueen-style workflows) so the fuzzer can observe operands from failed comparisons and bias mutations toward values that satisfy them.
29
+
30
+
```bash
31
+
./configure --cc=afl-clang-fast
32
+
make
33
+
cp ./target ./target.afl
34
+
35
+
make clean
36
+
AFL_LLVM_CMPLOG=1 ./configure --cc=afl-clang-fast
37
+
make
38
+
cp ./target ./target.cmplog
39
+
40
+
afl-fuzz -i in -o out -c ./target.cmplog -- ./target.afl @@
41
+
```
42
+
43
+
**Practical notes:**
44
+
45
+
- This is especially useful when the target gates deep logic behind **file signatures**, **protocol verbs**, **type tags**, or **version-dependent feature bits**.
46
+
- Pair it with **dictionaries** extracted from real samples, protocol specs, or debug logs. A small dictionary with grammar tokens, chunk names, verbs, and delimiters is often more valuable than a massive generic wordlist.
47
+
- If the target performs many sequential checks, solve the earliest “magic” comparisons first and then minimize the resulting corpus again so later stages start from already-valid prefixes.
48
+
49
+
## Stateful Fuzzing: Sequences Are Seeds
50
+
51
+
For **protocols**, **authenticated workflows**, and **multi-stage parsers**, the interesting unit is often not a single blob but a **message sequence**. Concatenating the whole transcript into one file and mutating it blindly is usually inefficient because the fuzzer mutates every step equally, even when only the later message reaches the fragile state.
52
+
53
+
A more effective pattern is to treat the **sequence itself as the seed** and use **observable state** (response codes, protocol states, parser phases, returned object types) as additional feedback:
54
+
55
+
- Keep **valid prefix messages** stable and focus mutations on the **transition-driving** message.
56
+
- Cache identifiers and server-generated values from prior responses when the next step depends on them.
57
+
- Prefer per-message mutation/splicing over mutating the whole serialized transcript as an opaque blob.
58
+
- If the protocol exposes meaningful response codes, use them as a **cheap state oracle** to prioritize sequences that progress deeper.
59
+
60
+
This is the same reason authenticated bugs, hidden transitions, or “only-after-handshake” parser bugs are often missed by vanilla file-style fuzzing: the fuzzer must preserve **order, state, and dependencies**, not just structure.
A practical way to hybridize **generative novelty** with **coverage reuse** is to **restart short-lived workers** against a persistent server. Each worker starts from an empty corpus, syncs after `T` seconds, runs another `T` seconds on the combined corpus, syncs again, then exits. This yields **fresh structures each generation** while still leveraging accumulated coverage.
@@ -65,9 +110,42 @@ while True:
65
110
- In grammar fuzzing mode, **initial server sync is skipped by default** (no need for `-skip_initial_server_sync`).
66
111
- Optimal `T` is **target-dependent**; switching after the worker has found most “easy” coverage tends to work best.
67
112
113
+
## Snapshot Fuzzing For Hard-To-Harness Targets
114
+
115
+
When the code you want to test only becomes reachable **after a large setup cost** (booting a VM, completing a login, receiving a packet, parsing a container, initializing a service), a useful alternative is **snapshot fuzzing**:
116
+
117
+
1. Run the target until the interesting state is ready.
118
+
2. Snapshot **memory + registers** at that point.
119
+
3. For every test case, write the mutated input directly into the relevant guest/process buffer.
120
+
4. Execute until crash/timeout/reset.
121
+
5. Restore only the **dirty pages** and repeat.
122
+
123
+
This avoids paying the full setup cost every iteration and is especially useful for **network services**, **firmware**, **post-auth attack surfaces**, and **binary-only targets** that are painful to refactor into a classic in-process harness.
124
+
125
+
A practical trick is to break immediately after a `recv`/`read`/packet-deserialization point, note the input buffer address, snapshot there, and then mutate that buffer directly in each iteration. This lets you fuzz the deep parsing logic without rebuilding the entire handshake every time.
126
+
127
+
## Harness Introspection: Find Shallow Fuzzers Early
128
+
129
+
When a campaign stalls, the problem is often not the mutator but the **harness**. Use **reachability/coverage introspection** to find functions that are statically reachable from your fuzz target but rarely or never covered dynamically. Those functions usually indicate one of three issues:
130
+
131
+
- The harness enters the target too late or too early.
132
+
- The seed corpus is missing a whole feature family.
133
+
- The target really needs a **second harness** instead of one oversized “do everything” harness.
134
+
135
+
If you use OSS-Fuzz / ClusterFuzz-style workflows, Fuzz Introspector is useful for this triage:
Use the report to decide whether to add a new harness for an untested parser path, expand the corpus for a specific feature, or split a monolithic harness into smaller entry points.
0 commit comments