Exemple #1
0
def invoke_dryrun(input_files, crash_dir, timeout_dir, target_cmd, timeout=60, num_threads=1):
    invalid_samples, timeout_samples = afl_vcrash.verify_samples(num_threads, input_files, target_cmd,
                                                                 timeout_secs=timeout)

    invalid_sample_set = set(invalid_samples+timeout_samples)
    input_sample_set = set(input_files)

    crashes_set = input_sample_set - invalid_sample_set
    crashes = list(crashes_set)

    if len(crashes) > 0:
        if not os.path.exists(crash_dir):
            os.makedirs(crash_dir, exist_ok=True)

        for c in crashes:
            shutil.move(c, os.path.join(crash_dir, os.path.basename(c)))

        print_warn("Moved %d crash samples from the corpus to %s." % (len(crashes), crash_dir))

        if len(timeout_samples) > 0:
            if not os.path.exists(timeout_dir):
                os.makedirs(timeout_dir, exist_ok=True)

            for t in timeout_samples:
                shutil.move(t, os.path.join(timeout_dir, os.path.basename(t)))

            print_warn("Moved %d timeouts from the corpus to %s." % (len(timeout_samples), timeout_dir))
    return
Exemple #2
0
def invoke_dryrun(input_files, crash_dir, timeout_dir, target_cmd, num_threads=1):
    invalid_samples, timeout_samples = afl_vcrash.verify_samples(num_threads, input_files, target_cmd)

    invalid_sample_set = set(invalid_samples+timeout_samples)
    input_sample_set = set(input_files)

    crashes_set = input_sample_set - invalid_sample_set
    crashes = list(crashes_set)

    if len(crashes) > 0:
        if not os.path.exists(crash_dir):
            os.makedirs(crash_dir, exist_ok=True)

        for c in crashes:
            shutil.move(c, os.path.join(crash_dir, os.path.basename(c)))

        print_warn("Moved %d crash samples from the corpus to %s." % (len(crashes), crash_dir))

        if len(timeout_samples) > 0:
            if not os.path.exists(timeout_dir):
                os.makedirs(timeout_dir, exist_ok=True)

            for t in timeout_samples:
                shutil.move(t, os.path.join(timeout_dir, os.path.basename(t)))

            print_warn("Moved %d timeouts from the corpus to %s." % (len(timeout_samples), timeout_dir))
    return
    def test_verify_samples(self):
        # test for invalid crash detection
        num_threads = 1
        samples = ['testdata/sync/fuzz000/fuzzer_stats']    # invalid (non-crashing) sample
        target_cmd = 'ls'
        timeout_secs = 3

        self.assertEqual((['testdata/sync/fuzz000/fuzzer_stats'], []),
                         afl_vcrash.verify_samples(num_threads, samples, target_cmd, timeout_secs))

        # test for timeout detection
        num_threads = 1
        samples = ['testdata/sync/fuzz000/fuzzer_stats']    # invalid (non-crashing) sample
        target_cmd = 'python testdata/dummy_process/dummyproc.py'
        timeout_secs = 1

        self.assertEqual(([], ['testdata/sync/fuzz000/fuzzer_stats']),
                         afl_vcrash.verify_samples(num_threads, samples, target_cmd, timeout_secs))
Exemple #4
0
    def test_verify_samples(self):
        # test for invalid crash detection
        num_threads = 1
        samples = ['testdata/sync/fuzz000/fuzzer_stats']  # invalid (non-crashing) sample
        target_cmd = 'ls'
        timeout_secs = 3

        self.assertEqual((['testdata/sync/fuzz000/fuzzer_stats'], []),
                         afl_vcrash.verify_samples(num_threads, samples, target_cmd, timeout_secs))

        # test for timeout detection
        num_threads = 1
        samples = ['testdata/sync/fuzz000/fuzzer_stats']  # invalid (non-crashing) sample
        target_cmd = 'python testdata/dummy_process/dummyproc.py'
        timeout_secs = 1

        self.assertEqual(([], ['testdata/sync/fuzz000/fuzzer_stats']),
                         afl_vcrash.verify_samples(num_threads, samples, target_cmd, timeout_secs))
Exemple #5
0
def invoke_dryrun(input_files, crash_dir, target_cmd):
    # TODO: handle timeouts, if possible
    invalid_samples = afl_vcrash.verify_samples(1, input_files, target_cmd)

    invalid_sample_set = set(invalid_samples)
    input_sample_set = set(input_files)

    crashes_set = input_sample_set - invalid_sample_set
    crashes = list(crashes_set)

    if len(crashes) > 0:
        if not os.path.exists(crash_dir):
            os.makedirs(crash_dir, exist_ok=True)

        for c in crashes:
            shutil.move(c, os.path.join(crash_dir, os.path.basename(c)))

    print("Moved %d crash samples from the corpus to %s." %
          (len(crashes), crash_dir))
    return
Exemple #6
0
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)))
Exemple #7
0
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()