Merge branch 'master' into shared_memory_mmap_refactor

This commit is contained in:
van Hauser 2019-07-14 20:02:20 +02:00 committed by GitHub
commit da8e03e18a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 5411 additions and 92 deletions

View File

@ -181,8 +181,8 @@ all_done: test_build
.NOTPARALLEL: 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 -rf out_dir qemu_mode/qemu-2.10.0
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-3.1.0
$(MAKE) -C llvm_mode clean
$(MAKE) -C libdislocator clean
$(MAKE) -C libtokencap clean
@ -194,7 +194,7 @@ install: all
rm -f $${DESTDIR}$(BIN_PATH)/afl-as
if [ -f afl-qemu-trace ]; then install -m 755 afl-qemu-trace $${DESTDIR}$(BIN_PATH); fi
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
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

View File

@ -189,6 +189,7 @@ static const u8* main_payload_32 =
" orb $1, (%edx, %edi, 1)\n"
#else
" 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 */
"\n"
"__afl_return:\n"
@ -441,6 +442,7 @@ static const u8* main_payload_64 =
" orb $1, (%rdx, %rcx, 1)\n"
#else
" 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 */
"\n"
"__afl_return:\n"

3911
afl-fuzz.c

File diff suppressed because it is too large Load Diff

View File

@ -76,6 +76,17 @@
/* 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 { \
if (_p) { \
if (ALLOC_C1(_p) ^ ALLOC_MAGIC_C1) {\
@ -87,6 +98,7 @@
ABORT("Corrupted tail alloc canary."); \
} \
} while (0)
*/
#define CHECK_PTR_EXPR(_p) ({ \
typeof (_p) _tmp = (_p); \

View File

@ -83,6 +83,7 @@
of 32-bit int overflows): */
#define HAVOC_MAX_MULT 16
#define HAVOC_MAX_MULT_MOPT 32
/* Absolute minimum number of havoc cycles (after all adjustments): */

View File

@ -17,6 +17,10 @@ sending a mail to <afl-users+subscribe@googlegroups.com>.
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
- added forkserver patch to afl-tmin, makes it much faster (originally from
github.com/nccgroup/TriforceAFL)
@ -27,9 +31,13 @@ Version ++2.52d (tbd):
see docs/python_mutators.txt (originally by choller@mozilla)
- added AFL_CAL_FAST for slow applications and AFL_DEBUG_CHILD_OUTPUT for
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
seed that is not updated. this is good for performance and path discovery
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?

View File

@ -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-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.
+ Qemu 3.1 upgrade with enhancement patches (github.com/andreafioraldi/afl)
+ Python mutator modules support (github.com/choeller/afl)
+ Whitelisting in LLVM mode (github.com/choeller/afl)
+ Python mutator modules support (github.com/choller/afl)
+ Whitelisting in LLVM mode (github.com/choller/afl)
+ forkserver patch for afl-tmin (github.com/nccgroup/TriforceAFL)

View File

@ -2,27 +2,31 @@
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
afl++ is maintained by Marc Heuse <mh@mh-sec.de> and Heiko Eissfeldt
<heiko.eissfeldt@hexco.de> as there have been no updates to afl since
November 2017.
afl++ is maintained by Marc Heuse <mh@mh-sec.de>, Heiko Eissfeldt
<heiko.eissfeldt@hexco.de> and Andrea Fioraldi as there have been no
updates to afl since November 2017.
This version has several bug fixes, new features and speed enhancements
based on community patches from https://github.com/vanhauser-thc/afl-patches
To see the list of which patches have been applied, see the PATCHES file.
Many improvements were made, e.g. more performant llvm_mode, supporting
llvm up to version 8, Qemu 3.1, more speed for Qemu, etc.
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
https://github.com/andreafioraldi/afl and got the community patches applied
to it.
C. Hollers afl-fuzz Python mutator module and llvm_mode whitelist support
was added too (https://github.com/choller/afl)
C. Hoellers afl-fuzz Python mutator module and llvm_mode whitelist support
was added too (https://github.com/choeller/afl)
New is the excellent MOpt mutator from
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 :-)

43
docs/README.MOpt Normal file
View 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
View 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

View File

@ -82,6 +82,9 @@ discussed in section #1, with the exception of:
- TMPDIR and AFL_KEEP_ASSEMBLY, since no temporary assembly files are
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:
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
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.
@ -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.
Note that AFL_INST_RATIO will behave a bit differently than for afl-gcc,
because functions are *not* instrumented unconditionally - so low values
will have a more striking effect. For this tool, 0 is not a valid choice.
OTHER
=====
- 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
------------------------

350
llvm_mode/LLVMInsTrim.so.cc Normal file
View 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);

View File

