コード例 #1
0
ファイル: runfuzzer.py プロジェクト: weizhunsun/TIFF
def main():
    # first lets create the base directorty to keep all temporary data
    try:
        shutil.rmtree(config.BASETMP)
    except OSError:
        pass
    if os.path.isdir(config.BASETMP) == False:
        os.mkdir(config.BASETMP)
    check_env()
    ## parse the arguments #########
    parser = argparse.ArgumentParser(description='VUzzer options')
    parser.add_argument('-s', '--sut', help='SUT commandline', required=True)
    parser.add_argument('-i',
                        '--inputd',
                        help='seed input directory (relative path)',
                        required=True)
    parser.add_argument(
        '-w',
        '--weight',
        help=
        'path of the pickle file(s) for BB wieghts (separated by comma, in case there are two) ',
        required=True)
    parser.add_argument(
        '-n',
        '--name',
        help=
        'Path of the pickle file(s) containing strings from CMP inst (separated by comma if there are two).',
        required=True)
    parser.add_argument(
        '-l',
        '--libnum',
        help=
        'Nunber of binaries to monitor (only application or used libraries)',
        required=False,
        default=1)
    parser.add_argument(
        '-o',
        '--offsets',
        help=
        'base-address of application and library (if used), separated by comma',
        required=False,
        default='0x00000000')
    parser.add_argument('-b',
                        '--libname',
                        help='library name to monitor',
                        required=False,
                        default='')
    args = parser.parse_args()
    config.SUT = args.sut
    config.INITIALD = os.path.join(config.INITIALD, args.inputd)
    config.LIBNUM = int(args.libnum)
    config.LIBTOMONITOR = args.libname
    config.LIBPICKLE = [w for w in args.weight.split(',')]
    config.NAMESPICKLE = [n for n in args.name.split(',')]
    config.LIBOFFSETS = [o for o in args.offsets.split(',')]
    ih = config.PINCMD.index("#")
    config.PINCMD[ih] = args.libname

    ###################################

    config.minLength = get_min_file(config.INITIALD)
    try:
        shutil.rmtree(config.KEEPD)
    except OSError:
        pass
    os.mkdir(config.KEEPD)

    try:
        os.mkdir("outd")
    except OSError:
        pass

    try:
        os.mkdir("outd/crashInputs")
    except OSError:
        gau.emptyDir("outd/crashInputs")
    try:
        os.mkdir("outd/hangs")
    except OSError:
        gau.emptyDir("outd/hangs")

    try:
        os.mkdir("outd/temp")
    except:
        gau.emptyDir("outd/temp")

    crashHash = []
    try:
        os.mkdir(config.SPECIAL)
    except OSError:
        gau.emptyDir(config.SPECIAL)

    try:
        os.mkdir(config.INTER)
    except OSError:
        gau.emptyDir(config.INTER)

    ###### open names pickle files
    gau.prepareBBOffsets()
    if len(config.cALLBB) > 0:
        config.BBFORPRUNE = list(config.cALLBB)
    else:
        print "[*]: cALLBB is not initialized. something is wrong!!\n"
        system.exit()

    if config.PTMODE:
        pt = simplept.simplept()
    else:
        pt = None
    if config.ERRORBBON == True:
        gbb, bbb = dry_run()
    else:
        gbb = 0
# gau.die("dry run over..")
    import timing
    #selftest()
    noprogress = 0
    currentfit = 0
    lastfit = 0

    config.CRASHIN.clear()
    stat = open("stats.log", 'w')
    stat.write("**** Fuzzing started at: %s ****\n" %
               (datetime.now().isoformat('+'), ))
    stat.write("**** Initial BB for seed inputs: %d ****\n" % (gbb, ))
    stat.flush()
    os.fsync(stat.fileno())
    stat.write(
        "Genaration\t MINfit\t MAXfit\t AVGfit MINlen\t Maxlen\t AVGlen\t #BB\t AppCov\t AllCov\n"
    )
    stat.flush()
    os.fsync(stat.fileno())
    starttime = time.clock()
    allnodes = set()
    alledges = set()
    try:
        shutil.rmtree(config.INPUTD)
        #shutil.rmtree(config.BUGD)
    except OSError:
        pass
    shutil.copytree(config.INITIALD, config.INPUTD)
    # fisrt we get taint of the intial inputs
    get_taint(config.INITIALD, 1)
    print "MOst common offsets and values:", config.MOSTCOMMON
    #gg=raw_input("press enter to continue..")
    #gau_new.initialFuzz()
    config.MOSTCOMFLAG = True
    crashhappend = False
    filest = os.listdir(config.INPUTD)
    filenum = len(filest)
    if filenum < config.POPSIZE:
        gau.create_files(config.POPSIZE - filenum)

    if len(os.listdir(config.INPUTD)) != config.POPSIZE:
        gau.die("something went wrong. number of files is not right!")

    efd = open(config.ERRORS, "w")
    gau.prepareBBOffsets()
    writecache = True
    genran = 0
    bbslide = 10  # this is used to call run_error_BB() functions
    keepslide = 3
    keepfilenum = config.BESTP
    config.SEENBB.clear()
    del config.SPECIALENTRY[:]
    todelete = set()
    inputs_new_run = []
    while True:
        print "[**] Generation %d\n***********" % (genran, )
        #del config.SPECIALENTRY[:]
        del config.TEMPTRACE[:]
        del config.BBSEENVECTOR[:]
        #config.SEENBB.clear()
        SPECIALCHANGED = False  # this is set when at least one new input is added to the config.SPECIAL folder.
        config.TMPBBINFO.clear()
        config.TMPBBINFO.update(config.PREVBBINFO)

        fitnes = dict()
        execs = 0
        config.cPERGENBB.clear()
        config.GOTSTUCK = False

        if config.ERRORBBON == True:
            if genran > config.GENNUM / 5:
                bbslide = max(bbslide, config.GENNUM / 20)
                keepslide = max(keepslide, config.GENNUM / 100)
                keepfilenum = keepfilenum / 2
        #config.cPERGENBB.clear()
        #config.GOTSTUCK=False
        #if 0< genran < config.GENNUM/5 and genran%keepslide == 0:
        #    copy_files(config.INPUTD,config.KEEPD,keepfilenum)

        #lets find out some of the error handling BBs
            if genran > 20 and genran % bbslide == 0:
                stat.write("\n**** Error BB cal started ****\n")
                stat.flush()
                os.fsync(stat.fileno())
                #run_error_bb(pt)
                #copy_files(config.KEEPD,config.INPUTD,len(os.listdir(config.KEEPD))*1/10)
            #copy_files(config.INITIALD,config.INPUTD,1)
        if genran == 1 or genran % 10 == 0:
            #f_log = open('out.txt', 'a')
            #print >> f_log, genran, inputs_new_run
            #f_log.close()
            gau.createBufferOverflowinputs(inputs_new_run)
            gau.createIntegerOverflowInputs(inputs_new_run)
            #gau.createMallocInputs(inputs_new_run)
            inputs_new_run = []
            files = os.listdir(config.BUGD)
            for fl in files:
                tfl = os.path.join(config.BUGD, fl)
                iln = os.path.getsize(tfl)
                args = (config.SUT % tfl).split(' ')
                progname = os.path.basename(args[0])
                #print ''
                #print 'Input file sha1:', sha1OfFile(tfl)
                #print 'Going to call:', ' '.join(args)
                retc = execute_without_analysis(tfl)
                if retc == None:
                    npath = os.path.join("outd/hangs", fl)
                    shutil.copyfile(tfl, npath)
                    os.remove(tfl)
                    continue
