mirror of
https://github.com/AFLplusplus/AFLplusplus.git
synced 2025-06-08 16:21:32 +00:00
Merge branch 'master' into shared_memory_mmap_refactor
This commit is contained in:
commit
da8e03e18a
6
Makefile
6
Makefile
@ -181,8 +181,8 @@ all_done: test_build
|
|||||||
.NOTPARALLEL: clean
|
.NOTPARALLEL: clean
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -f $(PROGS) afl-as as afl-g++ afl-clang afl-clang++ *.o *~ a.out core core.[1-9][0-9]* *.stackdump test .test .test1 .test2 test-instr .test-instr0 .test-instr1 qemu_mode/qemu-2.10.0.tar.bz2 afl-qemu-trace
|
rm -f $(PROGS) afl-as as afl-g++ afl-clang afl-clang++ *.o *~ a.out core core.[1-9][0-9]* *.stackdump test .test .test1 .test2 test-instr .test-instr0 .test-instr1 qemu_mode/qemu-3.1.0.tar.xz afl-qemu-trace
|
||||||
rm -rf out_dir qemu_mode/qemu-2.10.0
|
rm -rf out_dir qemu_mode/qemu-3.1.0
|
||||||
$(MAKE) -C llvm_mode clean
|
$(MAKE) -C llvm_mode clean
|
||||||
$(MAKE) -C libdislocator clean
|
$(MAKE) -C libdislocator clean
|
||||||
$(MAKE) -C libtokencap clean
|
$(MAKE) -C libtokencap clean
|
||||||
@ -194,7 +194,7 @@ install: all
|
|||||||
rm -f $${DESTDIR}$(BIN_PATH)/afl-as
|
rm -f $${DESTDIR}$(BIN_PATH)/afl-as
|
||||||
if [ -f afl-qemu-trace ]; then install -m 755 afl-qemu-trace $${DESTDIR}$(BIN_PATH); fi
|
if [ -f afl-qemu-trace ]; then install -m 755 afl-qemu-trace $${DESTDIR}$(BIN_PATH); fi
|
||||||
ifndef AFL_TRACE_PC
|
ifndef AFL_TRACE_PC
|
||||||
if [ -f afl-clang-fast -a -f afl-llvm-pass.so -a -f afl-llvm-rt.o ]; then set -e; install -m 755 afl-clang-fast $${DESTDIR}$(BIN_PATH); ln -sf afl-clang-fast $${DESTDIR}$(BIN_PATH)/afl-clang-fast++; install -m 755 afl-llvm-pass.so afl-llvm-rt.o $${DESTDIR}$(HELPER_PATH); fi
|
if [ -f afl-clang-fast -a -f libLLVMInsTrim.so -a -f afl-llvm-rt.o ]; then set -e; install -m 755 afl-clang-fast $${DESTDIR}$(BIN_PATH); ln -sf afl-clang-fast $${DESTDIR}$(BIN_PATH)/afl-clang-fast++; install -m 755 libLLVMInsTrim.so afl-llvm-rt.o $${DESTDIR}$(HELPER_PATH); fi
|
||||||
else
|
else
|
||||||
if [ -f afl-clang-fast -a -f afl-llvm-rt.o ]; then set -e; install -m 755 afl-clang-fast $${DESTDIR}$(BIN_PATH); ln -sf afl-clang-fast $${DESTDIR}$(BIN_PATH)/afl-clang-fast++; install -m 755 afl-llvm-rt.o $${DESTDIR}$(HELPER_PATH); fi
|
if [ -f afl-clang-fast -a -f afl-llvm-rt.o ]; then set -e; install -m 755 afl-clang-fast $${DESTDIR}$(BIN_PATH); ln -sf afl-clang-fast $${DESTDIR}$(BIN_PATH)/afl-clang-fast++; install -m 755 afl-llvm-rt.o $${DESTDIR}$(HELPER_PATH); fi
|
||||||
endif
|
endif
|
||||||
|
2
afl-as.h
2
afl-as.h
@ -189,6 +189,7 @@ static const u8* main_payload_32 =
|
|||||||
" orb $1, (%edx, %edi, 1)\n"
|
" orb $1, (%edx, %edi, 1)\n"
|
||||||
#else
|
#else
|
||||||
" incb (%edx, %edi, 1)\n"
|
" incb (%edx, %edi, 1)\n"
|
||||||
|
" adcb $0, (%edx, %edi, 1)\n" // never zero counter implementation. slightly better path discovery and little performance impact
|
||||||
#endif /* ^SKIP_COUNTS */
|
#endif /* ^SKIP_COUNTS */
|
||||||
"\n"
|
"\n"
|
||||||
"__afl_return:\n"
|
"__afl_return:\n"
|
||||||
@ -441,6 +442,7 @@ static const u8* main_payload_64 =
|
|||||||
" orb $1, (%rdx, %rcx, 1)\n"
|
" orb $1, (%rdx, %rcx, 1)\n"
|
||||||
#else
|
#else
|
||||||
" incb (%rdx, %rcx, 1)\n"
|
" incb (%rdx, %rcx, 1)\n"
|
||||||
|
" adcb $0, (%rdx, %rcx, 1)\n" // never zero counter implementation. slightly better path discovery and little performance impact
|
||||||
#endif /* ^SKIP_COUNTS */
|
#endif /* ^SKIP_COUNTS */
|
||||||
"\n"
|
"\n"
|
||||||
"__afl_return:\n"
|
"__afl_return:\n"
|
||||||
|
3913
afl-fuzz.c
3913
afl-fuzz.c
File diff suppressed because it is too large
Load Diff
12
alloc-inl.h
12
alloc-inl.h
@ -76,6 +76,17 @@
|
|||||||
|
|
||||||
/* Sanity-checking macros for pointers. */
|
/* Sanity-checking macros for pointers. */
|
||||||
|
|
||||||
|
#define CHECK_PTR(_p) do { \
|
||||||
|
if (_p) { \
|
||||||
|
if (ALLOC_C1(_p) ^ ALLOC_MAGIC_C1) {\
|
||||||
|
if (ALLOC_C1(_p) == ALLOC_MAGIC_F) \
|
||||||
|
ABORT("Use after free."); \
|
||||||
|
else ABORT("Corrupted head alloc canary."); \
|
||||||
|
} \
|
||||||
|
} \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
/*
|
||||||
#define CHECK_PTR(_p) do { \
|
#define CHECK_PTR(_p) do { \
|
||||||
if (_p) { \
|
if (_p) { \
|
||||||
if (ALLOC_C1(_p) ^ ALLOC_MAGIC_C1) {\
|
if (ALLOC_C1(_p) ^ ALLOC_MAGIC_C1) {\
|
||||||
@ -87,6 +98,7 @@
|
|||||||
ABORT("Corrupted tail alloc canary."); \
|
ABORT("Corrupted tail alloc canary."); \
|
||||||
} \
|
} \
|
||||||
} while (0)
|
} while (0)
|
||||||
|
*/
|
||||||
|
|
||||||
#define CHECK_PTR_EXPR(_p) ({ \
|
#define CHECK_PTR_EXPR(_p) ({ \
|
||||||
typeof (_p) _tmp = (_p); \
|
typeof (_p) _tmp = (_p); \
|
||||||
|
1
config.h
1
config.h
@ -83,6 +83,7 @@
|
|||||||
of 32-bit int overflows): */
|
of 32-bit int overflows): */
|
||||||
|
|
||||||
#define HAVOC_MAX_MULT 16
|
#define HAVOC_MAX_MULT 16
|
||||||
|
#define HAVOC_MAX_MULT_MOPT 32
|
||||||
|
|
||||||
/* Absolute minimum number of havoc cycles (after all adjustments): */
|
/* Absolute minimum number of havoc cycles (after all adjustments): */
|
||||||
|
|
||||||
|
@ -17,6 +17,10 @@ sending a mail to <afl-users+subscribe@googlegroups.com>.
|
|||||||
Version ++2.52d (tbd):
|
Version ++2.52d (tbd):
|
||||||
-----------------------------
|
-----------------------------
|
||||||
|
|
||||||
|
- added MOpt (github.com/puppet-meteor/MOpt-AFL) mode
|
||||||
|
- added never zero counters for afl-gcc and optional (because of an
|
||||||
|
optimization issue in llvm < 9) for llvm_mode (AFL_LLVM_NEVER_ZERO=1)
|
||||||
|
- added a new doc about binary only fuzzing: docs/binaryonly_fuzzing.txt
|
||||||
- more cpu power for afl-system-config
|
- more cpu power for afl-system-config
|
||||||
- added forkserver patch to afl-tmin, makes it much faster (originally from
|
- added forkserver patch to afl-tmin, makes it much faster (originally from
|
||||||
github.com/nccgroup/TriforceAFL)
|
github.com/nccgroup/TriforceAFL)
|
||||||
@ -27,9 +31,13 @@ Version ++2.52d (tbd):
|
|||||||
see docs/python_mutators.txt (originally by choller@mozilla)
|
see docs/python_mutators.txt (originally by choller@mozilla)
|
||||||
- added AFL_CAL_FAST for slow applications and AFL_DEBUG_CHILD_OUTPUT for
|
- added AFL_CAL_FAST for slow applications and AFL_DEBUG_CHILD_OUTPUT for
|
||||||
debugging
|
debugging
|
||||||
|
- added -V time and -E execs option to better comparison runs, runs afl-fuzz
|
||||||
|
for a specific time/executions.
|
||||||
- added a -s seed switch to allow afl run with a fixed initial
|
- added a -s seed switch to allow afl run with a fixed initial
|
||||||
seed that is not updated. this is good for performance and path discovery
|
seed that is not updated. this is good for performance and path discovery
|
||||||
tests as the random numbers are deterministic then
|
tests as the random numbers are deterministic then
|
||||||
|
- llvm_mode LAF_... env variables can now be specified as AFL_LLVM_LAF_...
|
||||||
|
that is longer but in line with other llvm specific env vars
|
||||||
- ... your idea or patch?
|
- ... your idea or patch?
|
||||||
|
|
||||||
|
|
||||||
|
@ -17,10 +17,12 @@ afl-qemu-optimize-entrypoint.diff by mh(at)mh-sec(dot)de
|
|||||||
afl-qemu-speed.diff by abiondo on github
|
afl-qemu-speed.diff by abiondo on github
|
||||||
afl-qemu-optimize-map.diff by mh(at)mh-sec(dot)de
|
afl-qemu-optimize-map.diff by mh(at)mh-sec(dot)de
|
||||||
|
|
||||||
|
+ instrim (https://github.com/csienslab/instrim) was integrated
|
||||||
|
+ MOpt (github.com/puppet-meteor/MOpt-AFL) was imported
|
||||||
+ AFLfast additions (github.com/mboehme/aflfast) were incorporated.
|
+ AFLfast additions (github.com/mboehme/aflfast) were incorporated.
|
||||||
+ Qemu 3.1 upgrade with enhancement patches (github.com/andreafioraldi/afl)
|
+ Qemu 3.1 upgrade with enhancement patches (github.com/andreafioraldi/afl)
|
||||||
+ Python mutator modules support (github.com/choeller/afl)
|
+ Python mutator modules support (github.com/choller/afl)
|
||||||
+ Whitelisting in LLVM mode (github.com/choeller/afl)
|
+ Whitelisting in LLVM mode (github.com/choller/afl)
|
||||||
+ forkserver patch for afl-tmin (github.com/nccgroup/TriforceAFL)
|
+ forkserver patch for afl-tmin (github.com/nccgroup/TriforceAFL)
|
||||||
|
|
||||||
|
|
||||||
|
30
docs/README
30
docs/README
@ -2,27 +2,31 @@
|
|||||||
american fuzzy lop plus plus
|
american fuzzy lop plus plus
|
||||||
============================
|
============================
|
||||||
|
|
||||||
Written by Michal Zalewski <lcamtuf@google.com>
|
Originally written by Michal Zalewski <lcamtuf@google.com>
|
||||||
|
|
||||||
Repository: https://github.com/vanhauser-thc/AFLplusplus
|
Repository: https://github.com/vanhauser-thc/AFLplusplus
|
||||||
|
|
||||||
afl++ is maintained by Marc Heuse <mh@mh-sec.de> and Heiko Eissfeldt
|
afl++ is maintained by Marc Heuse <mh@mh-sec.de>, Heiko Eissfeldt
|
||||||
<heiko.eissfeldt@hexco.de> as there have been no updates to afl since
|
<heiko.eissfeldt@hexco.de> and Andrea Fioraldi as there have been no
|
||||||
November 2017.
|
updates to afl since November 2017.
|
||||||
|
|
||||||
This version has several bug fixes, new features and speed enhancements
|
Many improvements were made, e.g. more performant llvm_mode, supporting
|
||||||
based on community patches from https://github.com/vanhauser-thc/afl-patches
|
llvm up to version 8, Qemu 3.1, more speed for Qemu, etc.
|
||||||
To see the list of which patches have been applied, see the PATCHES file.
|
|
||||||
|
|
||||||
Additionally AFLfast's power schedules by Marcel Boehme from
|
Additionally AFLfast's power schedules by Marcel Boehme from
|
||||||
github.com/mboehme/aflfast have been incorporated.
|
https://github.com/mboehme/aflfast have been incorporated.
|
||||||
|
|
||||||
Plus it was upgraded to qemu 3.1 from 2.1 with the work of
|
C. Hollers afl-fuzz Python mutator module and llvm_mode whitelist support
|
||||||
https://github.com/andreafioraldi/afl and got the community patches applied
|
was added too (https://github.com/choller/afl)
|
||||||
to it.
|
|
||||||
|
|
||||||
C. Hoellers afl-fuzz Python mutator module and llvm_mode whitelist support
|
New is the excellent MOpt mutator from
|
||||||
was added too (https://github.com/choeller/afl)
|
https://github.com/puppet-meteor/MOpt-AFL
|
||||||
|
|
||||||
|
Also newly integrated is instrim, a very effective CFG llvm_mode
|
||||||
|
instrumentation implementation which replaced the original afl one and is
|
||||||
|
from https://github.com/csienslab/instrim
|
||||||
|
|
||||||
|
A more thorough list is available in the PATCHES file.
|
||||||
|
|
||||||
So all in all this is the best-of AFL that is currently out there :-)
|
So all in all this is the best-of AFL that is currently out there :-)
|
||||||
|
|
||||||
|
43
docs/README.MOpt
Normal file
43
docs/README.MOpt
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
# MOpt(imized) AFL by <puppet@zju.edu.cn>
|
||||||
|
|
||||||
|
### 1. Description
|
||||||
|
MOpt-AFL is a AFL-based fuzzer that utilizes a customized Particle Swarm
|
||||||
|
Optimization (PSO) algorithm to find the optimal selection probability
|
||||||
|
distribution of operators with respect to fuzzing effectiveness.
|
||||||
|
More details can be found in the technical report.
|
||||||
|
|
||||||
|
### 2. Cite Information
|
||||||
|
Chenyang Lv, Shouling Ji, Chao Zhang, Yuwei Li, Wei-Han Lee, Yu Song and
|
||||||
|
Raheem Beyah, MOPT: Optimized Mutation Scheduling for Fuzzers,
|
||||||
|
USENIX Security 2019.
|
||||||
|
|
||||||
|
### 3. Seed Sets
|
||||||
|
We open source all the seed sets used in the paper
|
||||||
|
"MOPT: Optimized Mutation Scheduling for Fuzzers".
|
||||||
|
|
||||||
|
### 4. Experiment Results
|
||||||
|
The experiment results can be found in
|
||||||
|
https://drive.google.com/drive/folders/184GOzkZGls1H2NuLuUfSp9gfqp1E2-lL?usp=sharing. We only open source the crash files since the space is limited.
|
||||||
|
|
||||||
|
### 5. Technical Report
|
||||||
|
MOpt_TechReport.pdf is the technical report of the paper
|
||||||
|
"MOPT: Optimized Mutation Scheduling for Fuzzers", which contains more deatails.
|
||||||
|
|
||||||
|
### 6. Parameter Introduction
|
||||||
|
Most important, you must add the parameter `-L` (e.g., `-L 0`) to launch the
|
||||||
|
MOpt scheme.
|
||||||
|
<br>`-L` controls the time to move on to the pacemaker fuzzing mode.
|
||||||
|
<br>`-L t:` when MOpt-AFL finishes the mutation of one input, if it has not
|
||||||
|
discovered any new unique crash or path for more than t min, MOpt-AFL will
|
||||||
|
enter the pacemaker fuzzing mode.
|
||||||
|
<br>Setting 0 will enter the pacemaker fuzzing mode at first, which is
|
||||||
|
recommended in a short time-scale evaluation.
|
||||||
|
|
||||||
|
Other important parameters can be found in afl-fuzz.c, for instance,
|
||||||
|
<br>`swarm_num:` the number of the PSO swarms used in the fuzzing process.
|
||||||
|
<br>`period_pilot:` how many times MOpt-AFL will execute the target program in the pilot fuzzing module, then it will enter the core fuzzing module.
|
||||||
|
<br>`period_core:` how many times MOpt-AFL will execute the target program in the core fuzzing module, then it will enter the PSO updating module.
|
||||||
|
<br>`limit_time_bound:` control how many interesting test cases need to be found before MOpt-AFL quits the pacemaker fuzzing mode and reuses the deterministic stage.
|
||||||
|
0 < `limit_time_bound` < 1, MOpt-AFL-tmp. `limit_time_bound` >= 1, MOpt-AFL-ever.
|
||||||
|
|
||||||
|
Having fun with MOpt in AFL!
|
115
docs/binaryonly_fuzzing.txt
Normal file
115
docs/binaryonly_fuzzing.txt
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
|
||||||
|
Fuzzing binary-only programs with afl++
|
||||||
|
=======================================
|
||||||
|
|
||||||
|
afl++, libfuzzer and others are great if you have the source code, and
|
||||||
|
it allows for very fast and coverage guided fuzzing.
|
||||||
|
|
||||||
|
However, if there is only the binary program and not source code available,
|
||||||
|
then standard afl++ (dumb mode) is not effective.
|
||||||
|
|
||||||
|
The following is a description of how these can be fuzzed with afl++
|
||||||
|
|
||||||
|
!!!!!
|
||||||
|
DTLR: try DYNINST with afl-dyninst. If it produces too many crashes then
|
||||||
|
use afl -Q qemu_mode.
|
||||||
|
!!!!!
|
||||||
|
|
||||||
|
|
||||||
|
QEMU
|
||||||
|
----
|
||||||
|
Qemu is the "native" solution to the program.
|
||||||
|
It is available in the ./qemu_mode/ directory and once compiled it can
|
||||||
|
be accessed by the afl-fuzz -Q command line option.
|
||||||
|
The speed decrease is at about 50%
|
||||||
|
It the easiest to use alternative and even works for cross-platform binaries.
|
||||||
|
|
||||||
|
As it is included in afl++ this needs no URL.
|
||||||
|
|
||||||
|
|
||||||
|
DYNINST
|
||||||
|
-------
|
||||||
|
Dyninst is a binary instrumentation framework similar to Pintool and Dynamorio
|
||||||
|
(see far below). Howver whereas Pintool and Dynamorio work at runtime, dyninst
|
||||||
|
instruments the target at load time, and then let it run.
|
||||||
|
This is great for some things, e.g. fuzzing, and not so effective for others,
|
||||||
|
e.g. malware analysis.
|
||||||
|
|
||||||
|
So what we can do with dyninst is taking every basic block, and put afl's
|
||||||
|
instrumention code in there - and then save the binary.
|
||||||
|
Afterwards we can just fuzz the newly saved target binary with afl-fuzz.
|
||||||
|
Sounds great? It is. The issue though - this is a non-trivial problem to
|
||||||
|
insert instructions, which changes addresses in the process space and that
|
||||||
|
everything still works afterwards. Hence more often than not binaries
|
||||||
|
crash when they are run.
|
||||||
|
|
||||||
|
The speed decrease is about 15-35%, depending on the optimization options
|
||||||
|
used with afl-dyninst.
|
||||||
|
|
||||||
|
So if dyninst works, its the best option available. Otherwise it just doesn't
|
||||||
|
work well.
|
||||||
|
|
||||||
|
https://github.com/vanhauser-thc/afl-dyninst
|
||||||
|
|
||||||
|
|
||||||
|
INTEL-PT
|
||||||
|
--------
|
||||||
|
The big issue with Intel's PT is the small buffer size and the complex
|
||||||
|
encoding of the debug information collected through PT.
|
||||||
|
This makes the decoding very CPU intensive and hence slow.
|
||||||
|
As a result, the overall speed decrease is about 70-90% (depending on
|
||||||
|
the implementation and other factors)
|
||||||
|
|
||||||
|
there are two afl intel-pt implementations:
|
||||||
|
|
||||||
|
1. https://github.com/junxzm1990/afl-pt
|
||||||
|
=> this needs Ubuntu 14.04.05 without any updates and the 4.4 kernel.
|
||||||
|
|
||||||
|
2. https://github.com/hunter-ht-2018/ptfuzzer
|
||||||
|
=> this needs a 4.14 or 4.15 kernel. the "nopti" kernel boot option must
|
||||||
|
be used. This one is faster than the other.
|
||||||
|
|
||||||
|
|
||||||
|
CORESIGHT
|
||||||
|
---------
|
||||||
|
|
||||||
|
Coresight is the ARM answer to Intel's PT.
|
||||||
|
There is no implementation so far which handle coresight and getting
|
||||||
|
it working on an ARM Linux is very difficult due custom kernel building
|
||||||
|
on embedded systems is difficult. And finding one that has coresight in
|
||||||
|
the ARM chip is difficult too.
|
||||||
|
My guess is that it is slower than Qemu, but faster than Intel PT.
|
||||||
|
If anyone finds any coresight implemention for afl please ping me:
|
||||||
|
vh@thc.org
|
||||||
|
|
||||||
|
|
||||||
|
PIN & DYNAMORIO
|
||||||
|
---------------
|
||||||
|
|
||||||
|
Pintool and Dynamorio are dynamic instrumentation engines, and they can be
|
||||||
|
used for getting basic block information at runtime.
|
||||||
|
Pintool is only available for Intel x32/x64 on Linux, Mac OS and Windows
|
||||||
|
whereas Dynamorio is additionally available for ARM and AARCH64.
|
||||||
|
Dynamorio is also 10x faster than Pintool.
|
||||||
|
|
||||||
|
The big issue with Dynamorio (and therefore Pintool too) is speed.
|
||||||
|
Dynamorio has a speed decrease of 98-99%
|
||||||
|
Pintool has a speed decrease of 99.5%
|
||||||
|
|
||||||
|
Hence Dynamorio is the option to go for if everything fails, and Pintool
|
||||||
|
only if Dynamorio fails too.
|
||||||
|
|
||||||
|
Dynamorio solutions:
|
||||||
|
https://github.com/vanhauser-thc/afl-dynamorio
|
||||||
|
https://github.com/mxmssh/drAFL
|
||||||
|
https://github.com/googleprojectzero/winafl/ <= very good but windows only
|
||||||
|
|
||||||
|
Pintool solutions:
|
||||||
|
https://github.com/vanhauser-thc/afl-pin
|
||||||
|
https://github.com/mothran/aflpin
|
||||||
|
https://github.com/spinpx/afl_pin_mode <= only old Pintool version supported
|
||||||
|
|
||||||
|
|
||||||
|
That's it!
|
||||||
|
News, corrections, updates?
|
||||||
|
Email vh@thc.org
|
@ -82,6 +82,9 @@ discussed in section #1, with the exception of:
|
|||||||
- TMPDIR and AFL_KEEP_ASSEMBLY, since no temporary assembly files are
|
- TMPDIR and AFL_KEEP_ASSEMBLY, since no temporary assembly files are
|
||||||
created.
|
created.
|
||||||
|
|
||||||
|
- AFL_INST_RATIO, as we switched for instrim instrumentation which
|
||||||
|
is more effective but makes not much sense together with this option.
|
||||||
|
|
||||||
Then there are a few specific features that are only available in llvm_mode:
|
Then there are a few specific features that are only available in llvm_mode:
|
||||||
|
|
||||||
LAF-INTEL
|
LAF-INTEL
|
||||||
@ -89,11 +92,11 @@ Then there are a few specific features that are only available in llvm_mode:
|
|||||||
This great feature will split compares to series of single byte comparisons
|
This great feature will split compares to series of single byte comparisons
|
||||||
to allow afl-fuzz to find otherwise rather impossible paths.
|
to allow afl-fuzz to find otherwise rather impossible paths.
|
||||||
|
|
||||||
- Setting LAF_SPLIT_SWITCHES will split switch()es
|
- Setting AFL_LLVM_LAF_SPLIT_SWITCHES will split switch()es
|
||||||
|
|
||||||
- Setting LAF_TRANSFORM_COMPARES will split string compare functions
|
- Setting AFL_LLVM_LAF_TRANSFORM_COMPARES will split string compare functions
|
||||||
|
|
||||||
- Setting LAF_SPLIT_COMPARES will split > 8 bit CMP instructions
|
- Setting AFL_LLVM_LAF_SPLIT_COMPARES will split > 8 bit CMP instructions
|
||||||
|
|
||||||
See llvm_mode/README.laf-intel for more information.
|
See llvm_mode/README.laf-intel for more information.
|
||||||
|
|
||||||
@ -106,9 +109,17 @@ Then there are a few specific features that are only available in llvm_mode:
|
|||||||
|
|
||||||
See llvm_mode/README.whitelist for more information.
|
See llvm_mode/README.whitelist for more information.
|
||||||
|
|
||||||
Note that AFL_INST_RATIO will behave a bit differently than for afl-gcc,
|
OTHER
|
||||||
because functions are *not* instrumented unconditionally - so low values
|
=====
|
||||||
will have a more striking effect. For this tool, 0 is not a valid choice.
|
- Setting LOOPHEAD=1 optimized loops. afl-fuzz will only be able to
|
||||||
|
see the path the loop took, but not how many times it was called
|
||||||
|
(unless its a complex loop).
|
||||||
|
|
||||||
|
- Setting AFL_LLVM_NOT_ZERO=1 during compilation will use counters
|
||||||
|
that skip zero on overflow. This is the default for llvm >= 9,
|
||||||
|
however for llvm versions below that this will increase an unnecessary
|
||||||
|
slowdown due a performance issue that is only fixed in llvm 9+.
|
||||||
|
This feature increases path discovery by a little bit.
|
||||||
|
|
||||||
3) Settings for afl-fuzz
|
3) Settings for afl-fuzz
|
||||||
------------------------
|
------------------------
|
||||||
|
350
llvm_mode/LLVMInsTrim.so.cc
Normal file
350
llvm_mode/LLVMInsTrim.so.cc
Normal file
@ -0,0 +1,350 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include "llvm/ADT/DenseMap.h"
|
||||||
|
#include "llvm/ADT/DenseSet.h"
|
||||||
|
#include "llvm/IR/CFG.h"
|
||||||
|
#include "llvm/IR/Dominators.h"
|
||||||
|
#include "llvm/IR/IRBuilder.h"
|
||||||
|
#include "llvm/IR/Instructions.h"
|
||||||
|
#include "llvm/IR/LegacyPassManager.h"
|
||||||
|
#include "llvm/IR/Module.h"
|
||||||
|
#include "llvm/Pass.h"
|
||||||
|
#include "llvm/Support/raw_ostream.h"
|
||||||
|
#include "llvm/Transforms/IPO/PassManagerBuilder.h"
|
||||||
|
#include "llvm/Transforms/Utils/BasicBlockUtils.h"
|
||||||
|
#include "llvm/IR/DebugInfo.h"
|
||||||
|
#include "llvm/IR/BasicBlock.h"
|
||||||
|
#include "llvm/IR/CFG.h"
|
||||||
|
#include <unordered_set>
|
||||||
|
#include <random>
|
||||||
|
#include <list>
|
||||||
|
#include <string>
|
||||||
|
#include <fstream>
|
||||||
|
|
||||||
|
#include "../config.h"
|
||||||
|
#include "../debug.h"
|
||||||
|
|
||||||
|
#include "MarkNodes.h"
|
||||||
|
|
||||||
|
using namespace llvm;
|
||||||
|
|
||||||
|
static cl::opt<bool> MarkSetOpt("markset", cl::desc("MarkSet"),
|
||||||
|
cl::init(false));
|
||||||
|
static cl::opt<bool> LoopHeadOpt("loophead", cl::desc("LoopHead"),
|
||||||
|
cl::init(false));
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
struct InsTrim : public ModulePass {
|
||||||
|
|
||||||
|
protected:
|
||||||
|
std::list<std::string> myWhitelist;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::mt19937 generator;
|
||||||
|
int total_instr = 0;
|
||||||
|
|
||||||
|
unsigned genLabel() {
|
||||||
|
return generator() % 65536;
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
static char ID;
|
||||||
|
InsTrim() : ModulePass(ID), generator(0) {//}
|
||||||
|
|
||||||
|
// AFLCoverage() : ModulePass(ID) {
|
||||||
|
char* instWhiteListFilename = getenv("AFL_LLVM_WHITELIST");
|
||||||
|
if (instWhiteListFilename) {
|
||||||
|
std::string line;
|
||||||
|
std::ifstream fileStream;
|
||||||
|
fileStream.open(instWhiteListFilename);
|
||||||
|
if (!fileStream)
|
||||||
|
report_fatal_error("Unable to open AFL_LLVM_WHITELIST");
|
||||||
|
getline(fileStream, line);
|
||||||
|
while (fileStream) {
|
||||||
|
myWhitelist.push_back(line);
|
||||||
|
getline(fileStream, line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void getAnalysisUsage(AnalysisUsage &AU) const override {
|
||||||
|
AU.addRequired<DominatorTreeWrapperPass>();
|
||||||
|
}
|
||||||
|
|
||||||
|
#if LLVM_VERSION_MAJOR < 4
|
||||||
|
const char *
|
||||||
|
#else
|
||||||
|
StringRef
|
||||||
|
#endif
|
||||||
|
getPassName() const override {
|
||||||
|
return "InstTrim Instrumentation";
|
||||||
|
}
|
||||||
|
|
||||||
|
bool runOnModule(Module &M) override {
|
||||||
|
char be_quiet = 0;
|
||||||
|
|
||||||
|
if (isatty(2) && !getenv("AFL_QUIET")) {
|
||||||
|
SAYF(cCYA "LLVMInsTrim" VERSION cRST " by csienslab\n");
|
||||||
|
} else be_quiet = 1;
|
||||||
|
|
||||||
|
#if LLVM_VERSION_MAJOR < 9
|
||||||
|
char* neverZero_counters_str;
|
||||||
|
if ((neverZero_counters_str = getenv("AFL_LLVM_NOT_ZERO")) != NULL)
|
||||||
|
OKF("LLVM neverZero activated (by hexcoder)\n");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (getenv("LOOPHEAD")) {
|
||||||
|
LoopHeadOpt = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// this is our default
|
||||||
|
MarkSetOpt = true;
|
||||||
|
|
||||||
|
/* // I dont think this makes sense to port into LLVMInsTrim
|
||||||
|
char* inst_ratio_str = getenv("AFL_INST_RATIO");
|
||||||
|
unsigned int inst_ratio = 100;
|
||||||
|
if (inst_ratio_str) {
|
||||||
|
if (sscanf(inst_ratio_str, "%u", &inst_ratio) != 1 || !inst_ratio || inst_ratio > 100)
|
||||||
|
FATAL("Bad value of AFL_INST_RATIO (must be between 1 and 100)");
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
LLVMContext &C = M.getContext();
|
||||||
|
IntegerType *Int8Ty = IntegerType::getInt8Ty(C);
|
||||||
|
IntegerType *Int32Ty = IntegerType::getInt32Ty(C);
|
||||||
|
|
||||||
|
GlobalVariable *CovMapPtr = new GlobalVariable(
|
||||||
|
M, PointerType::getUnqual(Int8Ty), false, GlobalValue::ExternalLinkage,
|
||||||
|
nullptr, "__afl_area_ptr");
|
||||||
|
|
||||||
|
GlobalVariable *OldPrev = new GlobalVariable(
|
||||||
|
M, Int32Ty, false, GlobalValue::ExternalLinkage, 0, "__afl_prev_loc",
|
||||||
|
0, GlobalVariable::GeneralDynamicTLSModel, 0, false);
|
||||||
|
|
||||||
|
u64 total_rs = 0;
|
||||||
|
u64 total_hs = 0;
|
||||||
|
|
||||||
|
for (Function &F : M) {
|
||||||
|
if (!F.size()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!myWhitelist.empty()) {
|
||||||
|
bool instrumentBlock = false;
|
||||||
|
DebugLoc Loc;
|
||||||
|
StringRef instFilename;
|
||||||
|
|
||||||
|
for (auto &BB : F) {
|
||||||
|
BasicBlock::iterator IP = BB.getFirstInsertionPt();
|
||||||
|
IRBuilder<> IRB(&(*IP));
|
||||||
|
if (!Loc)
|
||||||
|
Loc = IP->getDebugLoc();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( Loc ) {
|
||||||
|
DILocation *cDILoc = dyn_cast<DILocation>(Loc.getAsMDNode());
|
||||||
|
|
||||||
|
unsigned int instLine = cDILoc->getLine();
|
||||||
|
instFilename = cDILoc->getFilename();
|
||||||
|
|
||||||
|
if (instFilename.str().empty()) {
|
||||||
|
/* If the original location is empty, try using the inlined location */
|
||||||
|
DILocation *oDILoc = cDILoc->getInlinedAt();
|
||||||
|
if (oDILoc) {
|
||||||
|
instFilename = oDILoc->getFilename();
|
||||||
|
instLine = oDILoc->getLine();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Continue only if we know where we actually are */
|
||||||
|
if (!instFilename.str().empty()) {
|
||||||
|
for (std::list<std::string>::iterator it = myWhitelist.begin(); it != myWhitelist.end(); ++it) {
|
||||||
|
if (instFilename.str().length() >= it->length()) {
|
||||||
|
if (instFilename.str().compare(instFilename.str().length() - it->length(), it->length(), *it) == 0) {
|
||||||
|
instrumentBlock = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Either we couldn't figure out our location or the location is
|
||||||
|
* not whitelisted, so we skip instrumentation. */
|
||||||
|
if (!instrumentBlock) {
|
||||||
|
if (!instFilename.str().empty())
|
||||||
|
SAYF(cYEL "[!] " cBRI "Not in whitelist, skipping %s ...\n", instFilename.str().c_str());
|
||||||
|
else
|
||||||
|
SAYF(cYEL "[!] " cBRI "No filename information found, skipping it");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unordered_set<BasicBlock *> MS;
|
||||||
|
if (!MarkSetOpt) {
|
||||||
|
for (auto &BB : F) {
|
||||||
|
MS.insert(&BB);
|
||||||
|
}
|
||||||
|
total_rs += F.size();
|
||||||
|
} else {
|
||||||
|
auto Result = markNodes(&F);
|
||||||
|
auto RS = Result.first;
|
||||||
|
auto HS = Result.second;
|
||||||
|
|
||||||
|
MS.insert(RS.begin(), RS.end());
|
||||||
|
if (!LoopHeadOpt) {
|
||||||
|
MS.insert(HS.begin(), HS.end());
|
||||||
|
total_rs += MS.size();
|
||||||
|
} else {
|
||||||
|
DenseSet<std::pair<BasicBlock *, BasicBlock *>> EdgeSet;
|
||||||
|
DominatorTreeWrapperPass *DTWP = &getAnalysis<DominatorTreeWrapperPass>(F);
|
||||||
|
auto DT = &DTWP->getDomTree();
|
||||||
|
|
||||||
|
total_rs += RS.size();
|
||||||
|
total_hs += HS.size();
|
||||||
|
|
||||||
|
for (BasicBlock *BB : HS) {
|
||||||
|
bool Inserted = false;
|
||||||
|
for (auto BI = pred_begin(BB), BE = pred_end(BB);
|
||||||
|
BI != BE; ++BI
|
||||||
|
) {
|
||||||
|
auto Edge = BasicBlockEdge(*BI, BB);
|
||||||
|
if (Edge.isSingleEdge() && DT->dominates(Edge, BB)) {
|
||||||
|
EdgeSet.insert({*BI, BB});
|
||||||
|
Inserted = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!Inserted) {
|
||||||
|
MS.insert(BB);
|
||||||
|
total_rs += 1;
|
||||||
|
total_hs -= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (auto I = EdgeSet.begin(), E = EdgeSet.end(); I != E; ++I) {
|
||||||
|
auto PredBB = I->first;
|
||||||
|
auto SuccBB = I->second;
|
||||||
|
auto NewBB = SplitBlockPredecessors(SuccBB, {PredBB}, ".split",
|
||||||
|
DT, nullptr,
|
||||||
|
#if LLVM_VERSION_MAJOR >= 8
|
||||||
|
nullptr,
|
||||||
|
#endif
|
||||||
|
false);
|
||||||
|
MS.insert(NewBB);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto *EBB = &F.getEntryBlock();
|
||||||
|
if (succ_begin(EBB) == succ_end(EBB)) {
|
||||||
|
MS.insert(EBB);
|
||||||
|
total_rs += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (BasicBlock &BB : F) {
|
||||||
|
if (MS.find(&BB) == MS.end()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
IRBuilder<> IRB(&*BB.getFirstInsertionPt());
|
||||||
|
IRB.CreateStore(ConstantInt::get(Int32Ty, genLabel()), OldPrev);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (BasicBlock &BB : F) {
|
||||||
|
auto PI = pred_begin(&BB);
|
||||||
|
auto PE = pred_end(&BB);
|
||||||
|
if (MarkSetOpt && MS.find(&BB) == MS.end()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
IRBuilder<> IRB(&*BB.getFirstInsertionPt());
|
||||||
|
Value *L = NULL;
|
||||||
|
if (PI == PE) {
|
||||||
|
L = ConstantInt::get(Int32Ty, genLabel());
|
||||||
|
} else {
|
||||||
|
auto *PN = PHINode::Create(Int32Ty, 0, "", &*BB.begin());
|
||||||
|
DenseMap<BasicBlock *, unsigned> PredMap;
|
||||||
|
for (auto PI = pred_begin(&BB), PE = pred_end(&BB);
|
||||||
|
PI != PE; ++PI
|
||||||
|
) {
|
||||||
|
BasicBlock *PBB = *PI;
|
||||||
|
auto It = PredMap.insert({PBB, genLabel()});
|
||||||
|
unsigned Label = It.first->second;
|
||||||
|
PN->addIncoming(ConstantInt::get(Int32Ty, Label), PBB);
|
||||||
|
}
|
||||||
|
L = PN;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Load prev_loc */
|
||||||
|
LoadInst *PrevLoc = IRB.CreateLoad(OldPrev);
|
||||||
|
PrevLoc->setMetadata(M.getMDKindID("nosanitize"), MDNode::get(C, None));
|
||||||
|
Value *PrevLocCasted = IRB.CreateZExt(PrevLoc, IRB.getInt32Ty());
|
||||||
|
|
||||||
|
/* Load SHM pointer */
|
||||||
|
LoadInst *MapPtr = IRB.CreateLoad(CovMapPtr);
|
||||||
|
MapPtr->setMetadata(M.getMDKindID("nosanitize"), MDNode::get(C, None));
|
||||||
|
Value *MapPtrIdx = IRB.CreateGEP(MapPtr, IRB.CreateXor(PrevLocCasted, L));
|
||||||
|
|
||||||
|
/* Update bitmap */
|
||||||
|
LoadInst *Counter = IRB.CreateLoad(MapPtrIdx);
|
||||||
|
Counter->setMetadata(M.getMDKindID("nosanitize"), MDNode::get(C, None));
|
||||||
|
|
||||||
|
Value *Incr = IRB.CreateAdd(Counter, ConstantInt::get(Int8Ty, 1));
|
||||||
|
|
||||||
|
#if LLVM_VERSION_MAJOR < 9
|
||||||
|
if (neverZero_counters_str != NULL) { // with llvm 9 we make this the default as the bug in llvm is then fixed
|
||||||
|
#else
|
||||||
|
#warning "neverZero implementation needs to be reviewed!"
|
||||||
|
#endif
|
||||||
|
/* hexcoder: Realize a counter that skips zero during overflow.
|
||||||
|
* Once this counter reaches its maximum value, it next increments to 1
|
||||||
|
*
|
||||||
|
* Instead of
|
||||||
|
* Counter + 1 -> Counter
|
||||||
|
* we inject now this
|
||||||
|
* Counter + 1 -> {Counter, OverflowFlag}
|
||||||
|
* Counter + OverflowFlag -> Counter
|
||||||
|
*/
|
||||||
|
auto cf = IRB.CreateICmpEQ(Incr, ConstantInt::get(Int8Ty, 0));
|
||||||
|
auto carry = IRB.CreateZExt(cf, Int8Ty);
|
||||||
|
Incr = IRB.CreateAdd(Incr, carry);
|
||||||
|
#if LLVM_VERSION_MAJOR < 9
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
IRB.CreateStore(Incr, MapPtrIdx)->setMetadata(M.getMDKindID("nosanitize"), MDNode::get(C, None));
|
||||||
|
|
||||||
|
/* Set prev_loc to cur_loc >> 1 */
|
||||||
|
/*
|
||||||
|
StoreInst *Store = IRB.CreateStore(ConstantInt::get(Int32Ty, cur_loc >> 1), AFLPrevLoc);
|
||||||
|
Store->setMetadata(M.getMDKindID("nosanitize"), MDNode::get(C, None));
|
||||||
|
*/
|
||||||
|
|
||||||
|
total_instr++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
OKF("Instrumented %u locations (%llu, %llu) (%s mode)\n"/*", ratio %u%%)."*/,
|
||||||
|
total_instr, total_rs, total_hs,
|
||||||
|
getenv("AFL_HARDEN") ? "hardened" :
|
||||||
|
((getenv("AFL_USE_ASAN") || getenv("AFL_USE_MSAN")) ?
|
||||||
|
"ASAN/MSAN" : "non-hardened")/*, inst_ratio*/);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}; // end of struct InsTrim
|
||||||
|
} // end of anonymous namespace
|
||||||
|
|
||||||
|
char InsTrim::ID = 0;
|
||||||
|
|
||||||
|
static void registerAFLPass(const PassManagerBuilder &,
|
||||||
|
legacy::PassManagerBase &PM) {
|
||||||
|
PM.add(new InsTrim());
|
||||||
|
}
|
||||||
|
|
||||||
|
static RegisterStandardPasses RegisterAFLPass(
|
||||||
|
PassManagerBuilder::EP_OptimizerLast, registerAFLPass);
|
||||||
|
|
||||||
|
static RegisterStandardPasses RegisterAFLPass0(
|
||||||
|
PassManagerBuilder::EP_EnabledOnOptLevel0, registerAFLPass);
|
@ -27,13 +27,18 @@ VERSION = $(shell grep '^\#define VERSION ' ../config.h | cut -d '"' -f2)
|
|||||||
|
|
||||||
LLVM_CONFIG ?= llvm-config
|
LLVM_CONFIG ?= llvm-config
|
||||||
LLVMVER = $(shell $(LLVM_CONFIG) --version)
|
LLVMVER = $(shell $(LLVM_CONFIG) --version)
|
||||||
#LLVM_OK = $(shell $(LLVM_CONFIG) --version | egrep -q '^[5-6]' && echo 0 || echo 1 )
|
LLVM_UNSUPPORTED = $(shell $(LLVM_CONFIG) --version | egrep -q '^9|3.0' && echo 1 || echo 0 )
|
||||||
LLVM_UNSUPPORTED = $(shell echo $(LLVMVER) | egrep -q '^9|3.0' && echo 1 || echo 1 )
|
LLVM_MAJOR = ($shell $(LLVM_CONFIG) --version | sed 's/\..*//')
|
||||||
|
|
||||||
ifeq "$(LLVM_UNSUPPORTED)" "1"
|
ifeq "$(LLVM_UNSUPPORTED)" "1"
|
||||||
$(warn llvm_mode only supports versions 3.8.0 up to 8.x )
|
$(warn llvm_mode only supports versions 3.8.0 up to 8.x )
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
# this is not visible yet:
|
||||||
|
ifeq "$(LLVM_MAJOR)" "9"
|
||||||
|
$(info llvm_mode deteted llvm 9, enabling neverZero implementation)
|
||||||
|
endif
|
||||||
|
|
||||||
CFLAGS ?= -O3 -funroll-loops
|
CFLAGS ?= -O3 -funroll-loops
|
||||||
CFLAGS += -Wall -D_FORTIFY_SOURCE=2 -g -Wno-pointer-sign \
|
CFLAGS += -Wall -D_FORTIFY_SOURCE=2 -g -Wno-pointer-sign \
|
||||||
-DAFL_PATH=\"$(HELPER_PATH)\" -DBIN_PATH=\"$(BIN_PATH)\" \
|
-DAFL_PATH=\"$(HELPER_PATH)\" -DBIN_PATH=\"$(BIN_PATH)\" \
|
||||||
@ -89,7 +94,7 @@ endif
|
|||||||
|
|
||||||
|
|
||||||
ifndef AFL_TRACE_PC
|
ifndef AFL_TRACE_PC
|
||||||
PROGS = ../afl-clang-fast ../afl-llvm-pass.so ../afl-llvm-rt.o ../afl-llvm-rt-32.o ../afl-llvm-rt-64.o ../compare-transform-pass.so ../split-compares-pass.so ../split-switches-pass.so
|
PROGS = ../afl-clang-fast ../libLLVMInsTrim.so ../afl-llvm-rt.o ../afl-llvm-rt-32.o ../afl-llvm-rt-64.o ../compare-transform-pass.so ../split-compares-pass.so ../split-switches-pass.so
|
||||||
else
|
else
|
||||||
PROGS = ../afl-clang-fast ../afl-llvm-rt.o ../afl-llvm-rt-32.o ../afl-llvm-rt-64.o ../compare-transform-pass.so ../split-compares-pass.so ../split-switches-pass.so
|
PROGS = ../afl-clang-fast ../afl-llvm-rt.o ../afl-llvm-rt-32.o ../afl-llvm-rt-64.o ../compare-transform-pass.so ../split-compares-pass.so ../split-switches-pass.so
|
||||||
endif
|
endif
|
||||||
@ -140,8 +145,8 @@ endif
|
|||||||
$(CC) $(CFLAGS) $< -o $@ $(LDFLAGS)
|
$(CC) $(CFLAGS) $< -o $@ $(LDFLAGS)
|
||||||
ln -sf afl-clang-fast ../afl-clang-fast++
|
ln -sf afl-clang-fast ../afl-clang-fast++
|
||||||
|
|
||||||
../afl-llvm-pass.so: afl-llvm-pass.so.cc | test_deps
|
../libLLVMInsTrim.so: LLVMInsTrim.so.cc MarkNodes.cc | test_deps
|
||||||
$(CXX) $(CLANG_CFL) -shared $< -o $@ $(CLANG_LFL)
|
$(CXX) $(CLANG_CFL) -DLLVMInsTrim_EXPORTS -fno-rtti -fPIC -std=gnu++11 -shared $< MarkNodes.cc -o $@ $(CLANG_LFL)
|
||||||
|
|
||||||
# laf
|
# laf
|
||||||
../split-switches-pass.so: split-switches-pass.so.cc | test_deps
|
../split-switches-pass.so: split-switches-pass.so.cc | test_deps
|
||||||
@ -165,7 +170,7 @@ endif
|
|||||||
|
|
||||||
test_build: $(PROGS)
|
test_build: $(PROGS)
|
||||||
@echo "[*] Testing the CC wrapper and instrumentation output..."
|
@echo "[*] Testing the CC wrapper and instrumentation output..."
|
||||||
unset AFL_USE_ASAN AFL_USE_MSAN AFL_INST_RATIO; AFL_QUIET=1 AFL_PATH=. AFL_CC=$(CC) LAF_SPLIT_SWITCHES=1 LAF_TRANSFORM_COMPARES=1 LAF_SPLIT_COMPARES=1 ../afl-clang-fast $(CFLAGS) ../test-instr.c -o test-instr $(LDFLAGS)
|
unset AFL_USE_ASAN AFL_USE_MSAN AFL_INST_RATIO; AFL_QUIET=1 AFL_PATH=. AFL_CC=$(CC) AFL_LLVM_LAF_SPLIT_SWITCHES=1 AFL_LLVM_LAF_TRANSFORM_COMPARES=1 AFL_LLVM_LAF_SPLIT_COMPARES=1 ../afl-clang-fast $(CFLAGS) ../test-instr.c -o test-instr $(LDFLAGS)
|
||||||
echo 0 | ../afl-showmap -m none -q -o .test-instr0 ./test-instr
|
echo 0 | ../afl-showmap -m none -q -o .test-instr0 ./test-instr
|
||||||
echo 1 | ../afl-showmap -m none -q -o .test-instr1 ./test-instr
|
echo 1 | ../afl-showmap -m none -q -o .test-instr1 ./test-instr
|
||||||
@rm -f test-instr
|
@rm -f test-instr
|
||||||
|
355
llvm_mode/MarkNodes.cc
Normal file
355
llvm_mode/MarkNodes.cc
Normal file
@ -0,0 +1,355 @@
|
|||||||
|
#include <algorithm>
|
||||||
|
#include <map>
|
||||||
|
#include <queue>
|
||||||
|
#include <set>
|
||||||
|
#include <vector>
|
||||||
|
#include "llvm/ADT/DenseMap.h"
|
||||||
|
#include "llvm/ADT/DenseSet.h"
|
||||||
|
#include "llvm/ADT/SmallVector.h"
|
||||||
|
#include "llvm/IR/BasicBlock.h"
|
||||||
|
#include "llvm/IR/CFG.h"
|
||||||
|
#include "llvm/IR/Constants.h"
|
||||||
|
#include "llvm/IR/Function.h"
|
||||||
|
#include "llvm/IR/IRBuilder.h"
|
||||||
|
#include "llvm/IR/Instructions.h"
|
||||||
|
#include "llvm/IR/Module.h"
|
||||||
|
#include "llvm/Pass.h"
|
||||||
|
#include "llvm/Support/Debug.h"
|
||||||
|
#include "llvm/Support/raw_ostream.h"
|
||||||
|
|
||||||
|
using namespace llvm;
|
||||||
|
|
||||||
|
DenseMap<BasicBlock *, uint32_t> LMap;
|
||||||
|
std::vector<BasicBlock *> Blocks;
|
||||||
|
std::set<uint32_t> Marked , Markabove;
|
||||||
|
std::vector< std::vector<uint32_t> > Succs , Preds;
|
||||||
|
|
||||||
|
void reset(){
|
||||||
|
LMap.clear();
|
||||||
|
Blocks.clear();
|
||||||
|
Marked.clear();
|
||||||
|
Markabove.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t start_point;
|
||||||
|
|
||||||
|
void labelEachBlock(Function *F) {
|
||||||
|
// Fake single endpoint;
|
||||||
|
LMap[NULL] = Blocks.size();
|
||||||
|
Blocks.push_back(NULL);
|
||||||
|
|
||||||
|
// Assign the unique LabelID to each block;
|
||||||
|
for (auto I = F->begin(), E = F->end(); I != E; ++I) {
|
||||||
|
BasicBlock *BB = &*I;
|
||||||
|
LMap[BB] = Blocks.size();
|
||||||
|
Blocks.push_back(BB);
|
||||||
|
}
|
||||||
|
|
||||||
|
start_point = LMap[&F->getEntryBlock()];
|
||||||
|
}
|
||||||
|
|
||||||
|
void buildCFG(Function *F) {
|
||||||
|
Succs.resize( Blocks.size() );
|
||||||
|
Preds.resize( Blocks.size() );
|
||||||
|
for( size_t i = 0 ; i < Succs.size() ; i ++ ){
|
||||||
|
Succs[ i ].clear();
|
||||||
|
Preds[ i ].clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
//uint32_t FakeID = 0;
|
||||||
|
for (auto S = F->begin(), E = F->end(); S != E; ++S) {
|
||||||
|
BasicBlock *BB = &*S;
|
||||||
|
uint32_t MyID = LMap[BB];
|
||||||
|
//if (succ_begin(BB) == succ_end(BB)) {
|
||||||
|
//Succs[MyID].push_back(FakeID);
|
||||||
|
//Marked.insert(MyID);
|
||||||
|
//}
|
||||||
|
for (auto I = succ_begin(BB), E = succ_end(BB); I != E; ++I) {
|
||||||
|
Succs[MyID].push_back(LMap[*I]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector< std::vector<uint32_t> > tSuccs;
|
||||||
|
std::vector<bool> tag , indfs;
|
||||||
|
|
||||||
|
void DFStree(size_t now_id) {
|
||||||
|
if(tag[now_id]) return;
|
||||||
|
tag[now_id]=true;
|
||||||
|
indfs[now_id]=true;
|
||||||
|
for (auto succ: tSuccs[now_id]) {
|
||||||
|
if(tag[succ] and indfs[succ]) {
|
||||||
|
Marked.insert(succ);
|
||||||
|
Markabove.insert(succ);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Succs[now_id].push_back(succ);
|
||||||
|
Preds[succ].push_back(now_id);
|
||||||
|
DFStree(succ);
|
||||||
|
}
|
||||||
|
indfs[now_id]=false;
|
||||||
|
}
|
||||||
|
void turnCFGintoDAG(Function *F) {
|
||||||
|
tSuccs = Succs;
|
||||||
|
tag.resize(Blocks.size());
|
||||||
|
indfs.resize(Blocks.size());
|
||||||
|
for (size_t i = 0; i < Blocks.size(); ++ i) {
|
||||||
|
Succs[i].clear();
|
||||||
|
tag[i]=false;
|
||||||
|
indfs[i]=false;
|
||||||
|
}
|
||||||
|
DFStree(start_point);
|
||||||
|
for (size_t i = 0; i < Blocks.size(); ++ i)
|
||||||
|
if( Succs[i].empty() ){
|
||||||
|
Succs[i].push_back(0);
|
||||||
|
Preds[0].push_back(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t timeStamp;
|
||||||
|
namespace DominatorTree{
|
||||||
|
std::vector< std::vector<uint32_t> > cov;
|
||||||
|
std::vector<uint32_t> dfn, nfd, par, sdom, idom, mom, mn;
|
||||||
|
|
||||||
|
bool Compare(uint32_t u, uint32_t v) {
|
||||||
|
return dfn[u] < dfn[v];
|
||||||
|
}
|
||||||
|
uint32_t eval(uint32_t u) {
|
||||||
|
if( mom[u] == u ) return u;
|
||||||
|
uint32_t res = eval( mom[u] );
|
||||||
|
if(Compare(sdom[mn[mom[u]]] , sdom[mn[u]])) {
|
||||||
|
mn[u] = mn[mom[u]];
|
||||||
|
}
|
||||||
|
return mom[u] = res;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DFS(uint32_t now) {
|
||||||
|
timeStamp += 1;
|
||||||
|
dfn[now] = timeStamp;
|
||||||
|
nfd[timeStamp - 1] = now;
|
||||||
|
for( auto succ : Succs[now] ) {
|
||||||
|
if( dfn[succ] == 0 ) {
|
||||||
|
par[succ] = now;
|
||||||
|
DFS(succ);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DominatorTree(Function *F) {
|
||||||
|
if( Blocks.empty() ) return;
|
||||||
|
uint32_t s = start_point;
|
||||||
|
|
||||||
|
// Initialization
|
||||||
|
mn.resize(Blocks.size());
|
||||||
|
cov.resize(Blocks.size());
|
||||||
|
dfn.resize(Blocks.size());
|
||||||
|
nfd.resize(Blocks.size());
|
||||||
|
par.resize(Blocks.size());
|
||||||
|
mom.resize(Blocks.size());
|
||||||
|
sdom.resize(Blocks.size());
|
||||||
|
idom.resize(Blocks.size());
|
||||||
|
|
||||||
|
for( uint32_t i = 0 ; i < Blocks.size() ; i ++ ) {
|
||||||
|
dfn[i] = 0;
|
||||||
|
nfd[i] = Blocks.size();
|
||||||
|
cov[i].clear();
|
||||||
|
idom[i] = mom[i] = mn[i] = sdom[i] = i;
|
||||||
|
}
|
||||||
|
|
||||||
|
timeStamp = 0;
|
||||||
|
DFS(s);
|
||||||
|
|
||||||
|
for( uint32_t i = Blocks.size() - 1 ; i >= 1u ; i -- ) {
|
||||||
|
uint32_t now = nfd[i];
|
||||||
|
if( now == Blocks.size() ) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for( uint32_t pre : Preds[ now ] ) {
|
||||||
|
if( dfn[ pre ] ) {
|
||||||
|
eval(pre);
|
||||||
|
if( Compare(sdom[mn[pre]], sdom[now]) ) {
|
||||||
|
sdom[now] = sdom[mn[pre]];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cov[sdom[now]].push_back(now);
|
||||||
|
mom[now] = par[now];
|
||||||
|
for( uint32_t x : cov[par[now]] ) {
|
||||||
|
eval(x);
|
||||||
|
if( Compare(sdom[mn[x]], par[now]) ) {
|
||||||
|
idom[x] = mn[x];
|
||||||
|
} else {
|
||||||
|
idom[x] = par[now];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for( uint32_t i = 1 ; i < Blocks.size() ; i += 1 ) {
|
||||||
|
uint32_t now = nfd[i];
|
||||||
|
if( now == Blocks.size() ) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if(idom[now] != sdom[now])
|
||||||
|
idom[now] = idom[idom[now]];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}; // End of DominatorTree
|
||||||
|
|
||||||
|
std::vector<uint32_t> Visited, InStack;
|
||||||
|
std::vector<uint32_t> TopoOrder, InDeg;
|
||||||
|
std::vector< std::vector<uint32_t> > t_Succ , t_Pred;
|
||||||
|
|
||||||
|
void Go(uint32_t now, uint32_t tt) {
|
||||||
|
if( now == tt ) return;
|
||||||
|
Visited[now] = InStack[now] = timeStamp;
|
||||||
|
|
||||||
|
for(uint32_t nxt : Succs[now]) {
|
||||||
|
if(Visited[nxt] == timeStamp and InStack[nxt] == timeStamp) {
|
||||||
|
Marked.insert(nxt);
|
||||||
|
}
|
||||||
|
t_Succ[now].push_back(nxt);
|
||||||
|
t_Pred[nxt].push_back(now);
|
||||||
|
InDeg[nxt] += 1;
|
||||||
|
if(Visited[nxt] == timeStamp) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Go(nxt, tt);
|
||||||
|
}
|
||||||
|
|
||||||
|
InStack[now] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TopologicalSort(uint32_t ss, uint32_t tt) {
|
||||||
|
timeStamp += 1;
|
||||||
|
|
||||||
|
Go(ss, tt);
|
||||||
|
|
||||||
|
TopoOrder.clear();
|
||||||
|
std::queue<uint32_t> wait;
|
||||||
|
wait.push(ss);
|
||||||
|
while( not wait.empty() ) {
|
||||||
|
uint32_t now = wait.front(); wait.pop();
|
||||||
|
TopoOrder.push_back(now);
|
||||||
|
for(uint32_t nxt : t_Succ[now]) {
|
||||||
|
InDeg[nxt] -= 1;
|
||||||
|
if(InDeg[nxt] == 0u) {
|
||||||
|
wait.push(nxt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector< std::set<uint32_t> > NextMarked;
|
||||||
|
bool Indistinguish(uint32_t node1, uint32_t node2) {
|
||||||
|
if(NextMarked[node1].size() > NextMarked[node2].size()){
|
||||||
|
uint32_t _swap = node1;
|
||||||
|
node1 = node2;
|
||||||
|
node2 = _swap;
|
||||||
|
}
|
||||||
|
for(uint32_t x : NextMarked[node1]) {
|
||||||
|
if( NextMarked[node2].find(x) != NextMarked[node2].end() ) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MakeUniq(uint32_t now) {
|
||||||
|
bool StopFlag = false;
|
||||||
|
if (Marked.find(now) == Marked.end()) {
|
||||||
|
for(uint32_t pred1 : t_Pred[now]) {
|
||||||
|
for(uint32_t pred2 : t_Pred[now]) {
|
||||||
|
if(pred1 == pred2) continue;
|
||||||
|
if(Indistinguish(pred1, pred2)) {
|
||||||
|
Marked.insert(now);
|
||||||
|
StopFlag = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (StopFlag) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(Marked.find(now) != Marked.end()) {
|
||||||
|
NextMarked[now].insert(now);
|
||||||
|
} else {
|
||||||
|
for(uint32_t pred : t_Pred[now]) {
|
||||||
|
for(uint32_t x : NextMarked[pred]) {
|
||||||
|
NextMarked[now].insert(x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MarkSubGraph(uint32_t ss, uint32_t tt) {
|
||||||
|
TopologicalSort(ss, tt);
|
||||||
|
if(TopoOrder.empty()) return;
|
||||||
|
|
||||||
|
for(uint32_t i : TopoOrder) {
|
||||||
|
NextMarked[i].clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
NextMarked[TopoOrder[0]].insert(TopoOrder[0]);
|
||||||
|
for(uint32_t i = 1 ; i < TopoOrder.size() ; i += 1) {
|
||||||
|
MakeUniq(TopoOrder[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MarkVertice(Function *F) {
|
||||||
|
uint32_t s = start_point;
|
||||||
|
|
||||||
|
InDeg.resize(Blocks.size());
|
||||||
|
Visited.resize(Blocks.size());
|
||||||
|
InStack.resize(Blocks.size());
|
||||||
|
t_Succ.resize(Blocks.size());
|
||||||
|
t_Pred.resize(Blocks.size());
|
||||||
|
NextMarked.resize(Blocks.size());
|
||||||
|
|
||||||
|
for( uint32_t i = 0 ; i < Blocks.size() ; i += 1 ) {
|
||||||
|
Visited[i] = InStack[i] = InDeg[i] = 0;
|
||||||
|
t_Succ[i].clear();
|
||||||
|
t_Pred[i].clear();
|
||||||
|
}
|
||||||
|
timeStamp = 0;
|
||||||
|
uint32_t t = 0;
|
||||||
|
//MarkSubGraph(s, t);
|
||||||
|
//return;
|
||||||
|
|
||||||
|
while( s != t ) {
|
||||||
|
MarkSubGraph(DominatorTree::idom[t], t);
|
||||||
|
t = DominatorTree::idom[t];
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// return {marked nodes}
|
||||||
|
std::pair<std::vector<BasicBlock *>,
|
||||||
|
std::vector<BasicBlock *> >markNodes(Function *F) {
|
||||||
|
assert(F->size() > 0 && "Function can not be empty");
|
||||||
|
|
||||||
|
reset();
|
||||||
|
labelEachBlock(F);
|
||||||
|
buildCFG(F);
|
||||||
|
turnCFGintoDAG(F);
|
||||||
|
DominatorTree::DominatorTree(F);
|
||||||
|
MarkVertice(F);
|
||||||
|
|
||||||
|
std::vector<BasicBlock *> Result , ResultAbove;
|
||||||
|
for( uint32_t x : Markabove ) {
|
||||||
|
auto it = Marked.find( x );
|
||||||
|
if( it != Marked.end() )
|
||||||
|
Marked.erase( it );
|
||||||
|
if( x )
|
||||||
|
ResultAbove.push_back(Blocks[x]);
|
||||||
|
}
|
||||||
|
for( uint32_t x : Marked ) {
|
||||||
|
if (x == 0) {
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
Result.push_back(Blocks[x]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { Result , ResultAbove };
|
||||||
|
}
|
11
llvm_mode/MarkNodes.h
Normal file
11
llvm_mode/MarkNodes.h
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
#ifndef __MARK_NODES__
|
||||||
|
#define __MARK_NODES__
|
||||||
|
|
||||||
|
#include "llvm/IR/BasicBlock.h"
|
||||||
|
#include "llvm/IR/Function.h"
|
||||||
|
#include<vector>
|
||||||
|
|
||||||
|
std::pair<std::vector<llvm::BasicBlock *>,
|
||||||
|
std::vector<llvm::BasicBlock *>> markNodes(llvm::Function *F);
|
||||||
|
|
||||||
|
#endif
|
@ -8,13 +8,13 @@ compile the target project.
|
|||||||
|
|
||||||
The following options exist:
|
The following options exist:
|
||||||
|
|
||||||
export LAF_SPLIT_SWITCHES=1 Enables the split-switches pass.
|
export AFL_LLVM_LAF_SPLIT_SWITCHES=1 Enables the split-switches pass.
|
||||||
|
|
||||||
export LAF_TRANSFORM_COMPARES=1 Enables the transform-compares pass
|
export AFL_LLVM_LAF_TRANSFORM_COMPARES=1 Enables the transform-compares pass
|
||||||
(strcmp, memcmp, strncmp, strcasecmp, strncasecmp).
|
(strcmp, memcmp, strncmp, strcasecmp, strncasecmp).
|
||||||
|
|
||||||
export LAF_SPLIT_COMPARES=1 Enables the split-compares pass.
|
export AFL_LLVM_LAF_SPLIT_COMPARES=1 Enables the split-compares pass.
|
||||||
By default it will split all compares with a bit width <= 64 bits.
|
By default it will split all compares with a bit width <= 64 bits.
|
||||||
You can change this behaviour by setting
|
You can change this behaviour by setting
|
||||||
export LAF_SPLIT_COMPARES_BITW=<bit_width>.
|
export AFL_LLVM_LAF_SPLIT_COMPARES_BITW=<bit_width>.
|
||||||
|
|
||||||
|
@ -38,8 +38,8 @@ co-exists with the original code.
|
|||||||
|
|
||||||
The idea and much of the implementation comes from Laszlo Szekeres.
|
The idea and much of the implementation comes from Laszlo Szekeres.
|
||||||
|
|
||||||
2) How to use
|
2) How to use this
|
||||||
-------------
|
------------------
|
||||||
|
|
||||||
In order to leverage this mechanism, you need to have clang installed on your
|
In order to leverage this mechanism, you need to have clang installed on your
|
||||||
system. You should also make sure that the llvm-config tool is in your path
|
system. You should also make sure that the llvm-config tool is in your path
|
||||||
@ -69,20 +69,41 @@ operating mode of AFL, e.g.:
|
|||||||
Be sure to also include CXX set to afl-clang-fast++ for C++ code.
|
Be sure to also include CXX set to afl-clang-fast++ for C++ code.
|
||||||
|
|
||||||
The tool honors roughly the same environmental variables as afl-gcc (see
|
The tool honors roughly the same environmental variables as afl-gcc (see
|
||||||
../docs/env_variables.txt). This includes AFL_INST_RATIO, AFL_USE_ASAN,
|
../docs/env_variables.txt). This includes AFL_USE_ASAN,
|
||||||
AFL_HARDEN, and AFL_DONT_OPTIMIZE.
|
AFL_HARDEN, and AFL_DONT_OPTIMIZE. However AFL_INST_RATIO is not honored
|
||||||
|
as it does not serve a good purpose with the more effective instrim CFG
|
||||||
|
analysis.
|
||||||
|
|
||||||
Note: if you want the LLVM helper to be installed on your system for all
|
Note: if you want the LLVM helper to be installed on your system for all
|
||||||
users, you need to build it before issuing 'make install' in the parent
|
users, you need to build it before issuing 'make install' in the parent
|
||||||
directory.
|
directory.
|
||||||
|
|
||||||
3) Gotchas, feedback, bugs
|
3) Options
|
||||||
|
|
||||||
|
Several options are present to make llvm_mode faster or help it rearrange
|
||||||
|
the code to make afl-fuzz path discovery easier.
|
||||||
|
|
||||||
|
If you need just to instrument specific parts of the code, you can whitelist
|
||||||
|
which C/C++ files to actually intrument. See README.whitelist
|
||||||
|
|
||||||
|
For splitting memcmp, strncmp, etc. please see README.laf-intel
|
||||||
|
|
||||||
|
As the original afl llvm_mode implementation has been replaced with
|
||||||
|
then much more effective instrim (https://github.com/csienslab/instrim/)
|
||||||
|
there is an option for optimizing loops. This optimization shows which
|
||||||
|
part of the loop has been selected, but not how many time a loop has been
|
||||||
|
called in a row (unless its a complex loop and a block inside was
|
||||||
|
instrumented). If you want to enable this set the environment variable
|
||||||
|
LOOPHEAD=1
|
||||||
|
|
||||||
|
|
||||||
|
4) Gotchas, feedback, bugs
|
||||||
--------------------------
|
--------------------------
|
||||||
|
|
||||||
This is an early-stage mechanism, so field reports are welcome. You can send bug
|
This is an early-stage mechanism, so field reports are welcome. You can send bug
|
||||||
reports to <afl-users@googlegroups.com>.
|
reports to <afl-users@googlegroups.com>.
|
||||||
|
|
||||||
4) Bonus feature #1: deferred instrumentation
|
5) Bonus feature #1: deferred instrumentation
|
||||||
---------------------------------------------
|
---------------------------------------------
|
||||||
|
|
||||||
AFL tries to optimize performance by executing the targeted binary just once,
|
AFL tries to optimize performance by executing the targeted binary just once,
|
||||||
@ -129,7 +150,7 @@ will keep working normally when compiled with a tool other than afl-clang-fast.
|
|||||||
Finally, recompile the program with afl-clang-fast (afl-gcc or afl-clang will
|
Finally, recompile the program with afl-clang-fast (afl-gcc or afl-clang will
|
||||||
*not* generate a deferred-initialization binary) - and you should be all set!
|
*not* generate a deferred-initialization binary) - and you should be all set!
|
||||||
|
|
||||||
5) Bonus feature #2: persistent mode
|
6) Bonus feature #2: persistent mode
|
||||||
------------------------------------
|
------------------------------------
|
||||||
|
|
||||||
Some libraries provide APIs that are stateless, or whose state can be reset in
|
Some libraries provide APIs that are stateless, or whose state can be reset in
|
||||||
@ -169,7 +190,7 @@ PS. Because there are task switches still involved, the mode isn't as fast as
|
|||||||
faster than the normal fork() model, and compared to in-process fuzzing,
|
faster than the normal fork() model, and compared to in-process fuzzing,
|
||||||
should be a lot more robust.
|
should be a lot more robust.
|
||||||
|
|
||||||
6) Bonus feature #3: new 'trace-pc-guard' mode
|
8) Bonus feature #3: new 'trace-pc-guard' mode
|
||||||
----------------------------------------------
|
----------------------------------------------
|
||||||
|
|
||||||
Recent versions of LLVM are shipping with a built-in execution tracing feature
|
Recent versions of LLVM are shipping with a built-in execution tracing feature
|
||||||
|
22
llvm_mode/README.neverzero
Normal file
22
llvm_mode/README.neverzero
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
Usage
|
||||||
|
=====
|
||||||
|
|
||||||
|
In larger, complex or reiterative programs the map that collects the edge pairs
|
||||||
|
can easily fill up and wrap.
|
||||||
|
This is not that much of an issue - unless by chance it wraps just to a 0
|
||||||
|
when the program execution ends.
|
||||||
|
In this case afl-fuzz is not able to see that the pair has been accessed and
|
||||||
|
will ignore it.
|
||||||
|
|
||||||
|
NeverZero prevents this behaviour. If a counter wraps, it jumps over the 0
|
||||||
|
directly to a 1. This improves path discovery (by a very little amount)
|
||||||
|
at a very little cost (one instruction per edge).
|
||||||
|
|
||||||
|
This is implemented in afl-gcc, however for llvm_mode this is optional if
|
||||||
|
the llvm version is below 9 - as there is a perfomance bug that is only fixed
|
||||||
|
in version 9 and onwards.
|
||||||
|
|
||||||
|
If you want to enable this for llvm < 9 then set
|
||||||
|
|
||||||
|
export AFL_LLVM_NOT_ZERO=1
|
||||||
|
|
@ -32,6 +32,7 @@
|
|||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
static u8* obj_path; /* Path to runtime libraries */
|
static u8* obj_path; /* Path to runtime libraries */
|
||||||
static u8** cc_params; /* Parameters passed to the real CC */
|
static u8** cc_params; /* Parameters passed to the real CC */
|
||||||
@ -87,7 +88,7 @@ static void find_obj(u8* argv0) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
FATAL("Unable to find 'afl-llvm-rt.o' or 'afl-llvm-pass.so'. Please set AFL_PATH");
|
FATAL("Unable to find 'afl-llvm-rt.o' or 'libLLVMInsTrim.so'. Please set AFL_PATH");
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -113,28 +114,28 @@ static void edit_params(u32 argc, char** argv) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* There are two ways to compile afl-clang-fast. In the traditional mode, we
|
/* There are two ways to compile afl-clang-fast. In the traditional mode, we
|
||||||
use afl-llvm-pass.so to inject instrumentation. In the experimental
|
use libLLVMInsTrim.so to inject instrumentation. In the experimental
|
||||||
'trace-pc-guard' mode, we use native LLVM instrumentation callbacks
|
'trace-pc-guard' mode, we use native LLVM instrumentation callbacks
|
||||||
instead. The latter is a very recent addition - see:
|
instead. The latter is a very recent addition - see:
|
||||||
|
|
||||||
http://clang.llvm.org/docs/SanitizerCoverage.html#tracing-pcs-with-guards */
|
http://clang.llvm.org/docs/SanitizerCoverage.html#tracing-pcs-with-guards */
|
||||||
|
|
||||||
// laf
|
// laf
|
||||||
if (getenv("LAF_SPLIT_SWITCHES")) {
|
if (getenv("LAF_SPLIT_SWITCHES")||getenv("AFL_LLVM_LAF_SPLIT_SWITCHES")) {
|
||||||
cc_params[cc_par_cnt++] = "-Xclang";
|
cc_params[cc_par_cnt++] = "-Xclang";
|
||||||
cc_params[cc_par_cnt++] = "-load";
|
cc_params[cc_par_cnt++] = "-load";
|
||||||
cc_params[cc_par_cnt++] = "-Xclang";
|
cc_params[cc_par_cnt++] = "-Xclang";
|
||||||
cc_params[cc_par_cnt++] = alloc_printf("%s/split-switches-pass.so", obj_path);
|
cc_params[cc_par_cnt++] = alloc_printf("%s/split-switches-pass.so", obj_path);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (getenv("LAF_TRANSFORM_COMPARES")) {
|
if (getenv("LAF_TRANSFORM_COMPARES")||getenv("AFL_LLVM_LAF_TRANSFORM_COMPARES")) {
|
||||||
cc_params[cc_par_cnt++] = "-Xclang";
|
cc_params[cc_par_cnt++] = "-Xclang";
|
||||||
cc_params[cc_par_cnt++] = "-load";
|
cc_params[cc_par_cnt++] = "-load";
|
||||||
cc_params[cc_par_cnt++] = "-Xclang";
|
cc_params[cc_par_cnt++] = "-Xclang";
|
||||||
cc_params[cc_par_cnt++] = alloc_printf("%s/compare-transform-pass.so", obj_path);
|
cc_params[cc_par_cnt++] = alloc_printf("%s/compare-transform-pass.so", obj_path);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (getenv("LAF_SPLIT_COMPARES")) {
|
if (getenv("LAF_SPLIT_COMPARES")||getenv("AFL_LLVM_LAF_SPLIT_COMPARES")) {
|
||||||
cc_params[cc_par_cnt++] = "-Xclang";
|
cc_params[cc_par_cnt++] = "-Xclang";
|
||||||
cc_params[cc_par_cnt++] = "-load";
|
cc_params[cc_par_cnt++] = "-load";
|
||||||
cc_params[cc_par_cnt++] = "-Xclang";
|
cc_params[cc_par_cnt++] = "-Xclang";
|
||||||
@ -150,7 +151,8 @@ static void edit_params(u32 argc, char** argv) {
|
|||||||
cc_params[cc_par_cnt++] = "-Xclang";
|
cc_params[cc_par_cnt++] = "-Xclang";
|
||||||
cc_params[cc_par_cnt++] = "-load";
|
cc_params[cc_par_cnt++] = "-load";
|
||||||
cc_params[cc_par_cnt++] = "-Xclang";
|
cc_params[cc_par_cnt++] = "-Xclang";
|
||||||
cc_params[cc_par_cnt++] = alloc_printf("%s/afl-llvm-pass.so", obj_path);
|
cc_params[cc_par_cnt++] = alloc_printf("%s/libLLVMInsTrim.so", obj_path);
|
||||||
|
// cc_params[cc_par_cnt++] = alloc_printf("%s/afl-llvm-pass.so", obj_path);
|
||||||
#endif /* ^USE_TRACE_PC */
|
#endif /* ^USE_TRACE_PC */
|
||||||
|
|
||||||
cc_params[cc_par_cnt++] = "-Qunused-arguments";
|
cc_params[cc_par_cnt++] = "-Qunused-arguments";
|
||||||
|
@ -118,6 +118,10 @@ bool AFLCoverage::runOnModule(Module &M) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if LLVM_VERSION_MAJOR < 9
|
||||||
|
char* neverZero_counters_str = getenv("AFL_LLVM_NOT_ZERO");
|
||||||
|
#endif
|
||||||
|
|
||||||
/* Get globals for the SHM region and the previous location. Note that
|
/* Get globals for the SHM region and the previous location. Note that
|
||||||
__afl_prev_loc is thread-local. */
|
__afl_prev_loc is thread-local. */
|
||||||
|
|
||||||
@ -227,21 +231,69 @@ bool AFLCoverage::runOnModule(Module &M) {
|
|||||||
|
|
||||||
LoadInst *MapPtr = IRB.CreateLoad(AFLMapPtr);
|
LoadInst *MapPtr = IRB.CreateLoad(AFLMapPtr);
|
||||||
MapPtr->setMetadata(M.getMDKindID("nosanitize"), MDNode::get(C, None));
|
MapPtr->setMetadata(M.getMDKindID("nosanitize"), MDNode::get(C, None));
|
||||||
Value *MapPtrIdx =
|
Value *MapPtrIdx = IRB.CreateGEP(MapPtr, IRB.CreateXor(PrevLocCasted, CurLoc));
|
||||||
IRB.CreateGEP(MapPtr, IRB.CreateXor(PrevLocCasted, CurLoc));
|
|
||||||
|
|
||||||
/* Update bitmap */
|
/* Update bitmap */
|
||||||
|
|
||||||
LoadInst *Counter = IRB.CreateLoad(MapPtrIdx);
|
LoadInst *Counter = IRB.CreateLoad(MapPtrIdx);
|
||||||
Counter->setMetadata(M.getMDKindID("nosanitize"), MDNode::get(C, None));
|
Counter->setMetadata(M.getMDKindID("nosanitize"), MDNode::get(C, None));
|
||||||
|
|
||||||
Value *Incr = IRB.CreateAdd(Counter, ConstantInt::get(Int8Ty, 1));
|
Value *Incr = IRB.CreateAdd(Counter, ConstantInt::get(Int8Ty, 1));
|
||||||
IRB.CreateStore(Incr, MapPtrIdx)
|
|
||||||
->setMetadata(M.getMDKindID("nosanitize"), MDNode::get(C, None));
|
#if LLVM_VERSION_MAJOR < 9
|
||||||
|
if (neverZero_counters_str != NULL) { // with llvm 9 we make this the default as the bug in llvm is then fixed
|
||||||
|
#endif
|
||||||
|
/* hexcoder: Realize a counter that skips zero during overflow.
|
||||||
|
* Once this counter reaches its maximum value, it next increments to 1
|
||||||
|
*
|
||||||
|
* Instead of
|
||||||
|
* Counter + 1 -> Counter
|
||||||
|
* we inject now this
|
||||||
|
* Counter + 1 -> {Counter, OverflowFlag}
|
||||||
|
* Counter + OverflowFlag -> Counter
|
||||||
|
*/
|
||||||
|
/* // we keep the old solutions just in case
|
||||||
|
// Solution #1
|
||||||
|
if (neverZero_counters_str[0] == '1') {
|
||||||
|
CallInst *AddOv = IRB.CreateBinaryIntrinsic(Intrinsic::uadd_with_overflow, Counter, ConstantInt::get(Int8Ty, 1));
|
||||||
|
AddOv->setMetadata(M.getMDKindID("nosanitize"), MDNode::get(C, None));
|
||||||
|
Value *SumWithOverflowBit = AddOv;
|
||||||
|
Incr = IRB.CreateAdd(IRB.CreateExtractValue(SumWithOverflowBit, 0), // sum
|
||||||
|
IRB.CreateZExt( // convert from one bit type to 8 bits type
|
||||||
|
IRB.CreateExtractValue(SumWithOverflowBit, 1), // overflow
|
||||||
|
Int8Ty));
|
||||||
|
// Solution #2
|
||||||
|
} else if (neverZero_counters_str[0] == '2') {
|
||||||
|
auto cf = IRB.CreateICmpEQ(Counter, ConstantInt::get(Int8Ty, 255));
|
||||||
|
Value *HowMuch = IRB.CreateAdd(ConstantInt::get(Int8Ty, 1), cf);
|
||||||
|
Incr = IRB.CreateAdd(Counter, HowMuch);
|
||||||
|
// Solution #3
|
||||||
|
} else if (neverZero_counters_str[0] == '3') {
|
||||||
|
*/
|
||||||
|
// this is the solution we choose because llvm9 should do the right thing here
|
||||||
|
auto cf = IRB.CreateICmpEQ(Incr, ConstantInt::get(Int8Ty, 0));
|
||||||
|
auto carry = IRB.CreateZExt(cf, Int8Ty);
|
||||||
|
Incr = IRB.CreateAdd(Incr, carry);
|
||||||
|
/*
|
||||||
|
// Solution #4
|
||||||
|
} else if (neverZero_counters_str[0] == '4') {
|
||||||
|
auto cf = IRB.CreateICmpULT(Incr, ConstantInt::get(Int8Ty, 1));
|
||||||
|
auto carry = IRB.CreateZExt(cf, Int8Ty);
|
||||||
|
Incr = IRB.CreateAdd(Incr, carry);
|
||||||
|
} else {
|
||||||
|
fprintf(stderr, "Error: unknown value for AFL_NZERO_COUNTS: %s (valid is 1-4)\n", neverZero_counters_str);
|
||||||
|
exit(-1);
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
#if LLVM_VERSION_MAJOR < 9
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
IRB.CreateStore(Incr, MapPtrIdx)->setMetadata(M.getMDKindID("nosanitize"), MDNode::get(C, None));
|
||||||
|
|
||||||
/* Set prev_loc to cur_loc >> 1 */
|
/* Set prev_loc to cur_loc >> 1 */
|
||||||
|
|
||||||
StoreInst *Store =
|
StoreInst *Store = IRB.CreateStore(ConstantInt::get(Int32Ty, cur_loc >> 1), AFLPrevLoc);
|
||||||
IRB.CreateStore(ConstantInt::get(Int32Ty, cur_loc >> 1), AFLPrevLoc);
|
|
||||||
Store->setMetadata(M.getMDKindID("nosanitize"), MDNode::get(C, None));
|
Store->setMetadata(M.getMDKindID("nosanitize"), MDNode::get(C, None));
|
||||||
|
|
||||||
inst_blocks++;
|
inst_blocks++;
|
||||||
|
@ -304,7 +304,8 @@ bool CompareTransform::transformCmps(Module &M, const bool processStrcmp, const
|
|||||||
|
|
||||||
bool CompareTransform::runOnModule(Module &M) {
|
bool CompareTransform::runOnModule(Module &M) {
|
||||||
|
|
||||||
llvm::errs() << "Running compare-transform-pass by laf.intel@gmail.com, extended by heiko@hexco.de\n";
|
if (getenv("AFL_QUIET") == NULL)
|
||||||
|
llvm::errs() << "Running compare-transform-pass by laf.intel@gmail.com, extended by heiko@hexco.de\n";
|
||||||
transformCmps(M, true, true, true, true, true);
|
transformCmps(M, true, true, true, true, true);
|
||||||
verifyModule(M);
|
verifyModule(M);
|
||||||
|
|
||||||
|
@ -477,6 +477,8 @@ bool SplitComparesTransform::runOnModule(Module &M) {
|
|||||||
int bitw = 64;
|
int bitw = 64;
|
||||||
|
|
||||||
char* bitw_env = getenv("LAF_SPLIT_COMPARES_BITW");
|
char* bitw_env = getenv("LAF_SPLIT_COMPARES_BITW");
|
||||||
|
if (!bitw_env)
|
||||||
|
bitw_env = getenv("AFL_LLVM_LAF_SPLIT_COMPARES_BITW");
|
||||||
if (bitw_env) {
|
if (bitw_env) {
|
||||||
bitw = atoi(bitw_env);
|
bitw = atoi(bitw_env);
|
||||||
}
|
}
|
||||||
@ -485,7 +487,8 @@ bool SplitComparesTransform::runOnModule(Module &M) {
|
|||||||
|
|
||||||
simplifySignedness(M);
|
simplifySignedness(M);
|
||||||
|
|
||||||
errs() << "Split-compare-pass by laf.intel@gmail.com\n";
|
if (getenv("AFL_QUIET") == NULL)
|
||||||
|
errs() << "Split-compare-pass by laf.intel@gmail.com\n";
|
||||||
|
|
||||||
switch (bitw) {
|
switch (bitw) {
|
||||||
case 64:
|
case 64:
|
||||||
|
@ -244,7 +244,8 @@ bool SplitSwitchesTransform::splitSwitches(Module &M) {
|
|||||||
|
|
||||||
/* If there is only the default destination or the condition checks 8 bit or less, don't bother with the code below. */
|
/* If there is only the default destination or the condition checks 8 bit or less, don't bother with the code below. */
|
||||||
if (!SI->getNumCases() || bitw <= 8) {
|
if (!SI->getNumCases() || bitw <= 8) {
|
||||||
errs() << "skip trivial switch..\n";
|
if (getenv("AFL_QUIET") == NULL)
|
||||||
|
errs() << "skip trivial switch..\n";
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -302,7 +303,8 @@ bool SplitSwitchesTransform::splitSwitches(Module &M) {
|
|||||||
|
|
||||||
bool SplitSwitchesTransform::runOnModule(Module &M) {
|
bool SplitSwitchesTransform::runOnModule(Module &M) {
|
||||||
|
|
||||||
llvm::errs() << "Running split-switches-pass by laf.intel@gmail.com\n";
|
if (getenv("AFL_QUIET") == NULL)
|
||||||
|
llvm::errs() << "Running split-switches-pass by laf.intel@gmail.com\n";
|
||||||
splitSwitches(M);
|
splitSwitches(M);
|
||||||
verifyModule(M);
|
verifyModule(M);
|
||||||
|
|
||||||
|
@ -9,3 +9,7 @@ simple-chunk-replace.py - this is a simple example where chunks are replaced
|
|||||||
|
|
||||||
common.py - this can be used for common functions and helpers.
|
common.py - this can be used for common functions and helpers.
|
||||||
the examples do not use this though. But you can :)
|
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
|
||||||
|
331
python_mutators/XmlMutatorMin.py
Normal file
331
python_mutators/XmlMutatorMin.py
Normal file
@ -0,0 +1,331 @@
|
|||||||
|
#!/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<func>[a-zA-Z:\-]+)\((?P<args>.*?)\)"
|
||||||
|
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)
|
||||||
|
|
117
python_mutators/wrapper_afl_min.py
Normal file
117
python_mutators/wrapper_afl_min.py
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
#!/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("<foo ddd='eeee'>ffff<a b='c' d='456' eee='ffffff'>zzzzzzzzzzzz</a><b yyy='YYY' zzz='ZZZ'></b></foo>")
|
||||||
|
in_2 = bytearray("<abc abc123='456' abcCBA='ppppppppppppppppppppppppppppp'/>")
|
||||||
|
out = fuzz(in_1, in_2)
|
||||||
|
print(out)
|
||||||
|
|
Loading…
x
Reference in New Issue
Block a user