Example #1
0
    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)
Example #3
0
    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 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)
Example #5
0
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
Example #6
0
    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)
Example #8
0
    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)
Example #9
0
    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)
Example #10
0
    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)
Example #11
0
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()
Example #12
0
 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()
Example #13
0
    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)
Example #14
0
    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)
Example #15
0
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()
Example #16
0
File: bff.py Project: wflk/certfuzz
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 _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()
Example #19
0
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()
Example #20
0
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)
Example #22
0
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()
Example #23
0
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()
Example #24
0
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()
Example #25
0
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()
Example #27
0
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()
Example #29
0
 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)
Example #30
0
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()
Example #31
0
File: bff.py Project: wflk/certfuzz
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