From 9789a1dd29469709508e4765e63ca296b4aa1635 Mon Sep 17 00:00:00 2001 From: ex0dus-0x Date: Mon, 15 Jul 2019 17:17:09 -0400 Subject: [PATCH 1/2] Migrate fuzzer frontends to submodule * Move frontends to deepstate.frontend * Add dynamic loading in __init__.py for future frontends * Refactor setup.py.in --- bin/deepstate/frontend/__init__.py | 21 +++++++++++++++++++++ bin/deepstate/{ => frontend}/angora.py | 0 bin/deepstate/{ => frontend}/eclipser.py | 0 bin/deepstate/{ => frontend}/frontend.py | 1 - bin/setup.py.in | 7 ++++--- 5 files changed, 25 insertions(+), 4 deletions(-) create mode 100644 bin/deepstate/frontend/__init__.py rename bin/deepstate/{ => frontend}/angora.py (100%) rename bin/deepstate/{ => frontend}/eclipser.py (100%) rename bin/deepstate/{ => frontend}/frontend.py (99%) diff --git a/bin/deepstate/frontend/__init__.py b/bin/deepstate/frontend/__init__.py new file mode 100644 index 0000000..67a977d --- /dev/null +++ b/bin/deepstate/frontend/__init__.py @@ -0,0 +1,21 @@ +import sys +import pkgutil +import importlib + +from .frontend import DeepStateFrontend + +def import_fuzzers(pkg_name): + """ + dynamically load fuzzer frontends using importlib + + TODO(alan): find way to alias modnames so we can check + them before importing (ie. fuzzer submods need to start with `front_*`) + """ + package = sys.modules[pkg_name] + return [ + importlib.import_module(pkg_name + '.' + submod) + for _, submod, _ in pkgutil.walk_packages(package.__path__) + #if submod != "frontend" + ] + +__all__ = import_fuzzers(__name__) diff --git a/bin/deepstate/angora.py b/bin/deepstate/frontend/angora.py similarity index 100% rename from bin/deepstate/angora.py rename to bin/deepstate/frontend/angora.py diff --git a/bin/deepstate/eclipser.py b/bin/deepstate/frontend/eclipser.py similarity index 100% rename from bin/deepstate/eclipser.py rename to bin/deepstate/frontend/eclipser.py diff --git a/bin/deepstate/frontend.py b/bin/deepstate/frontend/frontend.py similarity index 99% rename from bin/deepstate/frontend.py rename to bin/deepstate/frontend/frontend.py index c55323f..47ed950 100644 --- a/bin/deepstate/frontend.py +++ b/bin/deepstate/frontend/frontend.py @@ -119,7 +119,6 @@ class DeepStateFrontend(object): if cli_other is not None: self.cmd += cli_other - def execute_fuzzer(self): """ takes constructed cli command and executes fuzzer with subprocess.call diff --git a/bin/setup.py.in b/bin/setup.py.in index 265f4cf..7aa64b4 100644 --- a/bin/setup.py.in +++ b/bin/setup.py.in @@ -23,7 +23,7 @@ setuptools.setup( name="deepstate", version="0.1", package_dir={"": "${CMAKE_SOURCE_DIR}/bin"}, - packages=['deepstate'], + packages=['deepstate', 'deepstate.frontend'], description="DeepState augments C/C++ Test-Driven Development with Symbolic Execution", url="https://github.com/trailofbits/deepstate", author="Peter Goodman", @@ -37,7 +37,8 @@ setuptools.setup( 'deepstate-angr = deepstate.main_angr:main', 'deepstate-manticore = deepstate.main_manticore:main', 'deepstate-reduce = deepstate.reducer:main', - 'deepstate-eclipser = deepstate.eclipser:main', - 'deepstate-angora = deepstate.angora:main' + 'deepstate-afl = deepstate.frontend.afl:main', + 'deepstate-eclipser = deepstate.frontend.eclipser:main', + 'deepstate-angora = deepstate.frontend.angora:main' ] }) From adfae99e80b9f9266c38ff7b27492f2f9818d45b Mon Sep 17 00:00:00 2001 From: ex0dus-0x Date: Mon, 15 Jul 2019 17:18:17 -0400 Subject: [PATCH 2/2] Add default AFL frontend --- bin/deepstate/frontend/afl.py | 123 ++++++++++++++++++++++++++++++++++ 1 file changed, 123 insertions(+) create mode 100644 bin/deepstate/frontend/afl.py diff --git a/bin/deepstate/frontend/afl.py b/bin/deepstate/frontend/afl.py new file mode 100644 index 0000000..80e1adc --- /dev/null +++ b/bin/deepstate/frontend/afl.py @@ -0,0 +1,123 @@ +#!/usr/bin/env python3.6 +# Copyright (c) 2019 Trail of Bits, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +import sys +import argparse + +from .frontend import DeepStateFrontend + + +class AFL(DeepStateFrontend): + """ Defines default AFL fuzzer frontend """ + + @classmethod + def parse_args(cls): + parser = argparse.ArgumentParser(description="Use AFL as a back-end for DeepState.") + + compile_group = parser.add_argument_group("compilation and instrumentation arguments") + compile_group.add_argument("--compile_test", type=str, help="Path to DeepState test harness for compilation.") + compile_group.add_argument("--compiler_args", default=[], nargs='+', help="Compiler flags (excluding -o) to pass to compiler.") + compile_group.add_argument("--out_test_name", type=str, default="out", help="Set name of generated instrumented binary.") + + parser.add_argument("--dictionary", type=str, help="Optional fuzzer dictionary for AFL.") + parser.add_argument("--mem_limit", type=int, default=50, help="Child process memory limit in MB (default is 50).") + parser.add_argument("--file", type=str, help="Input file read by fuzzed program, if any.") + + parser.add_argument("--dirty_mode", action='store_true', help="Fuzz without deterministic steps.") + parser.add_argument("--dumb_mode", action='store_true', help="Fuzz without instrumentation.") + parser.add_argument("--qemu_mode", action='store_true', help="Fuzz with QEMU mode.") + parser.add_argument("--crash_explore", action='store_true', help="Fuzz with crash exploration.") + + cls.parser = parser + return super(AFL, cls).parse_args() + + + def compile(self): + args = self._args + + lib_path = "/usr/local/lib/" + if not os.path.isfile(lib_path + "libdeepstate_AFL.a"): + raise RuntimeError("no AFL-instrumented DeepState static library found in {}".format(lib_path)) + + compiler_args = [args.compile_test, "-std=c++11"] + args.compiler_args + \ + ["-ldeepstate_AFL", "-o", args.out_test_name + ".afl"] + super().compile(compiler_args) + + +def main(): + fuzzer = AFL("afl-fuzz", compiler="afl-clang-fast++") + args = fuzzer.parse_args() + + if args.fuzzer_help: + fuzzer.print_help() + sys.exit(0) + + if args.compile_test: + print("COMPILING DEEPSTATE HARNESS FOR FUZZING...") + fuzzer.compile() + sys.exit(0) + + if not args.seeds or not args.output_test_dir: + print("Error: --seeds and/or --output_test_dir required for fuzzing.") + sys.exit(1) + + if not os.path.exists(args.seeds): + print("CREATING INPUT SEED DIRECTORY...") + os.mkdir(args.seeds) + + if len([name for name in os.listdir(args.seeds)]) == 0: + print("Error: no seeds present in directory", args.seeds) + sys.exit(1) + + cmd_dict = { + "-i": args.seeds, + "-o": args.output_test_dir, + "-t": str(args.timeout), + "-m": str(args.mem_limit) + } + + # check if we are using one of AFL's many "modes" + if args.dirty_mode: + cmd_dict['-d'] = None + if args.dumb_mode: + cmd_dict['-n'] = None + if args.qemu_mode: + cmd_dict['-Q'] = None + if args.crash_explore: + cmd_dict['-C'] = None + + # other misc arguments + if args.dictionary: + cmd_dict['-x'] = args.dictionary + if args.file: + cmd_dict['-f'] = args.file + + cmd_dict['--'] = args.binary + + # if not specified, set DeepState flags to help AFL coverage + if len(args.args) == 0: + args.args = ["--input_test_file", "@@", "--abort_on_fail", "--no_fork"] + + fuzzer.cli_command(cmd_dict, cli_other=args.args) + + print("EXECUTING FUZZER...") + fuzzer.execute_fuzzer() + + return 0 + + +if __name__ == "__main__": + exit(main())