#raw_input()
                execs += 1
                #print "** %s: %d"%(fl,fitnes[fl])
                if retc < 0 and retc != -2:
                    shutil.copy(tfl, config.INPUTD)
        files = os.listdir(config.INPUTD)
        for fl in files:
            tfl = os.path.join(config.INPUTD, fl)
            iln = os.path.getsize(tfl)
            args = (config.SUT % tfl).split(' ')
            progname = os.path.basename(args[0])
            #print ''
            #print 'Input file sha1:', sha1OfFile(tfl)
            #print 'Going to call:', ' '.join(args)
            (bbs, retc) = execute(tfl)
            if os.path.exists(os.path.join("outd/hangs", fl)):
                continue
            if config.BBWEIGHT == True:
                fitnes[fl] = gau.fitnesCal2(bbs, fl, iln)
            else:
                fitnes[fl] = gau.fitnesNoWeight(bbs, fl, iln)

#raw_input()
            execs += 1
            SPECIALADDED = False
            if config.GOTSPECIAL == True:
                #spinputs=os.listdir("outd/hangs")
                #if fl in spinputs:
                #  break
                SPECIALADDED = True
                SPECIALCHANGED = True
                todelete.clear()
                form_bitvector2(bbs, fl, config.BBFORPRUNE,
                                config.SPECIALBITVECTORS)
                shutil.copy(tfl, config.SPECIAL)
                config.SPECIALENTRY.append(fl)
                inputs_new_run.append(fl)
                for sfl, bitv in config.SPECIALBITVECTORS.iteritems():
                    if sfl == fl:
                        continue
                    if (config.SPECIALBITVECTORS[fl] & bitv) == bitv:
                        tpath = os.path.join(config.SPECIAL, sfl)
                        os.remove(tpath)
                        todelete.add(sfl)
                        config.SPECIALENTRY.remove(sfl)
                        if sfl in config.TAINTMAP:
                            del config.TAINTMAP[sfl]
                            del config.ANALYSIS_MAP[sfl]
                            if sfl in inputs_new_run:
                                inputs_new_run.remove(sfl)
                for ele in todelete:
                    del config.SPECIALBITVECTORS[ele]

            #print "** %s: %d"%(fl,fitnes[fl])
            if retc < 0 and retc != -2:
                print "[*]Error code is %d" % (retc, )
                tmpHash = sha1OfFile(config.CRASHFILE)
                efd.write("%s: %d\n" % (tfl, retc))
                efd.flush()
                os.fsync(efd)
                config.err_in.append(fl)
                tnow = datetime.now().isoformat().replace(":", "-")
                nf = "%s-%s.%s" % (progname, tnow, fl)
                npath = os.path.join("outd/temp", nf)
                shutil.copyfile(tfl, npath)
                if tmpHash not in crashHash:
                    crashHash.append(tmpHash)
                    tnow = datetime.now().isoformat().replace(":", "-")
                    nf = "%s-%s.%s" % (progname, tnow,
                                       gau.splitFilename(fl)[1])
                    npath = os.path.join("outd/crashInputs", nf)
                    shutil.copyfile(tfl, npath)
                    if SPECIALADDED == False:
                        shutil.copy(tfl, config.SPECIAL)
                        #config.SPECIALENTRY.append(fl)
                        #SPECIALADDED=False
                    config.CRASHIN.add(fl)
                if config.STOPONCRASH == True:
                    #efd.close()
                    crashhappend = True
                    break
        fitscore = [v for k, v in fitnes.items()]
        maxfit = max(fitscore)
        avefit = sum(fitscore) / len(fitscore)
        mnlen, mxlen, avlen = gau.getFileMinMax(config.INPUTD)
        print "[*] Done with all input in Gen, starting SPECIAL. \n"
        #### copy special inputs in SPECIAL directory and update coverage info ###
        appcov, allcov = gau.calculateCov()
        tnow = datetime.now().isoformat().replace(":", "-")
        stat.write(
            "\t%d\t %d\t %d\t %d\t %d\t %d\t %d\t %d\t %d\t %d\t %d\t %s\t %s\n"
            % (genran, min(fitscore), maxfit, avefit, mnlen, mxlen, avlen,
               len(config.SEENBB), appcov, allcov, config.NUMINPUTS,
               config.fname, tnow))
        stat.flush()
        os.fsync(stat.fileno())
        print "[*] Wrote to stat.log\n"
        if crashhappend == True:
            break
        genran += 1
        #this part is to get initial fitness that will be used to determine if fuzzer got stuck.
        lastfit = currentfit
        #currentfit=maxfit
        currentfit = len(config.SEENBB)
        if currentfit == lastfit:  #lastfit-config.FITMARGIN < currentfit < lastfit+config.FITMARGIN:
            noprogress += 1
        else:
            noprogress = 0
        if noprogress > 20:
            config.GOTSTUCK = True
            stat.write("Heavy mutate happens now..\n")
            noprogress = 0
        if (genran >= config.GENNUM) and (config.STOPOVERGENNUM == True):
            break
        if len(os.listdir(config.SPECIAL)) > 0 and SPECIALCHANGED == True:
            if len(os.listdir(config.SPECIAL)) < config.NEWTAINTFILES:
                get_taint(config.SPECIAL)
            else:
                try:
                    os.mkdir(config.TAINTTMP)
                except OSError:
                    gau.emptyDir(config.TAINTTMP)
                if conditional_copy_files(config.SPECIAL, config.TAINTTMP,
                                          config.NEWTAINTFILES) == 0:
                    get_taint(config.TAINTTMP)
            #print "MOst common offsets and values:", config.MOSTCOMMON
            #gg=raw_input("press any key to continue..")
        print "[*] Going for new generation creation.\n"
        gau.createNextGeneration3(fitnes, genran)
        #raw_input("press any key...")

    efd.close()
    stat.close()
    libfd_mm.close()
    libfd.close()
    endtime = time.clock()

    print "[**] Totol time %f sec." % (endtime - starttime, )
    print "[**] Fuzzing done. Check %s to see if there were crashes.." % (
        config.ERRORS, )
