From df465216583afcc0e65e4468e6383afd7a688ddc Mon Sep 17 00:00:00 2001 From: h1994st Date: Tue, 3 Mar 2020 19:48:13 -0500 Subject: Finish refactoring APIs for the custom mutator and Python module - Remove AFL_PYTHON_ONLY (env) and python_only (variable) - Unify fuzz API of the custom mutator and Python module - Merge the custom mutator into the old python_stage, which is now renamed to custom_mutator_stage --- docs/env_variables.md | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) (limited to 'docs/env_variables.md') diff --git a/docs/env_variables.md b/docs/env_variables.md index 527f1c1b..5214f808 100644 --- a/docs/env_variables.md +++ b/docs/env_variables.md @@ -104,7 +104,7 @@ Then there are a few specific features that are only available in llvm_mode: - Setting AFL_LLVM_LAF_SPLIT_COMPARES will split all floating point and 64, 32 and 16 bit integer CMP instructions - See llvm_mode/README.laf-intel.md for more information. + See llvm_mode/README.laf-intel.md for more information. ### WHITELIST @@ -192,7 +192,7 @@ checks or alter some of the more exotic semantics of the tool: deciding if a particular test case is a "hang". The default is 1 second or the value of the -t parameter, whichever is larger. Dialing the value down can be useful if you are very concerned about slow inputs, or if you - don't want AFL to spend too much time classifying that stuff and just + don't want AFL to spend too much time classifying that stuff and just rapidly put all timeouts in that bin. - AFL_NO_ARITH causes AFL to skip most of the deterministic arithmetics. @@ -223,15 +223,15 @@ checks or alter some of the more exotic semantics of the tool: for more. - Setting AFL_CUSTOM_MUTATOR_LIBRARY to a shared library with - afl_custom_mutator() creates additional mutations through this library. + afl_custom_fuzz() creates additional mutations through this library. + If afl-fuzz is compiled with Python (which is autodetected during builing + afl-fuzz), setting AFL_PYTHON_MODULE to a Python module can also provide + additional mutations. If AFL_CUSTOM_MUTATOR_ONLY is also set, all mutations will solely be - performed with/from the library. See [custom_mutator.md](custom_mutator.md) - - - For AFL_PYTHON_MODULE and AFL_PYTHON_ONLY - they require afl-fuzz to - be compiled with Python (which is autodetected during builing afl-fuzz). - Please see [python_mutators.md](python_mutators.md). + performed with/from the library/Python module. This feature allows to configure custom mutators which can be very helpful in e.g. fuzzing XML or other highly flexible structured input. + Please see [custom_mutator.md](custom_mutator.md) or [python_mutators.md](python_mutators.md). - AFL_FAST_CAL keeps the calibration stage about 2.5x faster (albeit less precise), which can help when starting a session against a slow target. @@ -283,7 +283,7 @@ The QEMU wrapper used to instrument binary-only code supports several settings: - Setting AFL_INST_LIBS causes the translator to also instrument the code inside any dynamically linked libraries (notably including glibc). - + - Setting AFL_COMPCOV_LEVEL enables the CompareCoverage tracing of all cmp and sub in x86 and x86_64 and memory comparions functions (e.g. strcmp, memcmp, ...) when libcompcov is preloaded using AFL_PRELOAD. @@ -292,7 +292,7 @@ The QEMU wrapper used to instrument binary-only code supports several settings: only comparisons with immediate values / read-only memory and AFL_COMPCOV_LEVEL=2 that instruments all the comparions. Level 2 is more accurate but may need a larger shared memory. - + - Setting AFL_QEMU_COMPCOV enables the CompareCoverage tracing of all cmp and sub in x86 and x86_64. This is an alias of AFL_COMPCOV_LEVEL=1 when AFL_COMPCOV_LEVEL is @@ -304,25 +304,25 @@ The QEMU wrapper used to instrument binary-only code supports several settings: - AFL_DEBUG will print the found entrypoint for the binary to stderr. Use this if you are unsure if the entrypoint might be wrong - but - use it directly, e.g. afl-qemu-trace ./program + use it directly, e.g. afl-qemu-trace ./program - AFL_ENTRYPOINT allows you to specify a specific entrypoint into the binary (this can be very good for the performance!). The entrypoint is specified as hex address, e.g. 0x4004110 Note that the address must be the address of a basic block. - + - When the target is i386/x86_64 you can specify the address of the function that has to be the body of the persistent loop using AFL_QEMU_PERSISTENT_ADDR=`start addr`. - + - Another modality to execute the persistent loop is to specify also the AFL_QEMU_PERSISTENT_RET=`end addr` env variable. With this variable assigned, instead of patching the return address, the specified instruction is transformed to a jump towards `start addr`. - + - AFL_QEMU_PERSISTENT_GPR=1 QEMU will save the original value of general purpose registers and restore them in each persistent cycle. - + - With AFL_QEMU_PERSISTENT_RETADDR_OFFSET you can specify the offset from the stack pointer in which QEMU can find the return address when `start addr` is hitted. @@ -376,7 +376,7 @@ The library honors these environmental variables: - AFL_LD_NO_CALLOC_OVER inhibits abort() on calloc() overflows. Most of the common allocators check for that internally and return NULL, so it's a security risk only in more exotic setups. - + - AFL_ALIGNED_ALLOC=1 will force the alignment of the allocation size to max_align_t to be compliant with the C standard. @@ -410,7 +410,7 @@ optimal values if not already present in the environment: - In the same vein, by default, MSAN_OPTIONS are set to: - exit_code=86 (required for legacy reasons) + exit_code=86 (required for legacy reasons) abort_on_error=1 symbolize=0 msan_track_origins=0 -- cgit 1.4.1 From 445d4b7e594ba6933d69ef680ea7b3a64c214d82 Mon Sep 17 00:00:00 2001 From: h1994st Date: Tue, 3 Mar 2020 23:17:24 -0500 Subject: Update the documents of the custom mutator - Merge python_mutators.md into custom_mutator.md - Remove python_mutators.md --- docs/custom_mutator.md | 218 +++++++++++++++++++++++++++++++++++++++++------- docs/env_variables.md | 8 +- docs/python_mutators.md | 148 -------------------------------- src/afl-fuzz.c | 2 +- 4 files changed, 192 insertions(+), 184 deletions(-) delete mode 100644 docs/python_mutators.md (limited to 'docs/env_variables.md') diff --git a/docs/custom_mutator.md b/docs/custom_mutator.md index 68e27de7..2a6c365d 100644 --- a/docs/custom_mutator.md +++ b/docs/custom_mutator.md @@ -1,45 +1,201 @@ -# Adding custom mutators to AFL +# Custom Mutators in AFL++ This file describes how you can implement custom mutations to be used in AFL. +For now, we support C/C++ library and Python module, collectivelly named as the +custom mutator. -Implemented by Khaled Yakdan from Code Intelligence +Implemented by +- C/C++ library (`*.so`): Khaled Yakdan from Code Intelligence () +- Python module: Christian Holler from Mozilla () -## 1) Description +## 1) Introduction -Custom mutator libraries can be passed to afl-fuzz to perform custom mutations -on test cases beyond those available in AFL - for example, to enable -structure-aware fuzzing by using libraries that perform mutations according to -a given grammar. +Custom mutators can be passed to `afl-fuzz` to perform custom mutations on test +cases beyond those available in AFL. For example, to enable structure-aware +fuzzing by using libraries that perform mutations according to a given grammar. -The custom mutator library is passed to afl-fuzz via the -AFL_CUSTOM_MUTATOR_LIBRARY environment variable. The library must export -the afl_custom_fuzz() function and must be compiled as a shared object. -For example: +The custom mutator is passed to `afl-fuzz` via the `AFL_CUSTOM_MUTATOR_LIBRARY` +or `AFL_PYTHON_MODULE` environment variable., and must export a fuzz function. +Please see [APIs](#2-apis) and [Usage](#3-usage) for detail. + +The custom mutation stage is set to be the first non-deterministic stage (right before the havoc stage). + +Note: If `AFL_CUSTOM_MUTATOR_ONLY` is set, all mutations will solely be +performed with the custom mutator. + +## 2) APIs + +C/C++: +```c +void afl_custom_init(unsigned int seed); +size_t afl_custom_fuzz(u8* buf, size_t buf_size, + u8* add_buf, size_t add_buf_size, + u8* mutated_out, size_t max_size); +size_t afl_custom_pre_save(u8* buf, size_t buf_size, u8** out_buf); +u32 afl_custom_init_trim(u8* buf, size_t buf_size); +void afl_custom_trim(u8** out_buf, size_t* out_buf_size); +u32 afl_custom_post_trim(u8 success); +``` + +Python: +```python +def init(seed): + pass + +def fuzz(buf, add_buf, max_size): + return mutated_out + +def pre_save(buf): + return out_buf + +def init_trim(buf): + return cnt + +def trim(): + return out_buf + +def post_trim(success): + return next_index +``` + +### Custom Mutation + +- `init` (optional): + + This method is called when AFL++ starts up and is used to seed RNG. + +- `fuzz` (required): + + This method performs custom mutations on a given input. It also accepts an + additional test case. + +- `pre_save` (optional): + + For some cases, the format of the mutated data returned from the custom + mutator is not suitable to directly execute the target with this input. + For example, when using libprotobuf-mutator, the data returned is in a + protobuf format which corresponds to a given grammar. In order to execute + the target, the protobuf data must be converted to the plain-text format expected by the target. In such scenarios, the user can define the + `pre_save` function. This function is then transforms the data into the + format expected by the API before executing the target. + + +### Trimming Support + +The generic trimming routines implemented in AFL++ can easily destroy the +structure of complex formats, possibly leading to a point where you have a lot +of test cases in the queue that your Python module cannot process anymore but +your target application still accepts. This is especially the case when your +target can process a part of the input (causing coverage) and then errors out +on the remaining input. + +In such cases, it makes sense to implement a custom trimming routine. The API +consists of multiple methods because after each trimming step, we have to go +back into the C code to check if the coverage bitmap is still the same for the +trimmed input. Here's a quick API description: + +- `init_trim` (optional): + + This method is called at the start of each trimming operation and receives + the initial buffer. It should return the amount of iteration steps possible + on this input (e.g. if your input has n elements and you want to remove them + one by one, return n, if you do a binary search, return log(n), and so on). + + If your trimming algorithm doesn't allow you to determine the amount of + (remaining) steps easily (esp. while running), then you can alternatively + return 1 here and always return 0 in `post_trim` until you are finished and + no steps remain. In that case, returning 1 in `post_trim` will end the + trimming routine. The whole current index/max iterations stuff is only used + to show progress. + +- `trim` (optional) + + This method is called for each trimming operation. It doesn't have any + arguments because we already have the initial buffer from `init_trim` and we + can memorize the current state in global variables. This can also save + reparsing steps for each iteration. It should return the trimmed input + buffer, where the returned data must not exceed the initial input data in + length. Returning anything that is larger than the original data (passed to + `init_trim`) will result in a fatal abort of AFL++. + +- `post_trim` (optional) + + This method is called after each trim operation to inform you if your + trimming step was successful or not (in terms of coverage). If you receive + a failure here, you should reset your input to the last known good state. + In any case, this method must return the next trim iteration index (from 0 + to the maximum amount of steps you returned in `init_trim`). + +Omitting any of three methods will cause the trimming to be disabled and trigger +a fallback to the builtin default trimming routine. + +### Environment Variables + +Optionally, the following environment variables are supported: + +- `AFL_PYTHON_ONLY` + + Disable all other mutation stages. This can prevent broken testcases + (those that your Python module can't work with anymore) to fill up your + queue. Best combined with a custom trimming routine (see below) because + trimming can cause the same test breakage like havoc and splice. + +- `AFL_DEBUG` + + When combined with `AFL_NO_UI`, this causes the C trimming code to emit additional messages about the performance and actions of your custom trimmer. Use this to see if it works :) + +## 3) Usage + +### Prerequisite + +For Python mutator, the python 3 or 2 development package is required. On +Debian/Ubuntu/Kali this can be done: + +```bash +sudo apt install python3-dev +# or +sudo apt install python-dev ``` -$CC -shared -Wall -O3 .c -o .so + +Then, AFL++ can be compiled with Python support. The AFL++ Makefile detects +Python 2 and 3 through `python-config` if it is in the PATH and compiles +`afl-fuzz` with the feature if available. + +Note: for some distributions, you might also need the package `python[23]-apt`. +In case your setup is different, set the necessary variables like this: +`PYTHON_INCLUDE=/path/to/python/include LDFLAGS=-L/path/to/python/lib make`. + +### Custom Mutator Preparation + +For C/C++ mutator, the source code must be compiled as a shared object: +```bash +gcc -shared -Wall -O3 example.c -o example.so ``` -Note: unless AFL_CUSTOM_MUTATOR_ONLY is set, it is a state mutator like any -other, so it will be used for some test cases, and other mutators for others. -Only if AFL_CUSTOM_MUTATOR_ONLY is set the afl_custom_mutator() function will -be called every time it needs to mutate a test case. +### Run -For some cases, the format of the mutated data returned from the custom -mutator is not suitable to directly execute the target with this input. -For example, when using libprotobuf-mutator, the data returned is in a -protobuf format which corresponds to a given grammar. -In order to execute the target, the protobuf data must be converted to the -plain-text format expected by the target. -In such scenarios, the user can define the afl_pre_save_handler() function. -This function is then transforms the data into the format expected by the -API before executing the target. -afl_pre_save_handler is optional and does not have to be implemented if its -functionality is not needed. +C/C++ +```bash +export AFL_CUSTOM_MUTATOR_LIBRARY=/full/path/to/example.so +afl-fuzz /path/to/program +``` + +Python +```bash +export PYTHONPATH=`dirname /full/path/to/example.py` +export AFL_PYTHON_MODULE=example +afl-fuzz /path/to/program +``` -## 2) Example +## 4) Example -A simple example is provided in ../examples/custom_mutators/ +Please see [example.c](../examples/custom_mutators/example.c) and +[example.py](../examples/custom_mutators/example.py) -There is also a libprotobuf example available at [https://github.com/bruce30262/libprotobuf-mutator_fuzzing_learning/tree/master/4_libprotobuf_aflpp_custom_mutator](https://github.com/bruce30262/libprotobuf-mutator_fuzzing_learning/tree/master/4_libprotobuf_aflpp_custom_mutator) -Another implementation can be found at [https://github.com/thebabush/afl-libprotobuf-mutator](https://github.com/thebabush/afl-libprotobuf-mutator) +## 6) Other Resources +- AFL libprotobuf mutator + - [bruce30262/libprotobuf-mutator_fuzzing_learning](https://github.com/bruce30262/libprotobuf-mutator_fuzzing_learning/tree/master/4_libprotobuf_aflpp_custom_mutator) + - [thebabush/afl-libprotobuf-mutator](https://github.com/thebabush/afl-libprotobuf-mutator) +- [XML Fuzzing@NullCon 2017](https://www.agarri.fr/docs/XML_Fuzzing-NullCon2017-PUBLIC.pdf) + - [A bug detected by AFL + XML-aware mutators](https://bugs.chromium.org/p/chromium/issues/detail?id=930663) diff --git a/docs/env_variables.md b/docs/env_variables.md index 5214f808..83f5b7c0 100644 --- a/docs/env_variables.md +++ b/docs/env_variables.md @@ -228,10 +228,10 @@ checks or alter some of the more exotic semantics of the tool: afl-fuzz), setting AFL_PYTHON_MODULE to a Python module can also provide additional mutations. If AFL_CUSTOM_MUTATOR_ONLY is also set, all mutations will solely be - performed with/from the library/Python module. - This feature allows to configure custom mutators which can be very helpful - in e.g. fuzzing XML or other highly flexible structured input. - Please see [custom_mutator.md](custom_mutator.md) or [python_mutators.md](python_mutators.md). + performed with the custom mutator. + This feature allows to configure custom mutators which can be very helpful, + e.g. fuzzing XML or other highly flexible structured input. + Please see [custom_mutator.md](custom_mutator.md). - AFL_FAST_CAL keeps the calibration stage about 2.5x faster (albeit less precise), which can help when starting a session against a slow target. diff --git a/docs/python_mutators.md b/docs/python_mutators.md deleted file mode 100644 index a7e2c7de..00000000 --- a/docs/python_mutators.md +++ /dev/null @@ -1,148 +0,0 @@ -# Adding custom mutators to AFL using Python modules - - This file describes how you can utilize the external Python API to write - your own custom mutation routines. - - Note: This feature is highly experimental. Use at your own risk. - - Implemented by Christian Holler (:decoder) . - - NOTE: Only cPython 2.7, 3.7 and above are supported, although others may work. - Depending on with which version afl-fuzz was compiled against, you must use - python2 or python3 syntax in your scripts! - After a major version upgrade (e.g. 3.7 -> 3.8), a recompilation of afl-fuzz may be needed. - - For an example and a template see ../examples/python_mutators/ - - -## 1) Description and purpose - -While AFLFuzz comes with a good selection of generic deterministic and -non-deterministic mutation operations, it sometimes might make sense to extend -these to implement strategies more specific to the target you are fuzzing. - -For simplicity and in order to allow people without C knowledge to extend -AFLFuzz, I implemented a "Python" stage that can make use of an external -module (written in Python) that implements a custom mutation stage. - -The main motivation behind this is to lower the barrier for people -experimenting with this tool. Hopefully, someone will be able to do useful -things with this extension. - -If you find it useful, have questions or need additional features added to the -interface, feel free to send a mail to . - -See the following information to get a better pictures: - https://www.agarri.fr/docs/XML_Fuzzing-NullCon2017-PUBLIC.pdf - https://bugs.chromium.org/p/chromium/issues/detail?id=930663 - - -## 2) How the Python module looks like - -You can find a simple example in pymodules/example.py including documentation -explaining each function. In the same directory, you can find another simple -module that performs simple mutations. - -Right now, "init" is called at program startup and can be used to perform any -kinds of one-time initializations while "fuzz" is called each time a mutation -is requested. - -There is also optional support for a trimming API, see the section below for -further information about this feature. - - -## 3) How to compile AFLFuzz with Python support - -You must install the python 3 or 2 development package of your Linux -distribution before this will work. On Debian/Ubuntu/Kali this can be done -with either: - apt install python3-dev -or - apt install python-dev -Note that for some distributions you might also need the package python[23]-apt - -A prerequisite for using this mode is to compile AFLFuzz with Python support. - -The AFL++ Makefile detects Python 3 and 2 through `python-config` if is is in the PATH -and compiles afl-fuzz with the feature if available. - -In case your setup is different set the necessary variables like this: -PYTHON_INCLUDE=/path/to/python/include LDFLAGS=-L/path/to/python/lib make - - -## 4) How to run AFLFuzz with your custom module - -You must pass the module name inside the env variable AFL_PYTHON_MODULE. - -In addition, if you are trying to load the module from the local directory, -you must adjust your PYTHONPATH to reflect this circumstance. The following -command should work if you are inside the aflfuzz directory: - -$ AFL_PYTHON_MODULE="pymodules.test" PYTHONPATH=. ./afl-fuzz - -Optionally, the following environment variables are supported: - -AFL_PYTHON_ONLY - Disable all other mutation stages. This can prevent broken - testcases (those that your Python module can't work with - anymore) to fill up your queue. Best combined with a custom - trimming routine (see below) because trimming can cause the - same test breakage like havoc and splice. - -AFL_DEBUG - When combined with AFL_NO_UI, this causes the C trimming code - to emit additional messages about the performance and actions - of your custom Python trimmer. Use this to see if it works :) - - -## 5) Order and statistics - -The Python stage is set to be the first non-deterministic stage (right before -the havoc stage). In the statistics however, it shows up as the third number -under "havoc". That's because I'm lazy and I didn't want to mess with the UI -too much ;) - - -## 6) Trimming support - -The generic trimming routines implemented in AFLFuzz can easily destroy the -structure of complex formats, possibly leading to a point where you have a lot -of testcases in the queue that your Python module cannot process anymore but -your target application still accepts. This is especially the case when your -target can process a part of the input (causing coverage) and then errors out -on the remaining input. - -In such cases, it makes sense to implement a custom trimming routine in Python. -The API consists of multiple methods because after each trimming step, we have -to go back into the C code to check if the coverage bitmap is still the same -for the trimmed input. Here's a quick API description: - -init_trim: This method is called at the start of each trimming operation - and receives the initial buffer. It should return the amount - of iteration steps possible on this input (e.g. if your input - has n elements and you want to remove them one by one, return n, - if you do a binary search, return log(n), and so on...). - - If your trimming algorithm doesn't allow you to determine the - amount of (remaining) steps easily (esp. while running), then you - can alternatively return 1 here and always return 0 in post_trim - until you are finished and no steps remain. In that case, - returning 1 in post_trim will end the trimming routine. The whole - current index/max iterations stuff is only used to show progress. - -trim: This method is called for each trimming operation. It doesn't - have any arguments because we already have the initial buffer - from init_trim and we can memorize the current state in global - variables. This can also save reparsing steps for each iteration. - It should return the trimmed input buffer, where the returned data - must not exceed the initial input data in length. Returning anything - that is larger than the original data (passed to init_trim) will - result in a fatal abort of AFLFuzz. - -post_trim: This method is called after each trim operation to inform you - if your trimming step was successful or not (in terms of coverage). - If you receive a failure here, you should reset your input to the - last known good state. - In any case, this method must return the next trim iteration index - (from 0 to the maximum amount of steps you returned in init_trim). - -Omitting any of the methods will cause Python trimming to be disabled and -trigger a fallback to the builtin default trimming routine. diff --git a/src/afl-fuzz.c b/src/afl-fuzz.c index a96ee1d0..2d5a5743 100644 --- a/src/afl-fuzz.c +++ b/src/afl-fuzz.c @@ -194,7 +194,7 @@ static void usage(u8* argv0, int more_help) { "use \"-hh\".\n\n"); #ifdef USE_PYTHON - SAYF("Compiled with %s module support, see docs/python_mutators.md\n", + SAYF("Compiled with %s module support, see docs/custom_mutator.md\n", (char*)PYTHON_VERSION); #endif -- cgit 1.4.1 From 38e7dd2b9efbd9c6cda47774630a82660d3156b3 Mon Sep 17 00:00:00 2001 From: h1994st Date: Wed, 4 Mar 2020 01:09:37 -0500 Subject: Update examples of the custom mutator - Merge `examples/python_mutators` into `examples/custom_mutators` - Remove `examples/python_mutators` - Update existing examples to demonstrate new APIs --- docs/custom_mutator.md | 201 -------------- docs/custom_mutators.md | 201 ++++++++++++++ docs/env_variables.md | 2 +- examples/custom_mutators/README.md | 24 +- examples/custom_mutators/XmlMutatorMin.py | 332 +++++++++++++++++++++++ examples/custom_mutators/common.py | 40 +++ examples/custom_mutators/example.c | 177 ++++++++++++ examples/custom_mutators/example.py | 122 +++++++++ examples/custom_mutators/simple-chunk-replace.py | 64 +++++ examples/custom_mutators/simple_mutator.c | 49 ---- examples/custom_mutators/wrapper_afl_min.py | 118 ++++++++ examples/python_mutators/README | 18 -- examples/python_mutators/XmlMutatorMin.py | 331 ---------------------- examples/python_mutators/common.py | 37 --- examples/python_mutators/example.py | 103 ------- examples/python_mutators/simple-chunk-replace.py | 59 ---- examples/python_mutators/wrapper_afl_min.py | 117 -------- 17 files changed, 1076 insertions(+), 919 deletions(-) delete mode 100644 docs/custom_mutator.md create mode 100644 docs/custom_mutators.md create mode 100644 examples/custom_mutators/XmlMutatorMin.py create mode 100644 examples/custom_mutators/common.py create mode 100644 examples/custom_mutators/example.c create mode 100644 examples/custom_mutators/example.py create mode 100644 examples/custom_mutators/simple-chunk-replace.py delete mode 100644 examples/custom_mutators/simple_mutator.c create mode 100644 examples/custom_mutators/wrapper_afl_min.py delete mode 100644 examples/python_mutators/README delete mode 100644 examples/python_mutators/XmlMutatorMin.py delete mode 100644 examples/python_mutators/common.py delete mode 100644 examples/python_mutators/example.py delete mode 100644 examples/python_mutators/simple-chunk-replace.py delete mode 100644 examples/python_mutators/wrapper_afl_min.py (limited to 'docs/env_variables.md') diff --git a/docs/custom_mutator.md b/docs/custom_mutator.md deleted file mode 100644 index 4deb07e1..00000000 --- a/docs/custom_mutator.md +++ /dev/null @@ -1,201 +0,0 @@ -# Custom Mutators in AFL++ - -This file describes how you can implement custom mutations to be used in AFL. -For now, we support C/C++ library and Python module, collectivelly named as the -custom mutator. - -Implemented by -- C/C++ library (`*.so`): Khaled Yakdan from Code Intelligence () -- Python module: Christian Holler from Mozilla () - -## 1) Introduction - -Custom mutators can be passed to `afl-fuzz` to perform custom mutations on test -cases beyond those available in AFL. For example, to enable structure-aware -fuzzing by using libraries that perform mutations according to a given grammar. - -The custom mutator is passed to `afl-fuzz` via the `AFL_CUSTOM_MUTATOR_LIBRARY` -or `AFL_PYTHON_MODULE` environment variable., and must export a fuzz function. -Please see [APIs](#2-apis) and [Usage](#3-usage) for detail. - -The custom mutation stage is set to be the first non-deterministic stage (right before the havoc stage). - -Note: If `AFL_CUSTOM_MUTATOR_ONLY` is set, all mutations will solely be -performed with the custom mutator. - -## 2) APIs - -C/C++: -```c -void afl_custom_init(unsigned int seed); -size_t afl_custom_fuzz(u8* buf, size_t buf_size, - u8* add_buf, size_t add_buf_size, - u8* mutated_out, size_t max_size); -size_t afl_custom_pre_save(u8* buf, size_t buf_size, u8** out_buf); -u32 afl_custom_init_trim(u8* buf, size_t buf_size); -void afl_custom_trim(u8** out_buf, size_t* out_buf_size); -u32 afl_custom_post_trim(u8 success); -``` - -Python: -```python -def init(seed): - pass - -def fuzz(buf, add_buf, max_size): - return mutated_out - -def pre_save(buf): - return out_buf - -def init_trim(buf): - return cnt - -def trim(): - return out_buf - -def post_trim(success): - return next_index -``` - -### Custom Mutation - -- `init` (optional): - - This method is called when AFL++ starts up and is used to seed RNG. - -- `fuzz` (required): - - This method performs custom mutations on a given input. It also accepts an - additional test case. - -- `pre_save` (optional): - - For some cases, the format of the mutated data returned from the custom - mutator is not suitable to directly execute the target with this input. - For example, when using libprotobuf-mutator, the data returned is in a - protobuf format which corresponds to a given grammar. In order to execute - the target, the protobuf data must be converted to the plain-text format expected by the target. In such scenarios, the user can define the - `pre_save` function. This function is then transforms the data into the - format expected by the API before executing the target. - - -### Trimming Support - -The generic trimming routines implemented in AFL++ can easily destroy the -structure of complex formats, possibly leading to a point where you have a lot -of test cases in the queue that your Python module cannot process anymore but -your target application still accepts. This is especially the case when your -target can process a part of the input (causing coverage) and then errors out -on the remaining input. - -In such cases, it makes sense to implement a custom trimming routine. The API -consists of multiple methods because after each trimming step, we have to go -back into the C code to check if the coverage bitmap is still the same for the -trimmed input. Here's a quick API description: - -- `init_trim` (optional): - - This method is called at the start of each trimming operation and receives - the initial buffer. It should return the amount of iteration steps possible - on this input (e.g. if your input has n elements and you want to remove them - one by one, return n, if you do a binary search, return log(n), and so on). - - If your trimming algorithm doesn't allow you to determine the amount of - (remaining) steps easily (esp. while running), then you can alternatively - return 1 here and always return 0 in `post_trim` until you are finished and - no steps remain. In that case, returning 1 in `post_trim` will end the - trimming routine. The whole current index/max iterations stuff is only used - to show progress. - -- `trim` (optional) - - This method is called for each trimming operation. It doesn't have any - arguments because we already have the initial buffer from `init_trim` and we - can memorize the current state in global variables. This can also save - reparsing steps for each iteration. It should return the trimmed input - buffer, where the returned data must not exceed the initial input data in - length. Returning anything that is larger than the original data (passed to - `init_trim`) will result in a fatal abort of AFL++. - -- `post_trim` (optional) - - This method is called after each trim operation to inform you if your - trimming step was successful or not (in terms of coverage). If you receive - a failure here, you should reset your input to the last known good state. - In any case, this method must return the next trim iteration index (from 0 - to the maximum amount of steps you returned in `init_trim`). - -Omitting any of three methods will cause the trimming to be disabled and trigger -a fallback to the builtin default trimming routine. - -### Environment Variables - -Optionally, the following environment variables are supported: - -- `AFL_PYTHON_ONLY` - - Disable all other mutation stages. This can prevent broken testcases - (those that your Python module can't work with anymore) to fill up your - queue. Best combined with a custom trimming routine (see below) because - trimming can cause the same test breakage like havoc and splice. - -- `AFL_DEBUG` - - When combined with `AFL_NO_UI`, this causes the C trimming code to emit additional messages about the performance and actions of your custom trimmer. Use this to see if it works :) - -## 3) Usage - -### Prerequisite - -For Python mutator, the python 3 or 2 development package is required. On -Debian/Ubuntu/Kali this can be done: - -```bash -sudo apt install python3-dev -# or -sudo apt install python-dev -``` - -Then, AFL++ can be compiled with Python support. The AFL++ Makefile detects -Python 2 and 3 through `python-config` if it is in the PATH and compiles -`afl-fuzz` with the feature if available. - -Note: for some distributions, you might also need the package `python[23]-apt`. -In case your setup is different, set the necessary variables like this: -`PYTHON_INCLUDE=/path/to/python/include LDFLAGS=-L/path/to/python/lib make`. - -### Custom Mutator Preparation - -For C/C++ mutator, the source code must be compiled as a shared object: -```bash -gcc -shared -Wall -O3 example.c -o example.so -``` - -### Run - -C/C++ -```bash -export AFL_CUSTOM_MUTATOR_LIBRARY=/full/path/to/example.so -afl-fuzz /path/to/program -``` - -Python -```bash -export PYTHONPATH=`dirname /full/path/to/example.py` -export AFL_PYTHON_MODULE=example -afl-fuzz /path/to/program -``` - -## 4) Example - -Please see [example.c](../examples/custom_mutators/example.c) and -[example.py](../examples/custom_mutators/example.py) - -## 5) Other Resources - -- AFL libprotobuf mutator - - [bruce30262/libprotobuf-mutator_fuzzing_learning](https://github.com/bruce30262/libprotobuf-mutator_fuzzing_learning/tree/master/4_libprotobuf_aflpp_custom_mutator) - - [thebabush/afl-libprotobuf-mutator](https://github.com/thebabush/afl-libprotobuf-mutator) -- [XML Fuzzing@NullCon 2017](https://www.agarri.fr/docs/XML_Fuzzing-NullCon2017-PUBLIC.pdf) - - [A bug detected by AFL + XML-aware mutators](https://bugs.chromium.org/p/chromium/issues/detail?id=930663) diff --git a/docs/custom_mutators.md b/docs/custom_mutators.md new file mode 100644 index 00000000..4deb07e1 --- /dev/null +++ b/docs/custom_mutators.md @@ -0,0 +1,201 @@ +# Custom Mutators in AFL++ + +This file describes how you can implement custom mutations to be used in AFL. +For now, we support C/C++ library and Python module, collectivelly named as the +custom mutator. + +Implemented by +- C/C++ library (`*.so`): Khaled Yakdan from Code Intelligence () +- Python module: Christian Holler from Mozilla () + +## 1) Introduction + +Custom mutators can be passed to `afl-fuzz` to perform custom mutations on test +cases beyond those available in AFL. For example, to enable structure-aware +fuzzing by using libraries that perform mutations according to a given grammar. + +The custom mutator is passed to `afl-fuzz` via the `AFL_CUSTOM_MUTATOR_LIBRARY` +or `AFL_PYTHON_MODULE` environment variable., and must export a fuzz function. +Please see [APIs](#2-apis) and [Usage](#3-usage) for detail. + +The custom mutation stage is set to be the first non-deterministic stage (right before the havoc stage). + +Note: If `AFL_CUSTOM_MUTATOR_ONLY` is set, all mutations will solely be +performed with the custom mutator. + +## 2) APIs + +C/C++: +```c +void afl_custom_init(unsigned int seed); +size_t afl_custom_fuzz(u8* buf, size_t buf_size, + u8* add_buf, size_t add_buf_size, + u8* mutated_out, size_t max_size); +size_t afl_custom_pre_save(u8* buf, size_t buf_size, u8** out_buf); +u32 afl_custom_init_trim(u8* buf, size_t buf_size); +void afl_custom_trim(u8** out_buf, size_t* out_buf_size); +u32 afl_custom_post_trim(u8 success); +``` + +Python: +```python +def init(seed): + pass + +def fuzz(buf, add_buf, max_size): + return mutated_out + +def pre_save(buf): + return out_buf + +def init_trim(buf): + return cnt + +def trim(): + return out_buf + +def post_trim(success): + return next_index +``` + +### Custom Mutation + +- `init` (optional): + + This method is called when AFL++ starts up and is used to seed RNG. + +- `fuzz` (required): + + This method performs custom mutations on a given input. It also accepts an + additional test case. + +- `pre_save` (optional): + + For some cases, the format of the mutated data returned from the custom + mutator is not suitable to directly execute the target with this input. + For example, when using libprotobuf-mutator, the data returned is in a + protobuf format which corresponds to a given grammar. In order to execute + the target, the protobuf data must be converted to the plain-text format expected by the target. In such scenarios, the user can define the + `pre_save` function. This function is then transforms the data into the + format expected by the API before executing the target. + + +### Trimming Support + +The generic trimming routines implemented in AFL++ can easily destroy the +structure of complex formats, possibly leading to a point where you have a lot +of test cases in the queue that your Python module cannot process anymore but +your target application still accepts. This is especially the case when your +target can process a part of the input (causing coverage) and then errors out +on the remaining input. + +In such cases, it makes sense to implement a custom trimming routine. The API +consists of multiple methods because after each trimming step, we have to go +back into the C code to check if the coverage bitmap is still the same for the +trimmed input. Here's a quick API description: + +- `init_trim` (optional): + + This method is called at the start of each trimming operation and receives + the initial buffer. It should return the amount of iteration steps possible + on this input (e.g. if your input has n elements and you want to remove them + one by one, return n, if you do a binary search, return log(n), and so on). + + If your trimming algorithm doesn't allow you to determine the amount of + (remaining) steps easily (esp. while running), then you can alternatively + return 1 here and always return 0 in `post_trim` until you are finished and + no steps remain. In that case, returning 1 in `post_trim` will end the + trimming routine. The whole current index/max iterations stuff is only used + to show progress. + +- `trim` (optional) + + This method is called for each trimming operation. It doesn't have any + arguments because we already have the initial buffer from `init_trim` and we + can memorize the current state in global variables. This can also save + reparsing steps for each iteration. It should return the trimmed input + buffer, where the returned data must not exceed the initial input data in + length. Returning anything that is larger than the original data (passed to + `init_trim`) will result in a fatal abort of AFL++. + +- `post_trim` (optional) + + This method is called after each trim operation to inform you if your + trimming step was successful or not (in terms of coverage). If you receive + a failure here, you should reset your input to the last known good state. + In any case, this method must return the next trim iteration index (from 0 + to the maximum amount of steps you returned in `init_trim`). + +Omitting any of three methods will cause the trimming to be disabled and trigger +a fallback to the builtin default trimming routine. + +### Environment Variables + +Optionally, the following environment variables are supported: + +- `AFL_PYTHON_ONLY` + + Disable all other mutation stages. This can prevent broken testcases + (those that your Python module can't work with anymore) to fill up your + queue. Best combined with a custom trimming routine (see below) because + trimming can cause the same test breakage like havoc and splice. + +- `AFL_DEBUG` + + When combined with `AFL_NO_UI`, this causes the C trimming code to emit additional messages about the performance and actions of your custom trimmer. Use this to see if it works :) + +## 3) Usage + +### Prerequisite + +For Python mutator, the python 3 or 2 development package is required. On +Debian/Ubuntu/Kali this can be done: + +```bash +sudo apt install python3-dev +# or +sudo apt install python-dev +``` + +Then, AFL++ can be compiled with Python support. The AFL++ Makefile detects +Python 2 and 3 through `python-config` if it is in the PATH and compiles +`afl-fuzz` with the feature if available. + +Note: for some distributions, you might also need the package `python[23]-apt`. +In case your setup is different, set the necessary variables like this: +`PYTHON_INCLUDE=/path/to/python/include LDFLAGS=-L/path/to/python/lib make`. + +### Custom Mutator Preparation + +For C/C++ mutator, the source code must be compiled as a shared object: +```bash +gcc -shared -Wall -O3 example.c -o example.so +``` + +### Run + +C/C++ +```bash +export AFL_CUSTOM_MUTATOR_LIBRARY=/full/path/to/example.so +afl-fuzz /path/to/program +``` + +Python +```bash +export PYTHONPATH=`dirname /full/path/to/example.py` +export AFL_PYTHON_MODULE=example +afl-fuzz /path/to/program +``` + +## 4) Example + +Please see [example.c](../examples/custom_mutators/example.c) and +[example.py](../examples/custom_mutators/example.py) + +## 5) Other Resources + +- AFL libprotobuf mutator + - [bruce30262/libprotobuf-mutator_fuzzing_learning](https://github.com/bruce30262/libprotobuf-mutator_fuzzing_learning/tree/master/4_libprotobuf_aflpp_custom_mutator) + - [thebabush/afl-libprotobuf-mutator](https://github.com/thebabush/afl-libprotobuf-mutator) +- [XML Fuzzing@NullCon 2017](https://www.agarri.fr/docs/XML_Fuzzing-NullCon2017-PUBLIC.pdf) + - [A bug detected by AFL + XML-aware mutators](https://bugs.chromium.org/p/chromium/issues/detail?id=930663) diff --git a/docs/env_variables.md b/docs/env_variables.md index 83f5b7c0..d1cf6977 100644 --- a/docs/env_variables.md +++ b/docs/env_variables.md @@ -231,7 +231,7 @@ checks or alter some of the more exotic semantics of the tool: performed with the custom mutator. This feature allows to configure custom mutators which can be very helpful, e.g. fuzzing XML or other highly flexible structured input. - Please see [custom_mutator.md](custom_mutator.md). + Please see [custom_mutators.md](custom_mutators.md). - AFL_FAST_CAL keeps the calibration stage about 2.5x faster (albeit less precise), which can help when starting a session against a slow target. diff --git a/examples/custom_mutators/README.md b/examples/custom_mutators/README.md index 6da288ab..ce49436e 100644 --- a/examples/custom_mutators/README.md +++ b/examples/custom_mutators/README.md @@ -1,4 +1,22 @@ -# A simple example for AFL_CUSTOM_MUTATOR_LIBRARY +# Examples for the custom mutator -This is a simple example for the AFL_CUSTOM_MUTATOR_LIBRARY feature. -For more information see [docs/custom_mutator.md](../docs/custom_mutator.md) +These are example and helper files for the custom mutator feature. +See [docs/python_mutators.md](../docs/custom_mutators.md) for more information + +Note that if you compile with python3.7 you must use python3 scripts, and if +you use pyton2.7 to compile python2 scripts! + +example.c - this is a simple example written in C and should be compiled to a + shared library + +example.py - this is the template you can use, the functions are there but they + are empty + +simple-chunk-replace.py - this is a simple example where chunks are replaced + +common.py - this can be used for common functions and helpers. + the examples do not use this though. But you can :) + +wrapper_afl_min.py - mutation of XML documents, loads XmlMutatorMin.py + +XmlMutatorMin.py - module for XML mutation diff --git a/examples/custom_mutators/XmlMutatorMin.py b/examples/custom_mutators/XmlMutatorMin.py new file mode 100644 index 00000000..4c80a2ba --- /dev/null +++ b/examples/custom_mutators/XmlMutatorMin.py @@ -0,0 +1,332 @@ +#!/usr/bin/python + +""" Mutation of XML documents, should be called from one of its wrappers (CLI, AFL, ...) """ + +from __future__ import print_function +from copy import deepcopy +from lxml import etree as ET +import random, re, io + + +########################### +# The XmlMutatorMin class # +########################### + +class XmlMutatorMin: + + """ + Optionals parameters: + seed Seed used by the PRNG (default: "RANDOM") + verbose Verbosity (default: False) + """ + + def __init__(self, seed="RANDOM", verbose=False): + + """ Initialize seed, database and mutators """ + + # Verbosity + self.verbose = verbose + + # Initialize PRNG + self.seed = str(seed) + if self.seed == "RANDOM": + random.seed() + else: + if self.verbose: + print("Static seed '%s'" % self.seed) + random.seed(self.seed) + + # Initialize input and output documents + self.input_tree = None + self.tree = None + + # High-level mutators (no database needed) + hl_mutators_delete = ["del_node_and_children", "del_node_but_children", "del_attribute", "del_content"] # Delete items + hl_mutators_fuzz = ["fuzz_attribute"] # Randomly change attribute values + + # Exposed mutators + self.hl_mutators_all = hl_mutators_fuzz + hl_mutators_delete + + def __parse_xml(self, xml): + + """ Parse an XML string. Basic wrapper around lxml.parse() """ + + try: + # Function parse() takes care of comments / DTD / processing instructions / ... + tree = ET.parse(io.BytesIO(xml)) + except ET.ParseError: + raise RuntimeError("XML isn't well-formed!") + except LookupError as e: + raise RuntimeError(e) + + # Return a document wrapper + return tree + + def __exec_among(self, module, functions, min_times, max_times): + + """ Randomly execute $functions between $min and $max times """ + + for i in xrange(random.randint(min_times, max_times)): + # Function names are mangled because they are "private" + getattr(module, "_XmlMutatorMin__" + random.choice(functions))() + + def __serialize_xml(self, tree): + + """ Serialize a XML document. Basic wrapper around lxml.tostring() """ + + return ET.tostring(tree, with_tail=False, xml_declaration=True, encoding=tree.docinfo.encoding) + + def __ver(self, version): + + """ Helper for displaying lxml version numbers """ + + return ".".join(map(str, version)) + + def reset(self): + + """ Reset the mutator """ + + self.tree = deepcopy(self.input_tree) + + def init_from_string(self, input_string): + + """ Initialize the mutator from a XML string """ + + # Get a pointer to the top-element + self.input_tree = self.__parse_xml(input_string) + + # Get a working copy + self.tree = deepcopy(self.input_tree) + + def save_to_string(self): + + """ Return the current XML document as UTF-8 string """ + + # Return a text version of the tree + return self.__serialize_xml(self.tree) + + def __pick_element(self, exclude_root_node=False): + + """ Pick a random element from the current document """ + + # Get a list of all elements, but nodes like PI and comments + elems = list(self.tree.getroot().iter(tag=ET.Element)) + + # Is the root node excluded? + if exclude_root_node: + start = 1 + else: + start = 0 + + # Pick a random element + try: + elem_id = random.randint(start, len(elems) - 1) + elem = elems[elem_id] + except ValueError: + # Should only occurs if "exclude_root_node = True" + return (None, None) + + return (elem_id, elem) + + def __fuzz_attribute(self): + + """ Fuzz (part of) an attribute value """ + + # Select a node to modify + (rand_elem_id, rand_elem) = self.__pick_element() + + # Get all the attributes + attribs = rand_elem.keys() + + # Is there attributes? + if len(attribs) < 1: + if self.verbose: + print("No attribute: can't replace!") + return + + # Pick a random attribute + rand_attrib_id = random.randint(0, len(attribs) - 1) + rand_attrib = attribs[rand_attrib_id] + + # We have the attribute to modify + # Get its value + attrib_value = rand_elem.get(rand_attrib) + # print("- Value: " + attrib_value) + + # Should we work on the whole value? + func_call = "(?P[a-zA-Z:\-]+)\((?P.*?)\)" + p = re.compile(func_call) + l = p.findall(attrib_value) + if random.choice((True, False)) and l: + # Randomly pick one the function calls + (func, args) = random.choice(l) + # Split by "," and randomly pick one of the arguments + value = random.choice(args.split(',')) + # Remove superfluous characters + unclean_value = value + value = value.strip(" ").strip("'") + # print("Selected argument: [%s]" % value) + else: + value = attrib_value + + # For each type, define some possible replacement values + choices_number = ( \ + "0", \ + "11111", \ + "-128", \ + "2", \ + "-1", \ + "1/3", \ + "42/0", \ + "1094861636 idiv 1.0", \ + "-1123329771506872 idiv 3.8", \ + "17=$numericRTF", \ + str(3 + random.randrange(0, 100)), \ + ) + + choices_letter = ( \ + "P" * (25 * random.randrange(1, 100)), \ + "%s%s%s%s%s%s", \ + "foobar", \ + ) + + choices_alnum = ( \ + "Abc123", \ + "020F0302020204030204", \ + "020F0302020204030204" * (random.randrange(5, 20)), \ + ) + + # Fuzz the value + if random.choice((True,False)) and value == "": + + # Empty + new_value = value + + elif random.choice((True,False)) and value.isdigit(): + + # Numbers + new_value = random.choice(choices_number) + + elif random.choice((True,False)) and value.isalpha(): + + # Letters + new_value = random.choice(choices_letter) + + elif random.choice((True,False)) and value.isalnum(): + + # Alphanumeric + new_value = random.choice(choices_alnum) + + else: + + # Default type + new_value = random.choice(choices_alnum + choices_letter + choices_number) + + # If we worked on a substring, apply changes to the whole string + if value != attrib_value: + # No ' around empty values + if new_value != "" and value != "": + new_value = "'" + new_value + "'" + # Apply changes + new_value = attrib_value.replace(unclean_value, new_value) + + # Log something + if self.verbose: + print("Fuzzing attribute #%i '%s' of tag #%i '%s'" % (rand_attrib_id, rand_attrib, rand_elem_id, rand_elem.tag)) + + # Modify the attribute + rand_elem.set(rand_attrib, new_value.decode("utf-8")) + + def __del_node_and_children(self): + + """ High-level minimizing mutator + Delete a random node and its children (i.e. delete a random tree) """ + + self.__del_node(True) + + def __del_node_but_children(self): + + """ High-level minimizing mutator + Delete a random node but its children (i.e. link them to the parent of the deleted node) """ + + self.__del_node(False) + + def __del_node(self, delete_children): + + """ Called by the __del_node_* mutators """ + + # Select a node to modify (but the root one) + (rand_elem_id, rand_elem) = self.__pick_element(exclude_root_node=True) + + # If the document includes only a top-level element + # Then we can't pick a element (given that "exclude_root_node = True") + + # Is the document deep enough? + if rand_elem is None: + if self.verbose: + print("Can't delete a node: document not deep enough!") + return + + # Log something + if self.verbose: + but_or_and = "and" if delete_children else "but" + print("Deleting tag #%i '%s' %s its children" % (rand_elem_id, rand_elem.tag, but_or_and)) + + if delete_children is False: + # Link children of the random (soon to be deleted) node to its parent + for child in rand_elem: + rand_elem.getparent().append(child) + + # Remove the node + rand_elem.getparent().remove(rand_elem) + + def __del_content(self): + + """ High-level minimizing mutator + Delete the attributes and children of a random node """ + + # Select a node to modify + (rand_elem_id, rand_elem) = self.__pick_element() + + # Log something + if self.verbose: + print("Reseting tag #%i '%s'" % (rand_elem_id, rand_elem.tag)) + + # Reset the node + rand_elem.clear() + + def __del_attribute(self): + + """ High-level minimizing mutator + Delete a random attribute from a random node """ + + # Select a node to modify + (rand_elem_id, rand_elem) = self.__pick_element() + + # Get all the attributes + attribs = rand_elem.keys() + + # Is there attributes? + if len(attribs) < 1: + if self.verbose: + print("No attribute: can't delete!") + return + + # Pick a random attribute + rand_attrib_id = random.randint(0, len(attribs) - 1) + rand_attrib = attribs[rand_attrib_id] + + # Log something + if self.verbose: + print("Deleting attribute #%i '%s' of tag #%i '%s'" % (rand_attrib_id, rand_attrib, rand_elem_id, rand_elem.tag)) + + # Delete the attribute + rand_elem.attrib.pop(rand_attrib) + + def mutate(self, min=1, max=5): + + """ Execute some high-level mutators between $min and $max times, then some medium-level ones """ + + # High-level mutation + self.__exec_among(self, self.hl_mutators_all, min, max) + diff --git a/examples/custom_mutators/common.py b/examples/custom_mutators/common.py new file mode 100644 index 00000000..9a1ef0a3 --- /dev/null +++ b/examples/custom_mutators/common.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python +# encoding: utf-8 +''' +Module containing functions shared between multiple AFL modules + +@author: Christian Holler (:decoder) + +@license: + +This Source Code Form is subject to the terms of the Mozilla Public +License, v. 2.0. If a copy of the MPL was not distributed with this +file, You can obtain one at http://mozilla.org/MPL/2.0/. + +@contact: choller@mozilla.com +''' + +from __future__ import print_function +import random +import os +import re + + +def randel(l): + if not l: + return None + return l[random.randint(0, len(l)-1)] + + +def randel_pop(l): + if not l: + return None + return l.pop(random.randint(0, len(l)-1)) + + +def write_exc_example(data, exc): + exc_name = re.sub(r'[^a-zA-Z0-9]', '_', repr(exc)) + + if not os.path.exists(exc_name): + with open(exc_name, 'w') as f: + f.write(data) diff --git a/examples/custom_mutators/example.c b/examples/custom_mutators/example.c new file mode 100644 index 00000000..63e4d6da --- /dev/null +++ b/examples/custom_mutators/example.c @@ -0,0 +1,177 @@ +/* + New Custom Mutator for AFL++ + Written by Khaled Yakdan + Andrea Fioraldi + Shengtuo Hu +*/ + +#include +#include +#include + +static const char *commands[] = { + + "GET", + "PUT", + "DEL", + +}; + +static size_t data_size = 100; + +void afl_custom_init(unsigned int seed) { + + srand(seed); + +} + +/** + * Perform custom mutations on a given input + * + * (Optional for now. Required in the future) + * + * @param[in] buf Input data to be mutated + * @param[in] buf_size Size of input data + * @param[in] add_buf Buffer containing the additional test case + * @param[in] add_buf_size Size of the additional test case + * @param[out] mutated_out Buffer to store the mutated input + * @param[in] max_size Maximum size of the mutated output. The mutation must not + * produce data larger than max_size. + * @return Size of the mutated output. + */ +size_t afl_custom_fuzz(uint8_t *buf, size_t buf_size, + uint8_t *add_buf,size_t add_buf_size, // add_buf can be NULL + uint8_t *mutated_out, size_t max_size) { + + // Make sure that the packet size does not exceed the maximum size expected by + // the fuzzer + size_t mutated_size = data_size <= max_size ? data_size : max_size; + + // Randomly select a command string to add as a header to the packet + memcpy(mutated_out, commands[rand() % 3], 3); + + // Mutate the payload of the packet + for (int i = 3; i < mutated_size; i++) { + + mutated_out[i] = (data[i] + rand() % 10) & 0xff; + + } + + return mutated_size; + +} + +/** + * A post-processing function to use right before AFL writes the test case to + * disk in order to execute the target. + * + * (Optional) If this functionality is not needed, simply don't define this + * function. + * + * @param[in] buf Buffer containing the test case to be executed + * @param[in] buf_size Size of the test case + * @param[out] out_buf Pointer to the buffer containing the test case after + * processing. External library should allocate memory for out_buf. AFL++ + * will release the memory after saving the test case. + * @return Size of the output buffer after processing + */ +size_t afl_custom_pre_save(uint8_t *buf, size_t buf_size, uint8_t **out_buf) { + + size_t out_buf_size; + + out_buf_size = buf_size; + + // External mutator should allocate memory for `out_buf` + *out_buf = malloc(out_buf_size); + memcpy(*out_buf, buf, out_buf_size); + + return out_buf_size; + +} + +uint8_t *trim_buf; +size_t trim_buf_size +int trimmming_steps; +int cur_step; + +/** + * This method is called at the start of each trimming operation and receives + * the initial buffer. It should return the amount of iteration steps possible + * on this input (e.g. if your input has n elements and you want to remove + * them one by one, return n, if you do a binary search, return log(n), + * and so on...). + * + * If your trimming algorithm doesn't allow you to determine the amount of + * (remaining) steps easily (esp. while running), then you can alternatively + * return 1 here and always return 0 in post_trim until you are finished and + * no steps remain. In that case, returning 1 in post_trim will end the + * trimming routine. The whole current index/max iterations stuff is only used + * to show progress. + * + * (Optional) + * + * @param buf Buffer containing the test case + * @param buf_size Size of the test case + * @return The amount of possible iteration steps to trim the input + */ +int afl_custom_init_trim(uint8_t *buf, size_t buf_size) { + + // We simply trim once + trimmming_steps = 1; + + cur_step = 0; + trim_buf = buf; + trim_buf_size = buf_size; + + return trimmming_steps; + +} + +/** + * This method is called for each trimming operation. It doesn't have any + * arguments because we already have the initial buffer from init_trim and we + * can memorize the current state in global variables. This can also save + * reparsing steps for each iteration. It should return the trimmed input + * buffer, where the returned data must not exceed the initial input data in + * length. Returning anything that is larger than the original data (passed + * to init_trim) will result in a fatal abort of AFLFuzz. + * + * (Optional) + * + * @param[out] out_buf Pointer to the buffer containing the trimmed test case. + * External library should allocate memory for out_buf. AFL++ will release + * the memory after saving the test case. + * @param[out] out_buf_size Pointer to the size of the trimmed test case + */ +void afl_custom_trim(uint8_t **out_buf, size_t* out_buf_size) { + + *out_buf_size = trim_buf_size - 1; + + // External mutator should allocate memory for `out_buf` + *out_buf = malloc(*out_buf_size); + // Remove the last byte of the trimming input + memcpy(*out_buf, trim_buf, *out_buf_size); + +} + +/** + * This method is called after each trim operation to inform you if your + * trimming step was successful or not (in terms of coverage). If you receive + * a failure here, you should reset your input to the last known good state. + * + * (Optional) + * + * @param success Indicates if the last trim operation was successful. + * @return The next trim iteration index (from 0 to the maximum amount of + * steps returned in init_trim) + */ +int afl_custom_post_trim(int success) { + + if (success) { + ++cur_step; + return cur_step; + } + + return trimmming_steps; + +} diff --git a/examples/custom_mutators/example.py b/examples/custom_mutators/example.py new file mode 100644 index 00000000..a68f2ee5 --- /dev/null +++ b/examples/custom_mutators/example.py @@ -0,0 +1,122 @@ +#!/usr/bin/env python +# encoding: utf-8 +''' +Example Python Module for AFLFuzz + +@author: Christian Holler (:decoder) + +@license: + +This Source Code Form is subject to the terms of the Mozilla Public +License, v. 2.0. If a copy of the MPL was not distributed with this +file, You can obtain one at http://mozilla.org/MPL/2.0/. + +@contact: choller@mozilla.com +''' + +import random + + +def init(seed): + ''' + Called once when AFLFuzz starts up. Used to seed our RNG. + + @type seed: int + @param seed: A 32-bit random value + ''' + random.seed(seed) + + +def fuzz(buf, add_buf, max_size): + ''' + Called per fuzzing iteration. + + @type buf: bytearray + @param buf: The buffer that should be mutated. + + @type add_buf: bytearray + @param add_buf: A second buffer that can be used as mutation source. + + @type max_size: int + @param max_size: Maximum size of the mutated output. The mutation must not + produce data larger than max_size. + + @rtype: bytearray + @return: A new bytearray containing the mutated data + ''' + ret = bytearray(buf) + # Do something interesting with ret + + return ret + +# Uncomment and implement the following methods if you want to use a custom +# trimming algorithm. See also the documentation for a better API description. + +# def init_trim(buf): +# ''' +# Called per trimming iteration. +# +# @type buf: bytearray +# @param buf: The buffer that should be trimmed. +# +# @rtype: int +# @return: The maximum number of trimming steps. +# ''' +# global ... +# +# # Initialize global variables +# +# # Figure out how many trimming steps are possible. +# # If this is not possible for your trimming, you can +# # return 1 instead and always return 0 in post_trim +# # until you are done (then you return 1). +# +# return steps +# +# def trim(): +# ''' +# Called per trimming iteration. +# +# @rtype: bytearray +# @return: A new bytearray containing the trimmed data. +# ''' +# global ... +# +# # Implement the actual trimming here +# +# return bytearray(...) +# +# def post_trim(success): +# ''' +# Called after each trimming operation. +# +# @type success: bool +# @param success: Indicates if the last trim operation was successful. +# +# @rtype: int +# @return: The next trim index (0 to max number of steps) where max +# number of steps indicates the trimming is done. +# ''' +# global ... +# +# if not success: +# # Restore last known successful input, determine next index +# else: +# # Just determine the next index, based on what was successfully +# # removed in the last step +# +# return next_index +# +# def pre_save(buf): +# ''' +# Called just before the execution to write the test case in the format +# expected by the target +# +# @type buf: bytearray +# @param buf: The buffer containing the test case to be executed +# +# @rtype: bytearray +# @return: The buffer containing the test case after +# ''' +# return buf +# diff --git a/examples/custom_mutators/simple-chunk-replace.py b/examples/custom_mutators/simple-chunk-replace.py new file mode 100644 index 00000000..df2f4ca7 --- /dev/null +++ b/examples/custom_mutators/simple-chunk-replace.py @@ -0,0 +1,64 @@ +#!/usr/bin/env python +# encoding: utf-8 +''' +Simple Chunk Cross-Over Replacement Module for AFLFuzz + +@author: Christian Holler (:decoder) + +@license: + +This Source Code Form is subject to the terms of the Mozilla Public +License, v. 2.0. If a copy of the MPL was not distributed with this +file, You can obtain one at http://mozilla.org/MPL/2.0/. + +@contact: choller@mozilla.com +''' + +import random + + +def init(seed): + ''' + Called once when AFLFuzz starts up. Used to seed our RNG. + + @type seed: int + @param seed: A 32-bit random value + ''' + # Seed our RNG + random.seed(seed) + + +def fuzz(buf, add_buf, max_size): + ''' + Called per fuzzing iteration. + + @type buf: bytearray + @param buf: The buffer that should be mutated. + + @type add_buf: bytearray + @param add_buf: A second buffer that can be used as mutation source. + + @type max_size: int + @param max_size: Maximum size of the mutated output. The mutation must not + produce data larger than max_size. + + @rtype: bytearray + @return: A new bytearray containing the mutated data + ''' + # Make a copy of our input buffer for returning + ret = bytearray(buf) + + # Take a random fragment length between 2 and 32 (or less if add_buf is shorter) + fragment_len = random.randint(1, min(len(add_buf), 32)) + + # Determine a random source index where to take the data chunk from + rand_src_idx = random.randint(0, len(add_buf) - fragment_len) + + # Determine a random destination index where to put the data chunk + rand_dst_idx = random.randint(0, len(buf)) + + # Make the chunk replacement + ret[rand_dst_idx:rand_dst_idx + fragment_len] = add_buf[rand_src_idx:rand_src_idx + fragment_len] + + # Return data + return ret diff --git a/examples/custom_mutators/simple_mutator.c b/examples/custom_mutators/simple_mutator.c deleted file mode 100644 index bf655679..00000000 --- a/examples/custom_mutators/simple_mutator.c +++ /dev/null @@ -1,49 +0,0 @@ -/* - Simple Custom Mutator for AFL - - Written by Khaled Yakdan - - This a simple mutator that assumes that the generates messages starting with - one of the three strings GET, PUT, or DEL followed by a payload. The mutator - randomly selects a commend and mutates the payload of the seed provided as - input. -*/ - -#include -#include -#include - -static const char *commands[] = { - - "GET", - "PUT", - "DEL", - -}; - -static size_t data_size = 100; - -size_t afl_custom_mutator(uint8_t *data, size_t size, uint8_t *mutated_out, - size_t max_size, unsigned int seed) { - - // Seed the PRNG - srand(seed); - - // Make sure that the packet size does not exceed the maximum size expected by - // the fuzzer - size_t mutated_size = data_size <= max_size ? data_size : max_size; - - // Randomly select a command string to add as a header to the packet - memcpy(mutated_out, commands[rand() % 3], 3); - - // Mutate the payload of the packet - for (int i = 3; i < mutated_size; i++) { - - mutated_out[i] = (data[i] + rand() % 10) & 0xff; - - } - - return mutated_size; - -} - diff --git a/examples/custom_mutators/wrapper_afl_min.py b/examples/custom_mutators/wrapper_afl_min.py new file mode 100644 index 00000000..ecb03b55 --- /dev/null +++ b/examples/custom_mutators/wrapper_afl_min.py @@ -0,0 +1,118 @@ +#!/usr/bin/env python + +from XmlMutatorMin import XmlMutatorMin + +# Default settings (production mode) + +__mutator__ = None +__seed__ = "RANDOM" +__log__ = False +__log_file__ = "wrapper.log" + + +# AFL functions +def log(text): + """ + Logger + """ + + global __seed__ + global __log__ + global __log_file__ + + if __log__: + with open(__log_file__, "a") as logf: + logf.write("[%s] %s\n" % (__seed__, text)) + + +def init(seed): + """ + Called once when AFL starts up. Seed is used to identify the AFL instance in log files + """ + + global __mutator__ + global __seed__ + + # Get the seed + __seed__ = seed + + # Create a global mutation class + try: + __mutator__ = XmlMutatorMin(__seed__, verbose=__log__) + log("init(): Mutator created") + except RuntimeError as e: + log("init(): Can't create mutator: %s" % e.message) + + +def fuzz(buf, add_buf, max_size): + """ + Called for each fuzzing iteration. + """ + + global __mutator__ + + # Do we have a working mutator object? + if __mutator__ is None: + log("fuzz(): Can't fuzz, no mutator available") + return buf + + # Try to use the AFL buffer + via_buffer = True + + # Interpret the AFL buffer (an array of bytes) as a string + if via_buffer: + try: + buf_str = str(buf) + log("fuzz(): AFL buffer converted to a string") + except Exception: + via_buffer = False + log("fuzz(): Can't convert AFL buffer to a string") + + # Load XML from the AFL string + if via_buffer: + try: + __mutator__.init_from_string(buf_str) + log("fuzz(): Mutator successfully initialized with AFL buffer (%d bytes)" % len(buf_str)) + except Exception: + via_buffer = False + log("fuzz(): Can't initialize mutator with AFL buffer") + + # If init from AFL buffer wasn't succesful + if not via_buffer: + log("fuzz(): Returning unmodified AFL buffer") + return buf + + # Sucessful initialization -> mutate + try: + __mutator__.mutate(max=5) + log("fuzz(): Input mutated") + except Exception: + log("fuzz(): Can't mutate input => returning buf") + return buf + + # Convert mutated data to a array of bytes + try: + data = bytearray(__mutator__.save_to_string()) + log("fuzz(): Mutated data converted as bytes") + except Exception: + log("fuzz(): Can't convert mutated data to bytes => returning buf") + return buf + + # Everything went fine, returning mutated content + log("fuzz(): Returning %d bytes" % len(data)) + return data + + +# Main (for debug) +if __name__ == '__main__': + + __log__ = True + __log_file__ = "/dev/stdout" + __seed__ = "RANDOM" + + init(__seed__) + + in_1 = bytearray("ffffzzzzzzzzzzzz") + in_2 = bytearray("") + out = fuzz(in_1, in_2) + print(out) diff --git a/examples/python_mutators/README b/examples/python_mutators/README deleted file mode 100644 index 8e378405..00000000 --- a/examples/python_mutators/README +++ /dev/null @@ -1,18 +0,0 @@ -These are example and helper files for the AFL_PYTHON_MODULE feature. -See [docs/python_mutators.md](../docs/python_mutators.md) for more information - -Note that if you compile with python3.7 you must use python3 scripts, and if -you use pyton2.7 to compile python2 scripts! - - -example.py - this is the template you can use, the functions are there - but they are empty - -simple-chunk-replace.py - this is a simple example where chunks are replaced - -common.py - this can be used for common functions and helpers. - the examples do not use this though. But you can :) - -wrapper_afl_min.py - mutation of XML documents, loads XmlMutatorMin.py - -XmlMutatorMin.py - module for XML mutation diff --git a/examples/python_mutators/XmlMutatorMin.py b/examples/python_mutators/XmlMutatorMin.py deleted file mode 100644 index 058b7e61..00000000 --- a/examples/python_mutators/XmlMutatorMin.py +++ /dev/null @@ -1,331 +0,0 @@ -#!/usr/bin/python - -""" Mutation of XML documents, should be called from one of its wrappers (CLI, AFL, ...) """ - -from __future__ import print_function -from copy import deepcopy -from lxml import etree as ET -import random, re, io - -########################### -# The XmlMutatorMin class # -########################### - -class XmlMutatorMin: - - """ - Optionals parameters: - seed Seed used by the PRNG (default: "RANDOM") - verbose Verbosity (default: False) - """ - - def __init__(self, seed="RANDOM", verbose=False): - - """ Initialize seed, database and mutators """ - - # Verbosity - self.verbose = verbose - - # Initialize PRNG - self.seed = str(seed) - if self.seed == "RANDOM": - random.seed() - else: - if self.verbose: - print("Static seed '%s'" % self.seed) - random.seed(self.seed) - - # Initialize input and output documents - self.input_tree = None - self.tree = None - - # High-level mutators (no database needed) - hl_mutators_delete = [ "del_node_and_children", "del_node_but_children", "del_attribute", "del_content" ] # Delete items - hl_mutators_fuzz = ["fuzz_attribute"] # Randomly change attribute values - - # Exposed mutators - self.hl_mutators_all = hl_mutators_fuzz + hl_mutators_delete - - def __parse_xml (self, xml): - - """ Parse an XML string. Basic wrapper around lxml.parse() """ - - try: - # Function parse() takes care of comments / DTD / processing instructions / ... - tree = ET.parse(io.BytesIO(xml)) - except ET.ParseError: - raise RuntimeError("XML isn't well-formed!") - except LookupError as e: - raise RuntimeError(e) - - # Return a document wrapper - return tree - - def __exec_among (self, module, functions, min_times, max_times): - - """ Randomly execute $functions between $min and $max times """ - - for i in xrange (random.randint (min_times, max_times)): - # Function names are mangled because they are "private" - getattr (module, "_XmlMutatorMin__" + random.choice(functions)) () - - def __serialize_xml (self, tree): - - """ Serialize a XML document. Basic wrapper around lxml.tostring() """ - - return ET.tostring(tree, with_tail=False, xml_declaration=True, encoding=tree.docinfo.encoding) - - def __ver (self, version): - - """ Helper for displaying lxml version numbers """ - - return ".".join(map(str, version)) - - def reset (self): - - """ Reset the mutator """ - - self.tree = deepcopy(self.input_tree) - - def init_from_string (self, input_string): - - """ Initialize the mutator from a XML string """ - - # Get a pointer to the top-element - self.input_tree = self.__parse_xml(input_string) - - # Get a working copy - self.tree = deepcopy(self.input_tree) - - def save_to_string (self): - - """ Return the current XML document as UTF-8 string """ - - # Return a text version of the tree - return self.__serialize_xml(self.tree) - - def __pick_element (self, exclude_root_node = False): - - """ Pick a random element from the current document """ - - # Get a list of all elements, but nodes like PI and comments - elems = list(self.tree.getroot().iter(tag=ET.Element)) - - # Is the root node excluded? - if exclude_root_node: - start = 1 - else: - start = 0 - - # Pick a random element - try: - elem_id = random.randint (start, len(elems) - 1) - elem = elems[elem_id] - except ValueError: - # Should only occurs if "exclude_root_node = True" - return (None, None) - - return (elem_id, elem) - - def __fuzz_attribute (self): - - """ Fuzz (part of) an attribute value """ - - # Select a node to modify - (rand_elem_id, rand_elem) = self.__pick_element() - - # Get all the attributes - attribs = rand_elem.keys() - - # Is there attributes? - if len(attribs) < 1: - if self.verbose: - print("No attribute: can't replace!") - return - - # Pick a random attribute - rand_attrib_id = random.randint (0, len(attribs) - 1) - rand_attrib = attribs[rand_attrib_id] - - # We have the attribute to modify - # Get its value - attrib_value = rand_elem.get(rand_attrib); - # print("- Value: " + attrib_value) - - # Should we work on the whole value? - func_call = "(?P[a-zA-Z:\-]+)\((?P.*?)\)" - p = re.compile(func_call) - l = p.findall(attrib_value) - if random.choice((True,False)) and l: - # Randomly pick one the function calls - (func, args) = random.choice(l) - # Split by "," and randomly pick one of the arguments - value = random.choice(args.split(',')) - # Remove superfluous characters - unclean_value = value - value = value.strip(" ").strip("'") - # print("Selected argument: [%s]" % value) - else: - value = attrib_value - - # For each type, define some possible replacement values - choices_number = ( \ - "0", \ - "11111", \ - "-128", \ - "2", \ - "-1", \ - "1/3", \ - "42/0", \ - "1094861636 idiv 1.0", \ - "-1123329771506872 idiv 3.8", \ - "17=$numericRTF", \ - str(3 + random.randrange(0, 100)), \ - ) - - choices_letter = ( \ - "P" * (25 * random.randrange(1, 100)), \ - "%s%s%s%s%s%s", \ - "foobar", \ - ) - - choices_alnum = ( \ - "Abc123", \ - "020F0302020204030204", \ - "020F0302020204030204" * (random.randrange(5, 20)), \ - ) - - # Fuzz the value - if random.choice((True,False)) and value == "": - - # Empty - new_value = value - - elif random.choice((True,False)) and value.isdigit(): - - # Numbers - new_value = random.choice(choices_number) - - elif random.choice((True,False)) and value.isalpha(): - - # Letters - new_value = random.choice(choices_letter) - - elif random.choice((True,False)) and value.isalnum(): - - # Alphanumeric - new_value = random.choice(choices_alnum) - - else: - - # Default type - new_value = random.choice(choices_alnum + choices_letter + choices_number) - - # If we worked on a substring, apply changes to the whole string - if value != attrib_value: - # No ' around empty values - if new_value != "" and value != "": - new_value = "'" + new_value + "'" - # Apply changes - new_value = attrib_value.replace(unclean_value, new_value) - - # Log something - if self.verbose: - print("Fuzzing attribute #%i '%s' of tag #%i '%s'" % (rand_attrib_id, rand_attrib, rand_elem_id, rand_elem.tag)) - - # Modify the attribute - rand_elem.set(rand_attrib, new_value.decode("utf-8")) - - def __del_node_and_children (self): - - """ High-level minimizing mutator - Delete a random node and its children (i.e. delete a random tree) """ - - self.__del_node(True) - - def __del_node_but_children (self): - - """ High-level minimizing mutator - Delete a random node but its children (i.e. link them to the parent of the deleted node) """ - - self.__del_node(False) - - def __del_node (self, delete_children): - - """ Called by the __del_node_* mutators """ - - # Select a node to modify (but the root one) - (rand_elem_id, rand_elem) = self.__pick_element (exclude_root_node = True) - - # If the document includes only a top-level element - # Then we can't pick a element (given that "exclude_root_node = True") - - # Is the document deep enough? - if rand_elem is None: - if self.verbose: - print("Can't delete a node: document not deep enough!") - return - - # Log something - if self.verbose: - but_or_and = "and" if delete_children else "but" - print("Deleting tag #%i '%s' %s its children" % (rand_elem_id, rand_elem.tag, but_or_and)) - - if delete_children is False: - # Link children of the random (soon to be deleted) node to its parent - for child in rand_elem: - rand_elem.getparent().append(child) - - # Remove the node - rand_elem.getparent().remove(rand_elem) - - def __del_content (self): - - """ High-level minimizing mutator - Delete the attributes and children of a random node """ - - # Select a node to modify - (rand_elem_id, rand_elem) = self.__pick_element() - - # Log something - if self.verbose: - print("Reseting tag #%i '%s'" % (rand_elem_id, rand_elem.tag)) - - # Reset the node - rand_elem.clear() - - def __del_attribute (self): - - """ High-level minimizing mutator - Delete a random attribute from a random node """ - - # Select a node to modify - (rand_elem_id, rand_elem) = self.__pick_element() - - # Get all the attributes - attribs = rand_elem.keys() - - # Is there attributes? - if len(attribs) < 1: - if self.verbose: - print("No attribute: can't delete!") - return - - # Pick a random attribute - rand_attrib_id = random.randint (0, len(attribs) - 1) - rand_attrib = attribs[rand_attrib_id] - - # Log something - if self.verbose: - print("Deleting attribute #%i '%s' of tag #%i '%s'" % (rand_attrib_id, rand_attrib, rand_elem_id, rand_elem.tag)) - - # Delete the attribute - rand_elem.attrib.pop(rand_attrib) - - def mutate (self, min=1, max=5): - - """ Execute some high-level mutators between $min and $max times, then some medium-level ones """ - - # High-level mutation - self.__exec_among(self, self.hl_mutators_all, min, max) - diff --git a/examples/python_mutators/common.py b/examples/python_mutators/common.py deleted file mode 100644 index 28b8ee80..00000000 --- a/examples/python_mutators/common.py +++ /dev/null @@ -1,37 +0,0 @@ -#!/usr/bin/env python -# encoding: utf-8 -''' -Module containing functions shared between multiple AFL modules - -@author: Christian Holler (:decoder) - -@license: - -This Source Code Form is subject to the terms of the Mozilla Public -License, v. 2.0. If a copy of the MPL was not distributed with this -file, You can obtain one at http://mozilla.org/MPL/2.0/. - -@contact: choller@mozilla.com -''' - -from __future__ import print_function -import random -import os -import re - -def randel(l): - if not l: - return None - return l[random.randint(0,len(l)-1)] - -def randel_pop(l): - if not l: - return None - return l.pop(random.randint(0,len(l)-1)) - -def write_exc_example(data, exc): - exc_name = re.sub(r'[^a-zA-Z0-9]', '_', repr(exc)) - - if not os.path.exists(exc_name): - with open(exc_name, 'w') as f: - f.write(data) diff --git a/examples/python_mutators/example.py b/examples/python_mutators/example.py deleted file mode 100644 index d32a7eb2..00000000 --- a/examples/python_mutators/example.py +++ /dev/null @@ -1,103 +0,0 @@ -#!/usr/bin/env python -# encoding: utf-8 -''' -Example Python Module for AFLFuzz - -@author: Christian Holler (:decoder) - -@license: - -This Source Code Form is subject to the terms of the Mozilla Public -License, v. 2.0. If a copy of the MPL was not distributed with this -file, You can obtain one at http://mozilla.org/MPL/2.0/. - -@contact: choller@mozilla.com -''' - -import random - -def init(seed): - ''' - Called once when AFLFuzz starts up. Used to seed our RNG. - - @type seed: int - @param seed: A 32-bit random value - ''' - random.seed(seed) - return 0 - -def fuzz(buf, add_buf): - ''' - Called per fuzzing iteration. - - @type buf: bytearray - @param buf: The buffer that should be mutated. - - @type add_buf: bytearray - @param add_buf: A second buffer that can be used as mutation source. - - @rtype: bytearray - @return: A new bytearray containing the mutated data - ''' - ret = bytearray(buf) - # Do something interesting with ret - - return ret - -# Uncomment and implement the following methods if you want to use a custom -# trimming algorithm. See also the documentation for a better API description. - -# def init_trim(buf): -# ''' -# Called per trimming iteration. -# -# @type buf: bytearray -# @param buf: The buffer that should be trimmed. -# -# @rtype: int -# @return: The maximum number of trimming steps. -# ''' -# global ... -# -# # Initialize global variables -# -# # Figure out how many trimming steps are possible. -# # If this is not possible for your trimming, you can -# # return 1 instead and always return 0 in post_trim -# # until you are done (then you return 1). -# -# return steps -# -# def trim(): -# ''' -# Called per trimming iteration. -# -# @rtype: bytearray -# @return: A new bytearray containing the trimmed data. -# ''' -# global ... -# -# # Implement the actual trimming here -# -# return bytearray(...) -# -# def post_trim(success): -# ''' -# Called after each trimming operation. -# -# @type success: bool -# @param success: Indicates if the last trim operation was successful. -# -# @rtype: int -# @return: The next trim index (0 to max number of steps) where max -# number of steps indicates the trimming is done. -# ''' -# global ... -# -# if not success: -# # Restore last known successful input, determine next index -# else: -# # Just determine the next index, based on what was successfully -# # removed in the last step -# -# return next_index diff --git a/examples/python_mutators/simple-chunk-replace.py b/examples/python_mutators/simple-chunk-replace.py deleted file mode 100644 index 218dd4f8..00000000 --- a/examples/python_mutators/simple-chunk-replace.py +++ /dev/null @@ -1,59 +0,0 @@ -#!/usr/bin/env python -# encoding: utf-8 -''' -Simple Chunk Cross-Over Replacement Module for AFLFuzz - -@author: Christian Holler (:decoder) - -@license: - -This Source Code Form is subject to the terms of the Mozilla Public -License, v. 2.0. If a copy of the MPL was not distributed with this -file, You can obtain one at http://mozilla.org/MPL/2.0/. - -@contact: choller@mozilla.com -''' - -import random - -def init(seed): - ''' - Called once when AFLFuzz starts up. Used to seed our RNG. - - @type seed: int - @param seed: A 32-bit random value - ''' - # Seed our RNG - random.seed(seed) - return 0 - -def fuzz(buf, add_buf): - ''' - Called per fuzzing iteration. - - @type buf: bytearray - @param buf: The buffer that should be mutated. - - @type add_buf: bytearray - @param add_buf: A second buffer that can be used as mutation source. - - @rtype: bytearray - @return: A new bytearray containing the mutated data - ''' - # Make a copy of our input buffer for returning - ret = bytearray(buf) - - # Take a random fragment length between 2 and 32 (or less if add_buf is shorter) - fragment_len = random.randint(1, min(len(add_buf), 32)) - - # Determine a random source index where to take the data chunk from - rand_src_idx = random.randint(0, len(add_buf) - fragment_len) - - # Determine a random destination index where to put the data chunk - rand_dst_idx = random.randint(0, len(buf)) - - # Make the chunk replacement - ret[rand_dst_idx:rand_dst_idx + fragment_len] = add_buf[rand_src_idx:rand_src_idx + fragment_len] - - # Return data - return ret diff --git a/examples/python_mutators/wrapper_afl_min.py b/examples/python_mutators/wrapper_afl_min.py deleted file mode 100644 index df09b40a..00000000 --- a/examples/python_mutators/wrapper_afl_min.py +++ /dev/null @@ -1,117 +0,0 @@ -#!/usr/bin/env python - -from XmlMutatorMin import XmlMutatorMin - -# Default settings (production mode) - -__mutator__ = None -__seed__ = "RANDOM" -__log__ = False -__log_file__ = "wrapper.log" - -# AFL functions - -def log(text): - """ - Logger - """ - - global __seed__ - global __log__ - global __log_file__ - - if __log__: - with open(__log_file__, "a") as logf: - logf.write("[%s] %s\n" % (__seed__, text)) - -def init(seed): - """ - Called once when AFL starts up. Seed is used to identify the AFL instance in log files - """ - - global __mutator__ - global __seed__ - - # Get the seed - __seed__ = seed - - # Create a global mutation class - try: - __mutator__ = XmlMutatorMin(__seed__, verbose=__log__) - log("init(): Mutator created") - except RuntimeError as e: - log("init(): Can't create mutator: %s" % e.message) - -def fuzz(buf, add_buf): - """ - Called for each fuzzing iteration. - """ - - global __mutator__ - - # Do we have a working mutator object? - if __mutator__ is None: - log("fuzz(): Can't fuzz, no mutator available") - return buf - - # Try to use the AFL buffer - via_buffer = True - - # Interpret the AFL buffer (an array of bytes) as a string - if via_buffer: - try: - buf_str = str(buf) - log("fuzz(): AFL buffer converted to a string") - except: - via_buffer = False - log("fuzz(): Can't convert AFL buffer to a string") - - # Load XML from the AFL string - if via_buffer: - try: - __mutator__.init_from_string(buf_str) - log("fuzz(): Mutator successfully initialized with AFL buffer (%d bytes)" % len(buf_str)) - except: - via_buffer = False - log("fuzz(): Can't initialize mutator with AFL buffer") - - # If init from AFL buffer wasn't succesful - if not via_buffer: - log("fuzz(): Returning unmodified AFL buffer") - return buf - - # Sucessful initialization -> mutate - try: - __mutator__.mutate(max=5) - log("fuzz(): Input mutated") - except: - log("fuzz(): Can't mutate input => returning buf") - return buf - - # Convert mutated data to a array of bytes - try: - data = bytearray(__mutator__.save_to_string()) - log("fuzz(): Mutated data converted as bytes") - except: - log("fuzz(): Can't convert mutated data to bytes => returning buf") - return buf - - # Everything went fine, returning mutated content - log("fuzz(): Returning %d bytes" % len(data)) - return data - -# Main (for debug) - -if __name__ == '__main__': - - __log__ = True - __log_file__ = "/dev/stdout" - __seed__ = "RANDOM" - - init(__seed__) - - in_1 = bytearray("ffffzzzzzzzzzzzz") - in_2 = bytearray("") - out = fuzz(in_1, in_2) - print(out) - -- cgit 1.4.1