Ejemplo n.º 1
0
class Parser(BaseParser):
    def __init__(self, parser_info):
        super().__init__(parser_info)
        self.mappers = parser_info['mappers']
        self.used_facts = parser_info['used_facts']
        self.log = Logger('parsing_svc')

    def parse(self, blob):
        relationships = []
        json_output = self._load_json(blob)
        if json_output is not None:
            for mp in self.mappers:
                if 'json_key' not in dir(mp):
                    self.log.warning(
                        'JSON Parser not given a json_key, not parsing')
                    continue
                json_type = mp.json_type if 'json_type' in dir(mp) else None
                for match in self._get_vals_from_json(json_output, mp.json_key,
                                                      json_type):
                    source = self.set_value(mp.source, match, self.used_facts)
                    target = self.set_value(mp.target, match, self.used_facts)
                    relationships.append(
                        Relationship(source=(mp.source, source),
                                     edge=mp.edge,
                                     target=(mp.target, target)))
        return relationships

    def _load_json(self, blob):
        try:
            return json.loads(blob)
        except Exception:
            self.log.warning('Output not JSON, use a different parser')
            return None

    def _get_vals_from_json(self, json_output, key, json_type):
        """
        Get all values for a specified key recursively from JSON output.
        :param json_output:
        :param key:
        :param json_type: a list of types to filter matches by. Example options are - 'str', 'int', 'list', 'dict'
        :return: generator that yields matched values
        """
        if isinstance(json_output, list):
            for item in json_output:
                for res in self._get_vals_from_json(item, key, json_type):
                    yield res
        elif isinstance(json_output, dict):
            for k, v in json_output.items():
                if k == key and (json_type is None
                                 or v.__class__.__name__ in json_type):
                    yield json.dumps(v) if v.__class__.__name__ in [
                        'list', 'dict'
                    ] else v
                if isinstance(v, list) or isinstance(v, dict):
                    for res in self._get_vals_from_json(v, key, json_type):
                        yield res
Ejemplo n.º 2
0
class Parser(BaseParser):

    def __init__(self, parser_info):
        self.mappers = parser_info['mappers']
        self.used_facts = parser_info['used_facts']
        self.log = Logger('Parser')

    def gd_parser(self, text):
        results = dict()

        for block in text.split('\r\n\r\n'):
            if block:
                hostname = None
                pvi = None
                for line in block.splitlines():
                    hostname = self._parse_hostname(line, hostname)
                    pvi = self._parse_version(line, pvi)
                    if line.startswith('Exception') and '(0x80005000)' in line:
                        # Domain communication error
                        self.log.warning('Get-Domain parser: Domain Issue 0x80005000: Verify that the rat is running '
                                         'under a Domain Account, and that the Domain Controller can be reached.')
                if hostname and pvi:
                    results[hostname] = dict(parsed_version_info=pvi)
        if not results:
            self.log.warning('Get-Domain Parser: Returned data contained no parseable information!')
        return results

    def parse(self, blob):
        relationships = []
        try:
            parse_data = self.gd_parser(blob)
            for match in parse_data:
                for mp in self.mappers:
                    relationships.append(
                        Relationship(source=(mp.source, match),
                                     edge=mp.edge,
                                     target=(mp.target, None)))
        except Exception as error:
            self.log.warning('Get-Domain parser encountered an error - {}. Continuing...'.format(error))
        return relationships

    '''    PRIVATE FUNCTION     '''

    @staticmethod
    def _parse_hostname(line, current):
        if line.startswith('dnshostname'):
            field_name, value = [c.strip() for c in line.split(':')]
            return value.lower()
        return current

    @staticmethod
    def _parse_version(line, current):
        if line.startswith('operatingsystemversion'):
            value = line.split(':')[-1].strip()  # Looks like: '10.0 (14393)'
            os_version, build_number = value.split(' ')
            build_number = build_number[1:-1]  # remove parens
            major_version, minor_version = os_version.split('.')
            return dict(os_name='windows', major_version=major_version,
                        minor_version=minor_version, build_number=build_number)
        return current
