~~~ fuzzing with grammar-based mutations ~~~
This project is a reimplementation of [Gramatron](https://github.com/HexHive/Gramatron) that is
- __performant__: 4x higher throughput than Gramatron or LibAFL's Gramatron implementation
- __versatile__: usable with LibAFL, libfuzzer, in a custom AFL++ mutator or standalone
- __easy to use__: no more orchestration of different scripts to get the fuzzing campaign running, everything is batteries-included
- __extendable__: at its core, peacock is a library that you can use at your leisure to customize every step of the grammar fuzzing process
- __backwards compatible__: it works with grammars that you have already written for other tools
## How to use it
Clone the repo and execute
```
cargo build --release
```
This creates 5 ready-to-use tools:
1. `peacock-fuzz`: A coverage-guided fuzzer that can fuzz any binary compiled with AFL++'s compilers or anything that speaks AFL's forkserver protocol
2. `peacock-dump`: peacock-fuzz saves crashes and queue items in a raw, binary format to disk. Use this tool to get a human readable output from any such file. All these binary files have the prefix `peacock-raw-`
3. `peacock-compile`: Takes a grammar and compiles it to C code
4. `peacock-merge`: Merge multiple grammar files into one or convert a grammar file from one format into another
5. `peacock-gen`: Generate individual inputs from a grammar
If you want more fine-grained control you can use the crate `peacock_fuzz`, which is the backbone of all the tools from above.
See the documentation at [docs.rs](https://docs.rs/peacock-fuzz) in order to get started with peacock as a library.
## How it works
Peacock is a fuzzer that implements so-called "grammar-based mutations". This means that it will mutate its inputs in such a way that they will always adhere to a given [grammar](https://en.wikipedia.org/wiki/Formal_grammar).
The way mutations work is the same as in Gramatron. A grammar is converted to a [PDA](https://en.wikipedia.org/wiki/Pushdown_automaton) such that an input can be represented as a walk through the automaton. Then, a mutation of an input is simply a modification of an automaton walk. We cut off the walk at a random point and let it find a new random path through the automaton from there.
While Gramatron and LibAFL realize the automaton as an adjacency matrix,
peacock generates C code that encodes the automaton in its control flow. This saves us a lot of memory accesses and makes the mutation procedure faster.
The generated C code exposes a certain API that can be used by any application, e.g. a libfuzzer harness, an AFL++ custom mutator or even Rust code.
Peacock also ships a ready to use fuzzer that can fuzz any binary that has been compiled with AFL++'s compilers or implements an AFL-style forkserver.
## How to write grammars
Peacock accepts its context-free grammars in JSON format.
A context-free grammar has production rules of the form:
```
A -> X Y Z ...
```
where `A` _must_ be a non-terminal and `X`,`Y`,`Z` can be non-terminals or terminals. The right-hand-side must contain at least one symbol.
Non-terminals are enclosed in `<>`, so the non-terminal `A` would be represented as ``. Terminals are enclosed in `''`.
The set of rules
```
A -> a B
A -> a
B -> b B
B -> Ɛ
```
would be written as
```jsonc
{
// Comments are also possible :)
"": [
["'a'", ""],
["'a'"]
],
"": [
["'b'", ""],
["''"] // Ɛ = ''
]
}
```
and corresponds to the regular expression `a(b*)`.
Peacock also supports the Gramatron format, which is a bit different and does not allow for comments.
The non-terminal `` is the entrypoint of the grammar.
## C API Documentation
- `void seed_generator (size_t new_seed)`
Supply a seed for the RNG of the mutator.
- `size_t unparse_sequence (size_t* seq_buf, size_t seq_capacity, unsigned char* input, size_t input_len)`
Given an input that adheres to the grammar, find the corresponding automaton walk. _This function may be slow, use outside of hot loop._
- `seq_buf`: Automaton walk will be written into this buffer
- `seq_capacity`: Maximum number of elements that `seq_buf` can hold (not number of bytes)
- `input`: User input adhering to grammar
- `input_len`: Length of `input`
Returns the number of elements written to `seq_buf` or 0 if input does not adhere to grammar.
- `size_t mutate_sequence (size_t* buf, size_t len, size_t capacity)`
Given an automaton walk, create a random mutant of the walk.
- `buf`: Pointer to array that holds automaton walk
- `len`: Number of items in `buf` (not number of bytes)
- `capacity`: Maximum number of items that `buf` can hold (not number of bytes)
Returns the length of the new walk.
- `size_t serialize_sequence (size_t* seq, size_t seq_len, unsigned char* out, size_t out_len)`
Given an automaton walk, create the corresponding output.
- `seq`: Pointer to automaton walk
- `seq_len`: Number of items in `seq` (not number of bytes)
- `out`: Output will be written into that buffer
- `out_len`: Number of bytes in `out`
Returns how many bytes have been written to `out`.
Macros:
- `MAKE_THREAD_SAFE`: Define this to make the mutator completely thread-safe
- `MAKE_VISIBLE`: Define this to explicitly set the visibility of the functions from above to "default"
- `STATIC_SEED=`: Compile-time seed for the RNG
- `DISABLE_rand`: Don't include the internal `rand` function and use an external one with the signature `size_t rand (void)`
- `DISABLE_seed_generator`: Don't include the function `seed_generator`