コード例 #2
0
ファイル: runfuzzer.py プロジェクト: vusec/vuzzer64
def main():
    # first lets create the base directorty to keep all temporary data
    try:
        shutil.rmtree(config.BASETMP)
    except OSError:
        pass
    if os.path.isdir(config.BASETMP) == False:
        os.mkdir(config.BASETMP)
    check_env()
    ## parse the arguments #########
    parser = argparse.ArgumentParser(description='VUzzer options')
    parser.add_argument(
        '-s',
        '--sut',
        help='SUT commandline with %s as placeholder for SUT input',
        required=True)
    parser.add_argument('-i',
                        '--inputd',
                        help='seed input directory (relative path)',
                        required=True)
    parser.add_argument(
        '-w',
        '--weight',
        help=
        'path of the pickle file(s) for BB wieghts (separated by comma, in case there are two) ',
        required=True)
    parser.add_argument(
        '-n',
        '--name',
        help=
        'Path of the pickle file(s) containing strings from CMP inst (separated by comma if there are two).',
        required=True)
    parser.add_argument(
        '-l',
        '--libnum',
        help=
        'Nunber of binaries to monitor (only application or used libraries)',
        required=False,
        default=1)
    parser.add_argument(
        '-o',
        '--offsets',
        help=
        'base-address of application and library (if used), separated by comma',
        required=False,
        default='0x0000000000000000')
    parser.add_argument('-b',
                        '--libname',
                        help='library name to monitor',
                        required=False,
                        default='')
    args = parser.parse_args()
    config.SUT = args.sut
    config.INITIALD = os.path.join(config.INITIALD, args.inputd)
    config.LIBNUM = int(args.libnum)
    config.LIBTOMONITOR = args.libname
    config.LIBPICKLE = [w for w in args.weight.split(',')]
    config.NAMESPICKLE = [n for n in args.name.split(',')]
    config.LIBOFFSETS = [o for o in args.offsets.split(',')]
    config.LIBS = args.libname
    #ih=config.BBCMD.index("LIBS=") # this is just to find the index of the placeholder in BBCMD list to replace it with the libname
    ih = config.BBCMD.index(
        "#"
    )  # this is just to find the index of the placeholder in BBCMD list to replace it with the libname
    #config.BBCMD[ih]="LIBS=%s" % args.libname
    config.BBCMD[ih] = args.libname

    ###################################

    config.minLength = get_min_file(config.INITIALD)
    try:
        shutil.rmtree(config.KEEPD)
    except OSError:
        pass
    os.mkdir(config.KEEPD)

    try:
        os.mkdir("outd")
    except OSError:
        pass

    try:
        os.mkdir("outd/crashInputs")
    except OSError:
        gau.emptyDir("outd/crashInputs")

    crashHash = []
    try:
        os.mkdir(config.SPECIAL)
    except OSError:
        gau.emptyDir(config.SPECIAL)

    try:
        os.mkdir(config.INTER)
    except OSError:
        gau.emptyDir(config.INTER)
#############################################################################
#let us get the base address of the main executable.
    ifiles = os.listdir(config.INITIALD)
    for fl in ifiles:
        tfl = os.path.join(config.INITIALD, fl)
        try:
            f = open(tfl, 'r')
            f.close()
        except:
            gau.die("can not open our own input %s!" % (tfl, ))
        (ibbs, iretc) = execute(tfl)
        break  # we just want to run the executable once to get its load address

    imgOffFd = open("imageOffset.txt", 'r')
    for ln in imgOffFd:
        if "Main:" in ln:
            lst = ln.split()
            break
    config.LIBOFFSETS[0] = lst[1][:]
    imgOffFd.close()
    #############################################################################

    ###### open names pickle files
    gau.prepareBBOffsets()
    # lets initialize the BBFORPRUNE list from thie cALLBB set.
    if len(config.cALLBB) > 0:
        config.BBFORPRUNE = list(config.cALLBB)
    else:
        print "[*]: cALLBB is not initialized. something is wrong!!\n"
        system.exit()

    if config.PTMODE:
        pt = simplept.simplept()
    else:
        pt = None
    if config.ERRORBBON == True:
        gbb, bbb = dry_run()
    else:
        gbb = 0
