1*f4a2713aSLionel Sambuc#!/usr/bin/env python 2*f4a2713aSLionel Sambuc 3*f4a2713aSLionel Sambuc""" 4*f4a2713aSLionel SambucThis is a generic fuzz testing tool, see --help for more information. 5*f4a2713aSLionel Sambuc""" 6*f4a2713aSLionel Sambuc 7*f4a2713aSLionel Sambucimport os 8*f4a2713aSLionel Sambucimport sys 9*f4a2713aSLionel Sambucimport random 10*f4a2713aSLionel Sambucimport subprocess 11*f4a2713aSLionel Sambucimport itertools 12*f4a2713aSLionel Sambuc 13*f4a2713aSLionel Sambucclass TestGenerator: 14*f4a2713aSLionel Sambuc def __init__(self, inputs, delete, insert, replace, 15*f4a2713aSLionel Sambuc insert_strings, pick_input): 16*f4a2713aSLionel Sambuc self.inputs = [(s, open(s).read()) for s in inputs] 17*f4a2713aSLionel Sambuc 18*f4a2713aSLionel Sambuc self.delete = bool(delete) 19*f4a2713aSLionel Sambuc self.insert = bool(insert) 20*f4a2713aSLionel Sambuc self.replace = bool(replace) 21*f4a2713aSLionel Sambuc self.pick_input = bool(pick_input) 22*f4a2713aSLionel Sambuc self.insert_strings = list(insert_strings) 23*f4a2713aSLionel Sambuc 24*f4a2713aSLionel Sambuc self.num_positions = sum([len(d) for _,d in self.inputs]) 25*f4a2713aSLionel Sambuc self.num_insert_strings = len(insert_strings) 26*f4a2713aSLionel Sambuc self.num_tests = ((delete + (insert + replace)*self.num_insert_strings) 27*f4a2713aSLionel Sambuc * self.num_positions) 28*f4a2713aSLionel Sambuc self.num_tests += 1 29*f4a2713aSLionel Sambuc 30*f4a2713aSLionel Sambuc if self.pick_input: 31*f4a2713aSLionel Sambuc self.num_tests *= self.num_positions 32*f4a2713aSLionel Sambuc 33*f4a2713aSLionel Sambuc def position_to_source_index(self, position): 34*f4a2713aSLionel Sambuc for i,(s,d) in enumerate(self.inputs): 35*f4a2713aSLionel Sambuc n = len(d) 36*f4a2713aSLionel Sambuc if position < n: 37*f4a2713aSLionel Sambuc return (i,position) 38*f4a2713aSLionel Sambuc position -= n 39*f4a2713aSLionel Sambuc raise ValueError,'Invalid position.' 40*f4a2713aSLionel Sambuc 41*f4a2713aSLionel Sambuc def get_test(self, index): 42*f4a2713aSLionel Sambuc assert 0 <= index < self.num_tests 43*f4a2713aSLionel Sambuc 44*f4a2713aSLionel Sambuc picked_position = None 45*f4a2713aSLionel Sambuc if self.pick_input: 46*f4a2713aSLionel Sambuc index,picked_position = divmod(index, self.num_positions) 47*f4a2713aSLionel Sambuc picked_position = self.position_to_source_index(picked_position) 48*f4a2713aSLionel Sambuc 49*f4a2713aSLionel Sambuc if index == 0: 50*f4a2713aSLionel Sambuc return ('nothing', None, None, picked_position) 51*f4a2713aSLionel Sambuc 52*f4a2713aSLionel Sambuc index -= 1 53*f4a2713aSLionel Sambuc index,position = divmod(index, self.num_positions) 54*f4a2713aSLionel Sambuc position = self.position_to_source_index(position) 55*f4a2713aSLionel Sambuc if self.delete: 56*f4a2713aSLionel Sambuc if index == 0: 57*f4a2713aSLionel Sambuc return ('delete', position, None, picked_position) 58*f4a2713aSLionel Sambuc index -= 1 59*f4a2713aSLionel Sambuc 60*f4a2713aSLionel Sambuc index,insert_index = divmod(index, self.num_insert_strings) 61*f4a2713aSLionel Sambuc insert_str = self.insert_strings[insert_index] 62*f4a2713aSLionel Sambuc if self.insert: 63*f4a2713aSLionel Sambuc if index == 0: 64*f4a2713aSLionel Sambuc return ('insert', position, insert_str, picked_position) 65*f4a2713aSLionel Sambuc index -= 1 66*f4a2713aSLionel Sambuc 67*f4a2713aSLionel Sambuc assert self.replace 68*f4a2713aSLionel Sambuc assert index == 0 69*f4a2713aSLionel Sambuc return ('replace', position, insert_str, picked_position) 70*f4a2713aSLionel Sambuc 71*f4a2713aSLionel Sambucclass TestApplication: 72*f4a2713aSLionel Sambuc def __init__(self, tg, test): 73*f4a2713aSLionel Sambuc self.tg = tg 74*f4a2713aSLionel Sambuc self.test = test 75*f4a2713aSLionel Sambuc 76*f4a2713aSLionel Sambuc def apply(self): 77*f4a2713aSLionel Sambuc if self.test[0] == 'nothing': 78*f4a2713aSLionel Sambuc pass 79*f4a2713aSLionel Sambuc else: 80*f4a2713aSLionel Sambuc i,j = self.test[1] 81*f4a2713aSLionel Sambuc name,data = self.tg.inputs[i] 82*f4a2713aSLionel Sambuc if self.test[0] == 'delete': 83*f4a2713aSLionel Sambuc data = data[:j] + data[j+1:] 84*f4a2713aSLionel Sambuc elif self.test[0] == 'insert': 85*f4a2713aSLionel Sambuc data = data[:j] + self.test[2] + data[j:] 86*f4a2713aSLionel Sambuc elif self.test[0] == 'replace': 87*f4a2713aSLionel Sambuc data = data[:j] + self.test[2] + data[j+1:] 88*f4a2713aSLionel Sambuc else: 89*f4a2713aSLionel Sambuc raise ValueError,'Invalid test %r' % self.test 90*f4a2713aSLionel Sambuc open(name,'wb').write(data) 91*f4a2713aSLionel Sambuc 92*f4a2713aSLionel Sambuc def revert(self): 93*f4a2713aSLionel Sambuc if self.test[0] != 'nothing': 94*f4a2713aSLionel Sambuc i,j = self.test[1] 95*f4a2713aSLionel Sambuc name,data = self.tg.inputs[i] 96*f4a2713aSLionel Sambuc open(name,'wb').write(data) 97*f4a2713aSLionel Sambuc 98*f4a2713aSLionel Sambucdef quote(str): 99*f4a2713aSLionel Sambuc return '"' + str + '"' 100*f4a2713aSLionel Sambuc 101*f4a2713aSLionel Sambucdef run_one_test(test_application, index, input_files, args): 102*f4a2713aSLionel Sambuc test = test_application.test 103*f4a2713aSLionel Sambuc 104*f4a2713aSLionel Sambuc # Interpolate arguments. 105*f4a2713aSLionel Sambuc options = { 'index' : index, 106*f4a2713aSLionel Sambuc 'inputs' : ' '.join(quote(f) for f in input_files) } 107*f4a2713aSLionel Sambuc 108*f4a2713aSLionel Sambuc # Add picked input interpolation arguments, if used. 109*f4a2713aSLionel Sambuc if test[3] is not None: 110*f4a2713aSLionel Sambuc pos = test[3][1] 111*f4a2713aSLionel Sambuc options['picked_input'] = input_files[test[3][0]] 112*f4a2713aSLionel Sambuc options['picked_input_pos'] = pos 113*f4a2713aSLionel Sambuc # Compute the line and column. 114*f4a2713aSLionel Sambuc file_data = test_application.tg.inputs[test[3][0]][1] 115*f4a2713aSLionel Sambuc line = column = 1 116*f4a2713aSLionel Sambuc for i in range(pos): 117*f4a2713aSLionel Sambuc c = file_data[i] 118*f4a2713aSLionel Sambuc if c == '\n': 119*f4a2713aSLionel Sambuc line += 1 120*f4a2713aSLionel Sambuc column = 1 121*f4a2713aSLionel Sambuc else: 122*f4a2713aSLionel Sambuc column += 1 123*f4a2713aSLionel Sambuc options['picked_input_line'] = line 124*f4a2713aSLionel Sambuc options['picked_input_col'] = column 125*f4a2713aSLionel Sambuc 126*f4a2713aSLionel Sambuc test_args = [a % options for a in args] 127*f4a2713aSLionel Sambuc if opts.verbose: 128*f4a2713aSLionel Sambuc print '%s: note: executing %r' % (sys.argv[0], test_args) 129*f4a2713aSLionel Sambuc 130*f4a2713aSLionel Sambuc stdout = None 131*f4a2713aSLionel Sambuc stderr = None 132*f4a2713aSLionel Sambuc if opts.log_dir: 133*f4a2713aSLionel Sambuc stdout_log_path = os.path.join(opts.log_dir, '%s.out' % index) 134*f4a2713aSLionel Sambuc stderr_log_path = os.path.join(opts.log_dir, '%s.err' % index) 135*f4a2713aSLionel Sambuc stdout = open(stdout_log_path, 'wb') 136*f4a2713aSLionel Sambuc stderr = open(stderr_log_path, 'wb') 137*f4a2713aSLionel Sambuc else: 138*f4a2713aSLionel Sambuc sys.stdout.flush() 139*f4a2713aSLionel Sambuc p = subprocess.Popen(test_args, stdout=stdout, stderr=stderr) 140*f4a2713aSLionel Sambuc p.communicate() 141*f4a2713aSLionel Sambuc exit_code = p.wait() 142*f4a2713aSLionel Sambuc 143*f4a2713aSLionel Sambuc test_result = (exit_code == opts.expected_exit_code or 144*f4a2713aSLionel Sambuc exit_code in opts.extra_exit_codes) 145*f4a2713aSLionel Sambuc 146*f4a2713aSLionel Sambuc if stdout is not None: 147*f4a2713aSLionel Sambuc stdout.close() 148*f4a2713aSLionel Sambuc stderr.close() 149*f4a2713aSLionel Sambuc 150*f4a2713aSLionel Sambuc # Remove the logs for passes, unless logging all results. 151*f4a2713aSLionel Sambuc if not opts.log_all and test_result: 152*f4a2713aSLionel Sambuc os.remove(stdout_log_path) 153*f4a2713aSLionel Sambuc os.remove(stderr_log_path) 154*f4a2713aSLionel Sambuc 155*f4a2713aSLionel Sambuc if not test_result: 156*f4a2713aSLionel Sambuc print 'FAIL: %d' % index 157*f4a2713aSLionel Sambuc elif not opts.succinct: 158*f4a2713aSLionel Sambuc print 'PASS: %d' % index 159*f4a2713aSLionel Sambuc return test_result 160*f4a2713aSLionel Sambuc 161*f4a2713aSLionel Sambucdef main(): 162*f4a2713aSLionel Sambuc global opts 163*f4a2713aSLionel Sambuc from optparse import OptionParser, OptionGroup 164*f4a2713aSLionel Sambuc parser = OptionParser("""%prog [options] ... test command args ... 165*f4a2713aSLionel Sambuc 166*f4a2713aSLionel Sambuc%prog is a tool for fuzzing inputs and testing them. 167*f4a2713aSLionel Sambuc 168*f4a2713aSLionel SambucThe most basic usage is something like: 169*f4a2713aSLionel Sambuc 170*f4a2713aSLionel Sambuc $ %prog --file foo.txt ./test.sh 171*f4a2713aSLionel Sambuc 172*f4a2713aSLionel Sambucwhich will run a default list of fuzzing strategies on the input. For each 173*f4a2713aSLionel Sambucfuzzed input, it will overwrite the input files (in place), run the test script, 174*f4a2713aSLionel Sambucthen restore the files back to their original contents. 175*f4a2713aSLionel Sambuc 176*f4a2713aSLionel SambucNOTE: You should make sure you have a backup copy of your inputs, in case 177*f4a2713aSLionel Sambucsomething goes wrong!!! 178*f4a2713aSLionel Sambuc 179*f4a2713aSLionel SambucYou can cause the fuzzing to not restore the original files with 180*f4a2713aSLionel Sambuc'--no-revert'. Generally this is used with '--test <index>' to run one failing 181*f4a2713aSLionel Sambuctest and then leave the fuzzed inputs in place to examine the failure. 182*f4a2713aSLionel Sambuc 183*f4a2713aSLionel SambucFor each fuzzed input, %prog will run the test command given on the command 184*f4a2713aSLionel Sambucline. Each argument in the command is subject to string interpolation before 185*f4a2713aSLionel Sambucbeing executed. The syntax is "%(VARIABLE)FORMAT" where FORMAT is a standard 186*f4a2713aSLionel Sambucprintf format, and VARIABLE is one of: 187*f4a2713aSLionel Sambuc 188*f4a2713aSLionel Sambuc 'index' - the test index being run 189*f4a2713aSLionel Sambuc 'inputs' - the full list of test inputs 190*f4a2713aSLionel Sambuc 'picked_input' - (with --pick-input) the selected input file 191*f4a2713aSLionel Sambuc 'picked_input_pos' - (with --pick-input) the selected input position 192*f4a2713aSLionel Sambuc 'picked_input_line' - (with --pick-input) the selected input line 193*f4a2713aSLionel Sambuc 'picked_input_col' - (with --pick-input) the selected input column 194*f4a2713aSLionel Sambuc 195*f4a2713aSLionel SambucBy default, the script will run forever continually picking new tests to 196*f4a2713aSLionel Sambucrun. You can limit the number of tests that are run with '--max-tests <number>', 197*f4a2713aSLionel Sambucand you can run a particular test with '--test <index>'. 198*f4a2713aSLionel Sambuc 199*f4a2713aSLionel SambucYou can specify '--stop-on-fail' to stop the script on the first failure 200*f4a2713aSLionel Sambucwithout reverting the changes. 201*f4a2713aSLionel Sambuc 202*f4a2713aSLionel Sambuc""") 203*f4a2713aSLionel Sambuc parser.add_option("-v", "--verbose", help="Show more output", 204*f4a2713aSLionel Sambuc action='store_true', dest="verbose", default=False) 205*f4a2713aSLionel Sambuc parser.add_option("-s", "--succinct", help="Reduce amount of output", 206*f4a2713aSLionel Sambuc action="store_true", dest="succinct", default=False) 207*f4a2713aSLionel Sambuc 208*f4a2713aSLionel Sambuc group = OptionGroup(parser, "Test Execution") 209*f4a2713aSLionel Sambuc group.add_option("", "--expected-exit-code", help="Set expected exit code", 210*f4a2713aSLionel Sambuc type=int, dest="expected_exit_code", 211*f4a2713aSLionel Sambuc default=0) 212*f4a2713aSLionel Sambuc group.add_option("", "--extra-exit-code", 213*f4a2713aSLionel Sambuc help="Set additional expected exit code", 214*f4a2713aSLionel Sambuc type=int, action="append", dest="extra_exit_codes", 215*f4a2713aSLionel Sambuc default=[]) 216*f4a2713aSLionel Sambuc group.add_option("", "--log-dir", 217*f4a2713aSLionel Sambuc help="Capture test logs to an output directory", 218*f4a2713aSLionel Sambuc type=str, dest="log_dir", 219*f4a2713aSLionel Sambuc default=None) 220*f4a2713aSLionel Sambuc group.add_option("", "--log-all", 221*f4a2713aSLionel Sambuc help="Log all outputs (not just failures)", 222*f4a2713aSLionel Sambuc action="store_true", dest="log_all", default=False) 223*f4a2713aSLionel Sambuc parser.add_option_group(group) 224*f4a2713aSLionel Sambuc 225*f4a2713aSLionel Sambuc group = OptionGroup(parser, "Input Files") 226*f4a2713aSLionel Sambuc group.add_option("", "--file", metavar="PATH", 227*f4a2713aSLionel Sambuc help="Add an input file to fuzz", 228*f4a2713aSLionel Sambuc type=str, action="append", dest="input_files", default=[]) 229*f4a2713aSLionel Sambuc group.add_option("", "--filelist", metavar="LIST", 230*f4a2713aSLionel Sambuc help="Add a list of inputs files to fuzz (one per line)", 231*f4a2713aSLionel Sambuc type=str, action="append", dest="filelists", default=[]) 232*f4a2713aSLionel Sambuc parser.add_option_group(group) 233*f4a2713aSLionel Sambuc 234*f4a2713aSLionel Sambuc group = OptionGroup(parser, "Fuzz Options") 235*f4a2713aSLionel Sambuc group.add_option("", "--replacement-chars", dest="replacement_chars", 236*f4a2713aSLionel Sambuc help="Characters to insert/replace", 237*f4a2713aSLionel Sambuc default="0{}[]<>\;@#$^%& ") 238*f4a2713aSLionel Sambuc group.add_option("", "--replacement-string", dest="replacement_strings", 239*f4a2713aSLionel Sambuc action="append", help="Add a replacement string to use", 240*f4a2713aSLionel Sambuc default=[]) 241*f4a2713aSLionel Sambuc group.add_option("", "--replacement-list", dest="replacement_lists", 242*f4a2713aSLionel Sambuc help="Add a list of replacement strings (one per line)", 243*f4a2713aSLionel Sambuc action="append", default=[]) 244*f4a2713aSLionel Sambuc group.add_option("", "--no-delete", help="Don't delete characters", 245*f4a2713aSLionel Sambuc action='store_false', dest="enable_delete", default=True) 246*f4a2713aSLionel Sambuc group.add_option("", "--no-insert", help="Don't insert strings", 247*f4a2713aSLionel Sambuc action='store_false', dest="enable_insert", default=True) 248*f4a2713aSLionel Sambuc group.add_option("", "--no-replace", help="Don't replace strings", 249*f4a2713aSLionel Sambuc action='store_false', dest="enable_replace", default=True) 250*f4a2713aSLionel Sambuc group.add_option("", "--no-revert", help="Don't revert changes", 251*f4a2713aSLionel Sambuc action='store_false', dest="revert", default=True) 252*f4a2713aSLionel Sambuc group.add_option("", "--stop-on-fail", help="Stop on first failure", 253*f4a2713aSLionel Sambuc action='store_true', dest="stop_on_fail", default=False) 254*f4a2713aSLionel Sambuc parser.add_option_group(group) 255*f4a2713aSLionel Sambuc 256*f4a2713aSLionel Sambuc group = OptionGroup(parser, "Test Selection") 257*f4a2713aSLionel Sambuc group.add_option("", "--test", help="Run a particular test", 258*f4a2713aSLionel Sambuc type=int, dest="test", default=None, metavar="INDEX") 259*f4a2713aSLionel Sambuc group.add_option("", "--max-tests", help="Maximum number of tests", 260*f4a2713aSLionel Sambuc type=int, dest="max_tests", default=None, metavar="COUNT") 261*f4a2713aSLionel Sambuc group.add_option("", "--pick-input", 262*f4a2713aSLionel Sambuc help="Randomly select an input byte as well as fuzzing", 263*f4a2713aSLionel Sambuc action='store_true', dest="pick_input", default=False) 264*f4a2713aSLionel Sambuc parser.add_option_group(group) 265*f4a2713aSLionel Sambuc 266*f4a2713aSLionel Sambuc parser.disable_interspersed_args() 267*f4a2713aSLionel Sambuc 268*f4a2713aSLionel Sambuc (opts, args) = parser.parse_args() 269*f4a2713aSLionel Sambuc 270*f4a2713aSLionel Sambuc if not args: 271*f4a2713aSLionel Sambuc parser.error("Invalid number of arguments") 272*f4a2713aSLionel Sambuc 273*f4a2713aSLionel Sambuc # Collect the list of inputs. 274*f4a2713aSLionel Sambuc input_files = list(opts.input_files) 275*f4a2713aSLionel Sambuc for filelist in opts.filelists: 276*f4a2713aSLionel Sambuc f = open(filelist) 277*f4a2713aSLionel Sambuc try: 278*f4a2713aSLionel Sambuc for ln in f: 279*f4a2713aSLionel Sambuc ln = ln.strip() 280*f4a2713aSLionel Sambuc if ln: 281*f4a2713aSLionel Sambuc input_files.append(ln) 282*f4a2713aSLionel Sambuc finally: 283*f4a2713aSLionel Sambuc f.close() 284*f4a2713aSLionel Sambuc input_files.sort() 285*f4a2713aSLionel Sambuc 286*f4a2713aSLionel Sambuc if not input_files: 287*f4a2713aSLionel Sambuc parser.error("No input files!") 288*f4a2713aSLionel Sambuc 289*f4a2713aSLionel Sambuc print '%s: note: fuzzing %d files.' % (sys.argv[0], len(input_files)) 290*f4a2713aSLionel Sambuc 291*f4a2713aSLionel Sambuc # Make sure the log directory exists if used. 292*f4a2713aSLionel Sambuc if opts.log_dir: 293*f4a2713aSLionel Sambuc if not os.path.exists(opts.log_dir): 294*f4a2713aSLionel Sambuc try: 295*f4a2713aSLionel Sambuc os.mkdir(opts.log_dir) 296*f4a2713aSLionel Sambuc except OSError: 297*f4a2713aSLionel Sambuc print "%s: error: log directory couldn't be created!" % ( 298*f4a2713aSLionel Sambuc sys.argv[0],) 299*f4a2713aSLionel Sambuc raise SystemExit,1 300*f4a2713aSLionel Sambuc 301*f4a2713aSLionel Sambuc # Get the list if insert/replacement strings. 302*f4a2713aSLionel Sambuc replacements = list(opts.replacement_chars) 303*f4a2713aSLionel Sambuc replacements.extend(opts.replacement_strings) 304*f4a2713aSLionel Sambuc for replacement_list in opts.replacement_lists: 305*f4a2713aSLionel Sambuc f = open(replacement_list) 306*f4a2713aSLionel Sambuc try: 307*f4a2713aSLionel Sambuc for ln in f: 308*f4a2713aSLionel Sambuc ln = ln[:-1] 309*f4a2713aSLionel Sambuc if ln: 310*f4a2713aSLionel Sambuc replacements.append(ln) 311*f4a2713aSLionel Sambuc finally: 312*f4a2713aSLionel Sambuc f.close() 313*f4a2713aSLionel Sambuc 314*f4a2713aSLionel Sambuc # Unique and order the replacement list. 315*f4a2713aSLionel Sambuc replacements = list(set(replacements)) 316*f4a2713aSLionel Sambuc replacements.sort() 317*f4a2713aSLionel Sambuc 318*f4a2713aSLionel Sambuc # Create the test generator. 319*f4a2713aSLionel Sambuc tg = TestGenerator(input_files, opts.enable_delete, opts.enable_insert, 320*f4a2713aSLionel Sambuc opts.enable_replace, replacements, opts.pick_input) 321*f4a2713aSLionel Sambuc 322*f4a2713aSLionel Sambuc print '%s: note: %d input bytes.' % (sys.argv[0], tg.num_positions) 323*f4a2713aSLionel Sambuc print '%s: note: %d total tests.' % (sys.argv[0], tg.num_tests) 324*f4a2713aSLionel Sambuc if opts.test is not None: 325*f4a2713aSLionel Sambuc it = [opts.test] 326*f4a2713aSLionel Sambuc elif opts.max_tests is not None: 327*f4a2713aSLionel Sambuc it = itertools.imap(random.randrange, 328*f4a2713aSLionel Sambuc itertools.repeat(tg.num_tests, opts.max_tests)) 329*f4a2713aSLionel Sambuc else: 330*f4a2713aSLionel Sambuc it = itertools.imap(random.randrange, itertools.repeat(tg.num_tests)) 331*f4a2713aSLionel Sambuc for test in it: 332*f4a2713aSLionel Sambuc t = tg.get_test(test) 333*f4a2713aSLionel Sambuc 334*f4a2713aSLionel Sambuc if opts.verbose: 335*f4a2713aSLionel Sambuc print '%s: note: running test %d: %r' % (sys.argv[0], test, t) 336*f4a2713aSLionel Sambuc ta = TestApplication(tg, t) 337*f4a2713aSLionel Sambuc try: 338*f4a2713aSLionel Sambuc ta.apply() 339*f4a2713aSLionel Sambuc test_result = run_one_test(ta, test, input_files, args) 340*f4a2713aSLionel Sambuc if not test_result and opts.stop_on_fail: 341*f4a2713aSLionel Sambuc opts.revert = False 342*f4a2713aSLionel Sambuc sys.exit(1) 343*f4a2713aSLionel Sambuc finally: 344*f4a2713aSLionel Sambuc if opts.revert: 345*f4a2713aSLionel Sambuc ta.revert() 346*f4a2713aSLionel Sambuc 347*f4a2713aSLionel Sambuc sys.stdout.flush() 348*f4a2713aSLionel Sambuc 349*f4a2713aSLionel Sambucif __name__ == '__main__': 350*f4a2713aSLionel Sambuc main() 351