diff options
-rw-r--r-- | .gitmodules | 9 | ||||
-rw-r--r-- | GNUmakefile | 18 | ||||
-rw-r--r-- | docs/Changelog.md | 2 | ||||
-rw-r--r-- | docs/FAQ.md | 21 | ||||
-rw-r--r-- | docs/env_variables.md | 3 | ||||
-rw-r--r-- | docs/features.md | 29 | ||||
-rw-r--r-- | docs/fuzzing_binary-only_targets.md | 10 | ||||
-rw-r--r-- | docs/fuzzing_in_depth.md | 10 | ||||
-rw-r--r-- | docs/important_changes.md | 4 | ||||
-rw-r--r-- | frida_mode/src/instrument/instrument_arm64.c | 3 | ||||
-rw-r--r-- | include/forkserver.h | 47 | ||||
m--------- | nyx_mode/QEMU-Nyx | 0 | ||||
-rw-r--r-- | nyx_mode/README.md | 275 | ||||
-rwxr-xr-x | nyx_mode/build_nyx_support.sh | 69 | ||||
-rw-r--r-- | nyx_mode/custom_harness/example.c | 91 | ||||
-rw-r--r-- | nyx_mode/custom_harness/fuzz.sh | 13 | ||||
-rw-r--r-- | nyx_mode/custom_harness/fuzz_no_pt.sh | 13 | ||||
m--------- | nyx_mode/libnyx | 0 | ||||
m--------- | nyx_mode/packer | 0 | ||||
-rw-r--r-- | src/afl-cc.c | 16 | ||||
-rw-r--r-- | src/afl-forkserver.c | 170 | ||||
-rw-r--r-- | src/afl-fuzz-init.c | 39 | ||||
-rw-r--r-- | src/afl-fuzz-stats.c | 28 | ||||
-rw-r--r-- | src/afl-fuzz.c | 173 | ||||
-rw-r--r-- | src/afl-showmap.c | 11 |
25 files changed, 1006 insertions, 48 deletions
diff --git a/.gitmodules b/.gitmodules index 6569c0b1..8ba1c39d 100644 --- a/.gitmodules +++ b/.gitmodules @@ -19,3 +19,12 @@ [submodule "coresight_mode/coresight-trace"] path = coresight_mode/coresight-trace url = https://github.com/RICSecLab/coresight-trace.git +[submodule "nyx_mode/libnyx"] + path = nyx_mode/libnyx + url = https://github.com/nyx-fuzz/libnyx.git +[submodule "nyx_mode/QEMU-Nyx"] + path = nyx_mode/QEMU-Nyx + url = https://github.com/nyx-fuzz/qemu-nyx.git +[submodule "nyx_mode/packer"] + path = nyx_mode/packer + url = https://github.com/nyx-fuzz/packer.git diff --git a/GNUmakefile b/GNUmakefile index a2c80261..527cdcfc 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -346,8 +346,8 @@ help: @echo "HELP --- the following make targets exist:" @echo "==========================================" @echo "all: the main afl++ binaries and llvm/gcc instrumentation" - @echo "binary-only: everything for binary-only fuzzing: frida_mode, qemu_mode, frida_mode, unicorn_mode, coresight_mode, libdislocator, libtokencap" - @echo "source-only: everything for source code fuzzing: libdislocator, libtokencap" + @echo "binary-only: everything for binary-only fuzzing: frida_mode, nyx_mode, qemu_mode, frida_mode, unicorn_mode, coresight_mode, libdislocator, libtokencap" + @echo "source-only: everything for source code fuzzing: nyx_mode, libdislocator, libtokencap" @echo "distrib: everything (for both binary-only and source code fuzzing)" @echo "man: creates simple man pages from the help option of the programs" @echo "install: installs everything you have compiled with the build option above" @@ -564,7 +564,7 @@ all_done: test_build .PHONY: clean clean: - rm -rf $(PROGS) libradamsa.so afl-fuzz-document afl-as as afl-g++ afl-clang afl-clang++ *.o src/*.o *~ a.out core core.[1-9][0-9]* *.stackdump .test .test1 .test2 test-instr .test-instr0 .test-instr1 afl-cs-proxy afl-qemu-trace afl-gcc-fast afl-gcc-pass.so afl-g++-fast ld *.so *.8 test/unittests/*.o test/unittests/unit_maybe_alloc test/unittests/preallocable .afl-* afl-gcc afl-g++ afl-clang afl-clang++ test/unittests/unit_hash test/unittests/unit_rand *.dSYM + rm -rf $(PROGS) afl-fuzz-document afl-as as afl-g++ afl-clang afl-clang++ *.o src/*.o *~ a.out core core.[1-9][0-9]* *.stackdump .test .test1 .test2 test-instr .test-instr0 .test-instr1 afl-cs-proxy afl-qemu-trace afl-gcc-fast afl-g++-fast ld *.so *.8 test/unittests/*.o test/unittests/unit_maybe_alloc test/unittests/preallocable .afl-* afl-gcc afl-g++ afl-clang afl-clang++ test/unittests/unit_hash test/unittests/unit_rand *.dSYM -$(MAKE) -f GNUmakefile.llvm clean -$(MAKE) -f GNUmakefile.gcc_plugin clean $(MAKE) -C utils/libdislocator clean @@ -617,6 +617,9 @@ ifneq "$(SYS)" "Darwin" ifeq "$(ARCH)" "aarch64" -$(MAKE) -C coresight_mode endif +ifeq "$(SYS)" "Linux" + -cd nyx_mode && ./build_nyx_support.sh +endif -cd qemu_mode && sh ./build_qemu_support.sh -cd unicorn_mode && unset CFLAGS && sh ./build_unicorn_support.sh endif @@ -634,6 +637,9 @@ ifneq "$(SYS)" "Darwin" ifeq "$(ARCH)" "aarch64" -$(MAKE) -C coresight_mode endif +ifeq "$(SYS)" "Linux" + -cd nyx_mode && ./build_nyx_support.sh +endif -cd qemu_mode && sh ./build_qemu_support.sh -cd unicorn_mode && unset CFLAGS && sh ./build_unicorn_support.sh endif @@ -647,6 +653,9 @@ endif $(MAKE) -C utils/libdislocator $(MAKE) -C utils/libtokencap # -$(MAKE) -C utils/plot_ui +ifeq "$(SYS)" "Linux" + -cd nyx_mode && ./build_nyx_support.sh +endif %.8: % @echo .TH $* 8 $(BUILD_DATE) "afl++" > $@ @@ -684,6 +693,7 @@ install: all $(MANPAGES) @if [ -f socketfuzz32.so -o -f socketfuzz64.so ]; then $(MAKE) -C utils/socket_fuzzing install; fi @if [ -f argvfuzz32.so -o -f argvfuzz64.so ]; then $(MAKE) -C utils/argv_fuzzing install; fi @if [ -f afl-frida-trace.so ]; then install -m 755 afl-frida-trace.so $${DESTDIR}$(HELPER_PATH); fi + @if [ -f libnyx.so ]; then install -m 755 libnyx.so $${DESTDIR}$(HELPER_PATH); fi @if [ -f utils/afl_network_proxy/afl-network-server ]; then $(MAKE) -C utils/afl_network_proxy install; fi @if [ -f utils/aflpp_driver/libAFLDriver.a ]; then set -e; install -m 644 utils/aflpp_driver/libAFLDriver.a $${DESTDIR}$(HELPER_PATH); fi @if [ -f utils/aflpp_driver/libAFLQemuDriver.a ]; then set -e; install -m 644 utils/aflpp_driver/libAFLQemuDriver.a $${DESTDIR}$(HELPER_PATH); fi @@ -706,7 +716,7 @@ endif .PHONY: uninstall uninstall: -cd $${DESTDIR}$(BIN_PATH) && rm -f $(PROGS) $(SH_PROGS) afl-cs-proxy afl-qemu-trace afl-plot-ui afl-fuzz-document afl-network-server afl-g* afl-plot.sh afl-as afl-ld-lto afl-c* afl-lto* - -cd $${DESTDIR}$(HELPER_PATH) && rm -f afl-g*.*o afl-llvm-*.*o afl-compiler-*.*o libdislocator.so libtokencap.so libcompcov.so libqasan.so afl-frida-trace.so socketfuzz*.so argvfuzz*.so libAFLDriver.a libAFLQemuDriver.a as afl-as SanitizerCoverage*.so compare-transform-pass.so cmplog-*-pass.so split-*-pass.so dynamic_list.txt + -cd $${DESTDIR}$(HELPER_PATH) && rm -f afl-g*.*o afl-llvm-*.*o afl-compiler-*.*o libdislocator.so libtokencap.so libcompcov.so libqasan.so afl-frida-trace.so libnyx.so socketfuzz*.so argvfuzz*.so libAFLDriver.a libAFLQemuDriver.a as afl-as SanitizerCoverage*.so compare-transform-pass.so cmplog-*-pass.so split-*-pass.so dynamic_list.txt -rm -rf $${DESTDIR}$(MISC_PATH)/testcases $${DESTDIR}$(MISC_PATH)/dictionaries -sh -c "ls docs/*.md | sed 's|^docs/|$${DESTDIR}$(DOC_PATH)/|' | xargs rm -f" -cd $${DESTDIR}$(MAN_PATH) && rm -f $(MANPAGES) diff --git a/docs/Changelog.md b/docs/Changelog.md index 58eef2ee..c4786bf3 100644 --- a/docs/Changelog.md +++ b/docs/Changelog.md @@ -14,6 +14,8 @@ sending a mail to <afl-users+subscribe@googlegroups.com>. e.g. "unique crashes" -> "saved crashes", "total paths" -> "corpus count", "current path" -> "current item". This might need changing custom scripting! + - Nyx mode (full system emulation with snapshot capability) has been + added - thanks to @schumilo and @eqv! - new binary-only fuzzing mode: coresight_mode for aarch64 CPUs :) thanks to RICSecLab submitting! - if instrumented libaries are dlopen()'ed after the forkserver you diff --git a/docs/FAQ.md b/docs/FAQ.md index f1cffe00..3d3dce20 100644 --- a/docs/FAQ.md +++ b/docs/FAQ.md @@ -180,6 +180,27 @@ If you find an interesting or important question missing, submit it via [best_practices.md#improving-stability](best_practices.md#improving-stability). </p></details> +<details> + <summary id="what-are-power-schedules">What are power schedules?</summary><p> + + Not every item in our queue/corpus is the same, some are more interesting, + others provide little value. + A power schedule measures how "interesting" a value is, and depending on + the calculated value spends more or less time mutating it. + + AFL++ comes with several power schedules, initially ported from [AFLFast](https://github.com/mboehme/aflfast) + however modified to be more effective and several more modes added. + + The most effective modes are '-p fast` (default) and `-p explore`. + + If you fuzz with several parallel afl-fuzz instances, then it is beneficial + to assign a different schedule to each instance, however the majority should + be `fast` and `explore`. + + It does not make sense to explain the details of the calculation and + reasoning behind all of the schedules. If you are interested, read the source + code and the AFLFast paper. + ## Troubleshooting <details> diff --git a/docs/env_variables.md b/docs/env_variables.md index dc79bf9e..7ab5351c 100644 --- a/docs/env_variables.md +++ b/docs/env_variables.md @@ -105,7 +105,8 @@ fairly broad use of environment variables instead: within your program at a certain point (such as at the end of an `__AFL_LOOP()`), you can run the macro `__AFL_LEAK_CHECK();` which will cause an abort if any memory is leaked (you can combine this with the - `LSAN_OPTIONS=...` suppression option to suppress some known leaks). + `__AFL_LSAN_OFF();` and `__AFL_LSAN_ON();` macros to avoid checking for + memory leaks from memory allocated between these two calls. - `AFL_USE_MSAN=1` - activates the memory sanitizer (uninitialized memory) - `AFL_USE_TSAN=1` - activates the thread sanitizer to find thread race conditions diff --git a/docs/features.md b/docs/features.md index 431d9eb1..6104ca63 100644 --- a/docs/features.md +++ b/docs/features.md @@ -4,20 +4,20 @@ AFL++ supports llvm from 3.8 up to version 12, very fast binary fuzzing with QEMU 5.1 with laf-intel and redqueen, FRIDA mode, unicorn mode, gcc plugin, full *BSD, Mac OS, Solaris and Android support and much, much, much more. -| Feature/Instrumentation | afl-gcc | llvm | gcc_plugin | FRIDA mode(9) | QEMU mode(10) |unicorn_mode(10) |coresight_mode(11)| -| -------------------------|:-------:|:---------:|:----------:|:----------------:|:----------------:|:----------------:|:----------------:| -| Threadsafe counters | | x(3) | | | | | | -| NeverZero | x86[_64]| x(1) | x | x | x | x | | -| Persistent Mode | | x | x | x86[_64]/arm64 | x86[_64]/arm[64] | x | | -| LAF-Intel / CompCov | | x | | | x86[_64]/arm[64] | x86[_64]/arm[64] | | -| CmpLog | | x | | x86[_64]/arm64 | x86[_64]/arm[64] | | | -| Selective Instrumentation| | x | x | x | x | | | -| Non-Colliding Coverage | | x(4) | | | (x)(5) | | | -| Ngram prev_loc Coverage | | x(6) | | | | | | -| Context Coverage | | x(6) | | | | | | -| Auto Dictionary | | x(7) | | | | | | -| Snapshot LKM Support | | (x)(8) | (x)(8) | | (x)(5) | | | -| Shared Memory Test cases | | x | x | x86[_64]/arm64 | x | x | | +| Feature/Instrumentation | afl-gcc | llvm | gcc_plugin | FRIDA mode(9) | QEMU mode(10) |unicorn_mode(10) |nyx_mode(12)|coresight_mode(11)| +| -------------------------|:-------:|:---------:|:----------:|:----------------:|:----------------:|:----------------:|:----------:|:----------------:| +| Threadsafe counters | | x(3) | | | | | x | | +| NeverZero | x86[_64]| x(1) | x | x | x | x | | | +| Persistent Mode | | x | x | x86[_64]/arm64 | x86[_64]/arm[64] | x | | | +| LAF-Intel / CompCov | | x | | | x86[_64]/arm[64] | x86[_64]/arm[64] | x86[_64] | | +| CmpLog | | x | | x86[_64]/arm64 | x86[_64]/arm[64] | | | | +| Selective Instrumentation| | x | x | x | x | | | | +| Non-Colliding Coverage | | x(4) | | | (x)(5) | | | | +| Ngram prev_loc Coverage | | x(6) | | | | | | | +| Context Coverage | | x(6) | | | | | | | +| Auto Dictionary | | x(7) | | | | | | | +| Snapshot Support | | (x)(8) | (x)(8) | | (x)(5) | | x | | +| Shared Memory Test cases | | x | x | x86[_64]/arm64 | x | x | x | | 1. default for LLVM >= 9.0, environment variable for older version due an efficiency bug in previous llvm versions @@ -34,6 +34,7 @@ QEMU 5.1 with laf-intel and redqueen, FRIDA mode, unicorn mode, gcc plugin, full 10. QEMU/Unicorn is only supported on Linux 11. Coresight mode is only available on AARCH64 Linux with a CPU with Coresight extension +12. Nyx mode is only supported on Linux and currently restricted to x86_x64 Among others, the following features and patches have been integrated: diff --git a/docs/fuzzing_binary-only_targets.md b/docs/fuzzing_binary-only_targets.md index 0f2f84f6..855d7756 100644 --- a/docs/fuzzing_binary-only_targets.md +++ b/docs/fuzzing_binary-only_targets.md @@ -125,6 +125,16 @@ to check out our sister project libafl which supports Frida, too: [https://github.com/AFLplusplus/LibAFL](https://github.com/AFLplusplus/LibAFL). Working examples already exist :-) +### Nyx mode + +Nyx is a full system emulation fuzzing environment with snapshot support that +is built upon KVM and QEMU. +It is only available on Linux and currently restricted to x86_x64. + +For binary-only fuzzing a special 5.10 kernel is required. + +See [nyx_mode/README.md](../nyx_mode/README.md) + ### Unicorn Unicorn is a fork of QEMU. The instrumentation is, therefore, very similar. In diff --git a/docs/fuzzing_in_depth.md b/docs/fuzzing_in_depth.md index 2db6cfda..65a6de3d 100644 --- a/docs/fuzzing_in_depth.md +++ b/docs/fuzzing_in_depth.md @@ -201,7 +201,10 @@ The following sanitizers have built-in support in AFL++: security issue, but for developers this can be very valuable. Note that unlike the other sanitizers above this needs `__AFL_LEAK_CHECK();` added to all areas of the target source code where you find a leak check necessary! Enabled with - `export AFL_USE_LSAN=1` before compiling. + `export AFL_USE_LSAN=1` before compiling. To ignore the memory-leaking check + for certain allocations, `__AFL_LSAN_OFF();` can be used before memory is + allocated, and `__AFL_LSAN_OFF;` afterwards. Memory allocated between these + two macros will not be checked for memory leaks. It is possible to further modify the behavior of the sanitizers at run-time by setting `ASAN_OPTIONS=...`, `LSAN_OPTIONS` etc. - the available parameters can @@ -562,7 +565,8 @@ All other secondaries should be used like this: * a quarter to a third with the MOpt mutator enabled: `-L 0` * run with a different power schedule, recommended are: `fast (default), explore, coe, lin, quad, exploit and rare` which you can set - with, e.g., `-p explore` + with the `-p` option, e.g., `-p explore`. See the [FAQ](FAQ.md#what-are-power-schedules) + for details. * a few instances should use the old queue cycling with `-Z` Also, it is recommended to set `export AFL_IMPORT_FIRST=1` to load test cases @@ -889,4 +893,4 @@ covered. Note that there are also a lot of tools out there that help fuzzing with AFL++ (some might be deprecated or unsupported), see -[third_party_tools.md](third_party_tools.md). \ No newline at end of file +[third_party_tools.md](third_party_tools.md). diff --git a/docs/important_changes.md b/docs/important_changes.md index facaf3c1..0c1c34ef 100644 --- a/docs/important_changes.md +++ b/docs/important_changes.md @@ -5,10 +5,6 @@ changes. ## From version 3.00 onwards -With AFL++ 3.13-3.20, we introduce FRIDA mode (`-O`) to have an alternative for -binary-only fuzzing. It is a bit faster than QEMU mode and works on MacOS, -Android, iOS etc. - With AFL++ 4.00, we introduced the following changes from previous behaviors: * the complete documenation was overhauled and restructured thanks to @llzmb! * a new CMPLOG target format requires recompiling CMPLOG targets for use diff --git a/frida_mode/src/instrument/instrument_arm64.c b/frida_mode/src/instrument/instrument_arm64.c index 32e44e92..57b60317 100644 --- a/frida_mode/src/instrument/instrument_arm64.c +++ b/frida_mode/src/instrument/instrument_arm64.c @@ -36,7 +36,6 @@ typedef struct { // ldrb w1, [x0] // add w1, w1, #0x1 // add x1, x1, x1, lsr #8 - // uxtb w1, w1 // strb w1, [x0] // adrp x0, 0x7fb7738000 // mov x1, #0xc5c @@ -58,7 +57,6 @@ typedef struct { uint32_t ldrb_w1_x0; /* ldrb w1, [x0] */ uint32_t add_w1_w1_1; /* add w1, w1, #1 */ uint32_t add_w1_w1_w1_lsr_8; /* add x1, x1, x1, lsr #8 */ - uint32_t uxtb_w1_w1; /* uxtb w1, w1 */ uint32_t strb_w1_ptr_x0; /* strb w1, [x0] */ @@ -100,7 +98,6 @@ static const afl_log_code_asm_t template = .add_w1_w1_1 = 0x11000421, .add_w1_w1_w1_lsr_8 = 0x8b412021, - .uxtb_w1_w1 = 0x53001c21, .strb_w1_ptr_x0 = 0x39000001, diff --git a/include/forkserver.h b/include/forkserver.h index 464f208d..2418381f 100644 --- a/include/forkserver.h +++ b/include/forkserver.h @@ -33,6 +33,42 @@ #include "types.h" +#ifdef __linux__ +/** + * Nyx related typedefs taken from libnyx.h + */ + +typedef enum NyxReturnValue { + + Normal, + Crash, + Asan, + Timout, + InvalidWriteToPayload, + Error, + IoError, + Abort, + +} NyxReturnValue; + +typedef struct { + + void *(*nyx_new)(const char *sharedir, const char *workdir, + uint32_t worker_id, uint32_t cpu_id, bool create_snapshot); + void (*nyx_shutdown)(void *qemu_process); + void (*nyx_option_set_reload_mode)(void *qemu_process, bool enable); + void (*nyx_option_set_timeout)(void *qemu_process, uint8_t timeout_sec, + uint32_t timeout_usec); + void (*nyx_option_apply)(void *qemu_process); + void (*nyx_set_afl_input)(void *qemu_process, uint8_t *buffer, uint32_t size); + enum NyxReturnValue (*nyx_exec)(void *qemu_process); + uint8_t *(*nyx_get_bitmap_buffer)(void *qemu_process); + size_t (*nyx_get_bitmap_buffer_size)(void *qemu_process); + +} nyx_plugin_handler_t; + +#endif + typedef struct afl_forkserver { /* a program that includes afl-forkserver needs to define these */ @@ -121,6 +157,17 @@ typedef struct afl_forkserver { u8 kill_signal; +#ifdef __linux__ + nyx_plugin_handler_t *nyx_handlers; + char * out_dir_path; /* path to the output directory */ + u8 nyx_mode; /* if running in nyx mode or not */ + bool nyx_parent; /* create initial snapshot */ + bool nyx_standalone; /* don't serialize the snapshot */ + void * nyx_runner; /* nyx runner object */ + u32 nyx_id; /* nyx runner id (0 -> master) */ + u32 nyx_bind_cpu_id; /* nyx runner cpu id */ +#endif + } afl_forkserver_t; typedef enum fsrv_run_result { diff --git a/nyx_mode/QEMU-Nyx b/nyx_mode/QEMU-Nyx new file mode 160000 +Subproject acc90e462b45fab15bb6b28c064e9f78808cb34 diff --git a/nyx_mode/README.md b/nyx_mode/README.md new file mode 100644 index 00000000..646cba5a --- /dev/null +++ b/nyx_mode/README.md @@ -0,0 +1,275 @@ +# Nyx Mode + +Nyx is a full system emulation fuzzing mode that supports snapshotting and +can be used for both source code based instrumentation and binary-only targets. + +It is recommended to be used if the target cannot be fuzzed in persistent mode +(so default fork mode fuzzing is used). + +It is only available on Linux and is currently restricted to x86_x64 however +aarch64 support is in the works (but the host must then run on aarch64 too). + +Underneath it is built upon KVM and QEMU and requires a modern Linux kernel +(5.11+) for fuzzing source code based instrumented targets (e.g. +`afl-clang-fast`). To fuzz binary-only targets, this is done via Intel PT +and requires an Intel processor (6th generation onwards) and a special +5.10 kernel (see [KVM-Nyx](https://github.com/nyx-fuzz/KVM-Nyx)). + +## Building Nyx mode + +1. Install all the packages from [docs/INSTALL.md](../docs/INSTALL.md). + +2. Additionally install the following packages: + +```shell +apt-get install -y libgtk-3-dev pax-utils python3-msgpack python3-jinja2 +``` + +3. As Nyx is written in Rust, install the newest rust compiler (rust packages + in the Linux distribution are usually too old to be able to build Nyx): + +```shell +curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh +``` + +4. Finally build Nyx mode: + +```shell +./build_nyx_support.sh +``` + +5. Optional, for binary-only fuzzing: setup the required 5.10 kernel, see + [KVM-Nyx](https://github.com/nyx-fuzz/KVM-Nyx). + +## Preparing to fuzz a target with Nyx mode + +Nyx uses full system emulation hence your fuzzing targets have to be especially +packaged. + +**For source code based instrumentation with `afl-clang-fast` for the time +being these must be instrumented to `AFL_LLVM_INSTRUMENT=AFL` to work!** + +With your target ready at hand execute the following command +(note that for binary-only fuzzing with the special 5.10 kernel switch the +option `instrumentation` below with `process_trace`): + +```shell +python3 nyx_mode/packer/packer/nyx_packer.py \ + /PATH/TO/TARGET \ + PACKAGE-DIRECTORY \ + afl \ + instrumentation \ + --fast_reload_mode \ + --purge +``` + +This will create a directory with all necessary files and the Nyx configuration. +The name of the directory will be whatever you choose for PACKAGE-DIRECTORY +above. + +In the final step for the packaging we generate the Nyx configuration: +```shell +python3 nyx_mode/packer/packer/nyx_config_gen.py PACKAGE-DIRECTORY Kernel +``` + +## Fuzzing with Nyx mode + +All the hard parts are done, fuzzing with Nyx mode is easy - just supply +the PACKAGE-DIRECTORY as fuzzing target and specify the `-X` option to afl-fuzz: + +```shell +afl-fuzz -i in -o out -X -- ./PACKAGE-DIRECTORY +``` + +Most likely your first run will fail because the Linux modules have to be +specially set up, but afl-fuzz will tell you this on startup and how to +rectify the situation: +``` +sudo modprobe -r kvm-intel # or kvm-amd for AMD processors +sudo modprobe -r kvm +sudo modprobe kvm enable_vmware_backdoor=y +sudo modprobe kvm-intel # or kvm-amd for AMD processors +``` + +If you want to fuzz in parallel (and you should!), then this has to be done in a +special way: + + * Instead of `-X` (standalone mode) you specify `-Y` (multi processor mode). + * First a Main afl-fuzz instance has to be started with `-M 0` + * Only afterwards can you start Secondary afl-fuzz instances, which must have + an increasing number value, starting at 1, e.g. `-S 1` + +```shell +afl-fuzz -i in -o out -Y -M 0 -- ./PACKAGE-DIRECTORY +``` + +```shell +afl-fuzz -i in -o out -Y -S 1 -- ./PACKAGE-DIRECTORY +``` + +```shell +afl-fuzz -i in -o out -Y -S 2 -- ./PACKAGE-DIRECTORY +``` + +## Real-world examples + +### Fuzzing libxml2 with AFL++ in Nyx-mode + +This tutorial is based on the [Fuzzing libxml2 with AFL++](https://aflplus.plus/docs/tutorials/libxml2_tutorial/) tutorial. + +### Preparing libxml2 + +First, get the latest libxml2 source files by using `git`: + +``` +git clone https://gitlab.gnome.org/GNOME/libxml2 +cd libxml2 +``` + +Remember that currently only classic AFL instrumented is supported! + +``` +export AFL_LLVM_INSTRUMENT=AFL +./autogen.sh +./configure --enable-shared=no +make CC=afl-clang-fast CXX=afl-clang-fast++ LD=afl-clang-fast +``` + +#### Nyx share directories + +Nyx expects that the target is provided in a certain format. More specifically, the target is passed as a so-called „share directory“ to a Nyx-frontend implementation. The share directory contains the target as wells as a folder containing all dependencies and other files that are copied over to the guest. But more importantly, this share directory also contains a bootstrap script (`fuzz.sh`if you are using `KVM-Nyx`otherwise `fuzz_no_pt.sh`) that is also executed right after launching the fuzzer. Both bootstrap scripts use several tools to communicate with the "outer world": + +- `hcat` - this tool copies a given string to the host +- `hget` - this program requests a file from the host's share directory +- `hget_bulk` - an improved version of `hget`. It is quite useful if you want to transfer huge files. But please keep in mind that this version of `hget` has a much larger startup overhead and won't improve your transfer rates on small files (typically files smaller than 100MB). +- `habort` - this tool basically sends an abort signal to the host (useful if something went wrong during bootstrap) +- `hpush` - a tool to transfer a given file to the host (the transfered file will be put in the `dump/` folder of your Nyx workdir) + +Those tools are all using hypercalls which are defined in `packer/nyx.h`. We will give some more examples later on how to use these hypercalls directly to implement custom fuzzing harnesses. + +### Pack libxml2 into Nyx sharedir format + +To turn a given linux target into the Nyx format, you can simply use `nyx_packer.py`. To do so, move to the following directory: + +``` +cd nyx_mode/packer/packer +``` + + And run the tool with the following options to pack `libxml2`: + +```. +python3 ./nyx_packer.py \ + ~/libxml2/xmllint \ + /tmp/nyx_libxml2 \ + afl \ + instrumentation \ + -args "/tmp/input" \ + -file "/tmp/input" \ + --fast_reload_mode \ + --purge +``` + +In this example, the packer will take `xmllint`, recursively get all dependencies and put both into the specified share directory (`/tmp/nyx_libxml2` in this case). Because we have selected the `afl` option, an `ld_preload`-based agent is also automatically built and put into the sharedir. Another option would be `spec`. Without going into too much detail here, the `spec`mode is only used by Nyx's [spec-fuzzer](https://github.com/nyx-fuzz/spec-fuzzer) implementation. Next, since our target is built with compile-time instrumentations, we must select the `instrumentation` option, othwise we could also use `processor-trace` option to enable Intel-PT fuzzing on targets without instrumentation. + +To specify that the input generated by the fuzzer is passed as a seperate file to the target, we need to set the `-file` option. Otherwise, the input will be passed over to the target via `stdin`. To specify any required `argv` options you can use the `-args`parameter. + +In case you want to fuzz the target only with fast snapshots enabled, you can also set the ` --fast_reload_mode`option to improve performance. + +Finally, we need to generate a Nyx configuration file. Simply run the following command and you're good to proceed: + +``` +python3 ./nyx_config_gen.py /tmp/nyx_libxml2/ Kernel +``` + +### Run Nyx mode + +From here on, we are almost done. Move to the AFL++ top directory and start the fuzzer with the following arguments: + +```shell +mkdir /tmp/in/ # create an input folder +echo "AAAA" >> /tmp/in/A # create a dummy input file + ./afl-fuzz -i /tmp/in/ -o /tmp/out -X /tmp/nyx_libxml2/ +``` + +If everything has been successfully set up to this point, you will now be welcomed by the following AFL++ screen: + +``` + american fuzzy lop ++3.15a {default} (/tmp/nyx_libxml2/) [fast] - NYX +┌─ process timing ────────────────────────────────────┬─ overall results ────┐ +│ run time : 0 days, 0 hrs, 0 min, 14 sec │ cycles done : 0 │ +│ last new find : 0 days, 0 hrs, 0 min, 0 sec │ corpus count : 96 │ +│last saved crash : none seen yet │saved crashes : 0 │ +│ last saved hang : none seen yet │ saved hangs : 0 │ +├─ cycle progress ─────────────────────┬─ map coverage┴──────────────────────┤ +│ now processing : 28.0 (29.2%) │ map density : 2.17% / 3.61% │ +│ runs timed out : 0 (0.00%) │ count coverage : 1.67 bits/tuple │ +├─ stage progress ─────────────────────┼─ findings in depth ─────────────────┤ +│ now trying : havoc │ favored items : 27 (28.12%) │ +│ stage execs : 22.3k/32.8k (68.19%) │ new edges on : 58 (60.42%) │ +│ total execs : 55.9k │ total crashes : 0 (0 saved) │ +│ exec speed : 3810/sec │ total tmouts : 0 (0 saved) │ +├─ fuzzing strategy yields ────────────┴─────────────┬─ item geometry ───────┤ +│ bit flips : disabled (default, enable with -D) │ levels : 3 │ +│ byte flips : disabled (default, enable with -D) │ pending : 95 │ +│ arithmetics : disabled (default, enable with -D) │ pend fav : 27 │ +│ known ints : disabled (default, enable with -D) │ own finds : 95 │ +│ dictionary : n/a │ imported : 0 │ +│havoc/splice : 57/32.8k, 0/0 │ stability : 100.00% │ +│py/custom/rq : unused, unused, unused, unused ├───────────────────────┘ +│ trim/eff : n/a, disabled │ [cpu000: 25%] +└────────────────────────────────────────────────────┘ +``` + +If you want to run the fuzzer in distributed mode, which might be especially useful if you want to keep your memory footprint low, we got you covered. To start an initiating `parent` process, which will also create the snapshot which is later shared across all other `child`s, simply run AFL++Nyx with the following arguments: + +``` +./afl-fuzz -i /tmp/in/ -o /tmp/out -d -Y -M 0 /tmp/nyx_libxml2/ +``` + +To attach other child processes adjust the `-S <id>` and run the following command: + +``` +./afl-fuzz -i /tmp/in/ -o /tmp/out -d -Y -S 1 /tmp/nyx_libxml2/ +``` + +If you want to disable fast snapshots (except for crashes), you can simply set the `NYX_DISABLE_SNAPSHOT_MODE` environment variable. + +### Run AFL++Nyx with a custom agent + +Most of the common use-cases for linux userland targets are already handled by our general purpose [agent](https://github.com/nyx-fuzz/packer/blob/main/packer/linux_x86_64-userspace/src/ld_preload_fuzz.c) implementation. But in case you want to build your own agent, or write a custom harness for a specific target or you just want implement all the hypercall and shared memory communication on your own, you can use our custom harness example as a starting point for that. You can find the code [here](custom_harness/) + +This custom harness can be statically compiled with by gcc or clang. There is no need to use an AFL compiler, becaues this agent implements its own very basic coverage tracking by simply setting specific bytes in the "coverage" bitmap after specific branches have been covered. + +To prepare this target, we must first create a new folder that will later become the sharedir. + +```` +mkdir /tmp/nyx_custom_agent/ +```` + + To compile this example, run the following command (remove the `-DNO_PT_NYX` option if you are using KVM-Nyx ): + +``` +gcc example.c -DNO_PT_NYX -static -I ./packer/ -o /tmp/nyx_custom_agent/target +``` + +Copy both bootstrap scripts into the sharedir: + +``` +cp fuzz.sh /tmp/nyx_custom_agent +cp fuzz_no_pt.sh /tmp/nyx_custom_agent +``` + +Copy all `htools` executable into the sharedir: + +``` +cd ~/AFLplusplus/packer/packer/linux_x86_64-userspace/ +sh compile_64.sh +cp bin64/h* /tmp/nyx_custom_agent/ +``` + +And finally, generate a Nyx configuration: + +``` +cd ~/AFLplusplus/packer/packer +python3 ./nyx_config_gen.py /tmp/nyx_custom_agent/ Kernel +``` diff --git a/nyx_mode/build_nyx_support.sh b/nyx_mode/build_nyx_support.sh new file mode 100755 index 00000000..b73423d2 --- /dev/null +++ b/nyx_mode/build_nyx_support.sh @@ -0,0 +1,69 @@ +#!/bin/bash +set -e + +echo "=================================================" +echo " Nyx build script" +echo "=================================================" +echo + +echo "[*] Performing basic sanity checks..." + +if [ ! "`uname -s`" = "Linux" ]; then + + echo "[-] Error: Nyx mode is only available on Linux." + exit 0 + +fi + +echo "[*] Making sure all Nyx is checked out" + +git status 1>/dev/null 2>/dev/null +if [ $? -eq 0 ]; then + git submodule init || exit 1 + echo "[*] initializing QEMU-Nyx submodule" + git submodule update ./QEMU-Nyx 2>/dev/null # ignore errors + echo "[*] initializing packer submodule" + git submodule update ./packer 2>/dev/null # ignore errors + echo "[*] initializing libnyx submodule" + git submodule update ./libnyx 2>/dev/null # ignore errors + +else + echo "[ ] not a git repo..." + exit 1 +fi + +test -d QEMU-Nyx || { echo "[-] Not checked out, please install git or check your internet connection." ; exit 1 ; } +test -d packer || { echo "[-] Not checked out, please install git or check your internet connection." ; exit 1 ; } +test -d libnyx || { echo "[-] Not checked out, please install git or check your internet connection." ; exit 1 ; } + +echo "[*] checking packer init.cpio.gz ..." +if [ ! -f "packer/linux_initramfs/init.cpio.gz" ]; then + cd packer/linux_initramfs/ + sh pack.sh + cd ../../ +fi + +echo "[*] Checking libnyx ..." +if [ ! -f "libnyx/libnyx/target/release/liblibnyx.a" ]; then + cd libnyx/libnyx + cargo build --release + cd ../../ +fi + +echo "[*] Checking QEMU-Nyx ..." +if [ ! -f "QEMU-Nyx/x86_64-softmmu/qemu-system-x86_64" ]; then + cd QEMU-Nyx/ + ./compile_qemu_nyx.sh + cd .. +fi + +echo "[*] Checking libnyx.so ..." +if [ -f "libnyx/libnyx/target/release/liblibnyx.so" ]; then + cp -v libnyx/libnyx/target/release/liblibnyx.so ../libnyx.so +else + echo "[ ] libnyx.so not found..." + exit 1 +fi +echo "[+] All done for nyx_mode, enjoy!" + +exit 0 diff --git a/nyx_mode/custom_harness/example.c b/nyx_mode/custom_harness/example.c new file mode 100644 index 00000000..b217ed55 --- /dev/null +++ b/nyx_mode/custom_harness/example.c @@ -0,0 +1,91 @@ +#include <stdint.h> +#include <stdio.h> +#include <stdbool.h> +#include <inttypes.h> +#include "nyx.h" + +/* this is our "bitmap" that is later shared with the fuzzer (you can also pass the pointer of the bitmap used by compile-time instrumentations in your target) */ +uint8_t* trace_buffer[64*1024] = {0}; + +int main(int argc, char** argv){ + /* if you want to debug code running in Nyx, hprintf() is the way to go. + * Long story short -- it's just a guest-to-hypervisor printf. Hence the name "hprintf" + */ + hprintf("Agent test\n"); + + /* Request information on available (host) capabilites (optional) */ + host_config_t host_config; + kAFL_hypercall(HYPERCALL_KAFL_GET_HOST_CONFIG, (uintptr_t)&host_config); + hprintf("[capablities] host_config.bitmap_size: 0x%"PRIx64"\n", host_config.bitmap_size); + hprintf("[capablities] host_config.ijon_bitmap_size: 0x%"PRIx64"\n", host_config.ijon_bitmap_size); + hprintf("[capablities] host_config.payload_buffer_size: 0x%"PRIx64"x\n", host_config.payload_buffer_size); + + /* Submit agent configuration */ + memset(trace_buffer, 0, 64*1024); // makes sure that the bitmap buffer is already mapped into the guest's memory (alternatively you can use mlock) */ + agent_config_t agent_config = {0}; + agent_config.agent_timeout_detection = 0; /* timeout detection is implemented by the agent (currently not used) */ + agent_config.agent_tracing = 1; /* set this flag to propagade that instrumentation-based fuzzing is availabe */ + agent_config.agent_ijon_tracing = 0; /* set this flag to propagade that IJON extension is implmented agent-wise */ + agent_config.trace_buffer_vaddr = (uintptr_t)trace_buffer; /* trace "bitmap" pointer - required for instrumentation-only fuzzing */ + agent_config.ijon_trace_buffer_vaddr = (uintptr_t)NULL; /* "IJON" buffer pointer */ + agent_config.agent_non_reload_mode = 1; /* non-reload mode is supported (usually because the agent implements a fork-server; currently not used) */ + kAFL_hypercall(HYPERCALL_KAFL_SET_AGENT_CONFIG, (uintptr_t)&agent_config); + + /* Tell hypervisor the virtual address of the payload (input) buffer (call mlock to ensure that this buffer stays in the guest's memory)*/ + kAFL_payload* payload_buffer = mmap((void*)0x4000000ULL, PAYLOAD_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS | MAP_FIXED, -1, 0); + mlock(payload_buffer, (size_t)PAYLOAD_SIZE); + memset(payload_buffer, 0, PAYLOAD_SIZE); + kAFL_hypercall(HYPERCALL_KAFL_GET_PAYLOAD, (uintptr_t)payload_buffer); + hprintf("[init] payload buffer is mapped at %p\n", payload_buffer); + + /* the main fuzzing loop */ + while(1){ + + /* Creates a root snapshot on first execution. Also we requested the next input with this hypercall */ + kAFL_hypercall(HYPERCALL_KAFL_USER_FAST_ACQUIRE, 0); // root snapshot <-- + +#ifdef DEBUG + hprintf("Size: %ld Data: %x %x %x %x\n", payload_buffer->size, + payload_buffer->data[4], + payload_buffer->data[5], + payload_buffer->data[6], + payload_buffer->data[7] + ); +#endif + + uint32_t len = payload_buffer->size; + + /* set a byte to make AFL++ happy (otherwise the fuzzer might refuse to start fuzzing at all) */ + ((uint8_t*)trace_buffer)[0] = 0x1; + + if (len >= 4){ + /* set a byte in the bitmap to guide your fuzzer */ + ((uint8_t*)trace_buffer)[0] = 0x1; + if (payload_buffer->data[0] == '!'){ + ((uint8_t*)trace_buffer)[1] = 0x1; + if (payload_buffer->data[1] == 'N'){ + ((uint8_t*)trace_buffer)[2] = 0x1; + if (payload_buffer->data[2] == 'Y'){ + ((uint8_t*)trace_buffer)[3] = 0x1; + if (payload_buffer->data[3] == 'X'){ + ((uint8_t*)trace_buffer)[4] = 0x1; + /* Notifiy the hypervisor and the fuzzer that a "crash" has occured. Also a string is passed by this hypercall (this is currently not supported by AFL++-Nyx) */ + kAFL_hypercall(HYPERCALL_KAFL_PANIC_EXTENDED, (uintptr_t)"Something went wrong\n"); + } + } + } + } + } + /* this hypercall is used to notify the hypervisor and the fuzzer that a single fuzzing "execution" has finished. + * If the reload-mode is enabled, we will jump back to our root snapshot. + * Otherwise, the hypervisor passes control back to the guest once the bitmap buffer has been "processed" by the fuzzer. + */ + kAFL_hypercall(HYPERCALL_KAFL_RELEASE, 0); + + /* This shouldn't happen if you have enabled the reload mode */ + hprintf("Das sollte niemals passieren :)\n"); + } + + + return 0; +} diff --git a/nyx_mode/custom_harness/fuzz.sh b/nyx_mode/custom_harness/fuzz.sh new file mode 100644 index 00000000..98138f70 --- /dev/null +++ b/nyx_mode/custom_harness/fuzz.sh @@ -0,0 +1,13 @@ +chmod +x hget +cp hget /tmp/ +cd /tmp/ +echo 0 > /proc/sys/kernel/randomize_va_space +echo 0 > /proc/sys/kernel/printk +./hget hcat hcat +./hget habort habort +./hget target target +chmod +x hcat +chmod +x habort +chmod +x target +./target +./habort "Target has terminated without initializing the fuzzing agent ..." diff --git a/nyx_mode/custom_harness/fuzz_no_pt.sh b/nyx_mode/custom_harness/fuzz_no_pt.sh new file mode 100644 index 00000000..b65a6493 --- /dev/null +++ b/nyx_mode/custom_harness/fuzz_no_pt.sh @@ -0,0 +1,13 @@ +chmod +x hget +cp hget /tmp/ +cd /tmp/ +echo 0 > /proc/sys/kernel/randomize_va_space +echo 0 > /proc/sys/kernel/printk +./hget hcat_no_pt hcat +./hget habort_no_pt habort +./hget target target +chmod +x hcat +chmod +x habort +chmod +x target +./target +./habort "Target has terminated without initializing the fuzzing agent ..." diff --git a/nyx_mode/libnyx b/nyx_mode/libnyx new file mode 160000 +Subproject ecbcb2d7234fef0b5e1db8ca6019e6137ee0582 diff --git a/nyx_mode/packer b/nyx_mode/packer new file mode 160000 +Subproject 87837335d6a9834516aacf4121cbe0e2b969212 diff --git a/src/afl-cc.c b/src/afl-cc.c index f90f62a8..9e5eed93 100644 --- a/src/afl-cc.c +++ b/src/afl-cc.c @@ -556,7 +556,16 @@ static void edit_params(u32 argc, char **argv, char **envp) { if (lto_mode && !have_c) { u8 *ld_path = NULL; - if (getenv("AFL_REAL_LD")) { ld_path = strdup(getenv("AFL_REAL_LD")); } + if (getenv("AFL_REAL_LD")) { + + ld_path = strdup(getenv("AFL_REAL_LD")); + + } else { + + ld_path = strdup(AFL_REAL_LD); + + } + if (!ld_path || !*ld_path) { ld_path = strdup("ld.lld"); } if (!ld_path) { PFATAL("Could not allocate mem for ld_path"); } #if defined(AFL_CLANG_LDPATH) && LLVM_MAJOR >= 12 @@ -867,7 +876,10 @@ static void edit_params(u32 argc, char **argv, char **envp) { cc_params[cc_par_cnt++] = "-fsanitize=leak"; cc_params[cc_par_cnt++] = "-includesanitizer/lsan_interface.h"; - cc_params[cc_par_cnt++] = "-D__AFL_LEAK_CHECK()=__lsan_do_leak_check()"; + cc_params[cc_par_cnt++] = "-D__AFL_LEAK_CHECK()={if(__lsan_do_recoverable_leak_check() > 0) _exit(23); }"; + cc_params[cc_par_cnt++] = "-D__AFL_LSAN_OFF()=__lsan_disable();"; + cc_params[cc_par_cnt++] = "-D__AFL_LSAN_ON()=__lsan_enable();"; + } diff --git a/src/afl-forkserver.c b/src/afl-forkserver.c index b871ea8c..4d57b95d 100644 --- a/src/afl-forkserver.c +++ b/src/afl-forkserver.c @@ -71,6 +71,17 @@ static void fsrv_exec_child(afl_forkserver_t *fsrv, char **argv) { void afl_fsrv_init(afl_forkserver_t *fsrv) { +#ifdef __linux__ + fsrv->nyx_handlers = NULL; + fsrv->out_dir_path = NULL; + fsrv->nyx_mode = 0; + fsrv->nyx_parent = false; + fsrv->nyx_standalone = false; + fsrv->nyx_runner = NULL; + fsrv->nyx_id = 0xFFFFFFFF; + fsrv->nyx_bind_cpu_id = 0xFFFFFFFF; +#endif + // this structure needs default so we initialize it if this was not done // already fsrv->out_fd = -1; @@ -375,6 +386,82 @@ void afl_fsrv_start(afl_forkserver_t *fsrv, char **argv, s32 rlen; char *ignore_autodict = getenv("AFL_NO_AUTODICT"); +#ifdef __linux__ + if (unlikely(fsrv->nyx_mode)) { + + if (fsrv->nyx_runner != NULL) { return; } + + if (!be_quiet) { ACTF("Spinning up the NYX backend..."); } + + if (fsrv->out_dir_path == NULL) { FATAL("Nyx workdir path not found..."); } + + char *x = alloc_printf("%s/workdir", fsrv->out_dir_path); + + if (fsrv->nyx_id == 0xFFFFFFFF) { FATAL("Nyx ID is not set..."); } + + if (fsrv->nyx_bind_cpu_id == 0xFFFFFFFF) { + + FATAL("Nyx CPU ID is not set..."); + + } + + if (fsrv->nyx_parent) { + + fsrv->nyx_runner = fsrv->nyx_handlers->nyx_new( + fsrv->target_path, x, fsrv->nyx_id, fsrv->nyx_bind_cpu_id, + !fsrv->nyx_standalone); + + } else { + + fsrv->nyx_runner = fsrv->nyx_handlers->nyx_new( + fsrv->target_path, x, fsrv->nyx_id, fsrv->nyx_bind_cpu_id, true); + + } + + if (fsrv->nyx_runner == NULL) { FATAL("Something went wrong ..."); } + + u32 tmp_map_size = + fsrv->nyx_handlers->nyx_get_bitmap_buffer_size(fsrv->nyx_runner); + fsrv->real_map_size = fsrv->map_size; + fsrv->map_size = (((tmp_map_size + 63) >> 6) << 6); + if (!be_quiet) { ACTF("Target map size: %u", fsrv->real_map_size); } + + fsrv->trace_bits = + fsrv->nyx_handlers->nyx_get_bitmap_buffer(fsrv->nyx_runner); + + fsrv->nyx_handlers->nyx_option_set_reload_mode( + fsrv->nyx_runner, getenv("NYX_DISABLE_SNAPSHOT_MODE") == NULL); + fsrv->nyx_handlers->nyx_option_apply(fsrv->nyx_runner); + + fsrv->nyx_handlers->nyx_option_set_timeout(fsrv->nyx_runner, 2, 0); + fsrv->nyx_handlers->nyx_option_apply(fsrv->nyx_runner); + + /* dry run */ + fsrv->nyx_handlers->nyx_set_afl_input(fsrv->nyx_runner, "INIT", 4); + switch (fsrv->nyx_handlers->nyx_exec(fsrv->nyx_runner)) { + + case Abort: + fsrv->nyx_handlers->nyx_shutdown(fsrv->nyx_runner); + FATAL("Error: Nyx abort occured..."); + break; + case IoError: + FATAL("Error: QEMU-Nyx has died..."); + break; + case Error: + fsrv->nyx_handlers->nyx_shutdown(fsrv->nyx_runner); + FATAL("Error: Nyx runtime error has occured..."); + break; + default: + break; + + } + + return; + + } + +#endif + if (!be_quiet) { ACTF("Spinning up the fork server..."); } #ifdef AFL_PERSISTENT_RECORD @@ -1085,6 +1172,10 @@ void afl_fsrv_kill(afl_forkserver_t *fsrv) { fsrv->fsrv_pid = -1; fsrv->child_pid = -1; +#ifdef __linux__ + if (fsrv->nyx_mode) { fsrv->nyx_handlers->nyx_shutdown(fsrv->nyx_runner); } +#endif + } /* Get the map size from the target forkserver */ @@ -1101,6 +1192,16 @@ u32 afl_fsrv_get_mapsize(afl_forkserver_t *fsrv, char **argv, void afl_fsrv_write_to_testcase(afl_forkserver_t *fsrv, u8 *buf, size_t len) { +#ifdef __linux__ + if (unlikely(fsrv->nyx_mode)) { + + fsrv->nyx_handlers->nyx_set_afl_input(fsrv->nyx_runner, buf, len); + return; + + } + +#endif + #ifdef AFL_PERSISTENT_RECORD if (unlikely(fsrv->persistent_record)) { @@ -1214,13 +1315,80 @@ fsrv_run_result_t afl_fsrv_run_target(afl_forkserver_t *fsrv, u32 timeout, u32 exec_ms; u32 write_value = fsrv->last_run_timed_out; +#ifdef __linux__ + if (fsrv->nyx_mode) { + + static uint32_t last_timeout_value = 0; + + if (last_timeout_value != timeout) { + + fsrv->nyx_handlers->nyx_option_set_timeout( + fsrv->nyx_runner, timeout / 1000, (timeout % 1000) * 1000); + fsrv->nyx_handlers->nyx_option_apply(fsrv->nyx_runner); + last_timeout_value = timeout; + + } + + enum NyxReturnValue ret_val = + fsrv->nyx_handlers->nyx_exec(fsrv->nyx_runner); + + fsrv->total_execs++; + + switch (ret_val) { + + case Normal: + return FSRV_RUN_OK; + case Crash: + case Asan: + return FSRV_RUN_CRASH; + case Timout: + return FSRV_RUN_TMOUT; + case InvalidWriteToPayload: + /* ??? */ + FATAL("FixMe: Nyx InvalidWriteToPayload handler is missing"); + break; + case Abort: + fsrv->nyx_handlers->nyx_shutdown(fsrv->nyx_runner); + FATAL("Error: Nyx abort occured..."); + case IoError: + if (*stop_soon_p) { + + return 0; + + } else { + + FATAL("Error: QEMU-Nyx has died..."); + + } + + break; + case Error: + FATAL("Error: Nyx runtime error has occured..."); + break; + + } + + return FSRV_RUN_OK; + + } + +#endif /* After this memset, fsrv->trace_bits[] are effectively volatile, so we must prevent any earlier operations from venturing into that territory. */ - memset(fsrv->trace_bits, 0, fsrv->map_size); +#ifdef __linux__ + if (!fsrv->nyx_mode) { + + memset(fsrv->trace_bits, 0, fsrv->map_size); + MEM_BARRIER(); + + } +#else + memset(fsrv->trace_bits, 0, fsrv->map_size); MEM_BARRIER(); +#endif /* we have the fork server (or faux server) up and running First, tell it if the previous run timed out. */ diff --git a/src/afl-fuzz-init.c b/src/afl-fuzz-init.c index 7a8bd674..dc18f1a9 100644 --- a/src/afl-fuzz-init.c +++ b/src/afl-fuzz-init.c @@ -412,6 +412,9 @@ void bind_to_free_cpu(afl_state_t *afl) { if (bind_cpu(afl, i)) { + #ifdef __linux__ + if (afl->fsrv.nyx_mode) { afl->fsrv.nyx_bind_cpu_id = i; } + #endif /* Success :) */ break; @@ -1090,6 +1093,14 @@ void perform_dry_run(afl_state_t *afl) { FATAL("Unable to execute target application ('%s')", afl->argv[0]); case FSRV_RUN_NOINST: +#ifdef __linux__ + if (afl->fsrv.nyx_mode && afl->fsrv.nyx_runner != NULL) { + + afl->fsrv.nyx_handlers->nyx_shutdown(afl->fsrv.nyx_runner); + + } + +#endif FATAL("No instrumentation detected"); case FSRV_RUN_NOBITS: @@ -2443,6 +2454,9 @@ void fix_up_sync(afl_state_t *afl) { x = alloc_printf("%s/%s", afl->out_dir, afl->sync_id); +#ifdef __linux__ + if (afl->fsrv.nyx_mode) { afl->fsrv.out_dir_path = afl->out_dir; } +#endif afl->sync_dir = afl->out_dir; afl->out_dir = x; @@ -2580,6 +2594,28 @@ void check_binary(afl_state_t *afl, u8 *fname) { if (strchr(fname, '/') || !(env_path = getenv("PATH"))) { afl->fsrv.target_path = ck_strdup(fname); +#ifdef __linux__ + if (afl->fsrv.nyx_mode) { + + /* check if target_path is a nyx sharedir */ + if (stat(afl->fsrv.target_path, &st) || S_ISDIR(st.st_mode)) { + + char *tmp = alloc_printf("%s/config.ron", afl->fsrv.target_path); + if (stat(tmp, &st) || S_ISREG(st.st_mode)) { + + free(tmp); + return; + + } + + } + + FATAL("Directory '%s' not found or is not a nyx share directory", + afl->fsrv.target_path); + + } + +#endif if (stat(afl->fsrv.target_path, &st) || !S_ISREG(st.st_mode) || !(st.st_mode & 0111) || (f_len = st.st_size) < 4) { @@ -2719,6 +2755,9 @@ void check_binary(afl_state_t *afl, u8 *fname) { #endif /* ^!__APPLE__ */ if (!afl->fsrv.qemu_mode && !afl->fsrv.frida_mode && !afl->unicorn_mode && +#ifdef __linux__ + !afl->fsrv.nyx_mode && +#endif !afl->fsrv.cs_mode && !afl->non_instrumented_mode && !memmem(f_data, f_len, SHM_ENV_VAR, strlen(SHM_ENV_VAR) + 1)) { diff --git a/src/afl-fuzz-stats.c b/src/afl-fuzz-stats.c index 289f7e09..ba8faaf0 100644 --- a/src/afl-fuzz-stats.c +++ b/src/afl-fuzz-stats.c @@ -679,11 +679,29 @@ void show_stats(afl_state_t *afl) { banner_pad = (79 - banner_len) / 2; memset(banner, ' ', banner_pad); - sprintf(banner + banner_pad, - "%s " cLCY VERSION cLBL " {%s} " cLGN "(%s) " cPIN "[%s]", - afl->crash_mode ? cPIN "peruvian were-rabbit" - : cYEL "american fuzzy lop", - si, afl->use_banner, afl->power_name); +#ifdef __linux__ + if (afl->fsrv.nyx_mode) { + + sprintf(banner + banner_pad, + "%s " cLCY VERSION cLBL " {%s} " cLGN "(%s) " cPIN "[%s] - Nyx", + afl->crash_mode ? cPIN "peruvian were-rabbit" + : cYEL "american fuzzy lop", + si, afl->use_banner, afl->power_name); + + } else { + +#endif + sprintf(banner + banner_pad, + "%s " cLCY VERSION cLBL " {%s} " cLGN "(%s) " cPIN "[%s]", + afl->crash_mode ? cPIN "peruvian were-rabbit" + : cYEL "american fuzzy lop", + si, afl->use_banner, afl->power_name); + +#ifdef __linux__ + + } + +#endif } diff --git a/src/afl-fuzz.c b/src/afl-fuzz.c index f52637f5..5c62262e 100644 --- a/src/afl-fuzz.c +++ b/src/afl-fuzz.c @@ -105,7 +105,7 @@ static void usage(u8 *argv0, int more_help) { " -p schedule - power schedules compute a seed's performance score:\n" " fast(default), explore, exploit, seek, rare, mmopt, " "coe, lin\n" - " quad -- see docs/power_schedules.md\n" + " quad -- see docs/FAQ.md for more information\n" " -f file - location read by the fuzzed program (default: stdin " "or @@)\n" " -t msec - timeout for each run (auto-scaled, default %u ms). " @@ -124,6 +124,10 @@ static void usage(u8 *argv0, int more_help) { " -W - use qemu-based instrumentation with Wine (Wine " "mode)\n" #endif +#if defined(__linux__) + " -X - use VM fuzzing (NYX mode - standalone mode)\n" + " -Y - use VM fuzzing (NYX mode - multiple instances mode)\n" +#endif "\n" "Mutator settings:\n" @@ -385,6 +389,60 @@ static void fasan_check_afl_preload(char *afl_preload) { } + #ifdef __linux__ + #include <dlfcn.h> + +nyx_plugin_handler_t *afl_load_libnyx_plugin(u8 *libnyx_binary) { + + void * handle; + nyx_plugin_handler_t *plugin = calloc(1, sizeof(nyx_plugin_handler_t)); + + ACTF("Trying to load libnyx.so plugin..."); + handle = dlopen((char *)libnyx_binary, RTLD_NOW); + if (!handle) { goto fail; } + + plugin->nyx_new = dlsym(handle, "nyx_new"); + if (plugin->nyx_new == NULL) { goto fail; } + + plugin->nyx_shutdown = dlsym(handle, "nyx_shutdown"); + if (plugin->nyx_shutdown == NULL) { goto fail; } + + plugin->nyx_option_set_reload_mode = + dlsym(handle, "nyx_option_set_reload_mode"); + if (plugin->nyx_option_set_reload_mode == NULL) { goto fail; } + + plugin->nyx_option_set_timeout = dlsym(handle, "nyx_option_set_timeout"); + if (plugin->nyx_option_set_timeout == NULL) { goto fail; } + + plugin->nyx_option_apply = dlsym(handle, "nyx_option_apply"); + if (plugin->nyx_option_apply == NULL) { goto fail; } + + plugin->nyx_set_afl_input = dlsym(handle, "nyx_set_afl_input"); + if (plugin->nyx_set_afl_input == NULL) { goto fail; } + + plugin->nyx_exec = dlsym(handle, "nyx_exec"); + if (plugin->nyx_exec == NULL) { goto fail; } + + plugin->nyx_get_bitmap_buffer = dlsym(handle, "nyx_get_bitmap_buffer"); + if (plugin->nyx_get_bitmap_buffer == NULL) { goto fail; } + + plugin->nyx_get_bitmap_buffer_size = + dlsym(handle, "nyx_get_bitmap_buffer_size"); + if (plugin->nyx_get_bitmap_buffer_size == NULL) { goto fail; } + + OKF("libnyx plugin is ready!"); + return plugin; + +fail: + + FATAL("failed to load libnyx: %s\n", dlerror()); + free(plugin); + return NULL; + +} + + #endif + /* Main entry point */ int main(int argc, char **argv_orig, char **envp) { @@ -441,7 +499,7 @@ int main(int argc, char **argv_orig, char **envp) { while ((opt = getopt( argc, argv, - "+Ab:B:c:CdDe:E:hi:I:f:F:l:L:m:M:nNOo:p:RQs:S:t:T:UV:Wx:Z")) > + "+Ab:B:c:CdDe:E:hi:I:f:F:l:L:m:M:nNOXYo:p:RQs:S:t:T:UV:Wx:Z")) > 0) { switch (opt) { @@ -845,6 +903,29 @@ int main(int argc, char **argv_orig, char **envp) { afl->use_banner = optarg; break; + #ifdef __linux__ + case 'X': /* NYX mode */ + + if (afl->fsrv.nyx_mode) { FATAL("Multiple -X options not supported"); } + + afl->fsrv.nyx_parent = true; + afl->fsrv.nyx_standalone = true; + afl->fsrv.nyx_mode = 1; + afl->fsrv.nyx_id = 0; + + break; + + case 'Y': /* NYX distributed mode */ + if (afl->fsrv.nyx_mode) { FATAL("Multiple -Y options not supported"); } + afl->fsrv.nyx_mode = 1; + + break; + #else + case 'X': + case 'Y': + FATAL("Nyx mode is only availabe on linux..."); + break; + #endif case 'A': /* CoreSight mode */ #if !defined(__aarch64__) || !defined(__linux__) @@ -1185,6 +1266,16 @@ int main(int argc, char **argv_orig, char **envp) { OKF("NOTE: This is v3.x which changes defaults and behaviours - see " "README.md"); + #ifdef __linux__ + if (afl->fsrv.nyx_mode) { + + OKF("afl++ Nyx mode is enabled (developed and mainted by Sergej Schumilo)"); + OKF("Nyx is open source, get it at " + "https://github.com/Nyx-Fuzz"); + + } + + #endif if (afl->sync_id && afl->is_main_node && afl->afl_env.afl_custom_mutator_only) { @@ -1227,6 +1318,56 @@ int main(int argc, char **argv_orig, char **envp) { } + #ifdef __linux__ + if (afl->fsrv.nyx_mode) { + + if (afl->fsrv.nyx_standalone && + strncmp(afl->sync_id, "default", strlen("default")) != 0) { + + FATAL( + "distributed fuzzing is not supported in this Nyx mode (use -Y " + "instead)"); + + } + + if (!afl->fsrv.nyx_standalone) { + + if (afl->is_main_node) { + + if (strncmp("0", afl->sync_id, strlen("0") != 0)) { + + FATAL( + "for Nyx -Y mode, the Main (-M) parameter has to be set to 0 (-M " + "0)"); + + } + + afl->fsrv.nyx_id = 0; + + } + + if (afl->is_secondary_node) { + + long nyx_id = strtol(afl->sync_id, NULL, 10); + + if (nyx_id == 0 || nyx_id == LONG_MAX) { + + FATAL( + "for Nyx -Y mode, the Secondary (-S) parameter has to be a " + "numeric value and >= 1 (e.g. -S 1)"); + + } + + afl->fsrv.nyx_id = nyx_id; + + } + + } + + } + + #endif + if (afl->sync_id) { if (strlen(afl->sync_id) > 24) { @@ -1450,8 +1591,28 @@ int main(int argc, char **argv_orig, char **envp) { afl->fsrv.use_fauxsrv = afl->non_instrumented_mode == 1 || afl->no_forkserver; + #ifdef __linux__ + if (!afl->fsrv.nyx_mode) { + + check_crash_handling(); + check_cpu_governor(afl); + + } else { + + u8 *libnyx_binary = find_afl_binary(argv[0], "libnyx.so"); + afl->fsrv.nyx_handlers = afl_load_libnyx_plugin(libnyx_binary); + if (afl->fsrv.nyx_handlers == NULL) { + + FATAL("failed to initialize libnyx.so..."); + + } + + } + + #else check_crash_handling(); check_cpu_governor(afl); + #endif if (getenv("LD_PRELOAD")) { @@ -1935,6 +2096,14 @@ int main(int argc, char **argv_orig, char **envp) { if (!afl->pending_not_fuzzed || !valid_seeds) { + #ifdef __linux__ + if (afl->fsrv.nyx_mode) { + + afl->fsrv.nyx_handlers->nyx_shutdown(afl->fsrv.nyx_runner); + + } + + #endif FATAL("We need at least one valid input seed that does not crash!"); } diff --git a/src/afl-showmap.c b/src/afl-showmap.c index 115f9f2a..e30819b3 100644 --- a/src/afl-showmap.c +++ b/src/afl-showmap.c @@ -105,15 +105,8 @@ static sharedmem_t * shm_fuzz; static const u8 count_class_human[256] = { - [0] = 0, - [1] = 1, - [2] = 2, - [3] = 3, - [4] = 4, - [8] = 5, - [16] = 6, - [32] = 7, - [128] = 8 + [0] = 0, [1] = 1, [2] = 2, [3] = 3, [4] = 4, + [8] = 5, [16] = 6, [32] = 7, [128] = 8 }; |