# gau.die("dry run over..")
    import timing
    #selftest()
    noprogress = 0
    currentfit = 0
    lastfit = 0

    config.CRASHIN.clear()
    stat = open("stats.log", 'w')
    stat.write("**** Fuzzing started at: %s ****\n" %
               (datetime.now().isoformat('+'), ))
    stat.write("**** Initial BB for seed inputs: %d ****\n" % (gbb, ))
    stat.flush()
    os.fsync(stat.fileno())
    stat.write(
        "Genaration\t MINfit\t MAXfit\t AVGfit MINlen\t Maxlen\t AVGlen\t #BB\t AppCov\t AllCov\n"
    )
    stat.flush()
    os.fsync(stat.fileno())
    starttime = time.clock()
    allnodes = set()
    alledges = set()
    try:
        shutil.rmtree(config.INPUTD)
    except OSError:
        pass
    shutil.copytree(config.INITIALD, config.INPUTD)
    # fisrt we get taint of the intial inputs
    get_taint(config.INITIALD, 1)
    #print "MOst common offsets and values:", config.MOSTCOMMON
    #print "Base address: %s"%config.LIBOFFSETS[0]
    #raw_input("Press enter to continue..")
    config.MOSTCOMFLAG = True
    crashhappend = False
    filest = os.listdir(config.INPUTD)
    filenum = len(filest)
    if filenum < config.POPSIZE:
        gau.create_files(config.POPSIZE - filenum)

    if len(os.listdir(config.INPUTD)) != config.POPSIZE:
        gau.die("something went wrong. number of files is not right!")

    efd = open(config.ERRORS, "w")
    gau.prepareBBOffsets()
    writecache = True
    genran = 0
    bbslide = 100  # this is used to call run_error_BB() functions. currently, i have decided to not call it thus a long wait
    keepslide = 3
    keepfilenum = config.BESTP
    config.SEENBB.clear()  #initialize set of BB seen so far, which is 0
    del config.SPECIALENTRY[:]
    todelete = set(
    )  #temp set to keep file names that will be deleted in the special folder
    while True:
        #print "[**] Generation %d\n***********"%(genran,)

        del config.TEMPTRACE[:]
        del config.BBSEENVECTOR[:]
        SPECIALCHANGED = False  # this is set when a config.SPECIAL gets at least one new input per generation.
        config.TMPBBINFO.clear()
        config.TMPBBINFO.update(config.PREVBBINFO)

        fitnes = dict()
        execs = 0
        config.cPERGENBB.clear()
        config.GOTSTUCK = False

        if config.ERRORBBON == True:
            if genran > config.GENNUM / 5:
                bbslide = max(bbslide, config.GENNUM / 20)
                keepslide = max(keepslide, config.GENNUM / 100)
                keepfilenum = keepfilenum / 2

            if 0 < genran < config.GENNUM / 5 and genran % keepslide == 0:
                copy_files(config.INPUTD, config.KEEPD, keepfilenum)

        #lets find out some of the error handling BBs
            if genran > 2000 and genran % bbslide == 0:  # large number 2000 is to prevent not starting intermediate error BB cal. it is expensive and I am working on it.
                stat.write("\n**** Error BB cal started ****\n")
                stat.flush()
                os.fsync(stat.fileno())
                run_error_bb(pt)
                copy_files(config.KEEPD, config.INPUTD,
                           len(os.listdir(config.KEEPD)) * 1 / 10)
            #copy_files(config.INITIALD,config.INPUTD,1)
        files = os.listdir(config.INPUTD)
        per_gen_fnum = 0
        for fl in files:
            per_gen_fnum += 1
            tfl = os.path.join(config.INPUTD, fl)
            iln = os.path.getsize(tfl)
            args = (config.SUT % tfl).split(' ')
            progname = os.path.basename(args[0])
            (bbs, retc) = execute(tfl)
            if per_gen_fnum % 10 == 0:
                print "[**] Gen: %d. Executed %d of %d.**" % (
                    genran, per_gen_fnum, config.POPSIZE)
            if config.BBWEIGHT == True:
                fitnes[fl] = gau.fitnesCal2(bbs, fl, iln)
            else:
                fitnes[fl] = gau.fitnesNoWeight(bbs, fl, iln)

#raw_input()
            execs += 1
            #let us prune the inputs(if at all), whose trace is subset of the new input just got executed.
            SPECIALADDED = False
            if config.GOTSPECIAL == True:
                SPECIALCHANGED = True
                SPECIALADDED = True
                todelete.clear()
                form_bitvector2(bbs, fl, config.BBFORPRUNE,
                                config.SPECIALBITVECTORS)
                shutil.copy(tfl, config.SPECIAL)
                config.SPECIALENTRY.append(fl)
                for sfl, bitv in config.SPECIALBITVECTORS.iteritems():
                    if sfl == fl:
                        continue
                    if (config.SPECIALBITVECTORS[fl] & bitv) == bitv:
                        tpath = os.path.join(config.SPECIAL, sfl)
                        os.remove(tpath)
                        todelete.add(sfl)
                        config.SPECIALENTRY.remove(sfl)
                        if sfl in config.TAINTMAP:
                            del config.TAINTMAP[sfl]
                for ele in todelete:
                    del config.SPECIALBITVECTORS[ele]

            if retc < 0 and retc != -2:
                #print "[*]Error code is %d"%(retc,)
                efd.write("%s: %d\n" % (tfl, retc))
                efd.flush()
                os.fsync(efd)
                tmpHash = sha1OfFile(config.CRASHFILE)
                if tmpHash not in crashHash:
                    crashHash.append(tmpHash)
                    tnow = datetime.now().isoformat().replace(":", "-")
                    nf = "%s-%s.%s" % (progname, tnow,
                                       gau.splitFilename(fl)[1])
                    npath = os.path.join("outd/crashInputs", nf)
                    shutil.copyfile(tfl, npath)
                    if SPECIALADDED == False:
                        shutil.copy(tfl, config.SPECIAL)

                    config.CRASHIN.add(fl)
                if config.STOPONCRASH == True:
                    #efd.close()
                    crashhappend = True
                    break
        fitscore = [v for k, v in fitnes.items()]
        maxfit = max(fitscore)
        avefit = sum(fitscore) / len(fitscore)
        mnlen, mxlen, avlen = gau.getFileMinMax(config.INPUTD)
        print "[*] Done with all input in Gen, starting SPECIAL. \n"
        appcov, allcov = gau.calculateCov()
        tnow = datetime.now().isoformat().replace(":", "-")
        #stat.write("\t%d\t %d\t %d\t %d\t %d\t %d\t %d\t %d\t %d\t %d\t %s\n"%(genran,min(fitscore),maxfit,avefit,mnlen,mxlen,avlen,len(config.cPERGENBB),appcov,allcov,tnow))
        stat.write("\t%d\t %d\t %d\t %d\t %d\t %d\t %d\t %d\t %d\t %d\t %s\n" %
                   (genran, min(fitscore), maxfit, avefit, mnlen, mxlen, avlen,
                    len(config.SEENBB), appcov, allcov, tnow))
        stat.flush()
        os.fsync(stat.fileno())
        print "[*] Wrote to stat.log\n"
        if crashhappend == True:
            break
        #lets find out some of the error handling BBs
        #if genran >20 and genran%5==0:
        #   run_error_bb(pt)
        genran += 1
        #this part is to get initial fitness that will be used to determine if fuzzer got stuck.
        lastfit = currentfit
        #currentfit=maxfit
        currentfit = len(config.SEENBB)
        if currentfit == lastfit:  #lastfit-config.FITMARGIN < currentfit < lastfit+config.FITMARGIN:
            noprogress += 1
        else:
            noprogress = 0
        if noprogress > 20:
            config.GOTSTUCK = True
            stat.write("Heavy mutate happens now..\n")
            noprogress = 0
        if (genran >= config.GENNUM) and (config.STOPOVERGENNUM == True):
            break
        if len(os.listdir(config.SPECIAL)) > 0 and SPECIALCHANGED == True:
            if len(os.listdir(config.SPECIAL)) < config.NEWTAINTFILES:
                get_taint(config.SPECIAL)
            else:
                try:
                    os.mkdir(config.TAINTTMP)
                except OSError:
                    gau.emptyDir(config.TAINTTMP)
                if conditional_copy_files(config.SPECIAL, config.TAINTTMP,
                                          config.NEWTAINTFILES) == 0:
                    get_taint(config.TAINTTMP)
            #print "MOst common offsets and values:", config.MOSTCOMMON
            #gg=raw_input("press any key to continue..")
        print "[*] Going for new generation creation.\n"
        gau.createNextGeneration3(fitnes, genran)
        #raw_input("press any key...")

    efd.close()
    stat.close()
    libfd_mm.close()
    libfd.close()
    endtime = time.clock()

    print "[**] Totol time %f sec." % (endtime - starttime, )
    print "[**] Fuzzing done. Check %s to see if there were crashes.." % (
        config.ERRORS, )
