1# Custom Mutators in AFL++ 2 3This file describes how you can implement custom mutations to be used in AFL. 4For now, we support C/C++ library and Python module, collectively named as the 5custom mutator. 6 7There is also experimental support for Rust in `custom_mutators/rust`. For 8documentation, refer to that directory. Run `cargo doc -p custom_mutator --open` 9in that directory to view the documentation in your web browser. 10 11Implemented by 12- C/C++ library (`*.so`): Khaled Yakdan from Code Intelligence 13 (<yakdan@code-intelligence.de>) 14- Python module: Christian Holler from Mozilla (<choller@mozilla.com>) 15 16## 1) Introduction 17 18Custom mutators can be passed to `afl-fuzz` to perform custom mutations on test 19cases beyond those available in AFL. For example, to enable structure-aware 20fuzzing by using libraries that perform mutations according to a given grammar. 21 22The custom mutator is passed to `afl-fuzz` via the `AFL_CUSTOM_MUTATOR_LIBRARY` 23or `AFL_PYTHON_MODULE` environment variable, and must export a fuzz function. 24Now AFL++ also supports multiple custom mutators which can be specified in the 25same `AFL_CUSTOM_MUTATOR_LIBRARY` environment variable like this. 26 27```bash 28export AFL_CUSTOM_MUTATOR_LIBRARY="full/path/to/mutator_first.so;full/path/to/mutator_second.so" 29``` 30 31For details, see [APIs](#2-apis) and [Usage](#3-usage). 32 33The custom mutation stage is set to be the first non-deterministic stage (right 34before the havoc stage). 35 36Note: If `AFL_CUSTOM_MUTATOR_ONLY` is set, all mutations will solely be 37performed with the custom mutator. 38 39## 2) APIs 40 41C/C++: 42 43```c 44void *afl_custom_init(afl_state_t *afl, unsigned int seed); 45unsigned int afl_custom_fuzz_count(void *data, const unsigned char *buf, size_t buf_size); 46size_t afl_custom_fuzz(void *data, unsigned char *buf, size_t buf_size, unsigned char **out_buf, unsigned char *add_buf, size_t add_buf_size, size_t max_size); 47const char *afl_custom_describe(void *data, size_t max_description_len); 48size_t afl_custom_post_process(void *data, unsigned char *buf, size_t buf_size, unsigned char **out_buf); 49int afl_custom_init_trim(void *data, unsigned char *buf, size_t buf_size); 50size_t afl_custom_trim(void *data, unsigned char **out_buf); 51int afl_custom_post_trim(void *data, unsigned char success); 52size_t afl_custom_havoc_mutation(void *data, unsigned char *buf, size_t buf_size, unsigned char **out_buf, size_t max_size); 53unsigned char afl_custom_havoc_mutation_probability(void *data); 54unsigned char afl_custom_queue_get(void *data, const unsigned char *filename); 55u8 afl_custom_queue_new_entry(void *data, const unsigned char *filename_new_queue, const unsigned int *filename_orig_queue); 56const char* afl_custom_introspection(my_mutator_t *data); 57void afl_custom_deinit(void *data); 58``` 59 60Python: 61 62```python 63def init(seed): 64 pass 65 66def fuzz_count(buf, add_buf, max_size): 67 return cnt 68 69def fuzz(buf, add_buf, max_size): 70 return mutated_out 71 72def describe(max_description_length): 73 return "description_of_current_mutation" 74 75def post_process(buf): 76 return out_buf 77 78def init_trim(buf): 79 return cnt 80 81def trim(): 82 return out_buf 83 84def post_trim(success): 85 return next_index 86 87def havoc_mutation(buf, max_size): 88 return mutated_out 89 90def havoc_mutation_probability(): 91 return probability # int in [0, 100] 92 93def queue_get(filename): 94 return True 95 96def queue_new_entry(filename_new_queue, filename_orig_queue): 97 return False 98 99def introspection(): 100 return string 101 102def deinit(): # optional for Python 103 pass 104``` 105 106### Custom Mutation 107 108- `init`: 109 110 This method is called when AFL++ starts up and is used to seed RNG and set 111 up buffers and state. 112 113- `queue_get` (optional): 114 115 This method determines whether the custom fuzzer should fuzz the current 116 queue entry or not 117 118- `fuzz_count` (optional): 119 120 When a queue entry is selected to be fuzzed, afl-fuzz selects the number of 121 fuzzing attempts with this input based on a few factors. If, however, the 122 custom mutator wants to set this number instead on how often it is called 123 for a specific queue entry, use this function. This function is most useful 124 if `AFL_CUSTOM_MUTATOR_ONLY` is **not** used. 125 126- `fuzz` (optional): 127 128 This method performs custom mutations on a given input. It also accepts an 129 additional test case. Note that this function is optional - but it makes 130 sense to use it. You would only skip this if `post_process` is used to fix 131 checksums etc. so if you are using it, e.g., as a post processing library. 132 Note that a length > 0 *must* be returned! 133 134- `describe` (optional): 135 136 When this function is called, it shall describe the current test case, 137 generated by the last mutation. This will be called, for example, to name 138 the written test case file after a crash occurred. Using it can help to 139 reproduce crashing mutations. 140 141- `havoc_mutation` and `havoc_mutation_probability` (optional): 142 143 `havoc_mutation` performs a single custom mutation on a given input. This 144 mutation is stacked with other mutations in havoc. The other method, 145 `havoc_mutation_probability`, returns the probability that `havoc_mutation` 146 is called in havoc. By default, it is 6%. 147 148- `post_process` (optional): 149 150 For some cases, the format of the mutated data returned from the custom 151 mutator is not suitable to directly execute the target with this input. For 152 example, when using libprotobuf-mutator, the data returned is in a protobuf 153 format which corresponds to a given grammar. In order to execute the target, 154 the protobuf data must be converted to the plain-text format expected by the 155 target. In such scenarios, the user can define the `post_process` function. 156 This function is then transforming the data into the format expected by the 157 API before executing the target. 158 159 This can return any python object that implements the buffer protocol and 160 supports PyBUF_SIMPLE. These include bytes, bytearray, etc. 161 162- `queue_new_entry` (optional): 163 164 This methods is called after adding a new test case to the queue. If the 165 contents of the file was changed, return True, False otherwise. 166 167- `introspection` (optional): 168 169 This method is called after a new queue entry, crash or timeout is 170 discovered if compiled with INTROSPECTION. The custom mutator can then 171 return a string (const char *) that reports the exact mutations used. 172 173- `deinit`: 174 175 The last method to be called, deinitializing the state. 176 177Note that there are also three functions for trimming as described in the next 178section. 179 180### Trimming Support 181 182The generic trimming routines implemented in AFL++ can easily destroy the 183structure of complex formats, possibly leading to a point where you have a lot 184of test cases in the queue that your Python module cannot process anymore but 185your target application still accepts. This is especially the case when your 186target can process a part of the input (causing coverage) and then errors out on 187the remaining input. 188 189In such cases, it makes sense to implement a custom trimming routine. The API 190consists of multiple methods because after each trimming step, we have to go 191back into the C code to check if the coverage bitmap is still the same for the 192trimmed input. Here's a quick API description: 193 194- `init_trim` (optional): 195 196 This method is called at the start of each trimming operation and receives 197 the initial buffer. It should return the amount of iteration steps possible 198 on this input (e.g., if your input has n elements and you want to remove 199 them one by one, return n, if you do a binary search, return log(n), and so 200 on). 201 202 If your trimming algorithm doesn't allow to determine the amount of 203 (remaining) steps easily (esp. while running), then you can alternatively 204 return 1 here and always return 0 in `post_trim` until you are finished and 205 no steps remain. In that case, returning 1 in `post_trim` will end the 206 trimming routine. The whole current index/max iterations stuff is only used 207 to show progress. 208 209- `trim` (optional) 210 211 This method is called for each trimming operation. It doesn't have any 212 arguments because there is already the initial buffer from `init_trim` and 213 we can memorize the current state in the data variables. This can also save 214 reparsing steps for each iteration. It should return the trimmed input 215 buffer. 216 217- `post_trim` (optional) 218 219 This method is called after each trim operation to inform you if your 220 trimming step was successful or not (in terms of coverage). If you receive a 221 failure here, you should reset your input to the last known good state. In 222 any case, this method must return the next trim iteration index (from 0 to 223 the maximum amount of steps you returned in `init_trim`). 224 225Omitting any of three trimming methods will cause the trimming to be disabled 226and trigger a fallback to the built-in default trimming routine. 227 228### Environment Variables 229 230Optionally, the following environment variables are supported: 231 232- `AFL_CUSTOM_MUTATOR_ONLY` 233 234 Disable all other mutation stages. This can prevent broken test cases (those 235 that your Python module can't work with anymore) to fill up your queue. Best 236 combined with a custom trimming routine (see below) because trimming can 237 cause the same test breakage like havoc and splice. 238 239- `AFL_PYTHON_ONLY` 240 241 Deprecated and removed, use `AFL_CUSTOM_MUTATOR_ONLY` instead. 242 243- `AFL_DEBUG` 244 245 When combined with `AFL_NO_UI`, this causes the C trimming code to emit 246 additional messages about the performance and actions of your custom 247 trimmer. Use this to see if it works :) 248 249## 3) Usage 250 251### Prerequisite 252 253For Python mutators, the python 3 or 2 development package is required. On 254Debian/Ubuntu/Kali it can be installed like this: 255 256```bash 257sudo apt install python3-dev 258# or 259sudo apt install python-dev 260``` 261 262Then, AFL++ can be compiled with Python support. The AFL++ Makefile detects 263Python 2 and 3 through `python-config` if it is in the PATH and compiles 264`afl-fuzz` with the feature if available. 265 266Note: for some distributions, you might also need the package `python[23]-apt`. 267In case your setup is different, set the necessary variables like this: 268`PYTHON_INCLUDE=/path/to/python/include LDFLAGS=-L/path/to/python/lib make`. 269 270### Custom Mutator Preparation 271 272For C/C++ mutators, the source code must be compiled as a shared object: 273 274```bash 275gcc -shared -Wall -O3 example.c -o example.so 276``` 277 278Note that if you specify multiple custom mutators, the corresponding functions 279will be called in the order in which they are specified. E.g., the first 280`post_process` function of `example_first.so` will be called and then that of 281`example_second.so`. 282 283### Run 284 285C/C++ 286 287```bash 288export AFL_CUSTOM_MUTATOR_LIBRARY="/full/path/to/example_first.so;/full/path/to/example_second.so" 289afl-fuzz /path/to/program 290``` 291 292Python 293 294```bash 295export PYTHONPATH=`dirname /full/path/to/example.py` 296export AFL_PYTHON_MODULE=example 297afl-fuzz /path/to/program 298``` 299 300## 4) Example 301 302See [example.c](../custom_mutators/examples/example.c) and 303[example.py](../custom_mutators/examples/example.py). 304 305## 5) Other Resources 306 307- AFL libprotobuf mutator 308 - [bruce30262/libprotobuf-mutator_fuzzing_learning](https://github.com/bruce30262/libprotobuf-mutator_fuzzing_learning/tree/master/4_libprotobuf_aflpp_custom_mutator) 309 - [thebabush/afl-libprotobuf-mutator](https://github.com/thebabush/afl-libprotobuf-mutator) 310- [XML Fuzzing@NullCon 2017](https://www.agarri.fr/docs/XML_Fuzzing-NullCon2017-PUBLIC.pdf) 311 - [A bug detected by AFL + XML-aware mutators](https://bugs.chromium.org/p/chromium/issues/detail?id=930663)