class Runner(FuzzObject): """ This is the main runner class of the Fuzzer Framework. It integrates the kitty/katnip components and the framework components. See documentation for the usage. """ def __init__(self, name='Runner', logger=None): super(Runner, self).__init__(name, logger) self.config = ConfigParser() self.framework_utils = FrameworkUtils() self.put_archive_to_s3 = PutArchiveToS3() self.proto_path = self.config.get_generic_proto_path() self.pb2_path = self.config.get_generic_pb2_path() self.verbosity = self.config.get_generic_verbosity() self.module_path = self.config.get_module_path() self.proto_modules = self.config.get_protobuf_modules() self.proto_classes = self.config.get_protobuf_classes_to_send() self.protocol_proto = self.config.get_protocol_protobuf() self.protocol_http = self.config.get_protocol_http() self.protocol_dns = self.config.get_protocol_dns() self.set_verbosity(self.verbosity) self.use_s3_to_archive = self.config.get_archive_to_s3() def search_files(self) -> tuple: """ Creates lists of raw and compiled proto files paired into tuple; :return: raw and compiled proto file lists in tuple; """ search_raw_proto = SearchFile(file_type='.proto', file_path=self.proto_path) raw_proto_files = search_raw_proto.search_file self.logger.debug(f'RAW proto files {raw_proto_files}') search_api = SearchFile(file_type='_pb2.py', file_path=self.pb2_path) compiled_proto_files = search_api.search_file self.logger.debug(f'Compiled proto files {compiled_proto_files}') return raw_proto_files, compiled_proto_files def checksum_files(self, raw_proto: list) -> list: # TODO for further usage, function not in use. checksum_files = self.framework_utils.md5_checksum_for_proto_files(raw_proto) self.logger.debug(f'Checksum {checksum_files}') return checksum_files @property def process_pb2(self) -> object: """ This function parses the compiled protobuf API and produces a Dictionary representation of it. :return: object """ compiled_proto_files = self.search_files()[1] for item in compiled_proto_files: if len(item.split('_')) > 2: raise FileNameError # raise if file name contains more then 2 underline. elif item.split('_')[0] in self.config.get_protobuf_modules(): parse_file = ParseProtoApi(pb2_api_file=item, module_path=self.module_path) return parse_file.execute_api_parse() else: self.logger.info(f'File {item} was not part of the inspection.') def tear_down(self): """ The common tear down sequence of a fuzzing session :return: None """ self.framework_utils.results_dir() self.framework_utils.rename_log_file() self.framework_utils.archive_locally() self.report() self.framework_utils.archive_xml() if self.use_s3_to_archive: self.put_archive_to_s3.do_upload(self.framework_utils.dir_files_to_archive()) def report(self): _report = CreateReport() _report.run_report() def run(self): if self.config.get_banner(): file1 = open(os.getcwd() + '/banner', 'r') Lines = file1.readlines() for line in Lines: print(f' {line.strip()}') for i in range(5, 0, -1): sys.stdout.write(str(i) + '.....') sys.stdout.flush() time.sleep(1) if self.protocol_proto: self.logger.info(f"[{time.strftime('%H:%M:%S')}] Starting PROTOBUF Fuzzer session") try: api = self.process_pb2 do_proto = ProtobufRunner(pb2_api=api) do_proto.run_proto() except SystemError as e: self.logger.error(f"Interpreter found an internal error: {e}") except EnvironmentError as e: self.logger(f"Error occur outside the Python environment: {e}") finally: self.logger.info(f"[{time.strftime('%H:%M:%S')}] Flush cache...") self.logger.info(f"[{time.strftime('%H:%M:%S')}] Rename kitty related log files...") self.tear_down() elif self.protocol_dns: self.logger.info(f"[{time.strftime('%H:%M:%S')}] Starting DNS Fuzzer session") try: # query record type do_dns = DnsRunner() do_dns.run_dns() self.logger.info(f"[{time.strftime('%H:%M:%S')}] DNS runner started") except SystemError as e: self.logger.error(f"Interpreter finds an internal problem {e}") except EnvironmentError as e: self.logger(f"Error occur outside the Python environment: {e}") finally: self.logger.info(f"[{time.strftime('%H:%M:%S')}] Rename kitty related log files...") self.tear_down() elif self.protocol_http: self.logger.info(f"[{time.strftime('%H:%M:%S')}] Starting HTTP Fuzzer session") try: do_http = HttpRunner() do_http.run_http() self.logger.info(f"[{time.strftime('%H:%M:%S')}] HTTP runner started") except SystemError as e: self.logger.error(f"Interpreter finds an internal problem {e}") except EnvironmentError as e: self.logger(f"Error occur outside the Python environment: {e}") finally: self.logger.info(f"[{time.strftime('%H:%M:%S')}] Rename kitty related log files...") self.tear_down()
class TestFramework(unittest.TestCase): def __init__(self, name='TestFramework'): super(TestFramework, self).__init__(name) self.framework_utils = FrameworkUtils() self.protobuf_target = ProtobufTarget() def test_01_convert_filepath_to_module_path(self): module_import_path = \ self.framework_utils.convert_filepath_to_module_path(file_path='/framework/models/pb2/api/') self.assertEqual(module_import_path, 'framework.models.pb2.api.') def test_02_encode_message(self): with open( os.path.abspath(os.path.join(os.path.dirname(__file__))) + '/msg_encode_assert') as file_data: data = file_data.read().encode() file_data.close() msg = self.protobuf_target._construct_message(data, query_pb2) msg_encoded = self.framework_utils.encode_message(msg) self.assertEqual(msg_encoded, msg_encoded) def test_03_decode_message(self): result = self.framework_utils.decode_message(msg_encoded, query_pb2, 'Request') msg = json_format.MessageToDict(result[0]) with open( os.path.join(os.path.dirname(__file__)) + '/msg_decoded_assert') as file_data: msg_decoded_assert = json.load(file_data) self.assertEqual(msg, msg_decoded_assert) def test_04_create_result_dir_if_not_exists(self): is_exits = False self.framework_utils.results_dir() if os.path.exists(os.getcwd() + '/results/'): is_exits = True self.assertTrue(is_exits, True) #shutil.rmtree(os.getcwd() + '/results/', ignore_errors=True) def test_05_rename_log_file(self): self._uuid = GenerateUUID.generate_uuid() self.framework_utils.rename_log_file() file_name = glob.glob(os.getcwd() + '/kittylogs/' + '*.log')[0].split('/')[-1:] expected_file_name = str( self._uuid) + '-kitty_' + time.strftime("%Y%m%d-%H%M%S") + '.log' shutil.rmtree(os.getcwd() + '/kittylogs/', ignore_errors=True) self.assertAlmostEqual(file_name[0], expected_file_name) def test_06_rename_log_file_file_not_found(self): shutil.rmtree(os.getcwd() + '/kittylogs/', ignore_errors=True) with self.assertRaises(FileNotFoundError): self.framework_utils.rename_log_file() def test_07_rename_log_file_could_not_read_from_file(self): os.makedirs(os.getcwd() + '/kittylogs/') with open(os.getcwd() + '/kittylogs/foo.gld', 'w') as file: file.write('dummy') file.close() with self.assertLogs('fuzzer', level='WARNING') as cm: self.framework_utils.rename_log_file() self.assertIn( 'WARNING:fuzzer:Could not read from file ' + os.getcwd() + '/kittylogs/', cm.output) shutil.rmtree(os.getcwd() + '/kittylogs/', ignore_errors=True) def test_08_archive_locally(self): dir_exists = False self.framework_utils.archive_locally() self._uuid = GenerateUUID.generate_uuid() dir_ = str(self._uuid) + '-log' if os.path.exists(os.getcwd() + '/' + dir_): dir_exists = True self.assertTrue(dir_exists, True) def test_09_extract_values(self): input_ = { "a": 1, "b": 2, "c": { "d": 3, "e": 4, "f": [{ "g": 5 }], "h": { "i": 6 } } } key_ = 'i' self.assertEqual(self.framework_utils.extract_values(input_, key_), [6]) def test_10_update_nested_object(self): context_ = [("a", 1), ("c", 2), ("h", 1), ("j", 1)] appnd_ = {"m": 9} key_ = "j" input_ = { "a": 1, "b": 2, "c": { "d": 3, "e": 4, "f": [{ "g": 5 }], "h": { "i": 6, "j": [{ "k": 7, "l": 8 }] } } } result = self.framework_utils.update_nested(input_, key_, appnd_, context_) expected = { 'a': 1, 'b': 2, 'c': { 'd': 3, 'e': 4, 'f': [{ 'g': 5 }], 'h': { 'i': 6, 'j': [{ 'k': 7, 'l': 8, 'm': 9 }] } } } self.assertEqual(result, expected) def test_11_update_nested_object_same_name(self): context_ = [("a", 1), ("c", 2), ("h", 1)] appnd_ = {"h": 9} key_ = "h" input_ = { "a": 1, "b": 2, "c": { "d": 3, "e": 4, "f": { "g": 5 }, "h": { "i": 6, "j": [{ "k": 7, "l": 8 }] } } } result = self.framework_utils.update_nested(input_, key_, appnd_, context_) expected = { 'a': 1, 'b': 2, 'c': { 'd': 3, 'e': 4, 'f': { 'g': 5 }, 'h': { 'i': 6, 'j': [{ 'k': 7, 'l': 8 }], 'h': 9 } } } self.assertEqual(result, expected) def test_12_update_nested_object_same_name_nested(self): context_ = [("a", 1), ("q", 2), ("q", 1)] appnd_ = {"q": 9} key_ = "q" input_ = { "a": 1, "b": 2, "q": { "q": { "x": 1 } }, "c": { "d": 3, "e": 4, "f": { "g": 5 }, "h": { "i": 6, "j": [{ "k": 7, "l": 8 }] } } } result = self.framework_utils.update_nested(input_, key_, appnd_, context_) expected = { 'a': 1, 'b': 2, 'q': { 'q': { 'x': 1, 'q': 9 } }, 'c': { 'd': 3, 'e': 4, 'f': { 'g': 5 }, 'h': { 'i': 6, 'j': [{ 'k': 7, 'l': 8 }] } } } self.assertEqual(result, expected) def test_13_field_randomizer_double(self): # field_randomizer(self, field_type, default_value=None, enum_list=None) is_close = False result = self.framework_utils.field_randomizer(1, default_value=None, enum_list=None) if 1.0 <= result <= 1.9: is_close = True self.assertTrue(is_close, True) def test_13_field_randomizer_double_default_value(self): # field_randomizer(self, field_type, default_value=None, enum_list=None) is_close = False result = self.framework_utils.field_randomizer( 1, default_value=1.12345679, enum_list=None) if 1.0 <= result <= 1.9: is_close = True self.assertTrue(is_close, True) def test_14_field_randomizer_float(self): # field_randomizer(self, field_type, default_value=None, enum_list=None) is_close = False result = self.framework_utils.field_randomizer(2, default_value=None, enum_list=None) if 1.0 <= result <= 1.9: is_close = True self.assertTrue(is_close, True) def test_15_field_randomizer_float_default(self): # field_randomizer(self, field_type, default_value=None, enum_list=None) is_close = False result = self.framework_utils.field_randomizer( 2, default_value=1.123456789, enum_list=None) if 1.0 <= result <= 1.9: is_close = True self.assertTrue(is_close, True) def test_14_field_randomizer_int64(self): # field_randomizer(self, field_type, default_value=None, enum_list=None) is_close = False result = self.framework_utils.field_randomizer(3, default_value=None, enum_list=None) if -9223372036854775808 <= result <= 9223372036854775808: is_close = True self.assertTrue(is_close, True) def test_15_field_randomizer_int64_default(self): # field_randomizer(self, field_type, default_value=None, enum_list=None) is_close = False result = self.framework_utils.field_randomizer( 3, default_value=1234567899999999, enum_list=None) if -9223372036854775808 <= result <= 9223372036854775808: is_close = True self.assertTrue(is_close, True) def test_16_field_randomizer_uint64(self): # field_randomizer(self, field_type, default_value=None, enum_list=None) is_close = False result = self.framework_utils.field_randomizer(4, default_value=None, enum_list=None) if -0 <= result <= 18446744073709551615: is_close = True self.assertTrue(is_close, True) def test_17_field_randomizer_uint64_default(self): # field_randomizer(self, field_type, default_value=None, enum_list=None) is_close = False result = self.framework_utils.field_randomizer( 4, default_value=1844674407370955161, enum_list=None) if -0 <= result <= 18446744073709551615: is_close = True self.assertTrue(is_close, True) def test_18_field_randomizer_int32(self): # field_randomizer(self, field_type, default_value=None, enum_list=None) is_close = False result = self.framework_utils.field_randomizer(5, default_value=None, enum_list=None) if -2147483648 <= result <= 2147483648: is_close = True self.assertTrue(is_close, True) def test_19_field_randomizer_int32_default(self): # field_randomizer(self, field_type, default_value=None, enum_list=None) is_close = False result = self.framework_utils.field_randomizer(5, default_value=1234567, enum_list=None) if -2147483648 <= result <= 2147483648: is_close = True self.assertTrue(is_close, True) def test_20_field_randomizer_fixed64(self): # field_randomizer(self, field_type, default_value=None, enum_list=None) is_close = False result = self.framework_utils.field_randomizer(6, default_value=None, enum_list=None) if -9223372036854775808 <= result <= 9223372036854775808: is_close = True self.assertTrue(is_close, True) def test_21_field_randomizer_fixed64_default(self): # field_randomizer(self, field_type, default_value=None, enum_list=None) is_close = False result = self.framework_utils.field_randomizer( 6, default_value=92233720368547758, enum_list=None) if -9223372036854775808 <= result <= 9223372036854775808: is_close = True self.assertTrue(is_close, True) def test_22_field_randomizer_fixed32(self): # field_randomizer(self, field_type, default_value=None, enum_list=None) is_close = False result = self.framework_utils.field_randomizer(7, default_value=None, enum_list=None) if -2147483648 <= result <= 2147483648: is_close = True self.assertTrue(is_close, True) def test_23_field_randomizer_fixed32_default(self): # field_randomizer(self, field_type, default_value=None, enum_list=None) is_close = False result = self.framework_utils.field_randomizer(7, default_value=214748364, enum_list=None) if -2147483648 <= result <= 2147483648: is_close = True self.assertTrue(is_close, True) def test_22_field_randomizer_bool(self): # field_randomizer(self, field_type, default_value=None, enum_list=None) is_close = False result = self.framework_utils.field_randomizer(8, default_value=None, enum_list=None) if result or not result: is_close = True self.assertTrue(is_close, True) def test_23_field_randomizer_bool_default(self): # field_randomizer(self, field_type, default_value=None, enum_list=None) is_close = False result = self.framework_utils.field_randomizer(8, default_value=True, enum_list=None) if result or not result: is_close = True self.assertTrue(is_close, True) def test_24_field_randomizer_string(self): # field_randomizer(self, field_type, default_value=None, enum_list=None) is_close = False regex = re.compile('[a-zA-Z0-9]', re.I) result = self.framework_utils.field_randomizer(9, default_value=None, enum_list=None) res = bool(regex.match(str(result))) if isinstance(result, str) and res: is_close = True self.assertTrue(is_close, True) def test_25_field_randomizer_string_default(self): # field_randomizer(self, field_type, default_value=None, enum_list=None) is_close = False regex = re.compile('[a-zA-Z0-9]', re.I) result = self.framework_utils.field_randomizer( 9, default_value='abcdefghijk', enum_list=None) res = bool(regex.match(str(result))) if isinstance(result, str) and res: is_close = True self.assertTrue(is_close, True) def test_26_field_randomizer_group(self): # field_randomizer(self, field_type, default_value=None, enum_list=None) with self.assertRaises(NotImplementedError): self.framework_utils.field_randomizer(10, default_value=None, enum_list=None) def test_27_field_randomizer_bytes(self): #TODO finish self.framework_utils.field_randomizer(12, default_value=None, enum_list=None) def test_28_field_randomizer_bytes_default(self): #TODO self.framework_utils.field_randomizer(12, default_value=0x23, enum_list=None) def test_29_field_randomizer_uint32(self): # field_randomizer(self, field_type, default_value=None, enum_list=None) is_close = False result = self.framework_utils.field_randomizer(13, default_value=None, enum_list=None) if 0 <= result <= 4294967295: is_close = True self.assertTrue(is_close, True) def test_30_field_randomizer_uint32_default(self): # field_randomizer(self, field_type, default_value=None, enum_list=None) is_close = False result = self.framework_utils.field_randomizer(13, default_value=429496729, enum_list=None) if 0 <= result <= 4294967295: is_close = True self.assertTrue(is_close, True) def test_31_field_randomizer_enum(self): # field_randomizer(self, field_type, default_value=None, enum_list=None) is_close = False enum_list = ['foo', 'bar', 'baz'] result = self.framework_utils.field_randomizer(14, default_value=None, enum_list=enum_list) if result.split('+')[0] in enum_list: is_close = True self.assertTrue(is_close, True) def test_32_field_randomizer_enum_default(self): # field_randomizer(self, field_type, default_value=None, enum_list=None) is_close = False enum_list = ['foo', 'bar', 'baz'] result = self.framework_utils.field_randomizer(14, default_value='bar', enum_list=enum_list) if result.split('+')[0] in enum_list: is_close = True self.assertTrue(is_close, True) def test_33_field_randomizer_sfixed32(self): # field_randomizer(self, field_type, default_value=None, enum_list=None) with self.assertRaises(NotImplementedError): self.framework_utils.field_randomizer(15, default_value=None, enum_list=None) def test_34_field_randomizer_sfixed64(self): # field_randomizer(self, field_type, default_value=None, enum_list=None) with self.assertRaises(NotImplementedError): self.framework_utils.field_randomizer(16, default_value=None, enum_list=None) def test_35_field_randomizer_sint32(self): # field_randomizer(self, field_type, default_value=None, enum_list=None) with self.assertRaises(NotImplementedError): self.framework_utils.field_randomizer(17, default_value=None, enum_list=None) def test_36_field_randomizer_sint64(self): # field_randomizer(self, field_type, default_value=None, enum_list=None) with self.assertRaises(NotImplementedError): self.framework_utils.field_randomizer(18, default_value=None, enum_list=None) def test_37_str_to_bool_true(self): result = self.framework_utils.str_to_bool('true') self.assertTrue(result, True) def test_38_str_to_bool_false(self): result = self.framework_utils.str_to_bool('false') self.assertFalse(result, False) def test_39_dir_files_to_archive(self): result = self.framework_utils.dir_files_to_archive() self.assertEqual(len(result), 1) def test_40_md5_checksum_for_proto_files(self): pass