コード例 #3
0
ファイル: operators.py プロジェクト: Asll666/vuzzer64
    def getOffset(self, file, org):

        org = list(org)

        usedFile = file

        if file not in self.currentTaintMap.keys():

            randomFile = self.r.choice(list(self.currentTaintMap.keys()))
            alloffset = self.currentTaintMap[randomFile][0]

            usedFile = randomFile
        else:
            alloffset = self.currentTaintMap[file][0]

        if len(alloffset) == 0:
            gautils.debug_print("[-] Fail to find offset in map")
            return []

        attemps = 10

        cmp = -1

        # I try to optimize the offset selection by selecting only not used offsets
        while attemps != 0:
            cmp = self.r.choice(alloffset)

            if cmp.offsetsInInput[0] in self.mutationHistory[file]:
                attemps -= 1
                continue
            else:
                break

        gautils.debug_print("[*] Testing offset 0x%x" %
                            (cmp.offsetsInInput[0]))

        conflictList = self.check_mutation_conflict_from_register_size(
            cmp.offsetsInInput[0], cmp.cmpSize, file)

        conflictWithParent = False

        createdChild = -1

        # create the mutation buffer
        self.currentMutation = self.r.sample(self.r.choice(self.allStrings),
                                             cmp.cmpSize)

        # is there is not offset without conflicts available
        if len(conflictList) > 0:

            gautils.debug_print("[-] There is conflict with the parent")

            createTheChild = random.uniform(
                0.1, 1.0) > (1.0 - config.CHILDINPUTCREATIONRANDOMNESS)

            # creating a child for each conflict
            if config.HEAVYCHILDINPUTCREATION == True and self.childCount < config.CHILDINPUTMAXSIZE and createTheChild:

                conflictWithParent = True

                # create the input child
                name, inputBuffer = self.create_child_input(
                    file, org, conflictList, cmp)

                # check is exist and write the file
                if not self.write_child_input(name, inputBuffer):
                    del self.mutationHistory[name]
                    createdChild = -1
                else:
                    gautils.debug_print("[+] Child input created %s" % (name))
                    createdChild = name
        else:
            gautils.debug_print("[+] There is no conflict in parent")

        # apply the cmp to the child inputs history
        if len(self.childMap) > 0 and config.HEAVYCHILDINPUTCREATION == True:

            # childMap will change during this
            tmpChildMap = self.childMap.copy()

            for childInput in tmpChildMap:

                if createdChild != -1 and childInput == createdChild:
                    continue

                # get conflicts for each child input
                conflictChildList = self.check_mutation_conflict_from_register_size(
                    cmp.offsetsInInput[0], cmp.cmpSize, childInput)

                path = os.path.join(config.INPUTD, childInput)

                # get child input buffer
                childInputBuffer = list(gautils.readFile(path))

                createTheChild = random.uniform(
                    0.1, 1.0) > (1.0 - config.CHILDINPUTCREATIONRANDOMNESS)

                # conflict found
                # NOTE : we still apply cmp to child inputs if there is no conflicts
                if len(
                        conflictChildList
                ) > 0 and self.childCount < config.CHILDINPUTMAXSIZE and createTheChild:

                    gautils.debug_print(
                        "[-] There is conflict with child inputs")

                    # create input name

                    # get the extension
                    # TODO : find a better way to do it
                    bn, ext = gautils.splitFilename(usedFile)
                    name = "heavy-child-g%d-%d.%s" % (config.CURRENTGEN,
                                                      self.childCount, ext)

                    # create the input child
                    inputBuffer = self.create_new_child_from_old(
                        name, childInput, childInputBuffer, conflictChildList,
                        cmp)

                    # check is exist and write the file
                    if not self.write_child_input(name, ''.join(inputBuffer)):
                        del self.mutationHistory[name]
                    else:
                        gautils.debug_print("[+] Child input created %s" %
                                            (name))

                # no conflict (maybe while self.childCount == config.CHILDINPUTMAXSIZE)
                elif len(conflictChildList) == 0:

                    gautils.debug_print(
                        "[+] There is no conflict in child inputs")
                    gautils.debug_print("[+] Updating child input %s" %
                                        (childInput))

                    # add the new mutation to the history
                    self.mutationHistory[childInput].update(
                        {cmp.offsetsInInput[0]: [cmp]})

                    # apply the changes and add it to history
                    childInputBuffer = self.change_bytes_from_cmp(
                        childInputBuffer, childInput, cmp)

                    # check is exist and write the file
                    if not self.write_child_input_update(
                            childInput, ''.join(childInputBuffer)):
                        del self.mutationHistory[childInput][
                            cmp.offsetsInInput[0]]
                    else:
                        gautils.debug_print("[+] Child input updated %s" %
                                            (childInput))

        # true only if config.HEAVYCHILDINPUTCREATION == True
        if conflictWithParent == True:
            # the parent will not use the mutation because of the conflict
            return []

        else:
            # TODO : improve
            self.mutationHistory[file].update({cmp.offsetsInInput[0]: [cmp]})

            # we will not use this cmp again
            self.currentTaintMap[usedFile][0].remove(cmp)

        # python tricks to not access to cmp function is it None
        return [cmp]
