def main(): debuggers.verify_supported_platform() from optparse import OptionParser hdlr = logging.StreamHandler() logger.addHandler(hdlr) usage = "usage: %prog [options] fuzzedfile" parser = OptionParser(usage) parser.add_option('', '--debug', dest='debug', action='store_true', help='Enable debug messages (overrides --verbose)') parser.add_option('', '--verbose', dest='verbose', action='store_true', help='Enable verbose messages') parser.add_option('-c', '--config', default='configs/foe.yaml', dest='config', help='path to the configuration file to use') parser.add_option('-w', '--windbg', dest='use_windbg', action='store_true', help='Use windbg instead of cdb') parser.add_option('-b', '--break', dest='break_on_start', action='store_true', help='Break on start of debugger session') parser.add_option('-d', '--debugheap', dest='debugheap', action='store_true', help='Use debug heap') parser.add_option('-p', '--debugger', dest='debugger', help='Use specified debugger') parser.add_option('-f', '--filepath', dest='filepath', action='store_true', help='Recreate original file path') (options, args) = parser.parse_args() if options.debug: logger.setLevel(logging.DEBUG) else: logger.setLevel(logging.INFO) cfg_file = options.config logger.debug('Config file: %s', cfg_file) if len(args) and os.path.exists(args[0]): fullpath_fuzzed_file = os.path.abspath(args[0]) fuzzed_file = BasicFile(fullpath_fuzzed_file) logger.info('Fuzzed file is %s', fuzzed_file) else: parser.error('fuzzedfile must be specified') config = Config(cfg_file).config iterationpath = '' template = string.Template(config['target']['cmdline_template']) if options.filepath: # Recreate same file path as fuzz iteration resultdir = os.path.dirname(fuzzed_file.path) for msecfile in all_files(resultdir, '*.msec'): print '** using msecfile: %s' % msecfile iterationpath = getiterpath(msecfile) break if iterationpath: iterationdir = os.path.dirname(iterationpath) iterationfile = os.path.basename(iterationpath) mkdir_p(iterationdir) copy_file(fuzzed_file.path, os.path.join(iterationdir, iterationfile)) fuzzed_file.path = iterationpath cmd_as_args = get_command_args_list(template, fuzzed_file.path)[1] targetdir = os.path.dirname(cmd_as_args[0]) args = [] if options.use_windbg and options.debugger: parser.error('Options --windbg and --debugger are mutually exclusive.') if options.debugger: debugger_app = options.debugger elif options.use_windbg: debugger_app = 'windbg' else: debugger_app = 'cdb' args.append(debugger_app) if not options.debugger: # Using cdb or windbg args.append('-amsec.dll') if options.debugheap: # do not use hd, xd options if debugheap is set pass else: args.extend(('-hd', '-xd', 'gp')) if not options.break_on_start: args.extend(('-xd', 'bpe', '-G')) args.extend(( '-xd', 'wob', '-o', )) args.extend(cmd_as_args) logger.info('args %s' % cmd_as_args) p = Popen(args, cwd=targetdir, universal_newlines=True) p.wait()
def __init__(self): fd, f = tempfile.mkstemp(suffix='.ext', prefix='fileroot') os.close(fd) self.fuzzedfile = BasicFile(f) self.debugger_template = 'foo'
def main(): from optparse import OptionParser hdlr = logging.StreamHandler() logger.addHandler(hdlr) usage = "usage: %prog [options] fuzzedfile" parser = OptionParser(usage) parser.add_option('', '--debug', dest='debug', action='store_true', help='Enable debug messages (overrides --verbose)') parser.add_option('', '--verbose', dest='verbose', action='store_true', help='Enable verbose messages') parser.add_option('-t', '--target', dest='target', help='the file to minimize to (typically the seedfile)') parser.add_option('-o', '--outdir', dest='outdir', help='dir to write output to') parser.add_option('-s', '--stringmode', dest='stringmode', action='store_true', help='minimize to a string rather than to a target file') parser.add_option( '-x', '--preferx', dest='prefer_x_target', action='store_true', help='Minimize to \'x\' characters instead of Metasploit string pattern' ) parser.add_option( '-f', '--faddr', dest='keep_uniq_faddr', action='store_true', help='Use exception faulting addresses as part of testcase signature') parser.add_option( '-b', '--bitwise', dest='bitwise', action='store_true', help='if set, use bitwise hamming distance. Default is bytewise') parser.add_option('-c', '--confidence', dest='confidence', help='The desired confidence level (default: 0.999)', type='float') parser.add_option('-g', '--target-size-guess', dest='initial_target_size', help='A guess at the minimal value (int)', type='int') parser.add_option('', '--config', dest='config', help='path to the configuration file to use') parser.add_option( '', '--timeout', dest='timeout', metavar='N', type='int', default=0, help='Stop minimizing after N seconds (default is 0, never time out).') parser.add_option( '-k', '--keepothers', dest='keep_other_crashes', action='store_true', help='Keep other testcase hashes encountered during minimization') (options, args) = parser.parse_args() if options.debug: logger.setLevel(logging.DEBUG) elif options.verbose: logger.setLevel(logging.INFO) if options.config: cfg_file = os.path.expanduser(options.config) else: if os.path.isfile("../configs/bff.yaml"): cfg_file = "../configs/bff.yaml" elif os.path.isfile("configs/bff.yaml"): cfg_file = "configs/bff.yaml" else: parser.error( 'Configuration file (--config) option must be specified.') logger.debug('Config file: %s', cfg_file) if options.stringmode and options.target: parser.error( 'Options --stringmode and --target are mutually exclusive.') # Set some default options. Fast and loose if in string mode # More precise with minimize to seedfile if not options.confidence: if options.stringmode: options.confidence = 0.5 else: options.confidence = 0.999 if not options.initial_target_size: if options.stringmode: options.initial_target_size = 100 else: options.initial_target_size = 1 if options.confidence: try: options.confidence = float(options.confidence) except: parser.error('Confidence must be a float.') if not 0.0 < options.confidence < 1.0: parser.error('Confidence must be in the range 0.0 < c < 1.0') confidence = options.confidence if options.outdir: outdir = options.outdir else: outdir = "./minimizer_out" outdir = os.path.abspath(outdir) if not os.path.exists(outdir): filetools.make_directories(outdir) if not os.path.isdir(outdir): parser.error('--outdir must either already be a dir or not exist: %s' % outdir) if len(args) and os.path.exists(args[0]): fuzzed_file = BasicFile(args[0]) logger.info('Fuzzed file is: %s', fuzzed_file.path) else: parser.error('fuzzedfile must be specified') if options.target: seedfile = BasicFile(options.target) else: seedfile = None min2seed = not options.stringmode filename_modifier = '' crashers_dir = '.' cfg = load_and_fix_config(cfg_file) debugger_timeout = cfg['runner']['runtimeout'] * 2 if debugger_timeout < 10: debugger_timeout = 10 proc_compat = platform.system() == 'Linux' cfg['debugger']['proc_compat'] = proc_compat cfg['debugger']['runtimeout'] = debugger_timeout with LinuxTestcase(cfg=cfg, seedfile=seedfile, fuzzedfile=fuzzed_file, program=cfg['target']['program'], cmd_template=cfg['target']['cmdline_template'], debugger_timeout=debugger_timeout, cmdlist=get_command_args_list( cfg['target']['cmdline_template'], fuzzed_file.path)[1], backtrace_lines=cfg['debugger']['backtracelevels'], crashers_dir=crashers_dir, workdir_base=outdir, keep_faddr=options.keep_uniq_faddr) as testcase: filetools.make_directories(testcase.tempdir) logger.info('Copying %s to %s', fuzzed_file.path, testcase.tempdir) filetools.copy_file(fuzzed_file.path, testcase.tempdir) minlog = os.path.join(outdir, 'min_log.txt') with Minimizer(cfg=cfg, testcase=testcase, crash_dst_dir=outdir, seedfile_as_target=min2seed, bitwise=options.bitwise, confidence=confidence, logfile=minlog, tempdir=testcase.tempdir, maxtime=options.timeout, preferx=options.prefer_x_target, keep_uniq_faddr=options.keep_uniq_faddr) as minimize: minimize.save_others = options.keep_other_crashes minimize.target_size_guess = int(options.initial_target_size) minimize.go() if options.stringmode: logger.debug('x character substitution') length = len(minimize.fuzzed_content) if options.prefer_x_target: # We minimized to 'x', so we attempt to get metasploit as a # freebie targetstring = list(text.metasploit_pattern_orig(length)) filename_modifier = '-mtsp' else: # We minimized to metasploit, so we attempt to get 'x' as a # freebie targetstring = list('x' * length) filename_modifier = '-x' fuzzed = list(minimize.fuzzed_content) for idx in minimize.bytemap: logger.debug('Swapping index %d', idx) targetstring[idx] = fuzzed[idx] filename = ''.join( (testcase.fuzzedfile.root, filename_modifier, testcase.fuzzedfile.ext)) metasploit_file = os.path.join(testcase.tempdir, filename) with open(metasploit_file, 'wb') as f: f.writelines(targetstring) testcase.copy_files(outdir) testcase.clean_tmpdir()
def main(): from optparse import OptionParser hdlr = logging.StreamHandler() logger.addHandler(hdlr) usage = "usage: %prog [options] fuzzedfile" parser = OptionParser(usage) parser.add_option('', '--debug', dest='debug', action='store_true', help='Enable debug messages (overrides --verbose)') parser.add_option('', '--verbose', dest='verbose', action='store_true', help='Enable verbose messages') parser.add_option('-c', '--config', default='configs/bff.yaml', dest='config', help='path to the configuration file to use') parser.add_option('-a', '--args', dest='print_args', action='store_true', help='Print function arguments') parser.add_option('-o', '--out', dest='outfile', help='PIN output file') parser.add_option('-f', '--filepath', dest='filepath', action='store_true', help='Recreate original file path') (options, args) = parser.parse_args() if options.debug: logger.setLevel(logging.DEBUG) else: logger.setLevel(logging.INFO) if options.outfile: outfile = options.outfile else: outfile = 'calltrace.log' cfg_file = options.config logger.debug('Config file: %s', cfg_file) if len(args) and os.path.exists(args[0]): fullpath_fuzzed_file = os.path.abspath(args[0]) fuzzed_file = BasicFile(fullpath_fuzzed_file) logger.info('Fuzzed file is: %s', fuzzed_file.path) else: parser.error('fuzzedfile must be specified') iterationpath = '' if options.filepath: # Recreate same file path as fuzz iteration resultdir = os.path.dirname(fuzzed_file.path) for gdbfile in all_files(resultdir, '*.gdb'): print '** using gdb: %s' % gdbfile iterationpath = getiterpath(gdbfile) break if iterationpath: iterationdir = os.path.dirname(iterationpath) iterationfile = os.path.basename(iterationpath) if iterationdir: mkdir_p(iterationdir) copy_file(fuzzed_file.path, os.path.join(iterationdir, iterationfile)) fullpath_fuzzed_file = iterationpath config = load_and_fix_config(cfg_file) cmd_as_args = get_command_args_list( config['target']['cmdline_template'], fullpath_fuzzed_file)[1] args = [] pin = os.path.expanduser('~/pin/pin') pintool = os.path.expanduser('~/pintool/calltrace.so') args = [pin, '-injection', 'child', '-t', pintool, '-o', outfile] if options.print_args: args.append('-a') args.append('--') args.extend(cmd_as_args) logger.info('args %s' % args) p = Popen(args, universal_newlines=True) p.wait()
def go(self): # start by copying the fuzzed_content file since as of now it's our # best fit filetools.copy_file(self.testcase.fuzzedfile.path, self.outputfile) # replace the fuzzedfile object in testcase with the minimized copy self.testcase.fuzzedfile = BasicFile(self.outputfile) self.logger.info('Attempting to minimize testcase(es) [%s]', self._crash_hashes_string()) # keep going until either: # a. we find a minimum hd of 1 # b. we run out of discard_chances # c. our discard_chance * minimum hd is less than one (we won't discard anything) # d. we've exhaustively searched all the possible files with hd less # than self.min_distance while not self.min_found and not self.try_exhaustive: if not self.set_discard_chance(): break if not self.set_n_misses(): break got_hit = False while self.consecutive_misses <= self.n_misses_allowed: if self.use_watchdog: # touch the watchdog file so we don't reboot during long # minimizations open(self.watchdogfile, 'w').close() # Fix for BFF-208 if self._time_exceeded(): logger.info( 'Max time for minimization exceeded, ending minimizer early.' ) self.min_found = True break if not self.set_discard_chance(): break if not self.set_n_misses(): break self.swap_bytes() self.total_tries += 1 # have we been at this level before? if not self.files_tried_at_hd.get(self.min_distance): # we've reached a new minimum, so create new sets self.files_tried_at_hd[self.min_distance] = set() self.files_tried_singlebyte_at_hd[ self.min_distance] = set() # have we exhausted all the possible files with smaller hd? possible_files = (2**self.min_distance) - 2 seen_files = len(self.files_tried_at_hd[self.min_distance]) # maybe we're done? if seen_files == possible_files: # we've exhaustively searched everything with hd < # self.min_distance self.logger.info( 'Exhaustively searched all files shorter than %d', self.min_distance) self.min_found = True break # have we exhausted all files that are 1 byte smaller hd? possible_singlebyte_diff_files = self.min_distance singlebyte_diff_files_seen = len( self.files_tried_singlebyte_at_hd[self.min_distance]) # maybe we're done? if singlebyte_diff_files_seen == possible_singlebyte_diff_files: self.logger.info( 'We have tried all %d files that are one byte closer than the current minimum', self.min_distance) self.min_found = True break # remember this file for next time around self.files_tried_at_hd[self.min_distance].add( self.newfuzzed_md5) if self.newfuzzed_hd == (self.min_distance - 1): self.files_tried_singlebyte_at_hd[self.min_distance].add( self.newfuzzed_md5) self.print_intermediate_log() if self.newfuzzed_md5 in self.files_tried: # we've already seen this attempt, so skip ahead to the next one # but still count it as a miss since our math assumes we're putting # the marbles back in the jar after each draw self.consecutive_misses += 1 self.total_misses += 1 continue # we didn't skip ahead, so it must have been new. Remember it # now self.files_tried.add(self.newfuzzed_md5) # we have a better match, write it to a file if not len(self.newfuzzed): raise MinimizerError( 'New fuzzed_content content is empty.') self._write_file() if self.is_same_crash(): # record the result # 1. copy the tempfile filetools.best_effort_move(self.tempfile, self.outputfile) # 2. replace the fuzzed_content file in the crasher with # the current one self.testcase.fuzzedfile = BasicFile(self.outputfile) # 3. replace the current fuzzed_content with newfuzzed self.fuzzed_content = self.newfuzzed self.min_distance = self.newfuzzed_hd got_hit = True if self.min_distance == 1: # we are done self.min_found = True elif self.newfuzzed_hd <= self.exhaustivesearch_threshold: self._set_bytemap() logger.info( 'Exhaustively checking remaining %s bytes' % self.newfuzzed_hd) self.try_exhaustive = True break else: # set up for next iteration self.consecutive_misses = 0 if not self.set_discard_chance(): break if not self.set_n_misses(): break else: # we missed. increment counter and try again self.total_misses += 1 self.consecutive_misses += 1 # Fix for BFF-225 # There may be some situation that causes testcase uniqueness # hashing to break. (e.g. BFF-224 ). Minimizer should bail # if the number of unique crashes encountered exceeds some # threshold. e.g. 20 maybe? if len(self.other_crashes ) > MAX_OTHER_CRASHES and self.seedfile_as_target: logger.info( 'Exceeded maximum number of other crashes (%d), ending minimizer early.', MAX_OTHER_CRASHES) self.min_found = True break if not got_hit: # we are self.confidence_level sure that self.target_size_guess is wrong # so increment it by 1 self.target_size_guess += 1 if self.try_exhaustive: for offset in list(self.bytemap): logger.debug('Verifying byte location: %s' % hex(offset)) self.revert_byte(offset) self._write_file() if self.is_same_crash(): logger.debug('Fuzzed byte at offset %s is not relevant' % hex(offset)) filetools.best_effort_move(self.tempfile, self.outputfile) self.testcase.fuzzedfile = BasicFile(self.outputfile) self.fuzzed_content = self.newfuzzed self.bytemap.remove(offset) # We're done minimizing. Set the bytemap (kept bytes) self._set_bytemap() self.logger.info('We were looking for [%s] ...', self._crash_hashes_string()) for (md5, count) in self.crash_sigs_found.items(): self.logger.info('\t...and found %s\t%d times', md5, count) if self.bytemap: hex_bytemap = [] for offset in self.bytemap: hex_bytemap.append(hex(offset)) self.logger.info('Bytemap: %s', hex_bytemap)
def main(): 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()