def test_read_config_options(self): (fd, f) = tempfile.mkstemp(text=True) os.close(fd) with open(f, 'wb') as configfile: self.config.write(configfile) cfg = read_config_options(f) self.assertEqual(cfg.killprocname, 'killprocname') self.assertEqual(cfg.killproctimeout, 1) self.assertEqual(cfg.watchdogtimeout, 2) self.assertEqual(cfg.copymode, 1) self.assertEqual(cfg.progtimeout, 3.4) self.assertEqual(cfg.seedfile_local_dir, 'seedfile_local_dir') self.assertEqual(cfg.output_dir, 'output_dir') self.assertEqual(cfg.local_dir, 'local_dir') self.assertEqual(cfg.debugger_timeout, 4) self.assertEqual(cfg.backtracelevels, 17) self.assertEqual(cfg.minimizecrashers, 1) self.assertEqual(cfg.valgrindtimeout, 6) self.delete_file(f)
def main(): debuggers.verify_supported_platform() from optparse import OptionParser hdlr = logging.StreamHandler() logger.addHandler(hdlr) usage = "usage: %prog [options] fuzzedfile" parser = OptionParser(usage) parser.add_option('', '--debug', dest='debug', action='store_true', help='Enable debug messages (overrides --verbose)') parser.add_option('', '--verbose', dest='verbose', action='store_true', help='Enable verbose messages') parser.add_option('-c', '--config', default='conf.d/bff.cfg', dest='config', help='path to the configuration file to use') parser.add_option('-e', '--edb', dest='use_edb', action='store_true', help='Use edb instead of gdb') parser.add_option('-p', '--debugger', dest='debugger', help='Use specified debugger') parser.add_option('-f', '--filepath', dest='filepath', action='store_true', help='Recreate original file path') (options, args) = parser.parse_args() if options.debug: logger.setLevel(logging.DEBUG) else: logger.setLevel(logging.INFO) cfg_file = options.config logger.debug('Config file: %s', cfg_file) if len(args) and os.path.exists(args[0]): fullpath_fuzzed_file = os.path.abspath(args[0]) fuzzed_file = BasicFile(fullpath_fuzzed_file) logger.info('Fuzzed file is %s', fuzzed_file) else: parser.error('fuzzedfile must be specified') iterationpath = '' if options.filepath: # Recreate same file path as fuzz iteration resultdir = os.path.dirname(fuzzed_file.path) for gdbfile in all_files(resultdir, '*.gdb'): print '** using gdb: %s' % gdbfile iterationpath = getiterpath(gdbfile) break iterationdir = os.path.dirname(iterationpath) iterationfile = os.path.basename(iterationpath) if iterationdir: mkdir_p(iterationdir) copy_file(fuzzed_file.path, os.path.join(iterationdir, iterationfile)) fullpath_fuzzed_file = iterationpath config = read_config_options(cfg_file) cmd_as_args = config.get_command_list(fullpath_fuzzed_file) program = cmd_as_args[0] if not os.path.exists(program): # edb wants a full path to the target app, so let's find it for path in os.environ["PATH"].split(":"): if os.path.exists(os.path.join(path, program)): program = os.path.join(path, program) # Recreate command args list with full path to target cmd_as_args = [] cmd_as_args.append(program) cmd_as_args.extend(config.get_command_args_list(fullpath_fuzzed_file)) args = [] if options.use_edb and options.debugger: parser.error('Options --edb and --debugger are mutually exclusive.') if options.debugger: debugger_app = options.debugger elif options.use_edb: debugger_app = 'edb' else: debugger_app = 'gdb' args.append(debugger_app) if options.use_edb: args.append('--run') else: # Using gdb args.append('--args') args.extend(cmd_as_args) logger.info('args %s' % args) p = Popen(args, universal_newlines=True) p.wait()
fmt = logging.Formatter('%(message)s') hdlr.setFormatter(fmt) hdlr.setLevel(logging.INFO) file_logger.addHandler(hdlr) # since we're logging to a file, we can suppress output to stdout # but we still want to keep warnings if not options.debug: logger.removeHandler(stdout_hdlr) if options.cfgfile: cfg_file = options.cfgfile else: cfg_file = os.path.join(parent_path, 'conf.d', 'bff.cfg') logger.debug('Using config file: %s', cfg_file) cfg = read_config_options(cfg_file) result_dir = cfg.crashers_dir logger.debug('Reading results from %s', result_dir) for crash_id in args: logger.debug('Crash_id=%s', crash_id) crash_dir = os.path.join(result_dir, crash_id) if not os.path.isdir(crash_dir): logger.debug('%s is not a dir', crash_dir) continue logger.debug('Looking for crash log in %s', crash_dir) log = os.path.join(crash_dir, crash_id + '.log') if not os.path.exists(log): logger.warning('No log found at %s', log)
def main(): debuggers.verify_supported_platform() from optparse import OptionParser hdlr = logging.StreamHandler() logger.addHandler(hdlr) usage = "usage: %prog [options] fuzzedfile" parser = OptionParser(usage) parser.add_option('', '--debug', dest='debug', action='store_true', help='Enable debug messages (overrides --verbose)') parser.add_option('', '--verbose', dest='verbose', action='store_true', help='Enable verbose messages') parser.add_option('-t', '--target', dest='target', help='the file to minimize to (typically the seedfile)') parser.add_option('-o', '--outdir', dest='outdir', help='dir to write output to') parser.add_option('-s', '--stringmode', dest='stringmode', action='store_true', help='minimize to a string rather than to a target file') parser.add_option( '-x', '--preferx', dest='prefer_x_target', action='store_true', help='Minimize to \'x\' characters instead of Metasploit string pattern' ) parser.add_option( '-f', '--faddr', dest='keep_uniq_faddr', action='store_true', help='Use exception faulting addresses as part of crash signature') parser.add_option( '-b', '--bitwise', dest='bitwise', action='store_true', help='if set, use bitwise hamming distance. Default is bytewise') parser.add_option('-c', '--confidence', dest='confidence', help='The desired confidence level (default: 0.999)', type='float') parser.add_option('-g', '--target-size-guess', dest='initial_target_size', help='A guess at the minimal value (int)', type='int') parser.add_option('', '--config', dest='config', help='path to the configuration file to use') parser.add_option( '', '--timeout', dest='timeout', metavar='N', type='int', default=0, help='Stop minimizing after N seconds (default is 0, never time out).') (options, args) = parser.parse_args() if options.debug: logger.setLevel(logging.DEBUG) elif options.verbose: logger.setLevel(logging.INFO) if options.config: cfg_file = os.path.expanduser(options.config) else: if os.path.isfile("../conf.d/bff.cfg"): cfg_file = "../conf.d/bff.cfg" elif os.path.isfile("conf.d/bff.cfg"): cfg_file = "conf.d/bff.cfg" else: parser.error( 'Configuration file (--config) option must be specified.') logger.debug('Config file: %s', cfg_file) if options.stringmode and options.target: parser.error( 'Options --stringmode and --target are mutually exclusive.') # Set some default options. Fast and loose if in string mode # More precise with minimize to seedfile if not options.confidence: if options.stringmode: options.confidence = 0.5 else: options.confidence = 0.999 if not options.initial_target_size: if options.stringmode: options.initial_target_size = 100 else: options.initial_target_size = 1 if options.confidence: try: options.confidence = float(options.confidence) except: parser.error('Confidence must be a float.') if not 0.0 < options.confidence < 1.0: parser.error('Confidence must be in the range 0.0 < c < 1.0') confidence = options.confidence if options.outdir: outdir = options.outdir else: outdir = "./minimizer_out" if not os.path.exists(outdir): filetools.make_directories(outdir) if not os.path.isdir(outdir): parser.error('--outdir must either already be a dir or not exist: %s' % outdir) if len(args) and os.path.exists(args[0]): fuzzed_file = BasicFile(args[0]) logger.info('Fuzzed file is %s', fuzzed_file) else: parser.error('fuzzedfile must be specified') cfg = read_config_options(cfg_file) if options.target: seedfile = BasicFile(options.target) else: seedfile = None min2seed = not options.stringmode filename_modifier = '' crashers_dir = '.' with BffCrash(cfg, seedfile, fuzzed_file, cfg.program, cfg.debugger_timeout, cfg.killprocname, cfg.backtracelevels, crashers_dir, options.keep_uniq_faddr) as crash: filetools.make_directories(crash.tempdir) logger.info('Copying %s to %s', fuzzed_file.path, crash.tempdir) filetools.copy_file(fuzzed_file.path, crash.tempdir) with Minimizer(cfg=cfg, crash=crash, crash_dst_dir=outdir, seedfile_as_target=min2seed, bitwise=options.bitwise, confidence=confidence, logfile='./min_log.txt', tempdir=crash.tempdir, maxtime=options.timeout, preferx=options.prefer_x_target, keep_uniq_faddr=options.keep_uniq_faddr) as minimize: minimize.save_others = False minimize.target_size_guess = int(options.initial_target_size) minimize.go() if options.stringmode: logger.debug('x character substitution') length = len(minimize.fuzzed) if options.prefer_x_target: #We minimized to 'x', so we attempt to get metasploit as a freebie targetstring = list(text.metasploit_pattern_orig(length)) filename_modifier = '-mtsp' else: #We minimized to metasploit, so we attempt to get 'x' as a freebie targetstring = list('x' * length) filename_modifier = '-x' fuzzed = list(minimize.fuzzed) for idx in minimize.bytemap: logger.debug('Swapping index %d', idx) targetstring[idx] = fuzzed[idx] filename = ''.join((crash.fuzzedfile.root, filename_modifier, crash.fuzzedfile.ext)) metasploit_file = os.path.join(crash.tempdir, filename) with open(metasploit_file, 'wb') as f: f.writelines(targetstring) crash.copy_files(outdir) crash.clean_tmpdir()
def main(): global START_SEED hashes = [] # give up if we don't have a debugger debuggers.verify_supported_platform() setup_logging_to_console(logger, logging.INFO) logger.info("Welcome to BFF!") scriptpath = os.path.dirname(sys.argv[0]) logger.info('Scriptpath is %s', scriptpath) # parse command line options logger.info('Parsing command line options') parser = OptionParser() parser.add_option('', '--debug', dest='debug', help='Turn on debugging output', action='store_true') parser.add_option('-c', '--config', dest='cfg', help='Config file location') (options, args) = parser.parse_args() #@UnusedVariable # Get the cfg file name if options.cfg: remote_cfg_file = options.cfg else: remote_cfg_file = get_config_file(scriptpath) # die unless the remote config is present assert os.path.exists( remote_cfg_file ), 'Cannot find remote config file: %s, Please create it or use --config option to specify a different location.' % remote_cfg_file # copy remote config to local: local_cfg_file = os.path.expanduser('~/bff.cfg') filetools.copy_file(remote_cfg_file, local_cfg_file) # Read the cfg file logger.info('Reading config from %s', local_cfg_file) cfg = cfg_helper.read_config_options(local_cfg_file) # set up local logging setup_logfile(cfg.local_dir, log_basename='bff.log', level=logging.DEBUG, max_bytes=1e8, backup_count=3) # set up remote logging setup_logfile(cfg.output_dir, log_basename='bff.log', level=logging.INFO, max_bytes=1e7, backup_count=5) try: check_for_script(cfg) except: logger.warning("Please configure BFF to fuzz a binary. Exiting...") sys.exit() z.setup_dirs_and_files(local_cfg_file, cfg) # make sure we cache it for the next run # cache_state(cfg.campaign_id, 'cfg', cfg, cfg.cached_config_file) sr = get_cached_state('seedrange', cfg.campaign_id, cfg.cached_seedrange_file) if not sr: sr = SeedRange(cfg.start_seed, cfg.seed_interval, cfg.max_seed) # set START_SEED global for timestamping START_SEED = sr.s1 start_process_killer(scriptpath, cfg) z.set_unbuffered_stdout() # set up the seedfile set so we can pick seedfiles for everything else... seedfile_set = get_cached_state('seedfile_set', cfg.campaign_id, cfg.cached_seedfile_set) if not seedfile_set: logger.info('Building seedfile set') sfs_logfile = os.path.join(cfg.seedfile_output_dir, 'seedfile_set.log') with SeedfileSet( campaign_id=cfg.campaign_id, originpath=cfg.seedfile_origin_dir, localpath=cfg.seedfile_local_dir, outputpath=cfg.seedfile_output_dir, logfile=sfs_logfile, ) as sfset: seedfile_set = sfset # set up the watchdog timeout within the VM and restart the daemon if cfg.watchdogtimeout: watchdog = WatchDog(cfg.watchdogfile, cfg.watchdogtimeout) touch_watchdog_file(cfg) watchdog.go() cache_state(cfg.campaign_id, 'seedfile_set', seedfile_set, cfg.cached_seedfile_set) sf = seedfile_set.next_item() # Run the program once to cache it into memory z.cache_program_once(cfg, sf.path) # Give target time to die time.sleep(1) # flag to indicate whether this is a fresh script start up or not first_chunk = True # remember our parent process id so we can tell if it changes later _last_ppid = os.getppid() # campaign.go while sr.in_max_range(): # wipe the tmp dir clean to try to avoid filling the VM disk TmpReaper().clean_tmp() sf = seedfile_set.next_item() r = sf.rangefinder.next_item() sr.set_s2() while sr.in_range(): # interval.go logger.debug('Starting interval %d-%d', sr.s1, sr.s2) # Prevent watchdog from rebooting VM. If /tmp/fuzzing exists and is stale, the machine will reboot touch_watchdog_file(cfg) # check parent process id _ppid_now = os.getppid() if not _ppid_now == _last_ppid: logger.warning('Parent process ID changed from %d to %d', _last_ppid, _ppid_now) _last_ppid = _ppid_now # do the fuzz cmdline = cfg.get_command(sf.path) if first_chunk: # disable the --quiet option in zzuf # on the first chunk only quiet_flag = False first_chunk = False else: quiet_flag = True zzuf = Zzuf( cfg.local_dir, sr.s1, sr.s2, cmdline, sf.path, cfg.zzuf_log_file, cfg.copymode, r.min, r.max, cfg.progtimeout, quiet_flag, ) saw_crash = zzuf.go() if not saw_crash: # we must have made it through this chunk without a crash # so go to next chunk try_count = sr.s1_s2_delta() sf.record_tries(tries=try_count) r.record_tries(tries=try_count) # emit a log entry crashcount = z.get_crashcount(cfg.crashers_dir) rate = get_rate(sr.s1) seed_str = "seeds=%d-%d" % (sr.s1, sr.s2) range_str = "range=%.6f-%.6f" % (r.min, r.max) rate_str = "Rate=(%.2f/s %.1f/m %.0f/h %.0f/d)" % ( rate, rate * 60, rate * 3600, rate * 86400) expected_density = seedfile_set.expected_crash_density xd_str = "expected=%.9f" % expected_density xr_str = 'expected_rate=%.6f uniq/day' % (expected_density * rate * 86400) logger.info('Fuzzing %s %s %s %s %s %s crash_count=%d', sf.path, seed_str, range_str, rate_str, xd_str, xr_str, crashcount) # set s1 to s2 so that as soon as we continue we'll break out of the sr.in_range() loop sr.set_s1_to_s2() continue # we must have seen a crash # get the results zzuf_log = ZzufLog(cfg.zzuf_log_file, cfg.zzuf_log_out(sf.output_dir)) # Don't generate cases for killed process or out-of-memory # In the default mode, zzuf will report a signal. In copy (and exit code) mode, zzuf will # report the exit code in its output log. The exit code is 128 + the signal number. crash_status = zzuf_log.crash_logged(cfg.copymode) sr.bookmark_s1() sr.s1 = zzuf_log.seed # record the fact that we've made it this far try_count = sr.s1_delta() sf.record_tries(tries=try_count) r.record_tries(tries=try_count) new_uniq_crash = False if crash_status: logger.info('Generating testcase for %s', zzuf_log.line) # a true crash zzuf_range = zzuf_log.range # create the temp dir for the results cfg.create_tmpdir() outfile = cfg.get_testcase_outfile(sf.path, sr.s1) logger.debug('Output file is %s', outfile) testcase = zzuf.generate_test_case(sf.path, sr.s1, zzuf_range, outfile) # Do internal verification using GDB / Valgrind / Stderr fuzzedfile = file_handlers.basicfile.BasicFile(outfile) with BffCrash(cfg, sf, fuzzedfile, cfg.program, cfg.debugger_timeout, cfg.killprocname, cfg.backtracelevels, cfg.crashers_dir, sr.s1, r) as c: if c.is_crash: new_uniq_crash = verify_crasher( c, hashes, cfg, seedfile_set) # record the zzuf log line for this crash if not c.logger: c.get_logger() c.logger.debug("zzuflog: %s", zzuf_log.line) c.logger.info('Command: %s', testcase.cmdline) cfg.clean_tmpdir() sr.increment_seed() # cache objects in case of reboot cache_state(cfg.campaign_id, 'seedrange', sr, cfg.cached_seedrange_file) pickled_seedfile_file = os.path.join(cfg.cached_objects_dir, sf.pkl_file()) cache_state(cfg.campaign_id, sf.cache_key(), sf, pickled_seedfile_file) cache_state(cfg.campaign_id, 'seedfile_set', seedfile_set, cfg.cached_seedfile_set) if new_uniq_crash: # we had a hit, so break the inner while() loop # so we can pick a new range. This is to avoid # having a crash-rich range run away with the # probability before other ranges have been tried break
fmt = logging.Formatter("%(message)s") hdlr.setFormatter(fmt) hdlr.setLevel(logging.INFO) file_logger.addHandler(hdlr) # since we're logging to a file, we can suppress output to stdout # but we still want to keep warnings if not options.debug: logger.removeHandler(stdout_hdlr) if options.cfgfile: cfg_file = options.cfgfile else: cfg_file = os.path.join(parent_path, "conf.d", "bff.cfg") logger.debug("Using config file: %s", cfg_file) cfg = read_config_options(cfg_file) result_dir = cfg.crashers_dir logger.debug("Reading results from %s", result_dir) for crash_id in args: logger.debug("Crash_id=%s", crash_id) crash_dir = os.path.join(result_dir, crash_id) if not os.path.isdir(crash_dir): logger.debug("%s is not a dir", crash_dir) continue logger.debug("Looking for crash log in %s", crash_dir) log = os.path.join(crash_dir, crash_id + ".log") if not os.path.exists(log): logger.warning("No log found at %s", log)
def main(): global START_SEED hashes = [] # give up if we don't have a debugger debuggers.verify_supported_platform() setup_logging_to_console(logger, logging.INFO) logger.info("Welcome to BFF!") scriptpath = os.path.dirname(sys.argv[0]) logger.info('Scriptpath is %s', scriptpath) # parse command line options logger.info('Parsing command line options') parser = OptionParser() parser.add_option('', '--debug', dest='debug', help='Turn on debugging output', action='store_true') parser.add_option('-c', '--config', dest='cfg', help='Config file location') (options, args) = parser.parse_args() #@UnusedVariable # Get the cfg file name if options.cfg: remote_cfg_file = options.cfg else: remote_cfg_file = get_config_file(scriptpath) # die unless the remote config is present assert os.path.exists(remote_cfg_file), 'Cannot find remote config file: %s, Please create it or use --config option to specify a different location.' % remote_cfg_file # copy remote config to local: local_cfg_file = os.path.expanduser('~/bff.cfg') filetools.copy_file(remote_cfg_file, local_cfg_file) # Read the cfg file logger.info('Reading config from %s', local_cfg_file) cfg = cfg_helper.read_config_options(local_cfg_file) # set up local logging setup_logfile(cfg.local_dir, log_basename='bff.log', level=logging.DEBUG, max_bytes=1e8, backup_count=3) # set up remote logging setup_logfile(cfg.output_dir, log_basename='bff.log', level=logging.INFO, max_bytes=1e7, backup_count=5) try: check_for_script(cfg) except: logger.warning("Please configure BFF to fuzz a binary. Exiting...") sys.exit() z.setup_dirs_and_files(local_cfg_file, cfg) # make sure we cache it for the next run # cache_state(cfg.campaign_id, 'cfg', cfg, cfg.cached_config_file) sr = get_cached_state('seedrange', cfg.campaign_id, cfg.cached_seedrange_file) if not sr: sr = SeedRange(cfg.start_seed, cfg.seed_interval, cfg.max_seed) # set START_SEED global for timestamping START_SEED = sr.s1 start_process_killer(scriptpath, cfg) z.set_unbuffered_stdout() # set up the seedfile set so we can pick seedfiles for everything else... seedfile_set = get_cached_state('seedfile_set', cfg.campaign_id, cfg.cached_seedfile_set) if not seedfile_set: logger.info('Building seedfile set') sfs_logfile = os.path.join(cfg.seedfile_output_dir, 'seedfile_set.log') with SeedfileSet(campaign_id=cfg.campaign_id, originpath=cfg.seedfile_origin_dir, localpath=cfg.seedfile_local_dir, outputpath=cfg.seedfile_output_dir, logfile=sfs_logfile, ) as sfset: seedfile_set = sfset # set up the watchdog timeout within the VM and restart the daemon if cfg.watchdogtimeout: watchdog = WatchDog(cfg.watchdogfile, cfg.watchdogtimeout) touch_watchdog_file(cfg) watchdog.go() cache_state(cfg.campaign_id, 'seedfile_set', seedfile_set, cfg.cached_seedfile_set) sf = seedfile_set.next_item() # Run the program once to cache it into memory z.cache_program_once(cfg, sf.path) # Give target time to die time.sleep(1) # flag to indicate whether this is a fresh script start up or not first_chunk = True # remember our parent process id so we can tell if it changes later _last_ppid = os.getppid() # campaign.go while sr.in_max_range(): # wipe the tmp dir clean to try to avoid filling the VM disk TmpReaper().clean_tmp() sf = seedfile_set.next_item() r = sf.rangefinder.next_item() sr.set_s2() while sr.in_range(): # interval.go logger.debug('Starting interval %d-%d', sr.s1, sr.s2) # Prevent watchdog from rebooting VM. If /tmp/fuzzing exists and is stale, the machine will reboot touch_watchdog_file(cfg) # check parent process id _ppid_now = os.getppid() if not _ppid_now == _last_ppid: logger.warning('Parent process ID changed from %d to %d', _last_ppid, _ppid_now) _last_ppid = _ppid_now # do the fuzz cmdline = cfg.get_command(sf.path) if first_chunk: # disable the --quiet option in zzuf # on the first chunk only quiet_flag = False first_chunk = False else: quiet_flag = True zzuf = Zzuf(cfg.local_dir, sr.s1, sr.s2, cmdline, sf.path, cfg.zzuf_log_file, cfg.copymode, r.min, r.max, cfg.progtimeout, quiet_flag, ) saw_crash = zzuf.go() if not saw_crash: # we must have made it through this chunk without a crash # so go to next chunk try_count = sr.s1_s2_delta() sf.record_tries(tries=try_count) r.record_tries(tries=try_count) # emit a log entry crashcount = z.get_crashcount(cfg.crashers_dir) rate = get_rate(sr.s1) seed_str = "seeds=%d-%d" % (sr.s1, sr.s2) range_str = "range=%.6f-%.6f" % (r.min, r.max) rate_str = "Rate=(%.2f/s %.1f/m %.0f/h %.0f/d)" % (rate, rate * 60, rate * 3600, rate * 86400) expected_density = seedfile_set.expected_crash_density xd_str = "expected=%.9f" % expected_density xr_str = 'expected_rate=%.6f uniq/day' % (expected_density * rate * 86400) logger.info('Fuzzing %s %s %s %s %s %s crash_count=%d', sf.path, seed_str, range_str, rate_str, xd_str, xr_str, crashcount) # set s1 to s2 so that as soon as we continue we'll break out of the sr.in_range() loop sr.set_s1_to_s2() continue # we must have seen a crash # get the results zzuf_log = ZzufLog(cfg.zzuf_log_file, cfg.zzuf_log_out(sf.output_dir)) # Don't generate cases for killed process or out-of-memory # In the default mode, zzuf will report a signal. In copy (and exit code) mode, zzuf will # report the exit code in its output log. The exit code is 128 + the signal number. crash_status = zzuf_log.crash_logged(cfg.copymode) sr.bookmark_s1() sr.s1 = zzuf_log.seed # record the fact that we've made it this far try_count = sr.s1_delta() sf.record_tries(tries=try_count) r.record_tries(tries=try_count) new_uniq_crash = False if crash_status: logger.info('Generating testcase for %s', zzuf_log.line) # a true crash zzuf_range = zzuf_log.range # create the temp dir for the results cfg.create_tmpdir() outfile = cfg.get_testcase_outfile(sf.path, sr.s1) logger.debug('Output file is %s', outfile) testcase = zzuf.generate_test_case(sf.path, sr.s1, zzuf_range, outfile) # Do internal verification using GDB / Valgrind / Stderr fuzzedfile = file_handlers.basicfile.BasicFile(outfile) with BffCrash(cfg, sf, fuzzedfile, cfg.program, cfg.debugger_timeout, cfg.killprocname, cfg.backtracelevels, cfg.crashers_dir, sr.s1, r) as c: if c.is_crash: new_uniq_crash = verify_crasher(c, hashes, cfg, seedfile_set) # record the zzuf log line for this crash if not c.logger: c.get_logger() c.logger.debug("zzuflog: %s", zzuf_log.line) c.logger.info('Command: %s', testcase.cmdline) cfg.clean_tmpdir() sr.increment_seed() # cache objects in case of reboot cache_state(cfg.campaign_id, 'seedrange', sr, cfg.cached_seedrange_file) pickled_seedfile_file = os.path.join(cfg.cached_objects_dir, sf.pkl_file()) cache_state(cfg.campaign_id, sf.cache_key(), sf, pickled_seedfile_file) cache_state(cfg.campaign_id, 'seedfile_set', seedfile_set, cfg.cached_seedfile_set) if new_uniq_crash: # we had a hit, so break the inner while() loop # so we can pick a new range. This is to avoid # having a crash-rich range run away with the # probability before other ranges have been tried break
def main(): debuggers.verify_supported_platform() from optparse import OptionParser hdlr = logging.StreamHandler() logger.addHandler(hdlr) usage = "usage: %prog [options] fuzzedfile" parser = OptionParser(usage) parser.add_option('', '--debug', dest='debug', action='store_true', help='Enable debug messages (overrides --verbose)') parser.add_option('', '--verbose', dest='verbose', action='store_true', help='Enable verbose messages') parser.add_option('-t', '--target', dest='target', help='the file to minimize to (typically the seedfile)') parser.add_option('-o', '--outdir', dest='outdir', help='dir to write output to') parser.add_option('-s', '--stringmode', dest='stringmode', action='store_true', help='minimize to a string rather than to a target file') parser.add_option('-x', '--preferx', dest='prefer_x_target', action='store_true', help='Minimize to \'x\' characters instead of Metasploit string pattern') parser.add_option('-f', '--faddr', dest='keep_uniq_faddr', action='store_true', help='Use exception faulting addresses as part of crash signature') parser.add_option('-b', '--bitwise', dest='bitwise', action='store_true', help='if set, use bitwise hamming distance. Default is bytewise') parser.add_option('-c', '--confidence', dest='confidence', help='The desired confidence level (default: 0.999)', type='float') parser.add_option('-g', '--target-size-guess', dest='initial_target_size', help='A guess at the minimal value (int)', type='int') parser.add_option('', '--config', dest='config', help='path to the configuration file to use') parser.add_option('', '--timeout', dest='timeout', metavar='N', type='int', default=0, help='Stop minimizing after N seconds (default is 0, never time out).') (options, args) = parser.parse_args() if options.debug: logger.setLevel(logging.DEBUG) elif options.verbose: logger.setLevel(logging.INFO) if options.config: cfg_file = os.path.expanduser(options.config) else: if os.path.isfile("../conf.d/bff.cfg"): cfg_file = "../conf.d/bff.cfg" elif os.path.isfile("conf.d/bff.cfg"): cfg_file = "conf.d/bff.cfg" else: parser.error('Configuration file (--config) option must be specified.') logger.debug('Config file: %s', cfg_file) if options.stringmode and options.target: parser.error('Options --stringmode and --target are mutually exclusive.') # Set some default options. Fast and loose if in string mode # More precise with minimize to seedfile if not options.confidence: if options.stringmode: options.confidence = 0.5 else: options.confidence = 0.999 if not options.initial_target_size: if options.stringmode: options.initial_target_size = 100 else: options.initial_target_size = 1 if options.confidence: try: options.confidence = float(options.confidence) except: parser.error('Confidence must be a float.') if not 0.0 < options.confidence < 1.0: parser.error('Confidence must be in the range 0.0 < c < 1.0') confidence = options.confidence if options.outdir: outdir = options.outdir else: outdir = "./minimizer_out" if not os.path.exists(outdir): filetools.make_directories(outdir) if not os.path.isdir(outdir): parser.error('--outdir must either already be a dir or not exist: %s' % outdir) if len(args) and os.path.exists(args[0]): fuzzed_file = BasicFile(args[0]) logger.info('Fuzzed file is %s', fuzzed_file) else: parser.error('fuzzedfile must be specified') cfg = read_config_options(cfg_file) if options.target: seedfile = BasicFile(options.target) else: seedfile = None min2seed = not options.stringmode filename_modifier = '' crashers_dir = '.' with BffCrash(cfg, seedfile, fuzzed_file, cfg.program, cfg.debugger_timeout, cfg.killprocname, cfg.backtracelevels, crashers_dir, options.keep_uniq_faddr) as crash: filetools.make_directories(crash.tempdir) logger.info('Copying %s to %s', fuzzed_file.path, crash.tempdir) filetools.copy_file(fuzzed_file.path, crash.tempdir) with Minimizer(cfg=cfg, crash=crash, crash_dst_dir=outdir, seedfile_as_target=min2seed, bitwise=options.bitwise, confidence=confidence, logfile='./min_log.txt', tempdir=crash.tempdir, maxtime=options.timeout, preferx=options.prefer_x_target, keep_uniq_faddr=options.keep_uniq_faddr) as minimize: minimize.save_others = False minimize.target_size_guess = int(options.initial_target_size) minimize.go() if options.stringmode: logger.debug('x character substitution') length = len(minimize.fuzzed) if options.prefer_x_target: #We minimized to 'x', so we attempt to get metasploit as a freebie targetstring = list(text.metasploit_pattern_orig(length)) filename_modifier = '-mtsp' else: #We minimized to metasploit, so we attempt to get 'x' as a freebie targetstring = list('x' * length) filename_modifier = '-x' fuzzed = list(minimize.fuzzed) for idx in minimize.bytemap: logger.debug('Swapping index %d', idx) targetstring[idx] = fuzzed[idx] filename = ''.join((crash.fuzzedfile.root, filename_modifier, crash.fuzzedfile.ext)) metasploit_file = os.path.join(crash.tempdir, filename) with open(metasploit_file, 'wb') as f: f.writelines(targetstring) crash.copy_files(outdir) crash.clean_tmpdir()