コード例 #4
0
ファイル: runfuzzer.py プロジェクト: Asll666/vuzzer64
def main():

    banner()

    # first lets create the base directorty to keep all temporary data
    try:
        shutil.rmtree(config.BASETMP)
    except OSError:
        pass
    if os.path.isdir(config.BASETMP) == False:
        os.mkdir(config.BASETMP)
    check_env()
    ## parse the arguments #########
    parser = argparse.ArgumentParser(description='VUzzer options')
    parser.add_argument('-s', '--sut', help='SUT commandline', required=True)
    parser.add_argument(
        '-i', '--inputd', help='seed input directory (relative path)', required=True)
    parser.add_argument(
        '-w', '--weight', help='path of the pickle file(s) for BB wieghts (separated by comma, in case there are two) ', required=True)
    parser.add_argument(
        '-n', '--name', help='Path of the pickle file(s) containing strings from CMP inst (separated by comma if there are two).', required=True)
    parser.add_argument(
        '-l', '--libnum', help='Nunber of binaries to monitor (only application or used libraries)', required=False, default=1)
    parser.add_argument('-o', '--offsets', help='base-address of application and library (if used), separated by comma',
                        required=False, default='0x00000000')
    parser.add_argument(
        '-b', '--libname', help='library name to monitor', required=False, default='#')
    args = parser.parse_args()
    config.SUT = args.sut
    config.INITIALD = os.path.join(config.INITIALD, args.inputd)
    config.LIBNUM = int(args.libnum)
    config.LIBTOMONITOR = args.libname
    config.LIBPICKLE = [w for w in args.weight.split(',')]
    config.NAMESPICKLE = [n for n in args.name.split(',')]
    config.LIBOFFSETS = [o for o in args.offsets.split(',')]
    config.LIBS = args.libname
    # this is just to find the index of the placeholder in BBCMD list to replace it with the libname
    ih = config.BBCMD.index("LIBS=")
    config.BBCMD[ih] = "LIBS=%s" % args.libname

    gau.log_print( "[*] Checking tmps files" )

    if config.CLEARSPECIALOUTPUT:
        gau.delete_special_out_file(config.SPECIALOUTPUT)

    if path.exists("vuzzerRun.log"):
        os.remove("vuzzerRun.log")

    gau.log_print( "[*] Checking directories" )

    if config.USEPATTERNDETECTION == True:
        initPatterns()

    ###################################

    afl.clearDir()

    config.minLength = get_min_file(config.INITIALD)
    try:
        shutil.rmtree(config.KEEPD)
    except OSError:
        pass
    os.mkdir(config.KEEPD)

    try:
        os.mkdir("outd")
    except OSError:
        pass

    try:
        os.mkdir("outd/crashInputs")
    except OSError:
        gau.emptyDir("outd/crashInputs")

    crashHash = []
    try:
        os.mkdir(config.SPECIAL)
    except OSError:
        gau.emptyDir(config.SPECIAL)

    try:
        os.mkdir(config.INTER)
    except OSError:
        gau.emptyDir(config.INTER)

    gau.log_print( "[*] Checking executable base address" )

    #############################################################################
    # let us get the base address of the main executable.
    ifiles = os.listdir(config.INITIALD)
    for fl in ifiles:
        tfl = os.path.join(config.INITIALD, fl)
        try:
            f = open(tfl, 'r')
            f.close()
        except:
            gau.die("[-] Can not open our own input %s !" % (tfl))
        (ibbs, iretc) = execute(tfl)

        if iretc != 128: # 0
            gau.die("[-] Can't run the target program '%s' !" % (os.path.basename(config.SUT.replace(" ","").replace("%s",""))))
        break  # we just want to run the executable once to get its load address

    imgOffFd = open("imageOffset.txt", 'r')
    for ln in imgOffFd:
        if "Main:" in ln:
            lst = ln.split()
            break
    config.LIBOFFSETS[0] = lst[1][:]
    imgOffFd.close()

    if config.LIBTOMONITOR != '' and config.LIBTOMONITOR != '#':
        gau.log_print("[+] Lib %s is at 0x%x" % (config.LIBTOMONITOR, int(config.LIBOFFSETS[1], 0)))

    gau.log_print( "[*] Checking pickles" )

    #############################################################################

    # open names pickle files
    gau.prepareBBOffsets()

    # lets initialize the BBFORPRUNE list from thie cALLBB set.
    if len(config.cALLBB) > 0:
        config.BBFORPRUNE = list(config.cALLBB)
    else:
        gau.log_print("[*] cALLBB is not initialized. something is wrong!!\n")
        system.exit()

    if config.PTMODE:
        pt = simplept.simplept()
    else:
        pt = None

    gau.log_print("[*] Running vuzzer for '%s'" % (os.path.basename(config.SUT.replace(" ","").replace("%s",""))))

    if config.ERRORBBON == True:
        gbb, bbb = dry_run()
    else:
        gbb = 0

    # gau.die("dry run over..")
    import timing
    # selftest()
    noprogress = 0
    currentfit = 0
    lastfit = 0

    config.CRASHIN.clear()
    stat = open("stats.log", 'w')
    stat.write("**** Fuzzing started at: %s ****\n" %
               (datetime.now().isoformat('+'),))
    stat.write("**** Initial BB for seed inputs: %d ****\n" % (gbb,))
    stat.flush()
    os.fsync(stat.fileno())
    stat.write(
        "Genaration\t MINfit\t MAXfit\t AVGfit MINlen\t Maxlen\t AVGlen\t #BB\t AppCov\t AllCov\n")
    stat.flush()
    os.fsync(stat.fileno())
    starttime = time.clock()
    allnodes = set()
    alledges = set()
    try:
        shutil.rmtree(config.INPUTD)
    except OSError:
        pass
    shutil.copytree(config.INITIALD, config.INPUTD)

    # fisrt we get taint of the intial inputs
    get_taint(config.INITIALD, 1)

    # print "MOst common offsets and values:", config.MOSTCOMMON
    # print "Base address: %s"%config.LIBOFFSETS[0]
    # raw_input("Press enter to continue..")
    config.MOSTCOMFLAG = True
    crashhappend = False
    filest = os.listdir(config.INPUTD)
    filenum = len(filest)

    if filenum < config.POPSIZE:
        gau.create_files(config.POPSIZE - filenum)

    gau.log_print( '[*] Population at start is about %d files' % (len(os.listdir(config.INPUTD))))

    efd = open(config.ERRORS, "w")
    gau.prepareBBOffsets()
    writecache = True
    genran = 0
    bbslide = 40  # this is used to call run_error_BB() functions
    keepslide = 3
    keepfilenum = config.BESTP
    config.SEENBB.clear()  # initialize set of BB seen so far, which is 0
    del config.SPECIALENTRY[:]
    todelete = set()  # temp set to keep file names that will be deleted in the special folder
    
    oldPrintSize = 0

    while True:
        # print "[**] Generation %d\n***********"%(genran,)

        del config.TEMPTRACE[:]
        del config.BBSEENVECTOR[:]
        # this is set when a config.SPECIAL gets at least one new input per generation.
        SPECIALCHANGED = False
        config.TMPBBINFO.clear()
        config.TMPBBINFO.update(config.PREVBBINFO)

        fitnes = dict()
        execs = 0
        config.cPERGENBB.clear()
        config.GOTSTUCK = False

        if config.ERRORBBON == True:
            if genran > config.GENNUM/5:
                bbslide = max(bbslide, config.GENNUM/20)
                keepslide = max(keepslide, config.GENNUM/100)
                keepfilenum = keepfilenum/2

            if 0 < genran < config.GENNUM/5 and genran % keepslide == 0:
                copy_files(config.INPUTD, config.KEEPD, keepfilenum)

        # lets find out some of the error handling BBs
            if genran > 40 and genran % bbslide == 0:
                stat.write("\n**** Error BB cal started ****\n")
                stat.flush()
                os.fsync(stat.fileno())
                run_error_bb(pt)
                copy_files(config.KEEPD, config.INPUTD,
                           len(os.listdir(config.KEEPD))*1/10)
            # copy_files(config.INITIALD,config.INPUTD,1)
        files = os.listdir(config.INPUTD)
        per_gen_fnum = 0
        for fl in files:
            per_gen_fnum += 1
            tfl = os.path.join(config.INPUTD, fl)
            iln = os.path.getsize(tfl)
            args = (config.SUT % tfl).split(' ')
            progname = os.path.basename(args[0])
            (bbs, retc) = execute(tfl)
            filecount = len(os.listdir(config.INPUTD))
            inputname = os.path.basename(args[1])

            per_current_stat = (float(per_gen_fnum)/float(filecount)) * 100

            per_show = (int(float(filecount) / 10.0))

            if per_show == 0:
                per_show = 10

            if per_gen_fnum % per_show == 0 or per_gen_fnum == filecount:

                logStr = "[%s] Gen %d : %d%% Executed %d of %d" % (time.strftime("%H:%M:%S"),genran,int(per_current_stat),per_gen_fnum, filecount)
                
                if oldPrintSize > 0:
                    dif = oldPrintSize - len(logStr)
                    if dif > 0: 
                        logStr += " "*dif

                oldPrintSize = len(logStr)

                gau.log_print( logStr )
            else:

                gau.log_print( ' '*oldPrintSize )
                
                sys.stdout.write("\033[F")

                logStr = "[%s] Gen %d : %d%% Executed %d of %d [%s]" % (time.strftime("%H:%M:%S"),genran,int(per_current_stat),per_gen_fnum, filecount,inputname)

                if oldPrintSize > 0:
                    dif = oldPrintSize - len(logStr)
                    if dif > 0: 
                        logStr += " "*dif

                oldPrintSize = len(logStr)

                gau.log_print( logStr )

                sys.stdout.write("\033[F")

            if config.BBWEIGHT == True:
                fitnes[fl] = gau.fitnesCal2(bbs, fl, iln)
            else:
                fitnes[fl] = gau.fitnesNoWeight(bbs, fl, iln)
            # raw_input()
            execs += 1
            # let us prune the inputs(if at all), whose trace is subset of the new input just got executed.
            SPECIALADDED = False
            if config.GOTSPECIAL == True:
                SPECIALCHANGED = True
                SPECIALADDED = True
                todelete.clear()
                form_bitvector2(bbs, fl, config.BBFORPRUNE,
                                config.SPECIALBITVECTORS)
                shutil.copy(tfl, config.SPECIAL)
                config.SPECIALENTRY.append(fl)
                for sfl, bitv in config.SPECIALBITVECTORS.iteritems():
                    if sfl == fl:
                        continue
                    if (config.SPECIALBITVECTORS[fl] & bitv) == bitv:
                        tpath = os.path.join(config.SPECIAL, sfl)
                        os.remove(tpath)
                        todelete.add(sfl)
                        config.SPECIALENTRY.remove(sfl)
                        if sfl in config.TAINTMAP:
                            del config.TAINTMAP[sfl]
                for ele in todelete:
                    del config.SPECIALBITVECTORS[ele]

            if retc < 0 and retc != -2:
                efd.write("%s: %d\n" % (tfl, retc))
                efd.flush()
                os.fsync(efd)
                tmpHash = sha1OfFile(config.CRASHFILE)
                if tmpHash not in crashHash:
                    crashHash.append(tmpHash)
                    tnow = datetime.now().isoformat().replace(":", "-")
                    nf = "%s-%s.%s" % (progname, tnow,
                                       gau.splitFilename(fl)[1])
                    npath = os.path.join("outd/crashInputs", nf)
                    shutil.copyfile(tfl, npath)
                    if SPECIALADDED == False:
                        shutil.copy(tfl, config.SPECIAL)

                    config.CRASHIN.add(fl)
                if config.STOPONCRASH == True:
                    # efd.close()
                    crashhappend = True
                    break
        fitscore = [v for k, v in fitnes.items()]
        maxfit = max(fitscore)
        avefit = sum(fitscore)/len(fitscore)
        mnlen, mxlen, avlen = gau.getFileMinMax(config.INPUTD)

        gau.log_print( "[*] Done with all input in Gen %d" % (genran) )
        gau.log_print( "[*] Calculating code coverage" )

        appcov, allcov = gau.calculateCov()
        tnow = datetime.now().isoformat().replace(":", "-")
        # stat.write("\t%d\t %d\t %d\t %d\t %d\t %d\t %d\t %d\t %d\t %d\t %s\n"%(genran,min(fitscore),maxfit,avefit,mnlen,mxlen,avlen,len(config.cPERGENBB),appcov,allcov,tnow))
        stat.write("\t%d\t %d\t %d\t %d\t %d\t %d\t %d\t %d\t %d\t %d\t %s\n" % (genran, min(
            fitscore), maxfit, avefit, mnlen, mxlen, avlen, len(config.SEENBB), appcov, allcov, tnow))
        stat.flush()
        os.fsync(stat.fileno())
        gau.log_print( "[*] Wrote to stat.log" )

        seenBB = len(config.SEENBB)

        codeCovStr = "[+] BB Code coverage is %d" % (seenBB)

        if config.LASTTURNCODECOV != 0:

            difLastTurnStr = "(no new BB found)"

            codeCovDif = seenBB - config.LASTTURNCODECOV

            if codeCovDif > 0:
                difLastTurnStr = "\033[0;32m(+%d BB)\033[0m" % (codeCovDif)
            elif codeCovDif < 0:
                difLastTurnStr = "\033[0;31m(-%d BB)\033[0m" % (codeCovDif)

            perBBTotal = (float(seenBB) / float(len(config.ALLBB))) * 100

            difGoodBB = seenBB - len(config.GOODBB)

            difGoodBBStr = "no"

            if difGoodBB > 0:
                difGoodBBStr = "+%d" % (difGoodBB)
            elif difGoodBB < 0:
                difGoodBBStr = "-%d" % (difGoodBB)

            codeCovStr += " %s (%d%% of all BB) (%s BB dif with good path)" % (difLastTurnStr, perBBTotal, difGoodBBStr)
            
        config.LASTTURNCODECOV = seenBB

        gau.log_print( "-"*len(codeCovStr) ) 
        gau.log_print( codeCovStr )
        gau.log_print( "-"*len(codeCovStr) )
        
        if crashhappend == True:
            break
        # lets find out some of the error handling BBs
        # if genran >20 and genran%5==0:
         #   run_error_bb(pt)
        genran += 1
        config.CURRENTGEN += 1
        # this part is to get initial fitness that will be used to determine if fuzzer got stuck.
        lastfit = currentfit
        # currentfit=maxfit
        currentfit = len(config.SEENBB)
        # lastfit-config.FITMARGIN < currentfit < lastfit+config.FITMARGIN:
        if currentfit == lastfit:
            noprogress += 1
        else:
            noprogress = 0
        if noprogress > 20:
            config.GOTSTUCK = True
            stat.write("Heavy mutate happens now..\n")
            noprogress = 0
        if (genran >= config.GENNUM) and (config.STOPOVERGENNUM == True):
            break
        if len(os.listdir(config.SPECIAL)) > 0 and SPECIALCHANGED == True:
            if len(os.listdir(config.SPECIAL)) < config.NEWTAINTFILES:
                get_taint(config.SPECIAL)
            else:
                try:
                    os.mkdir(config.TAINTTMP)
                except OSError:
                    gau.emptyDir(config.TAINTTMP)
                if conditional_copy_files(config.SPECIAL, config.TAINTTMP, config.NEWTAINTFILES) == 0:
                    get_taint(config.TAINTTMP)
            # print "MOst common offsets and values:", config.MOSTCOMMON
            # gg=raw_input("press any key to continue..")

        gau.log_print( "[*] Going for new generation creation" )
        gau.createNextGeneration3(fitnes, genran)
        # raw_input("press any key...")

    efd.close()
    stat.close()
    libfd_mm.close()
    libfd.close()
    endtime = time.clock()

    gau.log_print( "[**] Totol time %f sec" % (endtime-starttime,) )
    gau.log_print( "[**] Fuzzing done. Check %s to see if there were crashes" % (
        config.ERRORS,))