Ejemplo n.º 3
0
class Parser(BaseParser):

    def __init__(self, parser_info):
        self.mappers = parser_info['mappers']
        self.used_facts = parser_info['used_facts']
        self.parse_mode = 'wdigest'
        self.log = Logger('parsing_svc')

    def parse_katz(self, output):
        """
        Parses mimikatz output with the logonpasswords command and returns a list of dicts of the credentials.
        Args:
            output: stdout of "mimikatz.exe privilege::debug sekurlsa::logonpasswords exit"
        Returns:
            A list of MimikatzSection objects
        """
        sections = output.split('Authentication Id')  # split sections using "Authentication Id" as separator
        creds = []
        for section in sections:
            mk_section = MimikatzBlock()
            package = {}
            package_name = ''
            in_header = True
            pstate = False
            for line in section.splitlines():
                line = line.strip()
                if in_header:
                    in_header = self._parse_header(line, mk_section)
                    if in_header:
                        continue  # avoid excess parsing work
                pstate, package_name = self._process_package(line, package, package_name, mk_section)
                if pstate:
                    pstate = False
                    package = {}
            self._package_extend(package, package_name, mk_section)  # save the current ssp if necessary
            if mk_section.packages:  # save this section
                creds.append(mk_section)
        return creds

    def parse(self, blob):
        relationships = []
        try:
            parse_data = self.parse_katz(blob)
            for match in parse_data:
                if self.parse_mode in match.packages:
                    for mp in self.mappers:
                        relationships.append(
                            Relationship(source=(mp.source, match.packages[self.parse_mode][0]['Username']),
                                         edge=mp.edge,
                                         target=(mp.target, match.packages[self.parse_mode][0]['Password']))
                        )
        except Exception as error:
            self.log.warning('Mimikatz parser encountered an error - {}. Continuing...'.format(error))
        return relationships

    """    PRIVATE FUNCTION     """

    @staticmethod
    def _parse_header(line, mk_section):
        if line.startswith('msv'):
            return False
        session = re.match(r'^\s*Session\s*:\s*([^\r\n]*)', line)
        if session:
            mk_section.session = session.group(1)
        username = re.match(r'^\s*User Name\s*:\s*([^\r\n]*)', line)
        if username:
            mk_section.username = username.group(1)
        domain = re.match(r'^\s*Domain\s*:\s*([^\r\n]*)', line)
        if domain:
            mk_section.domain = domain.group(1)
        logon_server = re.match(r'^\s*Logon Server\s*:\s*([^\r\n]*)', line)
        if logon_server:
            mk_section.logon_server = logon_server.group(1)
        logon_time = re.match(r'^\s*Logon Time\s*:\s*([^\r\n]*)', line)
        if logon_time:
            mk_section.logon_time = logon_time.group(1)
        sid = re.match(r'^\s*SID\s*:\s*([^\r\n]*)', line)
        if sid:
            mk_section.sid = sid.group(1)
        return True

    def _process_package(self, line, package, package_name, mk_section):
        if line.startswith('['):
            self._package_extend(package, package_name, mk_section)  # this might indicate the start of a new account
            return True, package_name  # reset the package
        elif line.startswith('*'):
            m = re.match(r'\s*\* (.*?)\s*: (.*)', line)
            if m:
                package[m.group(1)] = m.group(2)
        elif line:
            match_group = re.match(r'([a-z]+) :', line)  # parse out the new section name
            if match_group:  # this is the start of a new ssp
                self._package_extend(package, package_name, mk_section)  # save the current ssp if necessary
                return True, match_group.group(1)  # reset the package
        return False, package_name

    @staticmethod
    def _package_extend(package, package_name, mk_section):
        if 'Username' in package and package['Username'] != '(null)' and \
                (('Password' in package and package['Password'] != '(null)') or 'NTLM' in package):
            mk_section.packages[package_name].append(package)