Example #1
0
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]
Example #2
0
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)
Example #3
0
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
Example #4
0
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
Example #5
0
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()
Example #6
0
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())
Example #7
0
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')
Example #8
0
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()
Example #9
0
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'))