class OuiMasver: """This class is designed to get a vendor by MAC address.""" def __init__(self): """This class is designed to get a vendor by poppy address.""" self.log = Log(name='OuiMasver') self.DIR = Path( os.path.abspath(os.path.join(os.path.dirname(__file__), '../data'))) self.oui_file_path = Path( os.path.abspath( os.path.join(self.DIR, 'OrganizationallyUniqueIdentifier.csv'))) self.oui = dict() self.__get_oui() def __get_oui(self): """creating a dictionary from the csv file""" try: with open(self.oui_file_path) as csv_file: oui_csv = [i for i in csv.DictReader(csv_file)] except IOError: self.log.warning( 'failed to read Organizationally Unique Identifier list!') else: for line in oui_csv: line = dict(line) self.oui[line['mac']] = line['vendor'] def get_mac_vendor(self, mac): """search by poppy address, returns vendor or None""" if isinstance(mac, str): mac = str(mac) mac = mac.replace(':', '') mac = mac.replace('-', '') query_string = mac[0:6].upper() if len(mac) != 12: return None if query_string in self.oui: return self.oui[query_string]
class Storage: """this class is designed to validate, store, add and get gummy_scan results""" def __init__(self): """class initialization method""" self.log = Log(name='storg') self.data = list() self.sockets = dict() self.last_received = None self.v_schema = { "$schema": "http://json-schema.org/draft-04/schema#", "title": "gummy_scan result", "definitions": { "host": { "type": "object", "properties": { "addr": { "type": "string", "oneOf": [ { "format": "ipv4" }, { "format": "ipv6" }, ] }, "mac": { "type": "string" }, "hostname": { "type": "string" }, "vendor": { "type": "string" }, "ports": { "type": "array", "items": { "$ref": "#/definitions/port" } } }, "required": ["addr"], "additionalProperties": False }, "port": { "type": "object", "properties": { "portid": { "type": "string" }, "protocol": { "enum": ["tcp", "udp"] }, "state": { "type": "string" } }, "required": ["portid", "protocol"], "additionalProperties": False } }, "type": "array", "items": { "$ref": "#/definitions/host" } } self.m_schema = { 'mergeStrategy': 'arrayMergeById', 'mergeOptions': { 'idRef': 'addr' }, "items": { "properties": { "ports": { 'mergeStrategy': 'arrayMergeById', 'mergeOptions': { 'idRef': '/' } } } } } self.DIR = Path( os.path.abspath(os.path.join(os.path.dirname(__file__), '../data'))) self.ports_des_file = Path( os.path.abspath(os.path.join(self.DIR, 'PortDescription.csv'))) self.ports_rating_file = Path( os.path.abspath(os.path.join(self.DIR, 'NmapPortRating.csv'))) self.ports_des = None self.__get_ports_des() def __validate_scan_res(self): """received object validation method""" try: validate(self.last_received, self.v_schema, format_checker=FormatChecker()) return True except Exception as e: self.log.warning(e) return False def __merge_scan_res(self): """merge of the received object with the main storage""" merger = Merger(self.m_schema) self.data = merger.merge(self.data, self.last_received) def __add__(self, other): """the main method gets the object and tries to add it to the database""" self.last_received = other if self.__validate_scan_res(): self.__merge_scan_res() def merge(self, *args): """public method that allows the addition of an arbitrary number of objects""" merger = Merger(self.m_schema) checker = FormatChecker() res = None for item in args: try: validate(item, self.v_schema, format_checker=checker) res = merger.merge(res, item) except Exception as e: self.log.warning(e) return res @property def get_sockets(self, protocol='tcp'): """property for getting sockets dict""" tree = objectpath.Tree(self.data) for ip in tree.execute(f"$..addr"): self.sockets[ip] = [ p for p in tree.execute( f"$.*[@.addr is '{ip}']..ports[@.protocol is '{protocol}' and @.state is 'open'].portid" ) ] return self.sockets @property def get_host_list(self): """property for getting hosts list""" tree = objectpath.Tree(self.data) return [i for i in tree.execute(f"$..addr")] @property def get_count_host(self): """property for getting number of host""" return len(self.data) @property def get_count_socket(self): """property for getting number of sockets""" tree = objectpath.Tree(self.data) return len([p for p in tree.execute(f"$.*..ports[@.state is 'open']")]) def __get_ports_des(self): """getting the description list for the ports""" data = [] try: with open(self.ports_des_file) as csv_file: ports = csv.DictReader(csv_file) for port in ports: data.append(dict(port)) self.ports_des = objectpath.Tree(data) except Exception as e: self.log.warning(e) def get_table(self): """this method is designed to display a table of hosts""" table = PrettyTable() table.field_names = [ 'IP', 'HOSTNAME', 'VENDOR', 'COUNT', 'TCP PORTS', 'UDP PORTS' ] table.sortby = 'COUNT' table.reversesort = True table.align = 'l' table.align['COUNT'] = 'c' tree = objectpath.Tree(self.data) for ip in tree.execute(f"$..addr"): hostname = tree.execute(f"$.*[@.addr is '{ip}'][0].hostname") if hostname is None: hostname = '-' hostname = hostname[:31] + '...' if len( hostname) > 30 else hostname vendor = tree.execute(f"$.*[@.addr is '{ip}'][0].vendor") if vendor is None: vendor = '-' vendor = vendor[:31] + '...' if len(vendor) > 30 else vendor table.add_row([ # IP ip, # HOSTNAME hostname, # VENDOR vendor, # COUNT len([ p for p in tree.execute( f"$.*[@.addr is '{ip}']..ports[@.state is 'open']") ]), # TCP PORTS ', '.join([ p for p in tree.execute( f"$.*[@.addr is '{ip}']..ports[@.protocol is 'tcp' and @.state is 'open'].portid" ) ]), # UDP PORTS ', '.join([ p for p in tree.execute( f"$.*[@.addr is '{ip}']..ports[@.protocol is 'udp' and @.state is 'open'].portid" ) ]), ]) return table def get_ports_info(self): """this method is designed to display a table of ports""" def gen_port_cat(file): linc = dict() catalog = dict() with open(file) as csv_file: ports = csv.DictReader(csv_file) for port in ports: port = dict(port) # gen port linc if '-' in port['Port']: range_ports = port['Port'].split('-') for p in range(int(range_ports[0]), int(range_ports[1])): linc[p] = port['Port'] else: linc[port['Port']] = port['Port'] # gen port catalog if port['Port']: catalog.setdefault(port['Port'], list()).append(port['Description']) return linc, catalog def get_port_dict(data): port_dict = dict() for host in data: if 'ports' in host: for port in host['ports']: if port['state'] == 'open': port_dict.setdefault( '/'.join([port['portid'], port['protocol']]), dict()) return port_dict def gen_port_rating_dict(file): port_rating = dict() with open(file) as csv_file: n_serv_csv = csv.DictReader(csv_file) for port in n_serv_csv: port = dict(port) port_rating[port['Port']] = port['Rate'] return port_rating def add_data_to_port_dict(data, port_dict, port_rating): for host in data: if 'ports' in host: for port in host['ports']: port_dict['/'.join([port['portid'], port['protocol']])].setdefault( 'hosts', list()).append(host['addr']) for port_item in port_dict: port_int = port_item.split('/')[0] if port_int in port_linc: port_dict[port_item]['help'] = port_catalog[ port_linc[port_int]] if port_item in port_rating: port_dict[port_item]['rating'] = port_rating[port_item] return port_dict def get_table(port_dict): table = PrettyTable() table.field_names = [ 'Port', 'Count', 'Rating', 'Description', 'Hosts' ] table.sortby = 'Count' table.reversesort = True table.max_width['Description'] = 100 table.max_width['Hosts'] = 80 table.align = 'l' for port in port_dict: host = port_dict[port]['hosts'] rating = port_dict[port]['rating'] if 'rating' in port_dict[ port] else '0' desc = port_dict[port]['help'] if 'help' in port_dict[ port] else '' table.add_row([ port, len(host), rating, '\n'.join(desc), ', '.join(host) ]) return table port_linc, port_catalog = gen_port_cat(file=self.ports_des_file) port_rating = gen_port_rating_dict(file=self.ports_rating_file) port_dict = get_port_dict(data=self.data) port_dict = add_data_to_port_dict(data=self.data, port_dict=port_dict, port_rating=port_rating) return get_table(port_dict)
class Config: """class for managing script configurations.""" def __init__(self): """initializing the configuration class.""" DIR = Path( os.path.abspath(os.path.join(os.path.dirname(__file__), '../'))) self.log = Log(name='conf ') self.default_config = None self.start_config_path = None self.start_config = None self.default_config_path = os.path.join(DIR, 'setting.ini') self.DEFAULT_CONFIG = { 'MAIN': { '# Contains the main application settings': None, '# Path:': None, 'result_path': Path(os.path.abspath(os.path.join(DIR, '../', 'scans'))), 'masscan_path': '/usr/bin/masscan', 'nmap_path': '/usr/bin/nmap', '# Reporting:': None, 'rep_type': 'None' }, 'LOGING': { '# Contains the logging settings': None, '# Logging:': None, '# log_level: DEBUG, INFO, WARNING, ERROR, CRITICAL': None, '# log_format: https://docs.python.org/3/library/logging.html#logrecord-attributes': None, '# log_format_date: ‘%Y-%m-%d %H:%M:%S,uuu’': None, 'log_level': 'INFO', 'log_format': '%(asctime)s | %(name)s | %(levelname)s | %(message)s', 'log_format_date': '%H:%M:%S', 'log_file_path': os.path.join(DIR, 'log') }, 'CONSTANTS': { '# Contains non-modifiable values': None, 'private_cidr': '10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16' }, 'MASSCAN': { '# The first step - hosts discovery': None, 'target': '127.0.0.1', 'target_exclude': '', 'port': '', 'top_ports': '', 'rate': '10000' }, 'NMAP': { '# The second step - detailed scanning of discovered hosts': None, 'scan_type': 'basic' }, } def __add__(self, other): """ this method allows you to update the workload configuration data with startup arguments. :param other: argument dictionary of the required format. """ for key in list(other.keys()): self.start_config[key].update(other[key]) self.log.debug(list(self.start_config[key].values())) def create_default_config(self): """method for creating a basic configuration file.""" config = configparser.RawConfigParser(allow_no_value=True, delimiters='=') config.read_dict(self.DEFAULT_CONFIG) with open(self.default_config_path, 'w') as config_file: config.write(config_file) self.log.info( f'Default configuration file {self.default_config_path} successfully created' ) def read_default_config(self): """method for read a basic configuration file.""" if not os.path.exists(self.default_config_path): self.log.info( f'The configuration file {self.default_config_path} was not found.' ) self.create_default_config() self.log.info('Read configuration file') try: config = configparser.RawConfigParser() config.read(self.default_config_path) self.log.info( f'Default configuration file {self.default_config_path} successfully read' ) except Exception: self.log.warning( f'Default configuration file {self.default_config_path} incorrect!' ) raise Exception() self.default_config = config self.start_config = config def read_start_config(self, file): """method for read a basic configuration file.""" if not os.path.exists(file): self.log.warning(f'The configuration file {file} was not found!') else: self.log.info('Read configuration file') try: config = configparser.RawConfigParser() config.read(file) self.log.info(f'Configuration file {file} successfully read') except Exception: self.log.warning(f'Configuration file {file} incorrect!') raise Exception() self.default_config = config self.start_config = config def create_start_config(self): """method for writing start parameters to the startup configuration file.""" with open(self.start_config_path, 'w') as config_file: self.start_config.write(config_file) self.log.info( f'Startup configuration file {self.start_config_path} successfully created' ) @property def get_start_config(self): """method for print start config in console""" list_start_config = list() for section in self.start_config.sections(): section_pr = section for key in self.start_config[section]: list_start_config.append( [section_pr, key, self.start_config[section][key]]) section_pr = '' return list_start_config def set_start_config_key(self, key, value): """method for changing parameter about current configuration""" for section in self.start_config.sections(): for k in self.start_config[section]: if k == key: self.start_config.set(section, k, value) return True return False def get_start_config_key(self, key): """method for getting current cinf parameter""" for section in self.start_config.sections(): for k in self.start_config[section]: if k == key: return self.start_config[section][k] return 'no key' def get_all_start_config_key(self): """method for generating a list of possible configuration parameters (for shell completer)""" keys = dict() for section in self.start_config.sections(): for key in self.start_config[section]: keys[key] = '' return keys
class PortMaster: """ This class generates a sorted by priority list of ports. The final rating is based on two lists (statistical and nman rating) or is taken from the manual list if it is found there. Priority list is set by variables nmap_weight and stat_weight. """ def __init__(self, nmap_weight=0.3, stat_weight=0.7): """ class initialization method :param nmap_weight: nmap list priority :param stat_weight: statistical list priority """ self.log = Log(name='PortMaster') self.DIR = Path( os.path.abspath(os.path.join(os.path.dirname(__file__), '../data'))) self.man_port_rating_file = Path( os.path.abspath(os.path.join(self.DIR, 'ManPortRating.csv'))) self.nmap_port_rating_file = Path( os.path.abspath(os.path.join(self.DIR, 'NmapPortRating.csv'))) self.stat_port_rating_file = Path( os.path.abspath(os.path.join(self.DIR, 'StatPortRating.csv'))) self.nmap_weight = nmap_weight self.stat_weight = stat_weight self.tcp_port = list() self.udp_port = list() self.__man_port_rating = dict() self.__nmap_port_rating = dict() self.__stat_port_rating = dict() self.__port_rating = dict() self.__udp_port_rating = dict() self.__tcp_port_rating = dict() self.__get_man_port_rating() self.__get_nmap_port_rating() self.__get_stat_port_rating() self.__get_port_rating() self.__sort_port() def __get_man_port_rating(self): """read manual port rate list""" try: with open(self.man_port_rating_file) as csv_file: n_serv_csv = [i for i in csv.DictReader(csv_file)] except IOError: self.log.warning('failed to read manual port rate list!') else: for port in n_serv_csv: port = dict(port) self.__man_port_rating[port['Port']] = float(port['Rate']) def __get_nmap_port_rating(self): """read nmap port rate list""" try: with open(self.nmap_port_rating_file) as csv_file: n_serv_csv = [i for i in csv.DictReader(csv_file)] except IOError: self.log.warning('failed to read nmap port rate list!') else: for port in n_serv_csv: port = dict(port) self.__nmap_port_rating[port['Port']] = float(port['Rate']) max_nmap_port_rating = max( (list(self.__nmap_port_rating.values()))) self.__nmap_port_rating = { k: v / max_nmap_port_rating for k, v in self.__nmap_port_rating.items() } def __get_stat_port_rating(self): """read statistical port rate list""" try: with open(self.stat_port_rating_file) as csv_file: n_serv_csv = [i for i in csv.DictReader(csv_file)] except IOError: self.log.warning('failed to read statistical port rate list!') else: for port in n_serv_csv: port = dict(port) val = [int(i) for i in list(port.values())[1:]] self.__stat_port_rating[port['Port']] = sum(val) sum_all_stat_ports = sum(self.__stat_port_rating.values()) self.__stat_port_rating = { k: v / sum_all_stat_ports for k, v in self.__stat_port_rating.items() } max_stat_port_rating = max( (list(self.__stat_port_rating.values()))) self.__stat_port_rating = { k: v / max_stat_port_rating for k, v in self.__stat_port_rating.items() } def __get_port_rating(self): """generation of the final port rating""" all_ports = list( set( list(self.__man_port_rating.keys()) + list(self.__nmap_port_rating.keys()) + list(self.__stat_port_rating.keys()))) for port in all_ports: if port in self.__man_port_rating: self.__port_rating[port] = self.__man_port_rating[port] else: nmap_rate = self.__nmap_port_rating[ port] if port in self.__nmap_port_rating else 0 stat_rating = self.__stat_port_rating[ port] if port in self.__stat_port_rating else 0 self.__port_rating[ port] = nmap_rate * self.nmap_weight + stat_rating * self.stat_weight def __sort_port(self): """sort the resulting list, add the missing ports with a zero rating""" for port in self.__port_rating: sp_port = port.split('/') if sp_port[1] == 'tcp': self.__tcp_port_rating[sp_port[0]] = self.__port_rating[port] elif sp_port[1] == 'udp': self.__udp_port_rating[sp_port[0]] = self.__port_rating[port] self.tcp_port = sorted(self.__tcp_port_rating, key=self.__tcp_port_rating.get)[::-1] self.udp_port = sorted(self.__udp_port_rating, key=self.__udp_port_rating.get)[::-1] diff_tcp = [ i for i in list(range(1, 65535)) if str(i) not in self.__tcp_port_rating ] diff_udp = [ i for i in list(range(1, 65535)) if str(i) not in self.__udp_port_rating ] self.tcp_port = [int(i) for i in self.tcp_port + diff_tcp] self.udp_port = [int(i) for i in self.udp_port + diff_udp] @staticmethod def list_to_arg(lst): """convert list to argument string""" prev_i = 0 group = [] res = '' for i in sorted(lst): if i != prev_i + 1 and len(group) != 0: res += str(group[0] if len(group) == 1 else f'{min(group)}-{max(group)}') + ',' group.clear() group.append(int(i)) prev_i = i return res[:-1] def __call__(self, start=1, end=65535, protocol='tcp'): """port selection in argument string format""" try: if protocol == 'tcp': res = self.list_to_arg(self.tcp_port[start - 1:end]) elif protocol == 'udp': res = self.list_to_arg(self.udp_port[start - 1:end]) else: res = '' self.log.warning('unknown protocol') exit() except IndexError: self.log.warning( 'the final list does not contain the requested range') exit() else: return res
class GummyShell: """Forms the cli control interface of the scanner""" def __init__(self, config, db, scanner): """class object initialization""" self.log = Log(name='shell') self.config = config self.db = db self.scan = scanner self.parser = Parser() self.collors = ( '#000000', '#800000', '#008000', '#808000', '#000080', '#800080', '#008080', '#c0c0c0', '#808080', '#ff0000', '#00ff00', '#ffff00', '#0000ff', '#ff00ff', '#00ffff', '#ffffff', '#000000', '#00005f', '#000087', '#0000af', '#0000d7', '#0000ff', '#005f00', '#005f5f', '#005f87', '#005faf', '#005fd7', '#005fff', '#008700', '#00875f', '#008787', '#0087af', '#0087d7', '#0087ff', '#00af00', '#00af5f', '#00af87', '#00afaf', '#00afd7', '#00afff', '#00d700', '#00d75f', '#00d787', '#00d7af', '#00d7d7', '#00d7ff', '#00ff00', '#00ff5f', '#00ff87', '#00ffaf', '#00ffd7', '#00ffff', '#5f0000', '#5f005f', '#5f5fd7', '#5faf5f', '#5f0087', '#5f00af', '#5f00d7', '#5f00ff', '#5f5f00', '#5f5f5f', '#5f5f87', '#5f5faf', '#5f5fff', '#5f8700', '#5f875f', '#5f8787', '#5f87af', '#5f87d7', '#5f87ff', '#5faf00', '#5faf87', '#5fafaf', '#5fafd7', '#5fafff', '#5fd700', '#5fd75f', '#5fd787', '#5fd7af', '#5fd7ff', '#5fff00', '#5fff5f', '#5fff87', '#5fffaf', '#5fffd7', '#5fffff', '#870000', '#870087', '#8700af', '#8700d7', '#8700ff', '#875f00', '#875f5f', '#875f87', '#875faf', '#875fff', '#878700', '#87875f', '#878787', '#8787af', '#8787d7', '#8787ff', '#87af00', '#87af87', '#87afaf', '#87afd7', '#87afff', '#87d700', '#87d75f', '#87d787', '#87d7af', '#87d7ff', '#87ff00', '#87ff5f', '#87ff87', '#87ffaf', '#87ffd7', '#87ffff', '#af0000', '#af0087', '#af00af', '#af00d7', '#af00ff', '#af5f00', '#af5f5f', '#af5f87', '#af5faf', '#af5fff', '#af8700', '#af875f', '#af8787', '#af87af', '#af87d7', '#af87ff', '#afaf00', '#afaf87', '#afafaf', '#afafd7', '#afafff', '#afd700', '#afd75f', '#afd787', '#afd7af', '#afd7ff', '#afff00', '#afff5f', '#afff87', '#afffaf', '#afffd7', '#afffff', '#d70000', '#d70087', '#d700af', '#d700d7', '#d700ff', '#d75f00', '#d75f5f', '#d75f87', '#d75faf', '#d75fff', '#d78700', '#d7875f', '#d78787', '#d787af', '#d787d7', '#d787ff', '#d7af00', '#d7af87', '#d7afaf', '#d7afd7', '#d7afff', '#d7d700', '#d7d75f', '#d7d787', '#d7d7af', '#d7d7ff', '#d7ff00', '#d7ff5f', '#d7ff87', '#d7ffaf', '#d7ffd7', '#d7ffff', '#ff0000', '#ff0087', '#ff00af', '#ff00d7', '#ff00ff', '#ff5f00', '#ff5f5f', '#ff5f87', '#ff5faf', '#ff5fff', '#ff8700', '#ff875f', '#ff8787', '#ff87af', '#ff87d7', '#ff87ff', '#ffaf00', '#ffaf87', '#ffafaf', '#ffafd7', '#ffafff', '#ffd700', '#ffd75f', '#ffd787', '#ffd7af', '#ffd7ff', '#ffff00', '#ffff5f', '#ffff87', '#ffffaf', '#ffffd7', '#ffffff', '#080808', '#1c1c1c', '#262626', '#303030', '#3a3a3a', '#444444', '#4e4e4e', '#585858', '#626262', '#767676', '#808080', '#8a8a8a', '#949494', '#9e9e9e', '#a8a8a8', '#b2b2b2', '#bcbcbc', '#d0d0d0', '#dadada', '#e4e4e4', '#eeeeee', '#5fd7d7', '#87005f', '#875fd7', '#875fd7', '#87af5f', '#87d7d7', '#af005f', '#af5fd7', '#afaf5f', '#afd7d7', '#d7005f', '#d75fd7', '#d7af5f', '#d7d7d7', '#ff005f', '#ff5fd7', '#ffaf5f', '#ffd7d7', '#121212', '#6c6c6c', '#c6c6c6', ) self.commands = { 'set': self.config.get_all_start_config_key(), 'show': { 'config': 'print curent config (takes param)', 'host': 'print host table (takes param)', 'port': 'print port table', 'task': 'print running tasks', 'log': 'print the last n lines of the log file' }, 'sync': { 'config': 'synchronizes the configuration file' }, 'run': self.get_scanner_methods(self.scan), 'workspase': self.get_all_workspase(), 'flush': {}, 'kill': {}, 'help': {}, 'exit': {} } self.c_function = { 'set': self.f_set, 'show': self.f_show, 'sync': self.f_sync, 'run': self.f_run, 'workspase': self.f_workspase, 'flush': self.f_flush, 'kill': self.f_kill, 'help': self.f_help, 'exit': self.f_exit } self.grammar = compile(""" (\s* (?P<command>[a-z]+) \s*) | (\s* (?P<command>[a-z]+) \s+ (?P<operator>[A-Za-z0-9_-]+) \s*) | (\s* (?P<command>[a-z]+) \s+ (?P<operator>[A-Za-z0-9_-]+) \s+ (?P<parameter>[A-Za-z0-9.,-_/+*]+) \s*) """) self.style = Style.from_dict({ 'command': '#216f21 bold', 'operator': '#6f216f bold', 'parameter': '#ff0000 bold', 'trailing-input': 'bg:#662222 #ffffff', 'bottom-toolbar': '#6f216f bg:#ffffff', # Logo. 'bear': random.choice(self.collors), 'text': random.choice(self.collors), # User input (default text). '': '#ff0066', # Prompt. 'prompt_for_input': '#6f216f', }) self.lexer = GrammarLexer(self.grammar, lexers={ 'command': SimpleLexer('class:command'), 'operator': SimpleLexer('class:operator'), 'parameter': SimpleLexer('class:parameter') }) self.completer = GCompleter(self.commands) self.history_path = Path( os.path.abspath( os.path.join(os.path.dirname(__file__), '../.history'))) self.history = FileHistory(self.history_path) version_str = ''.join(['v', gummy.__version__, ' ']) self.logo = HTML(f''' <text> </text><bear> _ _ </bear> <text> _____ _ _ __ __ __ ____ __</text><bear> (c).-.(c) </bear> <text> / ____| | | | \/ | \/ \ \ / /</text><bear> / ._. \ </bear> <text> | | __| | | | \ / | \ / |\ \_/ / </text><bear> __\( Y )/__ </bear> <text> | | |_ | | | | |\/| | |\/| | \ / </text><bear> (_.-/'-'\-._)</bear> <text> | |__| | |__| | | | | | | | | | </text><bear> || </bear><text>G</text><bear> || </bear> <text> \_____|\____/|_| |_|_| |_| |_| </text><bear> _.' `-' '._ </bear> <text> </text><bear> (.-./`-'\.-.)</bear> <text>{version_str:>38}</text><bear> `-' `-'</bear> ''') self.prompt_str = [('class:prompt_for_input', '>>> ')] self.counter = 0 self.sync_config_stat = 0 # 0 - never synchronized # 1 - changed but not synchronized # 7 - synchronized # user-invoked function block: def f_show(self, **kwargs): def show_port(self): for line in str(self.db.get_ports_info()).split('\n'): self.log.info(line) def show_task(self): for item_task in asyncio.Task.all_tasks(): self.log.info('-' * 50) self.log.info(item_task) def show_log(self, pr): pr = int(pr) if pr else 100 try: line_need = int(pr) except ValueError: self.log.info('use int in param') line_need = 100 with open( self.config.start_config['LOGING']['log_file_path']) as lp: log = lp.read().split('\n') print('-' * 8) for ind, line in enumerate(log): if len(log) - line_need <= ind: print(f'log {ind:4}| {line}') print('-' * 8) def show_config(self, pr): if pr: vl = self.config.get_start_config_key(key=pr) self.log.info(f'{pr}: {vl}') else: table = PrettyTable() table.field_names = ['SECTOR', 'KEY', 'VALUE'] table.align = 'l' table.align['SECTOR'] = 'c' conf = self.config.get_start_config for item in conf: table.add_row(item) for line in str(table).split('\n'): self.log.info(line) def show_host(self, pr): if pr: pp = pprint.PrettyPrinter(width=80) pr = pr.replace('*', '[\d.]*') pr = pr.replace('+', '[\d.]+') pr = ''.join([pr, '$']) try: regex = re.compile(pr) except Exception: self.log.warning('Invalid regexp') else: for host in self.db.data: try: search = regex.search(host['addr']) except Exception: search = False self.log.warning('Invalid regexp') if search: for line in pp.pformat(host).split('\n'): self.log.info(line) else: for line in str(self.db.get_table()).split('\n'): self.log.info(line) if kwargs.get('operator'): op = kwargs.get('operator') if op == 'config': show_config(self, pr=kwargs.get('parameter')) elif op == 'log': show_log(self, pr=kwargs.get('parameter')) elif op == 'host': show_host(self, pr=kwargs.get('parameter')) elif op == 'task': show_task(self) elif op == 'port': show_port(self) else: self.log.info('What to show?') self.log.info(', '.join(self.commands.get('show'))) def f_sync(self, **kwargs): if kwargs.get('operator'): op = kwargs.get('operator') if op == 'config': # create workspace folders result_path = self.config.default_config.get( "MAIN", "result_path") workspace = self.config.start_config['MAIN']['workspase'] workspace_path = '/'.join([result_path, workspace]) self.config.start_config.set('MAIN', 'workspace_path', workspace_path) start_config_path = '/'.join( [workspace_path, 'start_config.ini']) self.config.start_config.set('MAIN', 'start_config_path', start_config_path) mk_dir(result_path) mk_dir(workspace_path) # create starting config file self.config.start_config_path = start_config_path self.config.create_start_config() # sync gummy gummy_scan cinf self.scan.sync(self.config.start_config) self.sync_config_stat = 7 else: self.log.info('What to sync?') self.log.info(', '.join(self.commands.get('sync'))) def f_set(self, **kwargs): if kwargs.get('operator'): op = kwargs.get('operator') if kwargs.get('parameter'): pr = kwargs.get('parameter') else: pr = '' self.sync_config_stat = 1 self.config.set_start_config_key(key=op, value=pr) else: self.log.info('What to set?') self.log.info(', '.join(self.commands.get('set'))) def f_run(self, **kwargs): if self.sync_config_stat == 1: self.log.info('configuration changed but not synchronized!') if self.sync_config_stat in [0, 1]: self.log.info('automatic synchronization start') self.f_sync(operator='config') if kwargs.get('operator'): op = '_' + kwargs.get('operator') getattr(self.scan, op)() else: self.log.info('What to run?') self.log.info(', '.join(self.commands.get('run'))) def f_workspase(self, **kwargs): if kwargs.get('operator'): op = kwargs.get('operator') result_path = self.config.start_config['MAIN']['result_path'] workspase_path = f'{result_path}/{op}' self.log.info(f'Ok loding {result_path}/{op}') counter = self.get_max_scans(workspase_path) self.log.info(f'Set gummy_scan counter is: {counter}') self.scan.counter = counter workspase_config_path = f'{workspase_path}/start_config.ini' self.log.info(f'Read workspase config: {workspase_config_path}') self.config.read_start_config(file=workspase_config_path) self.log.info('Load gummy_scan results:') for scaner in ['m', 'n']: for file in self.get_xml_files(scan_path=workspase_path, scaner=scaner): self.log.info(f' -- {file}') self.parser(file) self.db + self.parser.result else: self.log.info('What workspace to load?') self.log.info(', '.join(self.get_all_workspase())) def f_kill(self): for item_task in asyncio.Task.all_tasks(): if '<Task pending coro=<GummyShell.start()' not in str(item_task): self.log.info('-' * 50) self.log.info(item_task) self.log.info(item_task.cancel()) def f_flush(self): scan_path = self.config.start_config['MAIN']['result_path'] log_path = self.config.start_config['LOGING']['log_file_path'] list_scans = os.listdir(path=scan_path) self.log.info('clear log file...') os.truncate(log_path, 0) self.log.info('clear history file...') os.truncate(self.history_path, 0) for scan in list_scans: current_path = f'{scan_path}/{scan}' self.log.info(f'remove gummy_scan: {current_path}') shutil.rmtree(current_path, ignore_errors=True) self.f_exit() def f_help(self): self.log.info('No one will help you.') def f_exit(self): self.log.info('...') raise EOFError @staticmethod def get_max_scans(path): """workspase function, updates the gummy_scan counter""" xml_files = glob.glob(pathname=f'{path}/[0-9][0-9][0-9]-[nm]-*.xml') regex = re.compile(f'^{path}/(?P<num>[0-9]{"{3}"}).*$') nums = [0] for file in xml_files: remach = regex.match(file) if remach: nums.append(int(remach.group('num'))) return max(nums) @staticmethod def get_xml_files(scan_path, scaner): """workspase function, getting all gummy_scan results in a directory""" xml_files = glob.glob( pathname=f'{scan_path}/[0-9][0-9][0-9]-{scaner}-*.xml') xml_files.sort() return xml_files def get_all_workspase(self): """workspase function, used to generate shell subcommands for workspase command""" result_path = self.config.start_config['MAIN']['result_path'] commands = dict() if os.path.exists(result_path): subfolders = [ f.path for f in os.scandir(result_path) if f.is_dir() ] for i, w in enumerate(subfolders): w_name = w[len(result_path) + 1:] m_len = len(self.get_xml_files(scan_path=w, scaner='m')) n_len = len(self.get_xml_files(scan_path=w, scaner='n')) commands[w_name] = f'scans: m[{m_len}], n[{n_len}]' return commands @staticmethod def get_scanner_methods(scanner): """function, used to generate shell subcommands for run command""" methods = dict() for func in dir(scanner): if callable(getattr(scanner, func)) and re.match( r'^_\d{3}_\w*$', func): methods[func[1:]] = getattr(scanner, func).__doc__ return methods @property def get_toolbar(self): """function to display the bottom toolbar""" t_left = f'workspace: {self.config.start_config["MAIN"]["workspase"]} | ' \ f'host: {self.db.get_count_host} | ' \ f'socket: {self.db.get_count_socket}' t_right = f'{datetime.datetime.now().strftime("%H:%M:%S")} bat: {get_battery()}' rows, columns = os.popen('stty size', 'r').read().split() toolbar = t_left + ' ' * (int(columns) - len(t_left) - len(t_right)) + t_right return toolbar async def start(self): """main function starting the interface loop task""" os.system('clear') print_formatted_text(self.logo, style=self.style) # Create Prompt. while True: try: session = PromptSession(message=self.prompt_str, completer=self.completer, lexer=self.lexer, style=self.style, history=self.history, enable_history_search=True, auto_suggest=AutoSuggestFromHistory(), bottom_toolbar=self.get_toolbar, wrap_lines=False) result = await session.prompt(async_=True) self.log.debug(f'input: {result}') if not result: continue elif result == 'clear': clear() continue elif result.strip().startswith('!P'): try: eval(result.strip().replace('!P', '')) except Exception as e: self.log.warning(e) continue else: m = self.grammar.match(result) if m: m_vars = m.variables() else: self.log.info('Invalid match') continue if m_vars.get('command') \ and m_vars.get('command') in list(self.commands.keys()) \ and m_vars.get('command') in list(self.c_function.keys()): cm = m_vars.get('command') cur_function = self.c_function[cm] if len(self.commands.get(cm)) == 0: cur_function() else: if m_vars.get('operator') and m_vars.get( 'operator') in list(self.commands.get(cm)): op = m_vars.get('operator') else: op = '' if m_vars.get('parameter'): pr = m_vars.get('parameter') else: pr = '' cur_function(operator=op, parameter=pr) else: self.log.info('invalid command') continue except (EOFError, KeyboardInterrupt): return def __call__(self): """function startin main asyncio loop""" async def sig_exit(): self.log.info('Why are you so?') loop = asyncio.get_event_loop() for sig_name in ('SIGINT', 'SIGTERM'): loop.add_signal_handler(getattr(signal, sig_name), lambda: asyncio.ensure_future(sig_exit())) use_asyncio_event_loop() shell_task = asyncio.gather(self.start()) with patch_stdout(): loop.run_until_complete(shell_task) for task in asyncio.Task.all_tasks(loop=loop): if task is not asyncio.Task.current_task(): task.cancel()
class Mscanner: """masscan port scanner module class""" def __init__(self, prog_path, scans_path, db): """ Initialization masscan scanner class object :param prog_path: path to the program :param scans_path: gummy_scan directory """ self.log = Log(name='mscan') self.db = db self._prog_path = prog_path self._scans_path = scans_path self.scan_name = None self.target = None self.target_exclude = None self.target_exclude_file = None self.port = None self.udp_port = None self.top_ports = None self.rate = None self._ob_last_name = '' self._ox_last_name = '' self._conf_last_name = '' self._hosts_file_last_name = '' self.ob_last_path = '' self.ox_last_path = '' self.conf_last_path = '' self.hosts_file_last_path = '' self._args = [] self.counter = 0 self.version = '' self.host = {} self._check_prog() def _check_prog(self): """checking the correctness of the path to the program""" m_reg = re.compile(r'(?:Masscan version) (?P<ver>[\d.]*) (?:[\s\S]+)$') try: procc = subprocess.Popen([self._prog_path, '-V'], bufsize=10000, stdout=subprocess.PIPE) mach = m_reg.search(bytes.decode(procc.communicate()[0])) self.version = mach.group('ver') self.log.info(f'Use: {self._prog_path} (Version {self.version})') except Exception: self.log.warning('Masscan was not found') raise Exception() async def __call__(self, **kwargs): """ start gummy_scan and save result in binary. :param kwargs: scan_name = first counter = 1 target = 10.10.1.0/16 includefile = <filename> target_exclude = 10.10.1.0/24, 10.10.2.0/24 port = '443' or '80,443' or '22-25' udp_port = '443' or '80,443' or '22-25' top_ports = 100 rate = 25000 """ self.scan_name = kwargs.get('scan_name') self.target = kwargs.get('target') self.includefile = kwargs.get('includefile') self.target_exclude = kwargs.get('target_exclude') self.hosts_file_last_path = kwargs.get('hosts_file_last_path') self.port = kwargs.get('port') self.udp_port = kwargs.get('udp_port') self.top_ports = kwargs.get('top_ports') self.rate = kwargs.get('rate') # parse start args if kwargs.get('counter') and kwargs.get('counter') is not None: self.counter = kwargs.get('counter') if self.scan_name and self.scan_name is not None: num = str(self.counter).zfill(3) if self.target: targ = self.target.replace('.', '-').replace('/', '#') targ = targ[:18] + '...' if len(targ) > 17 else targ else: targ = '' self._ob_last_name = f'{num}-m-{self.scan_name}-[{targ}].masscan' self._ox_last_name = f'{num}-m-{self.scan_name}-[{targ}].xml' self._conf_last_name = f'{num}-m-{self.scan_name}-[{targ}].conf' self._hosts_file_last_name = f'{num}-m-{self.scan_name}-[{targ}].host' self.ob_last_path = '/'.join((self._scans_path, self._ob_last_name)) self.ox_last_path = '/'.join((self._scans_path, self._ox_last_name)) self.conf_last_path = '/'.join((self._scans_path, self._conf_last_name)) self.hosts_file_last_path = '/'.join((self._scans_path, self._hosts_file_last_name)) else: self.log.warning('Missing required parameter: scan_name') return # generate masscan arg self._gen_args() self.counter += 1 await self._run_scan() await self._convert_masscan_to_xml() def _gen_args(self): """generating arguments to run gummy_scan""" # clear list self._args.clear() # prog_path self._args.append(self._prog_path) # target if self.target: self._args.append('--range') self._args.append(self.target) self.log.debug(f'Set: {"target":10} Value: {self.target}') elif self.includefile: self._args.append('--includefile') self._args.append(self.includefile) self.log.debug(f'Set: {"includefile":10} Value: {self.includefile}') else: self.log.warning('Missing required parameter: target') return # target_exclude if self.target_exclude: self._args.append('--exclude') self._args.append(self.target_exclude) self.log.debug(f'Set: {"target_exclude":10} Value: {self.target_exclude}') # target_exclude_file if self.target_exclude_file: self._args.append('--excludefile') self._args.append(self.target_exclude) self.log.debug(f'Set: {"target_exclude_file":10} Value: {self.target_exclude_file}') # port or top-ports if self.port or self.udp_port: if self.port: self._args.append('--ports') self._args.append(self.port) self.log.debug(f'Set: {"port":10} Value: {self.port}') if self.udp_port: self._args.append('--udp-ports') self._args.append(self.udp_port) self.log.debug(f'Set: {"udp-ports":10} Value: {self.udp_port}') elif self.top_ports: self._args.append('--top-ports') self._args.append(self.top_ports) self.log.debug(f'Set: {"top_ports":10} Value: {self.top_ports}') else: self.log.warning('Missing required parameter: port or top-ports or udp-ports') return # output self._args.append('-oB') self._args.append(self.ob_last_path) self.log.debug(f'Set: {"output":10} Value: {self.ob_last_path}') # rate if self.rate: self._args.append('--rate') self._args.append(self.rate) self.log.debug(f'Set: {"rate":10} Value: {self.rate}') else: self.log.warning('Argument "rate" not set base value is used') # static args self._args.append('--wait') self._args.append('1') self._args.append('--interactive') async def _read_stream(self, stream): """asynchronous output processing""" res_reg_rem = re.compile(r'(?:rate:\s*)' r'(?P<Rate>[\d.]+)' r'(?:[-,\w]+\s+)' r'(?P<Persent>[\d.]*)' r'(?:%\s*done,\s*)' r'(?P<Time>[\d:]*)' r'(?:\s*remaining,\s*found=)' r'(?P<Found>[\d]*)') res_reg_dis = re.compile(r'(?:Discovered open port )' r'(?P<Port>\d+)' r'(?:/)' r'(?P<Protocol>\w+)' r'(?: on )' r'(?P<IP>[\d.]+)') old_line = '' persent_old, found_old = 0, 0 mach_udp = 0 temp_soc_stor = list() while True: line = await stream.read(n=1000) if line: line = line.decode(locale.getpreferredencoding(False)) line = line.replace('\n', '') line = line.replace('\r', '') if line != old_line: mach_rem = res_reg_rem.search(line) mach_dis = res_reg_dis.search(line) if mach_rem: persent_new = mach_rem.group("Persent") found_new = mach_rem.group("Found") if found_new != found_old or float(persent_new) >= float(persent_old) + 5: persent_old, found_old = persent_new, found_new self.log.info(f'[{persent_new}%] ' f'Time: {mach_rem.group("Time")} ' f'Found: {int(found_new) + mach_udp}') if mach_dis: mach_soc = [{'addr': mach_dis.group('IP'), 'ports': [{'protocol': mach_dis.group('Protocol'), 'portid': mach_dis.group('Port'), 'state': 'open'}]}] if mach_soc not in temp_soc_stor: # check for duplicate records: temp_soc_stor.append(mach_soc) if mach_dis.group('Protocol') == 'udp': mach_udp += 1 self.db + mach_soc old_line = line else: break async def _run_scan(self): """run a gummy_scan using arguments""" self.log.debug(f'Write the command to a file {self.conf_last_path}') with open(self.conf_last_path, "w") as text_file: text_file.write(' '.join(self._args)) self.log.info('Scan start') self.log.debug(f'run: {" ".join(self._args)}') proc = await asyncio.create_subprocess_exec(*self._args, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.STDOUT) await asyncio.wait([self._read_stream(proc.stdout)]) await proc.wait() self.log.info('Scan complete') async def _convert_masscan_to_xml(self): """convert masscan binary to xml format""" if os.stat(self.ob_last_path).st_size == 0: self.log.warning('The file is empty') else: self.log.debug(f'Сonvert {self.ob_last_path} to {self.ox_last_path}') args = [self._prog_path] + \ ['--readscan', self.ob_last_path] + \ ['-oX', self.ox_last_path] proc = await asyncio.create_subprocess_exec(*args, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.STDOUT) stdout, stderr = await proc.communicate() self.log.info(stdout.decode().strip())
class Nscanner: """nmap scanner module class""" # TODO the process does not end at task.cancel() def __init__(self, prog_path, db, scans_path): """ initialization nmap scanner class object :param prog_path: path to the program :param scans_path: gummy_scan directory """ self.log = Log(name='nscan') self.db = db self.parser = Parser() self._prog_path = prog_path self._scans_path = scans_path self.scan_name = None self.target = None self.port = None self.udp_port = None self.scan_type = None self.version = None self._ox_last_name = None self.ox_last_path = None self._t_missing = 'Missing required parameter: {}' self._args = list() self.counter = 0 self._check_prog() self._args_basic = [ '-sV', '-Pn', '--disable-arp-ping', '-T4', '-O', '--version-light', '--stats-every', '1s' ] self._args_arp = ['-PR', '-sn', '--stats-every', '1s'] self._args_dns = ['-sL'] def _check_prog(self): """checking the correctness of the path to the program""" m_reg = re.compile(r'(?:Nmap version) (?P<ver>[\d.]*) (?:[\s\S]+)$') try: procc = subprocess.Popen([self._prog_path, '-V'], bufsize=10000, stdout=subprocess.PIPE) mach = m_reg.search(bytes.decode(procc.communicate()[0])) self.version = mach.group('ver') self.log.debug(f'Use: {self._prog_path} (Version {self.version})') except Exception: self.log.warning('Nmap was not found') raise Exception() async def __call__(self, **kwargs): """ Start gummy_scan and save result in XML. 001-basic-n-[192-168-1-50#24]-basic.xml :param kwargs: scan_name = first counter = 1 target = 10.10.1.0/16 port = '443' or '80,443' or '22-25' udp_port = '443' or '80,443' or '22-25' scan_type = 'fast' or 'basic' or 'full' :return: """ self.scan_name = kwargs.get('scan_name') self.target = kwargs.get('target') self.port = kwargs.get('port') self.udp_port = kwargs.get('udp_port') self.scan_type = kwargs.get('scan_type') if kwargs.get('counter') and kwargs.get('counter') is not None: self.counter = kwargs.get('counter') targ = self.target.replace('.', '-').replace('/', '#') targ = targ[:18] + '...' if len(targ) > 17 else targ self._ox_last_name = f'{str(self.counter).zfill(3)}-n-{self.scan_name}-[{targ}]-{self.scan_type}.xml' self.ox_last_path = '/'.join((self._scans_path, self._ox_last_name)) if self._gen_args(): await self._run_scan() self.parser(self.ox_last_path) # noinspection PyStatementEffect self.db + self.parser.result def _gen_args(self): """generating arguments to run gummy_scan""" # clear list self._args.clear() # required parameters: # prog_path self._args.append(self._prog_path) # target if self.target: self._args.append(self.target) self.log.debug(f'Set: {"target":10} Value: {self.target}') else: self.log.warning(self._t_missing.format('target')) return False # output self._args.append('-oX') self._args.append(self.ox_last_path) self.log.debug(f'Set: {"output":10} Value: {self.ox_last_path}') # optional parameters # port temp_ports_arg = [] temp_port = [] if self.port or self.udp_port: if self.port: temp_ports_arg.append('-sS') temp_port.append('T:' + self.port) if self.udp_port: temp_ports_arg.append('-sU') temp_port.append('U:' + self.udp_port) temp_ports_arg.append('-p') temp_ports_arg.append(','.join(temp_port)) self.log.debug(f'Set: {"port":10} Value: {",".join(temp_port)}') if self.scan_type == 'basic': self._args += temp_ports_arg self._args += self._args_basic elif self.scan_type == 'arp': self._args += self._args_arp elif self.scan_type == 'dns': self._args += self._args_dns return True async def _read_stream(self, stream): """asynchronous output processing""" full_body = '' last_print_line = 0 exclude_lines = [ r'^WARNING: Running Nmap setuid, as you are doing, is a major security risk.$', r'^WARNING: Running Nmap setgid, as you are doing, is a major security risk.$', r'^Starting Nmap .*$', r'^$', r'^Host is up.$', r'^Nmap scan report for [\d.]*$' ] while True: line = await stream.read(n=100) if line: line = line.decode(locale.getpreferredencoding(False)) full_body += line full_body_list = full_body.split('\n') total_line = len(full_body_list) - 1 if total_line > last_print_line: for line in full_body_list[last_print_line:total_line]: if any( re.search(regex, line) for regex in exclude_lines): self.log.debug(line) else: self.log.info(line) last_print_line = total_line else: break async def _run_scan(self): """run a gummy_scan using arguments""" self.log.info('Scan start') self.log.debug(f'run: {" ".join(self._args)}') proc = await asyncio.create_subprocess_exec( *self._args, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.STDOUT) await asyncio.wait([self._read_stream(proc.stdout)]) await proc.wait() self.counter += 1 self.log.info('Scan complete')
class Parser: """class for parsing XML file""" def __init__(self): """class initialization""" self.log = Log(name='pars ') self.file_path = None # xml.etree.ElementTree.ElementTree object self.tree = None # information about scanning self.scan = dict() # result current pars self.result = list() self.tcp_sockets = dict() self.udp_sockets = dict() self.hosts = list() self.all_scans = list() def __clear(self): """cleaning parser options""" self.file_path = None self.tree = None self.scan = dict() self.result = list() self.hosts = list() def __get_hosts(self): """getting host list""" self.hosts = [h['addr'] for h in self.result] def __load_file(self, file): """reading the file, and getting the xml tree""" self.file_path = file if not os.path.exists(file): self.log.warning('The file was not found!') return False try: tree = xml.etree.ElementTree.parse(file) except Exception: self.log.warning('Error parsing the file') return False else: self.tree = tree return True def __get_scan_info(self): """getting general information about scanning""" self.scan.clear() self.scan['num'] = len(self.all_scans) self.scan['file'] = self.file_path self.scan['scanner'] = self.tree.getroot().attrib['scanner'] self.scan['start'] = self.tree.getroot().attrib['start'] if self.scan['scanner'] == 'nmap': self.scan['args'] = self.tree.getroot().attrib['args'] def __pars_nmap(self): """main method for parsing the results of the nmap gummy_scan""" for item in self.tree.findall('host'): host_addr = None host_mac = None host_mac_vendor = None host_hostname = None host_ports = list() address = item.findall('address') for adress in address: if adress.attrib['addrtype'] == 'ipv4': host_addr = adress.attrib['addr'] elif adress.attrib['addrtype'] == 'mac': host_mac = adress.attrib['addr'] if 'vendor' in adress.attrib: host_mac_vendor = adress.attrib['vendor'] hostnames = item.findall('hostnames/hostname') for hostname in hostnames: if hostname.attrib['type'] == 'PTR': host_hostname = hostname.attrib['name'] ports = item.findall('ports/port') for port_odj in ports: state_obj = port_odj.find('state') host_port = { 'protocol': port_odj.attrib['protocol'], 'portid': port_odj.attrib['portid'], 'state': state_obj.attrib['state'] } host_ports.append(host_port) host = {'addr': host_addr} if host_mac is not None: host['mac'] = host_mac if host_hostname is not None: host['hostname'] = host_hostname if host_mac_vendor is not None: host['vendor'] = host_mac_vendor if len(host_ports) != 0: host['ports'] = host_ports # is_it_arp_scan = all(i in self.scan['args'] for i in ['-PR', '-sn']) is_it_dns_scan = '-sL' in self.scan['args'] if is_it_dns_scan: continue # if is_it_arp_scan and host_hostname is None: # continue self.result.append(host) def __pars_masscan(self): """main method for parsing the results of the masscan gummy_scan""" for item in self.tree.findall('host'): host_addr = item.find('address').attrib['addr'] if item.find('*/port') is not None: port_odj = item.find('*/port') state_obj = item.find('*/port/state') host_port = { 'protocol': port_odj.attrib['protocol'], 'portid': port_odj.attrib['portid'], 'state': state_obj.attrib['state'] } else: host_port = dict() host = {'addr': host_addr, 'ports': [host_port]} for host_item in self.result: if host_item['addr'] == host_addr: # duplicate line exclusion: if host_port not in host_item['ports']: host_item['ports'].append(host_port) break else: continue else: self.result.append(host) def __call__(self, file): """main persr call method""" self.__clear() self.__load_file(file=file) self.__get_scan_info() if self.scan['scanner'] == 'nmap': self.__pars_nmap() elif self.scan['scanner'] == 'masscan': self.__pars_masscan() else: self.log.warning('unexpected gummy_scan type!') current_scan = {'gummy_scan': self.scan, 'hosts': self.result} self.all_scans.append(current_scan) self.__get_hosts()
class Scanner: """This class describes gummy_scan profiles, links gummy_scan modules and auxiliary modules.""" def __init__(self, db=None): """class object initialization""" # basic param: self.db = db self.log = Log(name='gummy') self.port_master = PortMaster() self.counter = 0 # complex gummy_scan param: self.complex_n_scan = None self.complex_m_scan = None self.complex_pars = None self.complex_res = None self.complex_hosts_file_last_path = None self.complex_step1 = False self.complex_step2 = False self.complex_step3 = False self.complex_step4 = False self.complex_confirmation_time = datetime.datetime.now() # config param: self.config = None self.masscan_path = None self.nmap_path = None self.workspace_path = None self.target = None self.target_exclude = None self.port = None self.top_ports = None self.rate = None self.scan_type = None # port ranges param: self.tcp_stage_1 = self.port_master(start=1, end=1000, protocol='tcp') self.tcp_stage_2 = self.port_master(start=1001, end=65535, protocol='tcp') self.udp_stage_1 = self.port_master(start=1, end=1000, protocol='udp') self.udp_stage_2 = self.port_master(start=1001, end=4000, protocol='udp') def create_hosts_file(self, file, hosts): """function that creates a file with a list of hosts""" try: with open(file, "w") as host_file: for host in hosts: host_file.write(host + '\n') except IOError: self.log.warning('failed to create file!') def sync(self, config): """updates settings from configuration class instance""" self.config = config self.masscan_path = config['MAIN'].get('masscan_path') self.nmap_path = config['MAIN'].get('nmap_path') self.workspace_path = config['MAIN'].get('workspace_path') self.target = config['MASSCAN'].get('target') self.target_exclude = config['MASSCAN'].get('target_exclude') self.port = config['MASSCAN'].get('port') self.top_ports = config['MASSCAN'].get('top_ports') self.rate = config['MASSCAN'].get('rate') self.scan_type = config['NMAP'].get('scan_type') async def __complex(self, stage): """ updates settings from configuration class instance takes an array with the necessary scanning steps used own variables of self.complex_... type :param stage: list[int] :return: """ if 1 in stage: if all([i is None for i in [self.complex_m_scan, self.complex_n_scan, self.complex_pars]]) \ or datetime.datetime.now() < (self.complex_confirmation_time + datetime.timedelta(seconds=10)): self.complex_step2, self.complex_step3, self.complex_step4 = False, False, False self.complex_n_scan = Nscanner(prog_path=self.nmap_path, scans_path=self.workspace_path, db=self.db) self.complex_m_scan = Mscanner(prog_path=self.masscan_path, scans_path=self.workspace_path, db=self.db) self.complex_pars = Parser() self.counter += 1 self.log.info(f'{" STEP 1 ":#^40}') await self.complex_n_scan(scan_name='arp', counter=self.counter, target=self.target, scan_type='arp') self.complex_pars(file=self.complex_n_scan.ox_last_path) arp_host = self.complex_pars.hosts self.counter += 1 await self.complex_m_scan(scan_name='stage_1', counter=self.counter, target=self.target, target_exclude=self.target_exclude, port=self.tcp_stage_1, udp_port=self.udp_stage_1, rate=self.rate) self.complex_pars(file=self.complex_m_scan.ox_last_path) self.create_hosts_file( hosts=set(arp_host + self.complex_pars.hosts), file=self.complex_m_scan.hosts_file_last_path) self.complex_hosts_file_last_path = self.complex_m_scan.hosts_file_last_path self.complex_res = self.complex_pars.result self.db + self.complex_pars.result self.complex_step1 = True else: self.log.info( 'А complex gummy_scan has already been started, if you want to override it - ' 'repeat start command for the next 10 seconds') self.complex_confirmation_time = datetime.datetime.now() return if 2 in stage: if self.complex_step1 and len(self.complex_pars.result) != 0: self.counter += 1 self.log.info(f'{" STEP 2 ":#^40}') await self.complex_m_scan( scan_name='stage_2', counter=self.counter, # target=','.join(self.complex_pars.hosts), includefile=self.complex_hosts_file_last_path, target_exclude=self.target_exclude, port=self.tcp_stage_2, udp_port=self.udp_stage_2, rate=self.rate) self.complex_pars(file=self.complex_m_scan.ox_last_path) self.create_hosts_file( hosts=self.complex_pars.hosts, file=self.complex_m_scan.hosts_file_last_path) self.complex_res = self.db.merge(self.complex_res, self.complex_pars.result) self.db + self.complex_pars.result self.complex_step2 = True else: self.log.info('There are no results of the previous stage') if 3 in stage: if self.complex_step2: self.log.info(f'{" STEP 3 ":#^40}') self.complex_n_scan = Nscanner(prog_path=self.nmap_path, scans_path=self.workspace_path, db=self.db) for host in self.complex_res: self.counter += 1 tcp = list() udp = list() for port in host['ports']: if port['state'] == 'open': if port['protocol'] == 'tcp': tcp.append(port['portid']) elif port['protocol'] == 'udp': udp.append(port['portid']) self.log.info( f'{host["addr"]} tcp:{",".join(tcp)} udp:{",".join(udp)}' ) await self.complex_n_scan(scan_name='basic', counter=self.counter, target=host['addr'], port=','.join(tcp), udp_port=','.join(udp), scan_type='basic') self.complex_step3 = True else: self.log.info('There are no results of the previous stage') if 4 in stage: if self.complex_step3: self.counter += 1 self.log.info(f'{" STEP 4 ":#^40}') await self.complex_m_scan( scan_name='swamp', counter=self.counter, target=self.target, target_exclude=self.complex_hosts_file_last_path, port=self.tcp_stage_2, udp_port=self.udp_stage_2, rate=self.rate) self.complex_pars(file=self.complex_m_scan.ox_last_path) self.create_hosts_file( hosts=self.complex_pars.hosts, file=self.complex_m_scan.hosts_file_last_path) self.db + self.complex_pars.result self.log.info('I found something in the swamp !!!') self.log.info(self.complex_pars.hosts) self.complex_step4 = True self.log.info(f'{" END ":#^40}') else: self.log.info('There are no results of the previous stage') # next following are the functions displayed in the user interface # you must use the functions starting with _iii_... # and add a short description def _101_complex_1(self): """M Scan the top 1000 TCP and top 1000 UDP ports of the current range""" asyncio.gather(self.__complex(stage=[1])) def _102_complex_2(self): """M Scan the bottom 64553 TCP and next 3000 UDP ports of the detected hosts""" asyncio.gather(self.__complex(stage=[2])) def _103_complex_3(self): """N Scan the detected hosts (found ports)""" asyncio.gather(self.__complex(stage=[3])) def _104_complex_4(self): """M Scan the remaining swamp""" asyncio.gather(self.__complex(stage=[4])) def _111_complex_1_2(self): """Sequential start of steps 1-2 complex gummy_scan""" asyncio.gather(self.__complex(stage=[1, 2])) def _112_complex_1_3(self): """Sequential start of steps 1-3 complex gummy_scan""" asyncio.gather(self.__complex(stage=[1, 2, 3])) def _113_complex_1_4(self): """Sequential start of steps 1-4 complex gummy_scan""" asyncio.gather(self.__complex(stage=[1, 2, 3, 4])) def _001_masscan(self): """Run Masscan manually""" self.counter += 1 m_scan = Mscanner(prog_path=self.masscan_path, scans_path=self.workspace_path, db=self.db) asyncio.gather( m_scan(scan_name='basic', counter=self.counter, target=self.target, target_exclude=self.target_exclude, port=self.port, top_ports=self.top_ports, rate=self.rate)) def _002_nmap(self): """Run Nmap manually""" self.counter += 1 n_scan = Nscanner(prog_path=self.nmap_path, scans_path=self.workspace_path, db=self.db) asyncio.gather( n_scan(scan_name=self.scan_type, counter=self.counter, target=self.target, port=self.port, scan_type=self.scan_type)) def _201_arp_discovery(self): """Discovering hosts with ARP ping scans (-PR)""" self.counter += 1 n_scan = Nscanner(prog_path=self.nmap_path, scans_path=self.workspace_path, db=self.db) asyncio.gather( n_scan(scan_name='arp', counter=self.counter, target=self.target, scan_type='arp')) def _202_dns_discovery(self): """Reverse DNS resolution (-sL)""" self.counter += 1 n_scan = Nscanner(prog_path=self.nmap_path, scans_path=self.workspace_path, db=self.db) asyncio.gather( n_scan(scan_name='dns', counter=self.counter, target=self.target, scan_type='dns'))