def copy_files_to_temp(self): if self.fuzzedfile and self.copy_fuzzedfile: filetools.copy_file(self.fuzzedfile.path, self.tempdir) else: # We're in verify mode. Set the fuzzedfile to be the seedfile, # since we didn't mutate anything self.fuzzedfile = self.seedfile if self.seedfile: filetools.copy_file(self.seedfile.path, self.tempdir) # TODO: This seems hacky. Should be better way to have # minimizer_log.txt and core files survive update_crash_details minlog = os.path.join(self.fuzzedfile.dirname, 'minimizer_log.txt') if os.path.exists(minlog): filetools.copy_file(minlog, self.tempdir) corefile = os.path.join(self.workdir_base, 'core') if os.path.exists(corefile): filetools.copy_file(corefile, self.tempdir) calltracefile = os.path.join(self.fuzzedfile.dirname, '%s.calltrace' % self.fuzzedfile.basename) if os.path.exists(calltracefile): filetools.copy_file(calltracefile, self.tempdir) new_fuzzedfile = os.path.join(self.tempdir, self.fuzzedfile.basename) self.fuzzedfile = BasicFile(new_fuzzedfile)
def copy_files_to_temp(self): if self.fuzzedfile and self.copy_fuzzedfile: filetools.copy_file(self.fuzzedfile.path, self.tempdir) else: # We're in verify mode. Set the fuzzedfile to be the seedfile, # since we didn't mutate anything self.fuzzedfile = self.seedfile if self.seedfile: filetools.copy_file(self.seedfile.path, self.tempdir) # TODO: This seems hacky. Should be better way to have # minimizer_log.txt and core files survive update_crash_details minlog = os.path.join(self.fuzzedfile.dirname, 'minimizer_log.txt') if os.path.exists(minlog): filetools.copy_file(minlog, self.tempdir) corefile = os.path.join(self.workdir_base, 'core') if os.path.exists(corefile): filetools.copy_file(corefile, self.tempdir) calltracefile = os.path.join( self.fuzzedfile.dirname, '%s.calltrace' % self.fuzzedfile.basename) if os.path.exists(calltracefile): filetools.copy_file(calltracefile, self.tempdir) new_fuzzedfile = os.path.join(self.tempdir, self.fuzzedfile.basename) self.fuzzedfile = BasicFile(new_fuzzedfile)
def test_copy_file(self): (src_fd, src) = tempfile.mkstemp(text=True, dir=self.tempdir) (dummy, dst1) = tempfile.mkstemp(text=True, dir=self.tempdir) (dummy, dst2) = tempfile.mkstemp(text=True, dir=self.tempdir) # no need to keep the tmpfiles open os.close(src_fd) # remove the target files, since we'll # be copying to them in a sec for f in (dst1, dst2): os.remove(f) # make sure we're ready to test self.assertTrue(os.path.exists(src)) for f in (dst1, dst2): self.assertFalse(os.path.exists(f)) copy_file(src, dst1, dst2) # are the destinations there? for p in (src, dst1, dst2): self.assertTrue(os.path.exists(p)) # ok, it worked. Now clean up after yourself for f in (src, dst1, dst2): self.delete_file(f)
def verify_crasher(c, hashes, cfg, seedfile_set): logger.debug('verifying crash') found_new_crash = False crashes = [] crashes.append(c) for crash in crashes: # loop until we're out of crashes to verify logger.debug('crashes to verify: %d', len(crashes)) # crashes may be added as a result of minimization crash.is_unique = False determine_uniqueness(crash, hashes) crash.get_logger() if crash.is_unique: hashes.append(crash) # only toggle it once if not found_new_crash: found_new_crash = True logger.debug("%s did not exist in cache, crash is unique", crash.signature) more_crashes = analyze_crasher(cfg, crash) if cfg.recycle_crashers: logger.debug('Recycling crash as seedfile') iterstring = crash.fuzzedfile.basename.split('-')[1].split('.')[0] crasherseedname = 'sf_' + crash.seedfile.md5 + '-' + iterstring + crash.seedfile.ext crasherseed_path = os.path.join(cfg.seedfile_origin_dir, crasherseedname) filetools.copy_file(crash.fuzzedfile.path, crasherseed_path) seedfile_set.add_file(crasherseed_path) # add new crashes to the queue crashes.extend(more_crashes) crash.copy_files() uniqlogger = get_uniq_logger(cfg.uniq_log) uniqlogger.info('%s crash_id=%s seed=%d range=%s bitwise_hd=%d bytewise_hd=%d', crash.seedfile.basename, crash.signature, crash.seednum, crash.range, crash.hd_bits, crash.hd_bytes) logger.info('%s first seen at %d', crash.signature, crash.seednum) else: logger.debug('%s was found, not unique', crash.signature) # always clean up after yourself crash.clean_tmpdir() # clean up crash.delete_files() # whether it was unique or not, record some details for posterity # record the details of this crash so we can regenerate it later if needed crash.logger.info('seen in seedfile=%s at seed=%d range=%s outfile=%s', crash.seedfile.basename, crash.seednum, crash.range, crash.fuzzedfile.path) crash.logger.info('PC=%s', crash.pc) # score this crash for the seedfile crash.seedfile.record_success(crash.signature, tries=0) if crash.range: # ...and for the range crash.range.record_success(crash.signature, tries=0) return found_new_crash
def _crash_builder(self): self.logger.debug('Building new testcase object.') # copy our original testcase as the basis for the new testcase new_testcase = copy.deepcopy(self.testcase) # get a new dir for the next crasher newcrash_tmpdir = tempfile.mkdtemp(prefix='minimizer_crash_builder_', dir=self.tempdir) # get a new filename for the next crasher sfx = self.testcase.fuzzedfile.ext if self.testcase.seedfile: pfx = '%s-' % self.testcase.seedfile.root else: pfx = 'string-' (fd, f) = tempfile.mkstemp(suffix=sfx, prefix=pfx, dir=newcrash_tmpdir) os.close(fd) delete_files(f) outfile = f if os.path.exists(outfile): self._raise('Outfile should not already exist: %s' % outfile) self.logger.debug('\tCopying %s to %s', self.tempfile, outfile) filetools.copy_file(self.tempfile, outfile) if 'copyfuzzedto' in self.cfg['target']: copyfuzzedto = str(self.cfg['target'].get('copyfuzzedto', '')) logger.debug("Copying fuzzed file to " + copyfuzzedto) filetools.copy_file(self.tempfile, copyfuzzedto) if 'postprocessfuzzed' in self.cfg['target']: postprocessfuzzed = str(self.cfg['target']['postprocessfuzzed']) logger.debug("Executing postprocess " + postprocessfuzzed) os.system(postprocessfuzzed) new_testcase.fuzzedfile = BasicFile(outfile) self.logger.debug('\tNew fuzzed_content file: %s %s', new_testcase.fuzzedfile.path, new_testcase.fuzzedfile.md5) # clear out the copied testcase signature so that it will be # regenerated new_testcase.signature = None # replace old testcase details with new info specific to this testcase self.logger.debug('\tUpdating testcase details') new_testcase.update_crash_details() # the tempdir we created is no longer needed because # update_crash_details creates a fresh one shutil.rmtree(newcrash_tmpdir) if os.path.exists(newcrash_tmpdir): logger.warning("Failed to remove temp dir %s", newcrash_tmpdir) return new_testcase
def copy_file_from_origin(self, f): if (os.path.basename(f.path) == '.DS_Store'): return 0 # convert the local filenames from <foo>.<ext> to <md5>.<ext> basename = 'sf_' + f.md5 + f.ext targets = [os.path.join(d, basename) for d in (self.localpath, self.outputpath)] filetools.copy_file(f.path, *targets) for target in targets: filetools.make_writable(target)
def copy_file_from_origin(self, f): if (os.path.basename(f.path) == '.DS_Store'): return 0 # convert the local filenames from <foo>.<ext> to <md5>.<ext> basename = 'sf_' + f.md5 + f.ext targets = [ os.path.join(d, basename) for d in (self.localpath, self.outputpath) ] filetools.copy_file(f.path, *targets) for target in targets: filetools.make_writable(target)
def _write_file(self): if self.is_zipfile: self._writezip() else: write_file(''.join(self.newfuzzed), self.tempfile) if 'copyfuzzedto' in self.cfg['target']: copyfuzzedto = str(self.cfg['target'].get('copyfuzzedto', '')) logger.debug("Copying fuzzed file to " + copyfuzzedto) filetools.copy_file(self.testcase.fuzzedfile.path, copyfuzzedto) if 'postprocessfuzzed' in self.cfg['target']: postprocessfuzzed = str(self.cfg['target']['postprocessfuzzed']) logger.debug("Executing postprocess " + postprocessfuzzed) os.system(postprocessfuzzed)
def recycle(self, *targets): while True: testcase = (yield) if self.cfg['runoptions']['recycle_crashers']: logger.debug('Recycling crash as seedfile') iterstring = testcase.fuzzedfile.basename.split( '-')[1].split('.')[0] crasherseedname = 'sf_' + testcase.seedfile.md5 + \ '-' + iterstring + testcase.seedfile.ext crasherseed_path = os.path.join( self.cfg['directories']['seedfile_dir'], crasherseedname) filetools.copy_file( testcase.fuzzedfile.path, crasherseed_path) self.sf_set.add_file(crasherseed_path) for target in targets: target.send(testcase)
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()
def _setup_output(self): # construct run output directory filetools.make_directories(self.outdir) # copy config to run output dir filetools.copy_file(self.config_file, self.outdir) self._write_version()
def __init__(self, cfg=None, testcase=None, crash_dst_dir=None, seedfile_as_target=False, bitwise=False, confidence=0.999, logfile=None, tempdir=None, maxtime=3600, preferx=True, keep_uniq_faddr=False, watchcpu=False): if not cfg: self._raise('Config must be specified') if not testcase: self._raise('Crasher must be specified') self.cfg = cfg self.testcase = testcase self.seedfile_as_target = seedfile_as_target self.bitwise = bitwise self.preferx = preferx self.max_time = maxtime # if max_time is not positive, assume that we don't # want to time out ever self.use_timer = maxtime > 0.0 self.start_time = 0.0 self.keep_uniq_faddr = keep_uniq_faddr self.watchcpu = watchcpu self.exhaustivesearch_threshold = 10 self.max_target_hashes = 10 self.minchar = 'x' self.save_others = True self.ext = self.testcase.fuzzedfile.ext self.logger = None self.log_file_hdlr = None self.backtracelevels = 5 self.watchdogfile = '/tmp/bff_watchdog' logger.setLevel(logging.INFO) self.saved_arcinfo = None self.is_zipfile = check_zip_file(testcase.fuzzedfile.path) if tempdir and os.path.isdir(tempdir): self.tempdir = tempfile.mkdtemp(prefix='minimizer_', dir=tempdir) else: self.tempdir = tempfile.mkdtemp(prefix='minimizer_') logger.debug('Minimizer tempdir is %s', self.tempdir) # decide whether we're doing a bitwise comparison or bytewise if self.bitwise: self.hd_func = hamming.bitwise_hd self.swap_func = self.bitwise_swap2 else: self.hd_func = hamming.bytewise_hd self.swap_func = self.bytewise_swap2 if self.seedfile_as_target: minf = '%s-minimized%s' % (self.testcase.fuzzedfile.root, self.testcase.fuzzedfile.ext) minlog = os.path.join(self.testcase.fuzzedfile.dirname, 'minimizer_log.txt') if not os.path.exists(self.testcase.seedfile.path): self._raise('Seedfile not found at %s' % self.testcase.seedfile.path) elif self.preferx: minf = '%s-min-%s%s' % (self.testcase.fuzzedfile.root, self.minchar, self.testcase.fuzzedfile.ext) minlog = os.path.join(self.testcase.fuzzedfile.dirname, 'minimizer_%s_log.txt' % self.minchar) else: minf = '%s-min-mtsp%s' % (self.testcase.fuzzedfile.root, self.testcase.fuzzedfile.ext) minlog = os.path.join(self.testcase.fuzzedfile.dirname, 'minimizer_mtsp_log.txt') self.outputfile = os.path.join(self.testcase.fuzzedfile.dirname, minf) if logfile: self.minimizer_logfile = logfile else: self.minimizer_logfile = minlog if crash_dst_dir: self.crash_dst = crash_dst_dir else: self.crash_dst = self.testcase.fuzzedfile.dirname if not os.path.exists(self.crash_dst): self._raise("%s does not exist" % self.crash_dst) if not os.path.isdir(self.crash_dst): self._raise("%s is not a directory" % self.crash_dst) self._logger_setup() self.logger.info("Minimizer initializing for %s", self.testcase.fuzzedfile.path) if not os.path.exists(self.testcase.fuzzedfile.path): self._raise("%s does not exist" % self.testcase.fuzzedfile.path) self.testcase.set_debugger_template('bt_only') self.other_crashes = {} self.target_size_guess = 1 if str(cfg['runoptions']['minimize']).lower() == 'string': # Assume that a string minimization has a larger target (structure) self.target_size_guess = 100 self.total_tries = 0 self.total_misses = 0 self.consecutive_misses = 0 self.discard_chance = 0 self.debugger_runs = 0 self.min_found = False self.try_exhaustive = False self.confidence_level = confidence self.newfuzzed_hd = 0 self.newfuzzed_md5 = None self.n_misses_allowed = 0 self.newfuzzed = '' self.crash_sigs_found = {} self.files_tried = set() self.files_tried_at_hd = {} self.files_tried_singlebyte_at_hd = {} self.bytemap = [] # self.saved_arcinfo = {} # self.is_zipfile = False # self.is_zipfile = self.check_zipfile(self.testcase.fuzzedfile.path) self.fuzzed_content = self._read_fuzzed() self.seed = self._read_seed() # none of this will work if the files are of different size if len(self.seed) != len(self.fuzzed_content): self._raise( 'Minimizer requires seed and fuzzed_content to have the same length. %d != %d' % (len(self.seed), len(self.fuzzed_content))) # initialize the hamming distance self.start_distance = self.hd_func(self.seed, self.fuzzed_content) self.min_distance = self.start_distance # some programs testcase differently depending on where the # file is loaded from. So we'll reuse this file name for # everything f = tempfile.NamedTemporaryFile('wb', delete=True, dir=self.tempdir, prefix="minimizer_fuzzed_file_", suffix=self.testcase.fuzzedfile.ext) # since we set delete=True, f will be deleted on close, # which makes it available to us to reuse f.close() self.tempfile = f.name filetools.copy_file(self.testcase.fuzzedfile.path, self.tempfile) # figure out what testcase signatures belong to this fuzzedfile self.debugger_timeout = self.cfg['debugger']['runtimeout'] self.crash_hashes = [] self.measured_dbg_time = None self._set_crash_hashes() # set the debugger_timeout to the lesser of what we've measured # in getting the crash_hashes or what it already was if self.measured_dbg_time: self.debugger_timeout = min(self.debugger_timeout, self.measured_dbg_time) self.logger.info('\tusing debugger timeout: %0.5f', self.debugger_timeout) self.logger.info('\tconfidence level: %0.5f', self.confidence_level) self.logger.info('\tstarting Hamming Distance is %d', self.start_distance)
def go(self): # start by copying the fuzzed_content file since as of now it's our # best fit filetools.copy_file(self.testcase.fuzzedfile.path, self.outputfile) # replace the fuzzedfile object in testcase with the minimized copy self.testcase.fuzzedfile = BasicFile(self.outputfile) self.logger.info('Attempting to minimize testcase(es) [%s]', self._crash_hashes_string()) # keep going until either: # a. we find a minimum hd of 1 # b. we run out of discard_chances # c. our discard_chance * minimum hd is less than one (we won't discard anything) # d. we've exhaustively searched all the possible files with hd less # than self.min_distance while not self.min_found and not self.try_exhaustive: if not self.set_discard_chance(): break if not self.set_n_misses(): break got_hit = False while self.consecutive_misses <= self.n_misses_allowed: if self.use_watchdog: # touch the watchdog file so we don't reboot during long # minimizations open(self.watchdogfile, 'w').close() # Fix for BFF-208 if self._time_exceeded(): logger.info( 'Max time for minimization exceeded, ending minimizer early.' ) self.min_found = True break if not self.set_discard_chance(): break if not self.set_n_misses(): break self.swap_bytes() self.total_tries += 1 # have we been at this level before? if not self.files_tried_at_hd.get(self.min_distance): # we've reached a new minimum, so create new sets self.files_tried_at_hd[self.min_distance] = set() self.files_tried_singlebyte_at_hd[ self.min_distance] = set() # have we exhausted all the possible files with smaller hd? possible_files = (2**self.min_distance) - 2 seen_files = len(self.files_tried_at_hd[self.min_distance]) # maybe we're done? if seen_files == possible_files: # we've exhaustively searched everything with hd < # self.min_distance self.logger.info( 'Exhaustively searched all files shorter than %d', self.min_distance) self.min_found = True break # have we exhausted all files that are 1 byte smaller hd? possible_singlebyte_diff_files = self.min_distance singlebyte_diff_files_seen = len( self.files_tried_singlebyte_at_hd[self.min_distance]) # maybe we're done? if singlebyte_diff_files_seen == possible_singlebyte_diff_files: self.logger.info( 'We have tried all %d files that are one byte closer than the current minimum', self.min_distance) self.min_found = True break # remember this file for next time around self.files_tried_at_hd[self.min_distance].add( self.newfuzzed_md5) if self.newfuzzed_hd == (self.min_distance - 1): self.files_tried_singlebyte_at_hd[self.min_distance].add( self.newfuzzed_md5) self.print_intermediate_log() if self.newfuzzed_md5 in self.files_tried: # we've already seen this attempt, so skip ahead to the next one # but still count it as a miss since our math assumes we're putting # the marbles back in the jar after each draw self.consecutive_misses += 1 self.total_misses += 1 continue # we didn't skip ahead, so it must have been new. Remember it # now self.files_tried.add(self.newfuzzed_md5) # we have a better match, write it to a file if not len(self.newfuzzed): raise MinimizerError( 'New fuzzed_content content is empty.') self._write_file() if self.is_same_crash(): # record the result # 1. copy the tempfile filetools.best_effort_move(self.tempfile, self.outputfile) # 2. replace the fuzzed_content file in the crasher with # the current one self.testcase.fuzzedfile = BasicFile(self.outputfile) # 3. replace the current fuzzed_content with newfuzzed self.fuzzed_content = self.newfuzzed self.min_distance = self.newfuzzed_hd got_hit = True if self.min_distance == 1: # we are done self.min_found = True elif self.newfuzzed_hd <= self.exhaustivesearch_threshold: self._set_bytemap() logger.info( 'Exhaustively checking remaining %s bytes' % self.newfuzzed_hd) self.try_exhaustive = True break else: # set up for next iteration self.consecutive_misses = 0 if not self.set_discard_chance(): break if not self.set_n_misses(): break else: # we missed. increment counter and try again self.total_misses += 1 self.consecutive_misses += 1 # Fix for BFF-225 # There may be some situation that causes testcase uniqueness # hashing to break. (e.g. BFF-224 ). Minimizer should bail # if the number of unique crashes encountered exceeds some # threshold. e.g. 20 maybe? if len(self.other_crashes ) > MAX_OTHER_CRASHES and self.seedfile_as_target: logger.info( 'Exceeded maximum number of other crashes (%d), ending minimizer early.', MAX_OTHER_CRASHES) self.min_found = True break if not got_hit: # we are self.confidence_level sure that self.target_size_guess is wrong # so increment it by 1 self.target_size_guess += 1 if self.try_exhaustive: for offset in list(self.bytemap): logger.debug('Verifying byte location: %s' % hex(offset)) self.revert_byte(offset) self._write_file() if self.is_same_crash(): logger.debug('Fuzzed byte at offset %s is not relevant' % hex(offset)) filetools.best_effort_move(self.tempfile, self.outputfile) self.testcase.fuzzedfile = BasicFile(self.outputfile) self.fuzzed_content = self.newfuzzed self.bytemap.remove(offset) # We're done minimizing. Set the bytemap (kept bytes) self._set_bytemap() self.logger.info('We were looking for [%s] ...', self._crash_hashes_string()) for (md5, count) in self.crash_sigs_found.items(): self.logger.info('\t...and found %s\t%d times', md5, count) if self.bytemap: hex_bytemap = [] for offset in self.bytemap: hex_bytemap.append(hex(offset)) self.logger.info('Bytemap: %s', hex_bytemap)
def main(): 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='configs/bff.yaml', 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.path) 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 if iterationpath: 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 = load_and_fix_config(cfg_file) cmd_as_args = get_command_args_list(config['target']['cmdline_template'], fullpath_fuzzed_file)[1] 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' elif platform.system() == 'Darwin': debugger_app = 'lldb' else: debugger_app = 'gdb' args.append(debugger_app) if options.use_edb: args.append('--run') elif debugger_app == 'gdb': # Using gdb args.append('--args') args.extend(cmd_as_args) logger.info('args %s' % args) p = Popen(args, universal_newlines=True) p.wait()
def verify_crasher(c, hashes, cfg, seedfile_set): logger.debug('verifying crash') found_new_crash = False crashes = [] crashes.append(c) for crash in crashes: # loop until we're out of crashes to verify logger.debug('crashes to verify: %d', len(crashes)) # crashes may be added as a result of minimization crash.is_unique = False determine_uniqueness(crash, hashes) crash.get_logger() if crash.is_unique: hashes.append(crash) # only toggle it once if not found_new_crash: found_new_crash = True logger.debug("%s did not exist in cache, crash is unique", crash.signature) more_crashes = analyze_crasher(cfg, crash) if cfg.recycle_crashers: logger.debug('Recycling crash as seedfile') iterstring = crash.fuzzedfile.basename.split('-')[1].split( '.')[0] crasherseedname = 'sf_' + crash.seedfile.md5 + '-' + iterstring + crash.seedfile.ext crasherseed_path = os.path.join(cfg.seedfile_origin_dir, crasherseedname) filetools.copy_file(crash.fuzzedfile.path, crasherseed_path) seedfile_set.add_file(crasherseed_path) # add new crashes to the queue crashes.extend(more_crashes) crash.copy_files() uniqlogger = get_uniq_logger(cfg.uniq_log) uniqlogger.info( '%s crash_id=%s seed=%d range=%s bitwise_hd=%d bytewise_hd=%d', crash.seedfile.basename, crash.signature, crash.seednum, crash.range, crash.hd_bits, crash.hd_bytes) logger.info('%s first seen at %d', crash.signature, crash.seednum) else: logger.debug('%s was found, not unique', crash.signature) # always clean up after yourself crash.clean_tmpdir() # clean up crash.delete_files() # whether it was unique or not, record some details for posterity # record the details of this crash so we can regenerate it later if needed crash.logger.info('seen in seedfile=%s at seed=%d range=%s outfile=%s', crash.seedfile.basename, crash.seednum, crash.range, crash.fuzzedfile.path) crash.logger.info('PC=%s', crash.pc) # score this crash for the seedfile crash.seedfile.record_success(crash.signature, tries=0) if crash.range: # ...and for the range crash.range.record_success(crash.signature, tries=0) return found_new_crash
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)') parser.add_option('-g', '--target-size-guess', dest='initial_target_size', help='A guess at the minimal value (int)') parser.add_option('', '--config', default='configs/foe.yaml', 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).') parser.add_option('-k', '--keepothers', dest='keep_other_crashes', action='store_true', help='Keep other crash hashes encountered during minimization') (options, args) = parser.parse_args() if options.debug: logger.setLevel(logging.DEBUG) else: logger.setLevel(logging.INFO) if options.config: cfg_file = options.config else: cfg_file = "../conf.d/bff.cfg" 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: mydir = os.path.dirname(os.path.abspath(__file__)) parentdir = os.path.abspath(os.path.join(mydir, '..')) outdir = os.path.abspath(os.path.join(parentdir, 'minimizer_out')) filetools.make_directories(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') config = Config(cfg_file).config cfg = _create_minimizer_cfg(config) if options.target: seedfile = BasicFile(options.target) else: seedfile = None min2seed = not options.stringmode filename_modifier = '' retries = 0 debugger_class = msec.MsecDebugger template = string.Template(config['target']['cmdline_template']) cmd_as_args = get_command_args_list(template, fuzzed_file.path)[1] with FoeCrash(template, seedfile, fuzzed_file, cmd_as_args, None, debugger_class, config['debugger'], outdir, options.keep_uniq_faddr, config['target']['program'], retries) 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) minlog = os.path.join(outdir, 'min_log.txt') with Minimizer(cfg=cfg, crash=crash, crash_dst_dir=outdir, seedfile_as_target=min2seed, bitwise=options.bitwise, confidence=confidence, tempdir=outdir, logfile=minlog, maxtime=options.timeout, preferx=options.prefer_x_target) as minimize: minimize.save_others = options.keep_other_crashes 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) f = open(metasploit_file, 'wb') try: f.writelines(targetstring) finally: f.close() crash.copy_files(outdir) crash.clean_tmpdir()
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='configs/foe.yaml', dest='config', help='path to the configuration file to use') parser.add_option('-w', '--windbg', dest='use_windbg', action='store_true', help='Use windbg instead of cdb') parser.add_option('-b', '--break', dest='break_on_start', action='store_true', help='Break on start of debugger session') parser.add_option('-d', '--debugheap', dest='debugheap', action='store_true', help='Use debug heap') 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') config = Config(cfg_file).config iterationpath = '' template = string.Template(config['target']['cmdline_template']) if options.filepath: # Recreate same file path as fuzz iteration resultdir = os.path.dirname(fuzzed_file.path) for msecfile in all_files(resultdir, '*.msec'): print '** using msecfile: %s' % msecfile iterationpath = getiterpath(msecfile) break if iterationpath: iterationdir = os.path.dirname(iterationpath) iterationfile = os.path.basename(iterationpath) mkdir_p(iterationdir) copy_file(fuzzed_file.path, os.path.join(iterationdir, iterationfile)) fuzzed_file.path = iterationpath cmd_as_args = get_command_args_list(template, fuzzed_file.path)[1] targetdir = os.path.dirname(cmd_as_args[0]) args = [] if options.use_windbg and options.debugger: parser.error('Options --windbg and --debugger are mutually exclusive.') if options.debugger: debugger_app = options.debugger elif options.use_windbg: debugger_app = 'windbg' else: debugger_app = 'cdb' args.append(debugger_app) if not options.debugger: # Using cdb or windbg args.append('-amsec.dll') if options.debugheap: # do not use hd, xd options if debugheap is set pass else: args.extend(('-hd', '-xd', 'gp')) if not options.break_on_start: args.extend(('-xd', 'bpe', '-G')) args.extend(('-xd', 'wob', '-o',)) args.extend(cmd_as_args) logger.info('args %s' % cmd_as_args) p = Popen(args, cwd=targetdir, universal_newlines=True) p.wait()
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 copy_files(self, outdir): crash_files = os.listdir(self.tempdir) for f in crash_files: filepath = os.path.join(self.tempdir, f) if os.path.isfile(filepath): filetools.copy_file(filepath, outdir)
def main(): 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='configs/bff.yaml', dest='config', help='path to the configuration file to use') parser.add_option('-a', '--args', dest='print_args', action='store_true', help='Print function arguments') parser.add_option('-o', '--out', dest='outfile', help='PIN output file') 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) if options.outfile: outfile = options.outfile else: outfile = 'calltrace.log' 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.path) 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 if iterationpath: 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 = load_and_fix_config(cfg_file) cmd_as_args = get_command_args_list( config['target']['cmdline_template'], fullpath_fuzzed_file)[1] args = [] pin = os.path.expanduser('~/pin/pin') pintool = os.path.expanduser('~/pintool/calltrace.so') args = [pin, '-injection', 'child', '-t', pintool, '-o', outfile] if options.print_args: args.append('-a') args.append('--') args.extend(cmd_as_args) logger.info('args %s' % args) p = Popen(args, universal_newlines=True) p.wait()
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='configs/foe.yaml', dest='config', help='path to the configuration file to use') parser.add_option('-w', '--windbg', dest='use_windbg', action='store_true', help='Use windbg instead of cdb') parser.add_option('-b', '--break', dest='break_on_start', action='store_true', help='Break on start of debugger session') parser.add_option('-d', '--debugheap', dest='debugheap', action='store_true', help='Use debug heap') 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') config = Config(cfg_file).config iterationpath = '' template = string.Template(config['target']['cmdline_template']) if options.filepath: # Recreate same file path as fuzz iteration resultdir = os.path.dirname(fuzzed_file.path) for msecfile in all_files(resultdir, '*.msec'): print '** using msecfile: %s' % msecfile iterationpath = getiterpath(msecfile) break if iterationpath: iterationdir = os.path.dirname(iterationpath) iterationfile = os.path.basename(iterationpath) mkdir_p(iterationdir) copy_file(fuzzed_file.path, os.path.join(iterationdir, iterationfile)) fuzzed_file.path = iterationpath cmd_as_args = get_command_args_list(template, fuzzed_file.path)[1] targetdir = os.path.dirname(cmd_as_args[0]) args = [] if options.use_windbg and options.debugger: parser.error('Options --windbg and --debugger are mutually exclusive.') if options.debugger: debugger_app = options.debugger elif options.use_windbg: debugger_app = 'windbg' else: debugger_app = 'cdb' args.append(debugger_app) if not options.debugger: # Using cdb or windbg args.append('-amsec.dll') if options.debugheap: # do not use hd, xd options if debugheap is set pass else: args.extend(('-hd', '-xd', 'gp')) if not options.break_on_start: args.extend(('-xd', 'bpe', '-G')) args.extend(( '-xd', 'wob', '-o', )) args.extend(cmd_as_args) logger.info('args %s' % cmd_as_args) p = Popen(args, cwd=targetdir, universal_newlines=True) p.wait()
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(): 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 testcase 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).') parser.add_option('-k', '--keepothers', dest='keep_other_crashes', action='store_true', help='Keep other testcase hashes encountered during minimization') (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("../configs/bff.yaml"): cfg_file = "../configs/bff.yaml" elif os.path.isfile("configs/bff.yaml"): cfg_file = "configs/bff.yaml" 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" outdir = os.path.abspath(outdir) 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.path) else: parser.error('fuzzedfile must be specified') if options.target: seedfile = BasicFile(options.target) else: seedfile = None min2seed = not options.stringmode filename_modifier = '' crashers_dir = '.' cfg = load_and_fix_config(cfg_file) debugger_timeout = cfg['runner']['runtimeout'] * 2 if debugger_timeout < 10: debugger_timeout = 10 proc_compat = platform.system() == 'Linux' cfg['debugger']['proc_compat'] = proc_compat cfg['debugger']['runtimeout'] = debugger_timeout with LinuxTestcase(cfg=cfg, seedfile=seedfile, fuzzedfile=fuzzed_file, program=cfg['target']['program'], cmd_template=cfg['target']['cmdline_template'], debugger_timeout=debugger_timeout, cmdlist=get_command_args_list( cfg['target']['cmdline_template'], fuzzed_file.path)[1], backtrace_lines=cfg['debugger']['backtracelevels'], crashers_dir=crashers_dir, workdir_base=outdir, keep_faddr=options.keep_uniq_faddr) as testcase: filetools.make_directories(testcase.tempdir) logger.info('Copying %s to %s', fuzzed_file.path, testcase.tempdir) filetools.copy_file(fuzzed_file.path, testcase.tempdir) minlog = os.path.join(outdir, 'min_log.txt') with Minimizer(cfg=cfg, testcase=testcase, crash_dst_dir=outdir, seedfile_as_target=min2seed, bitwise=options.bitwise, confidence=confidence, logfile=minlog, tempdir=testcase.tempdir, maxtime=options.timeout, preferx=options.prefer_x_target, keep_uniq_faddr=options.keep_uniq_faddr) as minimize: minimize.save_others = options.keep_other_crashes minimize.target_size_guess = int(options.initial_target_size) minimize.go() if options.stringmode: logger.debug('x character substitution') length = len(minimize.fuzzed_content) 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_content) for idx in minimize.bytemap: logger.debug('Swapping index %d', idx) targetstring[idx] = fuzzed[idx] filename = ''.join( (testcase.fuzzedfile.root, filename_modifier, testcase.fuzzedfile.ext)) metasploit_file = os.path.join(testcase.tempdir, filename) with open(metasploit_file, 'wb') as f: f.writelines(targetstring) testcase.copy_files(outdir) testcase.clean_tmpdir()
def main(): 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 testcase 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)') parser.add_option('-g', '--target-size-guess', dest='initial_target_size', help='A guess at the minimal value (int)') parser.add_option('', '--config', default='configs/bff.yaml', 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).') parser.add_option('-k', '--keepothers', dest='keep_other_crashes', action='store_true', help='Keep other testcase hashes encountered during minimization') (options, args) = parser.parse_args() if options.debug: logger.setLevel(logging.DEBUG) else: logger.setLevel(logging.INFO) if options.config: cfg_file = options.config else: cfg_file = "../configs/bff.yaml" logger.debug('WindowsConfig 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' outdir = os.path.abspath(outdir) filetools.make_directories(outdir) if len(args) and os.path.exists(args[0]): fuzzed_file = BasicFile(args[0]) logger.info('Fuzzed file is: %s', fuzzed_file.path) else: parser.error('fuzzedfile must be specified') cfg = load_and_fix_config(cfg_file) if options.target: seedfile = BasicFile(options.target) else: seedfile = None min2seed = not options.stringmode filename_modifier = '' retries = 0 debugger_class = msec.MsecDebugger cmd_as_args = get_command_args_list( cfg['target']['cmdline_template'], fuzzed_file.path)[1] # Figure out an appropriate timeout to use based on the config winver = sys.getwindowsversion().major machine = platform.machine() hook_incompatible = winver > 5 or machine == 'AMD64' debugger_timeout = cfg['runner']['runtimeout'] if not hook_incompatible: # Assume the user has tuned timeout to the hook. # Allow extra time for the debugger to run debugger_timeout *= 2 if debugger_timeout < 10: debugger_timeout = 10 cfg['debugger']['runtimeout'] = debugger_timeout with WindowsTestcase(cfg=cfg, seedfile=seedfile, fuzzedfile=fuzzed_file, program=cfg['target']['program'], cmd_template=cfg['target']['cmdline_template'], debugger_timeout=cfg['debugger']['runtimeout'], cmdlist=cmd_as_args, dbg_opts=cfg['debugger'], workdir_base=outdir, keep_faddr=options.keep_uniq_faddr, heisenbug_retries=retries ) as testcase: filetools.make_directories(testcase.tempdir) logger.info('Copying %s to %s', fuzzed_file.path, testcase.tempdir) filetools.copy_file(fuzzed_file.path, testcase.tempdir) minlog = os.path.join(outdir, 'min_log.txt') with Minimizer(cfg=cfg, testcase=testcase, crash_dst_dir=outdir, seedfile_as_target=min2seed, bitwise=options.bitwise, confidence=confidence, tempdir=outdir, logfile=minlog, maxtime=options.timeout, preferx=options.prefer_x_target) as minimize: minimize.save_others = options.keep_other_crashes minimize.target_size_guess = int(options.initial_target_size) minimize.go() if options.stringmode: logger.debug('x character substitution') length = len(minimize.fuzzed_content) 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_content) for idx in minimize.bytemap: logger.debug('Swapping index %d', idx) targetstring[idx] = fuzzed[idx] filename = ''.join( (testcase.fuzzedfile.root, filename_modifier, testcase.fuzzedfile.ext)) metasploit_file = os.path.join(testcase.tempdir, filename) f = open(metasploit_file, 'wb') try: f.writelines(targetstring) finally: f.close() testcase.copy_files(outdir) testcase.clean_tmpdir()
def main(): 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='configs/bff.yaml', 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.path) 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 if iterationpath: 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 = load_and_fix_config(cfg_file) cmd_as_args = get_command_args_list( config['target']['cmdline_template'], fullpath_fuzzed_file)[1] 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' elif platform.system() == 'Darwin': debugger_app = 'lldb' else: debugger_app = 'gdb' args.append(debugger_app) if options.use_edb: args.append('--run') elif debugger_app == 'gdb': # Using gdb args.append('--args') args.extend(cmd_as_args) logger.info('args %s' % args) p = Popen(args, universal_newlines=True) p.wait()
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