Structure-aware fuzzing: when random bytes stop working

Coverage-guided mutation works great until your target parses something with strong structure. Then you plateau: the fuzzer spends its whole budget producing inputs that get rejected in the first 50 bytes.

Options, roughly in order of effort:

  • Dictionaries. Cheapest possible win. Feed AFL++/libFuzzer the magic constants, keywords, and tag names from the format. Often worth several percent coverage for ten minutes of work.
  • libprotobuf-mutator. Define the input as a protobuf message, mutate the message, serialize to the real format in the harness. You get structurally valid inputs for free.
  • Custom mutators. AFL++ lets you plug in your own. More work, maximum control — appropriate when the format has cross-field dependencies (lengths, offsets, checksums) that generic mutation will never satisfy.
  • Grammar-based generation. For text formats — languages, config files, query syntaxes. Nautilus and friends.

The tradeoff nobody mentions: structure-aware fuzzing constrains your inputs to valid ones, and plenty of real bugs live in how the parser handles invalid input. Best results I’ve had come from running both a structured campaign and a dumb one, with a shared corpus.

What are you fuzzing where plain mutation hit a wall?