コード例 #5
0
ファイル: operators.py プロジェクト: Asll666/vuzzer64
    def create_child_input(self, parentInputFL, parentInput, conflicts,
                           newMutation):
        ''' TODO '''

        mutatedInput = parentInput[:]

        # craft the child input name

        # get the extension
        # TODO : find a better way to do it
        bn, ext = gautils.splitFilename(parentInputFL)
        name = "heavy-child-g%d-%d.%s" % (config.CURRENTGEN, self.childCount,
                                          ext)

        self.mutationHistory.update({name: dict()})

        # remove old conflicts mutations
        # NOTE : if there is a index out of array here, there is a problem
        for c in conflicts:

            #for cmp in c.offsetsInInput:

            mutationHistoryData = self.mutationHistory[parentInputFL][
                c.offsetsInInput[0]][1]

            for offset in mutationHistoryData:
                i = 0
                for byte in mutationHistoryData[offset]:
                    mutatedInput[offset + i] = (byte)
                    i += 1

        # register the new mutation in history
        self.mutationHistory[name].update(
            {newMutation.offsetsInInput[0]: [newMutation]})

        # add the history directory
        self.mutationHistory[name][newMutation.offsetsInInput[0]].append(
            dict())

        mutationHistoryData = self.mutationHistory[name][
            newMutation.offsetsInInput[0]][1]

        # apply new mutation
        for offset in newMutation.offsetsInInput:

            mutationHistoryData.update({offset: []})

            mutationHistoryDataCurrent = mutationHistoryData[offset]

            for i in range(0, newMutation.cmpSize):

                if int(offset + i) >= len(mutatedInput):
                    break

                if newMutation.taintType == taintTypeEnum.UNKNOWN:

                    # save the old value
                    mutationHistoryDataCurrent.append(mutatedInput[(offset +
                                                                    i)])

                    # applying the conflicted parent mutation
                    mutatedInput[(offset + i)] = self.currentMutation[i]

                elif newMutation.taintType == taintTypeEnum.SINGLE_BYTE:

                    # save the old value
                    mutationHistoryDataCurrent.append(mutatedInput[(offset +
                                                                    i)])

                    # applying the conflicted parent mutation
                    mutatedInput[(offset + i)] = self.currentMutation[0]

                elif newMutation.taintType == taintTypeEnum.ARRAY:

                    # save the old value
                    mutationHistoryDataCurrent.append(mutatedInput[(offset +
                                                                    i)])

                    # applying the conflicted parent mutation
                    # TODO : improve strategy
                    mutatedInput[(offset + i)] = self.currentMutation[i]

        # apply magic bytes and most common bytes
        mutatedInput = gautils.apply_more_common_changes(mutatedInput)
        mutatedInput = gautils.apply_most_common_changes(mutatedInput)

        # build a string with the mutated input
        mutatedInput = ''.join(mutatedInput)

        return name, mutatedInput