diff options
-rw-r--r-- | CHANGES | 4 | ||||
-rw-r--r-- | README.txt | 4 | ||||
-rw-r--r-- | afl-dyninst.cpp | 230 | ||||
-rwxr-xr-x | afl-fuzz-dyninst.sh | 3 | ||||
-rw-r--r-- | libAflDyninst.cpp | 89 |
5 files changed, 262 insertions, 68 deletions
diff --git a/CHANGES b/CHANGES index fb19b41..b5ff722 100644 --- a/CHANGES +++ b/CHANGES @@ -2,6 +2,10 @@ Changelog ========= https://github.com/vanhauser-thc/afl-dyninst + - -e option now also understands function names, not only 0x1234 addresses + - searches for multiple entrypoints now: main, init, start and _NAME variants + - afl-dyninst now works fine with AARCH64 :) + - more verbose output, -vv -vvv support - fixed some typos - renamed afl-fuzz.sh to afl-fuzz-dyninst.sh and make install installs now the scripts diff --git a/README.txt b/README.txt index 24081a4..7d99e3d 100644 --- a/README.txt +++ b/README.txt @@ -69,12 +69,14 @@ This options is mainly to hunt down bugs in dyninst. Switch -D installs the afl fork server and forced exit functions but no basic block instrumentation. That would serve no purpose - unless there is -another interesting tool coming up ... :) +another interesting tool coming up: afl-pin (already available at +https://github.com/vanhauser-thc/afl-pin) and afl-dynamorio (wip) Compiling: ---------- +0. Clone, compile and install dyninst: https://github.com/dyninst/dyninst/ 1. Edit the Makefile and set DYNINST_ROOT and AFL_ROOT to appropriate paths. 2. make 3. make install diff --git a/afl-dyninst.cpp b/afl-dyninst.cpp index 5ba3d3c..b908b24 100644 --- a/afl-dyninst.cpp +++ b/afl-dyninst.cpp @@ -1,14 +1,19 @@ +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <getopt.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <unistd.h> +#include <string.h> +#include <sys/mman.h> #include <cstdlib> #include <iostream> #include <vector> #include <string> -#include <stdlib.h> #include <sstream> #include <climits> -using namespace std; - -// Command line parsing -#include <getopt.h> // DyninstAPI includes #include "BPatch.h" @@ -16,14 +21,18 @@ using namespace std; #include "BPatch_flowGraph.h" #include "BPatch_function.h" #include "BPatch_point.h" -#include "dyninstversion.h" +#include "BPatch_addressSpace.h" +#include "BPatch_process.h" +#include "dyninstversion.h" // if this include errors, compile and install https://github.com/dyninst/dyninst +using namespace std; using namespace Dyninst; //cmd line options char *originalBinary; char *instrumentedBinary; -bool verbose = false; +char *entryPointName = NULL; +int verbose = 0; Dyninst::Address entryPoint; set < string > instrumentLibraries; @@ -31,12 +40,16 @@ set < string > runtimeLibraries; set < string > skipAddresses; set < unsigned long > exitAddresses; unsigned int bbMinSize = 1; -int bbSkip = 0; -bool skipMainModule = false, do_bb = true, dynfix = false, performance = false; +int bbSkip = 0, performance = 0; +bool skipMainModule = false, do_bb = true, dynfix = false; +unsigned long int insertions = 0; +uintptr_t mapaddr = 0; BPatch_function *save_rdi; BPatch_function *restore_rdi; +const char *functions[] = { "main", "_main", "_initproc", "_init", "start", "_start", NULL }; + const char *instLibrary = "libAflDyninst.so"; static const char *OPT_STR = "fi:o:l:e:E:vs:dr:m:S:Dx"; @@ -50,7 +63,7 @@ static const char *USAGE = "-dfvxD -i <binary> -o <binary> -l <library> -e <addr -E: exit point - force exit(0) at this address (repeat for more than one)\n \ -s: number of initial basic blocks to skip in binary\n \ -m: minimum size of a basic bock to instrument (default: 1)\n \ - -f: try to fix a dyninst bug that leads to crashes\n \ + -f: try to fix a dyninst bug that leads to crashes (loss of 20%% performance)\n \ -S: do not instrument this function (repeat for more than one)\n \ -D: instrument only a simple fork server and also forced exit functions\n \ -x: experimental performance mode\n \ @@ -62,13 +75,25 @@ bool parseOptions(int argc, char **argv) { while ((c = getopt(argc, argv, OPT_STR)) != -1) { switch ((char) c) { case 'x': - performance = true; + performance++; + if (performance == 3) { +#if ( __amd64__ || __x86_64__ ) + fprintf(stderr, "Warning: performance level 3 is currently totally experimental\n"); +#else + fprintf(stderr, "Warning: maximum performance level for non-intelx64 x86 is 2\n"); + performance = 2; +#endif + } else if (performance > 3) { + fprintf(stderr, "Warning: maximum performance level is 3\n"); + performance = 3; + } break; case 'S': skipAddresses.insert(optarg); break; case 'e': - entryPoint = strtoul(optarg, NULL, 16); + if ((entryPoint = strtoul(optarg, NULL, 16)) < 0x1000) + entryPointName = optarg; break; case 'i': originalBinary = optarg; @@ -96,13 +121,15 @@ bool parseOptions(int argc, char **argv) { skipMainModule = true; break; case 'f': +#if (__amd64__ || __x86_64__) dynfix = true; +#endif break; case 'D': do_bb = false; break; case 'v': - verbose = true; + verbose++; break; default: cerr << "Usage: " << argv[0] << USAGE; @@ -110,6 +137,11 @@ bool parseOptions(int argc, char **argv) { } } + if (performance > 0 && do_bb == false) { + cerr << "Warning: -x performance options only enhance basic block coverage, not forkserver only mode" << endl; + performance = 0; + } + if (originalBinary == NULL) { cerr << "Input binary is required!" << endl; cerr << "Usage: " << argv[0] << USAGE; @@ -192,7 +224,7 @@ bool insertBBCallback(BPatch_binaryEdit *appBin, BPatch_function *curFunc, char BPatch_Set < BPatch_basicBlock * >::iterator iter; for (iter = allBlocks.begin(); iter != allBlocks.end(); iter++) { - if (*bbIndex < bbSkip || (*iter)->size() < bbMinSize) { // skip over first bbSkip bbs + if (*bbIndex < bbSkip || (*iter)->size() < bbMinSize) { // skip over first bbSkip bbs or below minimum size (*bbIndex)++; continue; } @@ -232,7 +264,8 @@ bool insertBBCallback(BPatch_binaryEdit *appBin, BPatch_function *curFunc, char cerr << "Failed to insert instrumention in basic block at 0x" << hex << address << endl; (*bbIndex)++; continue; - } + } else + insertions++; (*bbIndex)++; } @@ -241,6 +274,9 @@ bool insertBBCallback(BPatch_binaryEdit *appBin, BPatch_function *curFunc, char } int main(int argc, char **argv) { + char *func2patch = NULL; + int loop; + if (argc < 3 || strncmp(argv[1], "-h", 2) == 0 || strncmp(argv[1], "--h", 3) == 0) { cout << "Usage: " << argv[0] << USAGE; return false; @@ -249,9 +285,10 @@ int main(int argc, char **argv) { if (!parseOptions(argc, argv)) { return EXIT_FAILURE; } - +#if (__amd64__ || __x86_64__) if (do_bb == true) { - if (DYNINST_MAJOR_VERSION < 9 || (DYNINST_MAJOR_VERSION == 9 && DYNINST_MINOR_VERSION < 3) || (DYNINST_MAJOR_VERSION == 9 && DYNINST_MINOR_VERSION == 3 && DYNINST_PATCH_VERSION <= 2)) { + if (DYNINST_MAJOR_VERSION < 9 || (DYNINST_MAJOR_VERSION == 9 && DYNINST_MINOR_VERSION < 3) + || (DYNINST_MAJOR_VERSION == 9 && DYNINST_MINOR_VERSION == 3 && DYNINST_PATCH_VERSION <= 2)) { if (dynfix == false) fprintf(stderr, "Warning: your dyninst version does not include a critical fix, you should use the -f option!\n"); } else { @@ -259,6 +296,7 @@ int main(int argc, char **argv) { fprintf(stderr, "Notice: your dyninst version is fixed, the -f option should not be necessary.\n"); } } +#endif BPatch bpatch; BPatch_binaryEdit *appBin = bpatch.openBinary(originalBinary, instrumentLibraries.size() != 1); @@ -274,25 +312,34 @@ int main(int argc, char **argv) { vector < BPatch_module * >*modules = appImage->getModules(); vector < BPatch_module * >::iterator moduleIter; vector < BPatch_function * >*funcsInModule; - BPatch_module *defaultModule = NULL; + BPatch_module *defaultModule = NULL, *firstModule = NULL; string defaultModuleName; // look for _init if (defaultModuleName.empty()) { + for (loop = 0; functions[loop] != NULL && func2patch == NULL; loop++) { for (moduleIter = modules->begin(); moduleIter != modules->end(); ++moduleIter) { - funcsInModule = (*moduleIter)->getProcedures(); vector < BPatch_function * >::iterator funcsIterator; + char moduleName[1024]; + + if (firstModule == NULL) + firstModule = (*moduleIter); + (*moduleIter)->getName(moduleName, 1024); + funcsInModule = (*moduleIter)->getProcedures(); + if (verbose >= 2) + cout << "Looking for init function " << functions[loop] << " in " << moduleName << endl; for (funcsIterator = funcsInModule->begin(); funcsIterator != funcsInModule->end(); ++funcsIterator) { char funcName[1024]; (*funcsIterator)->getName(funcName, 1024); - if (string(funcName) == string("_init")) { - char moduleName[1024]; - - (*moduleIter)->getName(moduleName, 1024); + if (verbose >= 3 && loop == 0) + printf("module: %s function: %s\n", moduleName, funcName); + if (string(funcName) == string(functions[loop])) { + func2patch = (char *) functions[loop]; defaultModuleName = string(moduleName); - if (verbose) { - cout << "Found _init in " << moduleName << endl; + defaultModule = (*moduleIter); + if (verbose >= 1) { + cout << "Found " << func2patch << " in " << moduleName << endl; } break; } @@ -300,10 +347,15 @@ int main(int argc, char **argv) { if (!defaultModuleName.empty()) break; } + if (func2patch != NULL) + break; + } } // last resort, by name of the binary if (defaultModuleName.empty()) defaultModuleName = string(originalBinary).substr(string(originalBinary).find_last_of("\\/") + 1); + if (defaultModule == NULL) + defaultModule = firstModule; if (!appBin->loadLibrary(instLibrary)) { cerr << "Failed to open instrumentation library " << instLibrary << endl; @@ -311,18 +363,20 @@ int main(int argc, char **argv) { return EXIT_FAILURE; } - appImage = appBin->getImage(); - /* Find code coverage functions in the instrumentation library */ BPatch_function *initAflForkServer; + save_rdi = findFuncByName(appImage, (char *) "save_rdi"); restore_rdi = findFuncByName(appImage, (char *) "restore_rdi"); BPatch_function *bbCallback = findFuncByName(appImage, (char *) "bbCallback"); BPatch_function *forceCleanExit = findFuncByName(appImage, (char *) "forceCleanExit"); - if (do_bb == true) - initAflForkServer = findFuncByName(appImage, (char *) "initAflForkServer"); + if (do_bb == true) { + if (performance >= 3) + initAflForkServer = findFuncByName(appImage, (char *) "initAflForkServerVar"); else + initAflForkServer = findFuncByName(appImage, (char *) "initAflForkServer"); + } else initAflForkServer = findFuncByName(appImage, (char *) "initOnlyAflForkServer"); if (!initAflForkServer || !bbCallback || !save_rdi || !restore_rdi || !forceCleanExit) { @@ -332,7 +386,77 @@ int main(int argc, char **argv) { int bbIndex = 0; - // instrument all shared libraries: + // if an entrypoint was set then find function, else find _init + BPatch_function *funcToPatch = NULL; + + if (entryPoint == 0 && entryPointName == NULL) { + if (func2patch == NULL) { + cerr << "Couldn't locate _init, specify entry point manually with -e 0xaddr" << endl; + return EXIT_FAILURE; + } + BPatch_Vector < BPatch_function * >funcs; + defaultModule->findFunction(func2patch, funcs); + if (!funcs.size()) { + cerr << "Couldn't locate _init, specify entry point manually with -e 0xaddr" << endl; + return EXIT_FAILURE; + } + // there should really be only one + funcToPatch = funcs[0]; + } else { + if (entryPointName != NULL) { + for (moduleIter = modules->begin(); moduleIter != modules->end() && funcToPatch == 0; ++moduleIter) { + BPatch_Vector < BPatch_function * >funcs; + (*moduleIter)->findFunction(entryPointName, funcs); + if (funcs.size() > 0) { + char moduleName[1024]; + + funcToPatch = funcs[0]; + defaultModule = (*moduleIter); + defaultModule->getName(moduleName, 1024); + defaultModuleName = string(moduleName); + printf("Found entypoint %s in module %s\n", entryPointName, moduleName); + break; + } + } + } + if (!funcToPatch) { + if (verbose > 1) + printf("Looking for entrypoint %p\n", (char *) entryPoint); + funcToPatch = defaultModule->findFunctionByEntry(entryPoint); + if (!funcToPatch && defaultModule != firstModule) { + funcToPatch = firstModule->findFunctionByEntry(entryPoint); + if (funcToPatch) + defaultModule = firstModule; + } + if (!funcToPatch) { // ok lets go hardcore ... + if (verbose > 1) + printf("OK we did not find the entrypoint so far, lets dig deeper ...\n"); + for (moduleIter = modules->begin(); moduleIter != modules->end() && funcToPatch != NULL; ++moduleIter) { + vector < BPatch_function * >::iterator funcsIterator; + funcToPatch = (*moduleIter)->findFunctionByEntry(entryPoint); + if (funcToPatch) + defaultModule = (*moduleIter); + } + } + if (funcToPatch && verbose >= 1) { + char moduleName[1024]; + + defaultModule->getName(moduleName, 1024); + defaultModuleName = string(moduleName); + printf("Found entypoint %p in module %s\n", (void *) entryPoint, moduleName); + } + } + } + if (!funcToPatch) { + cerr << "Couldn't locate function at given entry point. " << endl; + cerr << "Try: readelf -ls " << originalBinary << " | egrep 'Entry|FUNC.*GLOBAL.*DEFAULT' | egrep -v '@|UND'" << endl; + return EXIT_FAILURE; + } + if (!insertCallToInit(appBin, initAflForkServer, defaultModule, funcToPatch)) { + cerr << "Could not insert init callback at given entry point." << endl; + return EXIT_FAILURE; + } + for (moduleIter = modules->begin(); moduleIter != modules->end(); ++moduleIter) { char moduleName[1024]; @@ -346,7 +470,6 @@ int main(int argc, char **argv) { } if (string(moduleName).find(defaultModuleName) != string::npos) { - defaultModule = (*moduleIter); if (skipMainModule) continue; } @@ -363,9 +486,9 @@ int main(int argc, char **argv) { int do_patch = 1; curFunc->getName(funcName, 1024); - if (string(funcName) == string("_start")) + if (string(funcName) == string("_init") || string(funcName) == string("__libc_csu_init") || string(funcName) == string("_start") + ) continue; // here's a bug on hlt // XXX: check what happens if removed - if (!skipAddresses.empty()) { set < string >::iterator saiter; for (saiter = skipAddresses.begin(); saiter != skipAddresses.end() && do_patch == 1; saiter++) @@ -381,30 +504,6 @@ int main(int argc, char **argv) { } } - // if an entrypoint was set then find function, else find _init - BPatch_function *funcToPatch = NULL; - - if (!entryPoint) { - BPatch_Vector < BPatch_function * >funcs; - defaultModule->findFunction("_init", funcs); - if (!funcs.size()) { - cerr << "Couldn't locate _init, specify entry point manually with -e 0xaddr" << endl; - return EXIT_FAILURE; - } - // there should really be only one - funcToPatch = funcs[0]; - } else { - funcToPatch = defaultModule->findFunctionByEntry(entryPoint); - } - if (!funcToPatch) { - cerr << "Couldn't locate function at given entry point. " << endl; - return EXIT_FAILURE; - } - if (!insertCallToInit(appBin, initAflForkServer, defaultModule, funcToPatch)) { - cerr << "Could not insert init callback at given entry point." << endl; - return EXIT_FAILURE; - } - if (!exitAddresses.empty()) { cout << "Instrumenting forced exit addresses." << endl; set < unsigned long >::iterator uliter; @@ -442,10 +541,8 @@ int main(int argc, char **argv) { vector < BPatch_module * >*modules = libImg->getModules(); moduleIter = modules->begin(); - for (; moduleIter != modules->end(); ++moduleIter) { char moduleName[1024]; - (*moduleIter)->getName(moduleName, 1024); cout << "Instrumenting module: " << moduleName << endl; vector < BPatch_function * >*allFunctions = (*moduleIter)->getProcedures(); @@ -454,10 +551,22 @@ int main(int argc, char **argv) { for (funcIter = allFunctions->begin(); funcIter != allFunctions->end(); ++funcIter) { BPatch_function *curFunc = *funcIter; char funcName[1024]; + int do_patch = 1; curFunc->getName(funcName, 1024); - if (string(funcName) == string("_start")) + if (string(funcName) == string("_init") || string(funcName) == string("__libc_csu_init") || string(funcName) == string("_start")) continue; + if (!skipAddresses.empty()) { + set < string >::iterator saiter; + for (saiter = skipAddresses.begin(); saiter != skipAddresses.end() && do_patch == 1; saiter++) + if (*saiter == string(funcName)) + do_patch = 0; + if (do_patch == 0) { + cout << "Skipping instrumenting function " << funcName << endl; + continue; + } + } + insertBBCallback(libBin, curFunc, funcName, bbCallback, &bbIndex); } } @@ -470,6 +579,7 @@ int main(int argc, char **argv) { } } + printf("Did a total of %lu basic block insertions\n", insertions); cout << "All done! Happy fuzzing!" << endl; return EXIT_SUCCESS; } diff --git a/afl-fuzz-dyninst.sh b/afl-fuzz-dyninst.sh index bb10a95..b5370cb 100755 --- a/afl-fuzz-dyninst.sh +++ b/afl-fuzz-dyninst.sh @@ -1,5 +1,8 @@ #!/bin/bash test -z "$1" -o "$1" = "-h" && { echo Syntax: $0 afl-fuzz-options ; echo sets the afl-dyninst environment variables ; exit 1 ; } +sysctl -w kernel.core_pattern="core" > /dev/null +sysctl -w kernel.randomize_va_space=0 > /dev/null +echo performance | tee /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor > /dev/null export AFL_SKIP_BIN_CHECK=1 export DYNINSTAPI_RT_LIB=/usr/local/lib/libdyninstAPI_RT.so export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:. diff --git a/libAflDyninst.cpp b/libAflDyninst.cpp index d556a22..fb0ba9c 100644 --- a/libAflDyninst.cpp +++ b/libAflDyninst.cpp @@ -14,17 +14,25 @@ using namespace std; -static u8 *trace_bits; +static u8 dummy[65536]; +static u8 *trace_bits = dummy; // this saves a test + jz instruction static s32 shm_id; static int __afl_temp_data; static pid_t __afl_fork_pid; -static unsigned short prev_id; +static unsigned short int prev_id = 0; +static bool forkserver_installed = false; +#if (__amd64__ || __x86_64__) static long saved_di; register long rdi asm("di"); // the warning is fine - we need the warning because of a bug in dyninst +#endif #define PRINT_ERROR(string) (void)(write(2, string, strlen(string))+1) // the (...+1) weirdness is so we do not get an ignoring return value warning void initAflForkServer() { + if (forkserver_installed == true) + return; + forkserver_installed = true; + // we can not use fprint* stdout/stderr functions here, it fucks up some programs char *shm_env_var = getenv(SHM_ENV_VAR); @@ -60,6 +68,7 @@ void initAflForkServer() { if (__afl_fork_pid == 0) { close(FORKSRV_FD); close(FORKSRV_FD + 1); + prev_id = 0; break; } else { // parrent stuff @@ -76,10 +85,8 @@ void initAflForkServer() { // Should be called on basic block entry void bbCallback(unsigned short id) { - if (trace_bits) { - trace_bits[prev_id ^ id]++; - prev_id = id >> 1; - } + trace_bits[prev_id ^ id]++; + prev_id = id >> 1; } void forceCleanExit() { @@ -87,15 +94,22 @@ void forceCleanExit() { } void save_rdi() { +#if __amd64__ || __x86_64__ saved_di = rdi; +#endif } void restore_rdi() { +#if __amd64__ || __x86_64__ rdi = saved_di; +#endif } - void initOnlyAflForkServer() { + if (forkserver_installed == true) + return; + forkserver_installed = true; + // enter fork() server thyme! int n = write(FORKSRV_FD + 1, &__afl_temp_data, 4); @@ -118,6 +132,67 @@ void initOnlyAflForkServer() { if (__afl_fork_pid == 0) { close(FORKSRV_FD); close(FORKSRV_FD + 1); + prev_id = 0; + break; + } else { + // parrent stuff + n = write(FORKSRV_FD + 1, &__afl_fork_pid, 4); + pid_t temp_pid = waitpid(__afl_fork_pid, &__afl_temp_data, 2); + + if (temp_pid == 0) { + return; + } + n = write(FORKSRV_FD + 1, &__afl_temp_data, 4); + } + } +} + + +void initAflForkServerVar(u8 *map) { + // we can not use fprint* stdout/stderr functions here, it fucks up some programs + if (forkserver_installed == true) + return; + forkserver_installed = true; + + u8 **ptr = (u8**) map; + char *shm_env_var = getenv(SHM_ENV_VAR); + if (!shm_env_var) { + char buf[256]; + PRINT_ERROR("Error getting shm\n"); + snprintf(buf, sizeof(buf), "trace_bits: %p\n", ptr); + PRINT_ERROR(buf); + return; + } + + shm_id = atoi(shm_env_var); + *ptr = (u8*)shmat(shm_id, NULL, 0); + if ((u8*)*ptr == (u8 *) - 1) { + PRINT_ERROR("Error: shmat\n"); + return; + } + // enter fork() server thyme! + int n = write(FORKSRV_FD + 1, &__afl_temp_data, 4); + + if (n != 4) { + PRINT_ERROR("Error writing fork server\n"); + return; + } + while (1) { + n = read(FORKSRV_FD, &__afl_temp_data, 4); + if (n != 4) { + PRINT_ERROR("Error reading fork server\n"); + return; + } + + __afl_fork_pid = fork(); + if (__afl_fork_pid < 0) { + PRINT_ERROR("Error on fork()\n"); + return; + } + if (__afl_fork_pid == 0) { + close(FORKSRV_FD); + close(FORKSRV_FD + 1); + prev_id = 0; break; } else { // parrent stuff |