def main(argv): parser = argparse.ArgumentParser( description="Post selected contents of fuzzer_stats to Twitter.", usage="afl-stats [-h] [-c config] [-d database] [-t]\n") parser.add_argument( "-c", "--config", dest="config_file", help="afl-stats config file (Default: afl-stats.conf)!", default="afl-stats.conf") parser.add_argument("-d", "--database", dest="database_file", help="Dump stats history into database.") parser.add_argument('-t', '--twitter', dest='twitter', action='store_const', const=True, help='Post stats to twitter (Default: off).', default=False) parser.add_argument('-q', '--quiet', dest='quiet', action='store_const', const=True, help='Suppress any output (Default: off).', default=False) args = parser.parse_args(argv[1:]) if not args.quiet: show_info() if args.database_file: db_file = os.path.abspath(os.path.expanduser(args.database_file)) else: db_file = None if db_file: lite_db = con_sqlite.sqliteConnector(db_file, verbose=False) else: lite_db = None config_settings = read_config(args.config_file) if lite_db: dump_stats(config_settings, lite_db) lite_db.commit_close() if args.twitter: twitter_inst = twitter_init(config_settings) else: twitter_inst = None fetch_stats(config_settings, twitter_inst)
def main(argv): show_info() parser = argparse.ArgumentParser(description="afl-collect copies all crash sample files from an afl sync dir used \ by multiple fuzzers when fuzzing in parallel into a single location providing easy access for further crash analysis.", usage="afl-collect [-d DATABASE] [-e|-g GDB_EXPL_SCRIPT_FILE] [-f LIST_FILENAME]\n \ [-h] [-j THREADS] [-m] [-r] [-rr] sync_dir collection_dir -- target_cmd") parser.add_argument("sync_dir", help="afl synchronisation directory crash samples will be collected from.") parser.add_argument("collection_dir", help="Output directory that will hold a copy of all crash samples and other generated files. \ Existing files in the collection directory will be overwritten!") parser.add_argument("-d", "--database", dest="database_file", help="Submit sample data into an sqlite3 database (\ only when used together with '-e'). afl-collect skips processing of samples already found in existing database.", default=None) parser.add_argument("-e", "--execute-gdb-script", dest="gdb_expl_script_file", help="Generate and execute a gdb+exploitable script after crash sample collection for crash \ classification. (Like option '-g', plus script execution.)", default=None) parser.add_argument("-f", "--filelist", dest="list_filename", default=None, help="Writes all collected crash sample filenames into a file in the collection directory.") parser.add_argument("-g", "--generate-gdb-script", dest="gdb_script_file", help="Generate gdb script to run 'exploitable.py' on all collected crash samples. Generated \ script will be placed into collection directory.", default=None) parser.add_argument("-j", "--threads", dest="num_threads", default=1, help="Enable parallel analysis by specifying the number of threads afl-collect will utilize.") parser.add_argument("-m", "--minimize-filenames", dest="min_filename", action="store_const", const=True, default=False, help="Minimize crash sample file names by only keeping fuzzer name and ID.") parser.add_argument("-r", "--remove-invalid", dest="remove_invalid", action="store_const", const=True, default=False, help="Verify collected crash samples and remove samples that do not lead to \ crashes (runs 'afl-vcrash.py -r' on collection directory). This step is done prior to any script file \ or file list generation/execution.") parser.add_argument("-rr", "--remove-unexploitable", dest="remove_unexploitable", action="store_const", const=True, default=False, help="Remove crash samples that have an exploitable classification of \ 'NOT_EXPLOITABLE' or 'PROBABLY_NOT_EXPLOITABLE'. Sample file removal will take place after gdb+exploitable \ script execution. Has no effect without '-e'.") parser.add_argument("target_cmd", nargs="+", help="Path to the target binary and its command line arguments. \ Use '@@' to specify crash sample input file position (see afl-fuzz usage).") args = parser.parse_args(argv[1:]) sync_dir = os.path.abspath(os.path.expanduser(args.sync_dir)) if not os.path.exists(sync_dir): print_err("No valid directory provided for <SYNC_DIR>!") return if args.collection_dir: out_dir = os.path.abspath(os.path.expanduser(args.collection_dir)) if not os.path.exists(out_dir): os.makedirs(out_dir, exist_ok=True) else: print_err("No valid directory provided for <OUT_DIR>!") return args.target_cmd = " ".join(args.target_cmd).split() args.target_cmd[0] = os.path.abspath(os.path.expanduser(args.target_cmd[0])) if not os.path.exists(args.target_cmd[0]): print_err("Target binary not found!") return args.target_cmd = " ".join(args.target_cmd) if args.database_file: db_file = os.path.abspath(os.path.expanduser(args.database_file)) else: db_file = None print_ok("Going to collect crash samples from '%s'." % sync_dir) # initialize database if db_file: lite_db = con_sqlite.sqliteConnector(db_file) lite_db.init_database() else: lite_db = None fuzzers = get_fuzzer_instances(sync_dir) print_ok("Found %d fuzzers, collecting crash samples." % len(fuzzers)) sample_index = build_sample_index(sync_dir, out_dir, fuzzers, lite_db, args.min_filename) if len(sample_index.index) > 0: print_ok("Successfully indexed %d crash samples." % len(sample_index.index)) elif db_file: print_warn("No unseen samples found. Check your database for results!") return else: print_warn("No samples found. Check directory settings!") return if args.remove_invalid: from afl_utils import afl_vcrash invalid_samples = afl_vcrash.verify_samples(int(args.num_threads), sample_index.inputs(), args.target_cmd) # store invalid samples in db print_ok("Saving invalid sample info to database.") if args.gdb_expl_script_file and db_file: for sample in invalid_samples: sample_name = sample_index.outputs(input_file=sample) dataset = {'sample': sample_name[0]['output'], 'classification': 'INVALID', 'description': 'Sample does not cause a crash in the target.', 'hash': ''} if not lite_db.dataset_exists(dataset): lite_db.insert_dataset(dataset) # remove invalid samples from sample index sample_index.remove_inputs(invalid_samples) print_warn("Removed %d invalid crash samples from index." % len(invalid_samples)) # generate gdb+exploitable script if args.gdb_expl_script_file: divided_index = sample_index.divide(int(args.num_threads)) for i in range(0, int(args.num_threads), 1): generate_gdb_exploitable_script(os.path.join(out_dir, args.gdb_expl_script_file), divided_index[i], args.target_cmd, i, intermediate=True) # execute gdb+exploitable script classification_data = execute_gdb_script(out_dir, args.gdb_expl_script_file, len(sample_index.inputs()), int(args.num_threads)) # Submit crash classification data into database print_ok("Saving sample classification info to database.") if db_file: for dataset in classification_data: if not lite_db.dataset_exists(dataset): lite_db.insert_dataset(dataset) # de-dupe by exploitable hash seen = set() seen_add = seen.add classification_data_dedupe = [x for x in classification_data if x['hash'] not in seen and not seen_add(x['hash'])] # remove dupe samples identified by exploitable hash uninteresting_samples = [x['sample'] for x in classification_data if x not in classification_data_dedupe] sample_index.remove_outputs(uninteresting_samples) print_warn("Removed %d duplicate samples from index. Will continue with %d remaining samples." % (len(uninteresting_samples), len(sample_index.index))) # remove crash samples that are classified uninteresting if args.remove_unexploitable: classification_unexploitable = [ 'NOT_EXPLOITABLE', 'PROBABLY_NOT_EXPLOITABLE', ] uninteresting_samples = [] for c in classification_data_dedupe: if c['classification'] in classification_unexploitable: uninteresting_samples.append(c['sample']) sample_index.remove_outputs(uninteresting_samples) print_warn("Removed %d uninteresting crash samples from index." % len(uninteresting_samples)) # generate output gdb script generate_gdb_exploitable_script(os.path.join(out_dir, args.gdb_expl_script_file), sample_index, args.target_cmd, 0) elif args.gdb_script_file: generate_gdb_exploitable_script(os.path.join(out_dir, args.gdb_script_file), sample_index, args.target_cmd) print_ok("Copying %d samples into output directory..." % len(sample_index.index)) files_collected = copy_samples(sample_index) # generate filelist of collected crash samples if args.list_filename: generate_sample_list(os.path.abspath(os.path.expanduser(args.list_filename)), files_collected) print_ok("Generated crash sample list '%s'." % os.path.abspath(os.path.expanduser(args.list_filename)))
def main(argv): show_info() parser = argparse.ArgumentParser( description= "afl-collect copies all crash sample files from an afl sync dir used \ by multiple fuzzers when fuzzing in parallel into a single location providing easy access for further crash analysis.", usage= "afl-collect [-d DATABASE] [-e|-g GDB_EXPL_SCRIPT_FILE] [-f LIST_FILENAME]\n \ [-h] [-j THREADS] [-m] [-r] [-rr] sync_dir collection_dir -- target_cmd") parser.add_argument( "sync_dir", help= "afl synchronisation directory crash samples will be collected from.") parser.add_argument( "collection_dir", help= "Output directory that will hold a copy of all crash samples and other generated files. \ Existing files in the collection directory will be overwritten!") parser.add_argument("-d", "--database", dest="database_file", help="Submit sample data into an sqlite3 database (\ only when used together with '-e'). afl-collect skips processing of samples already found in existing database.", default=None) parser.add_argument( "-e", "--execute-gdb-script", dest="gdb_expl_script_file", help= "Generate and execute a gdb+exploitable script after crash sample collection for crash \ classification. (Like option '-g', plus script execution.)", default=None) parser.add_argument( "-f", "--filelist", dest="list_filename", default=None, help= "Writes all collected crash sample filenames into a file in the collection directory." ) parser.add_argument( "-g", "--generate-gdb-script", dest="gdb_script_file", help= "Generate gdb script to run 'exploitable.py' on all collected crash samples. Generated \ script will be placed into collection directory.", default=None) parser.add_argument( "-j", "--threads", dest="num_threads", default=1, help= "Enable parallel analysis by specifying the number of threads afl-collect will utilize." ) parser.add_argument( "-m", "--minimize-filenames", dest="min_filename", action="store_const", const=True, default=False, help= "Minimize crash sample file names by only keeping fuzzer name and ID.") parser.add_argument( "-r", "--remove-invalid", dest="remove_invalid", action="store_const", const=True, default=False, help= "Verify collected crash samples and remove samples that do not lead to \ crashes or cause timeouts (runs 'afl-vcrash.py -r' on collection directory). This step is done prior to any script \ file execution or file list generation.") parser.add_argument( "-rr", "--remove-unexploitable", dest="remove_unexploitable", action="store_const", const=True, default=False, help="Remove crash samples that have an exploitable classification of \ 'NOT_EXPLOITABLE' or 'PROBABLY_NOT_EXPLOITABLE'. Sample file removal will take place after gdb+exploitable \ script execution. Has no effect without '-e'.") parser.add_argument( "target_cmd", nargs="+", help="Path to the target binary and its command line arguments. \ Use '@@' to specify crash sample input file position (see afl-fuzz usage).") args = parser.parse_args(argv[1:]) sync_dir = os.path.abspath(os.path.expanduser(args.sync_dir)) if not os.path.exists(sync_dir): print_err("No valid directory provided for <SYNC_DIR>!") return out_dir = os.path.abspath(os.path.expanduser(args.collection_dir)) if not os.path.exists(out_dir): os.makedirs(out_dir, exist_ok=True) args.target_cmd = " ".join(args.target_cmd).split() args.target_cmd[0] = os.path.abspath(os.path.expanduser( args.target_cmd[0])) if not os.path.exists(args.target_cmd[0]): print_err("Target binary not found!") return args.target_cmd = " ".join(args.target_cmd) if args.database_file: db_file = os.path.abspath(os.path.expanduser(args.database_file)) else: db_file = None print_ok("Going to collect crash samples from '%s'." % sync_dir) # initialize database if db_file: lite_db = con_sqlite.sqliteConnector(db_file) lite_db.init_database('Data', db_table_spec) else: lite_db = None fuzzers = get_fuzzer_instances(sync_dir) print_ok("Found %d fuzzers, collecting crash samples." % len(fuzzers)) sample_index = build_sample_index(sync_dir, out_dir, fuzzers, lite_db, args.min_filename) if len(sample_index.index) > 0: print_ok("Successfully indexed %d crash samples." % len(sample_index.index)) elif db_file: print_warn("No unseen samples found. Check your database for results!") return else: print_warn("No samples found. Check directory settings!") return if args.remove_invalid: from afl_utils import afl_vcrash invalid_samples, timeout_samples = afl_vcrash.verify_samples( int(args.num_threads), sample_index.inputs(), args.target_cmd, timeout_secs=10) # store invalid samples in db if args.gdb_expl_script_file and db_file: print_ok("Saving invalid sample info to database.") for sample in invalid_samples: sample_name = sample_index.outputs(input_file=sample) dataset = { 'Sample': sample_name[0], 'Classification': 'INVALID', 'Classification_Description': 'Sample does not cause a crash in the target.', 'Hash': '', 'User_Comment': '' } if not lite_db.dataset_exists('Data', dataset, ['Sample']): lite_db.insert_dataset('Data', dataset) for sample in timeout_samples: sample_name = sample_index.outputs(input_file=sample) dataset = { 'Sample': sample_name[0], 'Classification': 'TIMEOUT', 'Classification_Description': 'Sample caused a target execution timeout.', 'Hash': '', 'User_Comment': '' } if not lite_db.dataset_exists('Data', dataset, ['Sample']): lite_db.insert_dataset('Data', dataset) # remove invalid samples from sample index sample_index.remove_inputs(invalid_samples + timeout_samples) print_warn("Removed %d invalid crash samples from index." % len(invalid_samples)) print_warn("Removed %d timed out samples from index." % len(timeout_samples)) # generate gdb+exploitable script if args.gdb_expl_script_file: divided_index = sample_index.divide(int(args.num_threads)) for i in range(0, int(args.num_threads), 1): generate_gdb_exploitable_script(os.path.join( out_dir, args.gdb_expl_script_file), divided_index[i], args.target_cmd, i, intermediate=True) # execute gdb+exploitable script classification_data = execute_gdb_script(out_dir, args.gdb_expl_script_file, len(sample_index.inputs()), int(args.num_threads)) # Submit crash classification data into database if db_file: print_ok("Saving sample classification info to database.") for dataset in classification_data: if not lite_db.dataset_exists('Data', dataset, ['Sample']): lite_db.insert_dataset('Data', dataset) # de-dupe by exploitable hash seen = set() seen_add = seen.add classification_data_dedupe = [ x for x in classification_data if x['Hash'] not in seen and not seen_add(x['Hash']) ] # remove dupe samples identified by exploitable hash uninteresting_samples = [ x['Sample'] for x in classification_data if x not in classification_data_dedupe ] sample_index.remove_outputs(uninteresting_samples) print_warn( "Removed %d duplicate samples from index. Will continue with %d remaining samples." % (len(uninteresting_samples), len(sample_index.index))) # remove crash samples that are classified uninteresting if args.remove_unexploitable: classification_unexploitable = [ 'NOT_EXPLOITABLE', 'PROBABLY_NOT_EXPLOITABLE', ] uninteresting_samples = [] for c in classification_data_dedupe: if c['Classification'] in classification_unexploitable: uninteresting_samples.append(c['Sample']) sample_index.remove_outputs(uninteresting_samples) print_warn("Removed %d uninteresting crash samples from index." % len(uninteresting_samples)) # generate output gdb script generate_gdb_exploitable_script( os.path.join(out_dir, args.gdb_expl_script_file), sample_index, args.target_cmd, 0) elif args.gdb_script_file: generate_gdb_exploitable_script( os.path.join(out_dir, args.gdb_script_file), sample_index, args.target_cmd) print_ok("Copying %d samples into output directory..." % len(sample_index.index)) files_collected = copy_samples(sample_index) # generate filelist of collected crash samples if args.list_filename: generate_sample_list( os.path.abspath(os.path.expanduser(args.list_filename)), files_collected) print_ok("Generated crash sample list '%s'." % os.path.abspath(os.path.expanduser(args.list_filename))) # write db contents to file and close db connection if db_file: lite_db.commit_close()
def test_dump_stats(self): config_settings = {'fuzz_dirs': ['./testdata/sync/']} lite_db = con_sqlite.sqliteConnector('./testdata/afl-stats.db', verbose=True) self.assertIsNone(afl_stats.dump_stats(config_settings, lite_db)) self.assertTrue(os.path.exists('./testdata/afl-stats.db'))