@ -27,13 +27,18 @@ VERSION = $(shell grep '^\#define VERSION ' ../config.h | cut -d '"' -f2)
LLVM_CONFIG ?= llvm-config
LLVMVER = $(shell $(LLVM_CONFIG) --version)
#LLVM_OK = $(shell $(LLVM_CONFIG) --version | egrep -q '^[5-6]' && echo 0 || echo 1 )
LLVM_UNSUPPORTED = $(shell echo $(LLVMVER) | egrep -q '^9|3.0' && echo 1 || echo 1 )
LLVM_UNSUPPORTED = $(shell $(LLVM_CONFIG) --version | egrep -q '^9|3.0' && echo 1 || echo 0 )
LLVM_MAJOR = ($shell $(LLVM_CONFIG) --version | sed 's/\..*//')
ifeq "$(LLVM_UNSUPPORTED)" "1"
$(warn llvm_mode only supports versions 3.8.0 up to 8.x )
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 += -Wall -D_FORTIFY_SOURCE=2 -g -Wno-pointer-sign \
-DAFL_PATH=\"$(HELPER_PATH)\" -DBIN_PATH=\"$(BIN_PATH)\" \
@ -89,7 +94,7 @@ endif
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
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
@ -140,8 +145,8 @@ endif
$(CC) $(CFLAGS) $< -o $@ $(LDFLAGS)
ln -sf afl-clang-fast ../afl-clang-fast++
../afl-llvm-pass.so: afl-llvm-pass.so.cc | test_deps
$(CXX) $(CLANG_CFL) -shared $< -o $@ $(CLANG_LFL)
../libLLVMInsTrim.so: LLVMInsTrim.so.cc MarkNodes.cc | test_deps
$(CXX) $(CLANG_CFL) -DLLVMInsTrim_EXPORTS -fno-rtti -fPIC -std=gnu++11 -shared $< MarkNodes.cc -o $@ $(CLANG_LFL)
# laf
../split-switches-pass.so: split-switches-pass.so.cc | test_deps
@ -165,7 +170,7 @@ endif
test_build: $(PROGS)
@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 1 | ../afl-showmap -m none -q -o .test-instr1 ./test-instr
@rm -f test-instr

355
llvm_mode/MarkNodes.cc Normal file
View 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
View 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

View File

@ -8,13 +8,13 @@ compile the target project.
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).
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.
You can change this behaviour by setting
export LAF_SPLIT_COMPARES_BITW=<bit_width>.
export AFL_LLVM_LAF_SPLIT_COMPARES_BITW=<bit_width>.

View File

@ -38,8 +38,8 @@ co-exists with the original code.
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
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.
The tool honors roughly the same environmental variables as afl-gcc (see
../docs/env_variables.txt). This includes AFL_INST_RATIO, AFL_USE_ASAN,
AFL_HARDEN, and AFL_DONT_OPTIMIZE.
../docs/env_variables.txt). This includes AFL_USE_ASAN,
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
users, you need to build it before issuing 'make install' in the parent
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
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,
@ -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
*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
@ -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,
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

View 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

View File

@ -32,6 +32,7 @@
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
static u8* obj_path; /* Path to runtime libraries */
static u8** cc_params; /* Parameters passed to the real CC */
@ -87,7 +88,7 @@ static void find_obj(u8* argv0) {
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
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
instead. The latter is a very recent addition - see:
http://clang.llvm.org/docs/SanitizerCoverage.html#tracing-pcs-with-guards */
// 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++] = "-load";
cc_params[cc_par_cnt++] = "-Xclang";
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++] = "-load";
cc_params[cc_par_cnt++] = "-Xclang";
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++] = "-load";
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++] = "-load";
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 */
cc_params[cc_par_cnt++] = "-Qunused-arguments";

View File

@ -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
__afl_prev_loc is thread-local. */
@ -227,21 +231,69 @@ bool AFLCoverage::runOnModule(Module &M) {
LoadInst *MapPtr = IRB.CreateLoad(AFLMapPtr);
MapPtr->setMetadata(M.getMDKindID("nosanitize"), MDNode::get(C, None));
Value *MapPtrIdx =
IRB.CreateGEP(MapPtr, IRB.CreateXor(PrevLocCasted, CurLoc));
Value *MapPtrIdx = IRB.CreateGEP(MapPtr, IRB.CreateXor(PrevLocCasted, CurLoc));
/* 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));
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 */
StoreInst *Store =
IRB.CreateStore(ConstantInt::get(Int32Ty, cur_loc >> 1), AFLPrevLoc);
StoreInst *Store = IRB.CreateStore(ConstantInt::get(Int32Ty, cur_loc >> 1), AFLPrevLoc);
Store->setMetadata(M.getMDKindID("nosanitize"), MDNode::get(C, None));
inst_blocks++;

View File

@ -304,6 +304,7 @@ bool CompareTransform::transformCmps(Module &M, const bool processStrcmp, const
bool CompareTransform::runOnModule(Module &M) {
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);
verifyModule(M);

View File

@ -477,6 +477,8 @@ bool SplitComparesTransform::runOnModule(Module &M) {
int bitw = 64;
char* bitw_env = getenv("LAF_SPLIT_COMPARES_BITW");
if (!bitw_env)
bitw_env = getenv("AFL_LLVM_LAF_SPLIT_COMPARES_BITW");
if (bitw_env) {
bitw = atoi(bitw_env);
}
@ -485,6 +487,7 @@ bool SplitComparesTransform::runOnModule(Module &M) {
simplifySignedness(M);
if (getenv("AFL_QUIET") == NULL)
errs() << "Split-compare-pass by laf.intel@gmail.com\n";
switch (bitw) {

View File

@ -244,6 +244,7 @@ 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 (!SI->getNumCases() || bitw <= 8) {
if (getenv("AFL_QUIET") == NULL)
errs() << "skip trivial switch..\n";
continue;
}
@ -302,6 +303,7 @@ bool SplitSwitchesTransform::splitSwitches(Module &M) {
bool SplitSwitchesTransform::runOnModule(Module &M) {
if (getenv("AFL_QUIET") == NULL)
llvm::errs() << "Running split-switches-pass by laf.intel@gmail.com\n";
splitSwitches(M);
verifyModule(M);

View File

@ -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.
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

View 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)

View 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)