Example #1
0
    def _generator(self, syshive, sechive):
        bootkey = hashdump.Hashdump.get_bootkey(syshive)
        if not bootkey:
            raise ValueError('Unable to find bootkey')

        is_vista_or_later = poolscanner.os_distinguisher(
            version_check=lambda x: x >= (6, 0),
            fallback_checks=[("KdCopyDataBlock", None, True)])
        vista_or_later = is_vista_or_later(
            context=self.context, symbol_table=self.config['nt_symbols'])

        lsakey = lsadump.Lsadump.get_lsa_key(sechive, bootkey, vista_or_later)
        if not lsakey:
            raise ValueError('Unable to find lsa key')

        nlkm = self.get_nlkm(sechive, lsakey, vista_or_later)
        if not nlkm:
            raise ValueError('Unable to find nlkma key')

        cache = sechive.get_key("Cache")
        if not cache:
            raise ValueError('Unable to find cache key')

        for cache_item in cache.get_values():
            if cache_item.Name == "NL$Control":
                continue

            data = sechive.read(cache_item.Data + 4, cache_item.DataLength)
            if data == None:
                continue
            (uname_len, domain_len, domain_name_len, enc_data,
             ch) = self.parse_cache_entry(data)
            # Skip if nothing in this cache entry
            if uname_len == 0 or len(ch) == 0:
                continue
            dec_data = self.decrypt_hash(enc_data, nlkm, ch,
                                         not vista_or_later)

            (username, domain, domain_name,
             hashh) = self.parse_decrypted_cache(dec_data, uname_len,
                                                 domain_len, domain_name_len)
            yield (0, (username, domain, domain_name, hashh))
Example #2
0
    def _generator(self, syshive, sechive):

        is_vista_or_later = poolscanner.os_distinguisher(version_check = lambda x: x >= (6, 0),
                                                         fallback_checks = [("KdCopyDataBlock", None, True)])
        vista_or_later = is_vista_or_later(context = self.context, symbol_table = self.config['nt_symbols'])

        bootkey = hashdump.Hashdump.get_bootkey(syshive)
        lsakey = self.get_lsa_key(sechive, bootkey, vista_or_later)
        if not bootkey:
            raise ValueError('Unable to find bootkey')

        if not lsakey:
            raise ValueError('Unable to find lsa key')

        secrets_key = sechive.get_key('Policy\\Secrets')
        if not secrets_key:
            raise ValueError('Unable to find secrets key')

        for key in secrets_key.get_subkeys():

            sec_val_key = sechive.get_key('Policy\\Secrets\\' + key.get_key_path().split('\\')[3] + '\\CurrVal')
            if not sec_val_key:
                continue

            enc_secret_value = next(sec_val_key.get_values())
            if not enc_secret_value:
                continue

            enc_secret = sechive.read(enc_secret_value.Data + 4,
                                      enc_secret_value.DataLength)
            if not enc_secret:
                continue
            if not vista_or_later:
                secret = self.decrypt_secret(enc_secret[0xC:], lsakey)
            else:
                secret = self.decrypt_aes(enc_secret, lsakey)

            yield (0, (key.get_name(), secret.decode('latin1'), secret))
Example #3
0
    def _generator(self, procs):

        conhost_pids = []
        appinfo_service_pid = 0
        suspicious_procs = {}

        # For unknown reasons, the owner process id will point to the actual parent id + 2 with the exception of conhost which is +1.
        # OWNER_PROCESS_ID_OFFSET will be used to get the real parent PID from the OwnerProcessId field.
        OWNER_PROCESS_ID_OFFSET = -2
        # CONHOST_PROCESS_ID_OFFSET will be used to check if OwnerProcessId actually points to a console host process.
        CONHOST_PROCESS_ID_OFFSET = -1

        # OwnerProcessId only exists as a union from Windows 8 or later.
        is_win8_or_later = poolscanner.os_distinguisher(
            version_check=lambda x: x >= (6, 2),
            fallback_checks=[("_EPROCESS", "OwnerProcessId", True)])
        if not is_win8_or_later(self.context, self.config["nt_symbols"]):
            vollog.warning(
                "check_parent_spoof doesn't work in Windows versions prior to Windows 8"
            )
            return

        for proc in procs:
            if not proc.has_member("OwnerProcessId"):
                continue
            process_name = utility.array_to_string(proc.ImageFileName)
            inherited_process_pid = proc.InheritedFromUniqueProcessId

            # Save appinfo's service pid to exclude, this is hosted only by svchost.exe.
            if (appinfo_service_pid == 0) and ("svchost" in process_name):
                for entry in proc.load_order_modules():
                    dll_name = renderers.UnreadableValue()
                    try:
                        dll_name = entry.FullDllName.get_string()
                        if "appinfo.dll" not in dll_name:
                            continue

                    except exceptions.InvalidAddressException:
                        continue

                    appinfo_service_pid = proc.UniqueProcessId

                    # If a process was already flagged as suspicious and it's actually pointing to the appinfo service, it should not be flagged.
                    if proc.UniqueProcessId in suspicious_procs:
                        suspicious_procs.pop(proc.UniqueProcessId)

            # The Owner process id field is used a union for the owner process id and console host process id
            # We save the conhost process id's in order to exclude them from the check.
            if "conhost" in process_name:
                conhost_pids.append(proc.UniqueProcessId)

                # If a process was already flagged as suspicious and it's actually pointing to a conhost process, it should not be flagged.
                # Since suspicious_procs saves everything at OWNER_PROCESS_ID_OFFSET we correct the offset check here by adding the conhost offset.
                conhost_pid = proc.UniqueProcessId + CONHOST_PROCESS_ID_OFFSET
                if conhost_pid in suspicious_procs:
                    suspicious_procs.pop(conhost_pid)

            try:
                owner_process_pid = proc.OwnerProcessId

                # There are several checks here:
                # Most services and system processes are initialized with 0, exclude them and System by checking if the pid is under 0.
                # AppInfo service is responsible for UAC and could be triggered as a false positive as well.
                if (owner_process_pid < 10) or (owner_process_pid + OWNER_PROCESS_ID_OFFSET == inherited_process_pid) \
                        or (owner_process_pid + OWNER_PROCESS_ID_OFFSET == appinfo_service_pid) or (owner_process_pid + CONHOST_PROCESS_ID_OFFSET) in conhost_pids:
                    continue

            except Exception as e:
                continue

            suspicious_procs[owner_process_pid + OWNER_PROCESS_ID_OFFSET] = (
                proc.UniqueProcessId, process_name, inherited_process_pid,
                owner_process_pid + OWNER_PROCESS_ID_OFFSET)

        for owner_process_id, suspicious_proc in suspicious_procs.items():
            yield (0, (suspicious_proc[0], suspicious_proc[1],
                       suspicious_proc[2], suspicious_proc[3]))
Example #4
0
class SvcScan(interfaces.plugins.PluginInterface):
    """Scans for windows services."""

    _version = (1, 0, 0)

    is_vista_or_later = poolscanner.os_distinguisher(
        version_check=lambda x: x >= (6, 0),
        fallback_checks=[("KdCopyDataBlock", None, True)])

    is_windows_xp = poolscanner.os_distinguisher(
        version_check=lambda x: (5, 1) <= x < (5, 2),
        fallback_checks=[("KdCopyDataBlock", None, False),
                         ("_HANDLE_TABLE", "HandleCount", True)])

    is_xp_or_2003 = poolscanner.os_distinguisher(
        version_check=lambda x: (5, 1) <= x < (6, 0),
        fallback_checks=[("KdCopyDataBlock", None, False),
                         ("_HANDLE_TABLE", "HandleCount", True)])

    is_win10_up_to_15063 = poolscanner.os_distinguisher(
        version_check=lambda x: (10, 0) <= x < (10, 0, 16299),
        fallback_checks=[("ObHeaderCookie", None, True),
                         ("_HANDLE_TABLE", "HandleCount", False),
                         ("ObHeaderCookie", None, True)])

    is_win10_16299_or_later = poolscanner.os_distinguisher(
        version_check=lambda x: x >= (10, 0, 16299),
        fallback_checks=[("ObHeaderCookie", None, True),
                         ("_HANDLE_TABLE", "HandleCount", False),
                         ("ObHeaderCookie", None, True)])

    @classmethod
    def get_requirements(
            cls) -> List[interfaces.configuration.RequirementInterface]:
        # Since we're calling the plugin, make sure we have the plugin's requirements
        return [
            requirements.TranslationLayerRequirement(
                name='primary',
                description='Memory layer for the kernel',
                architectures=["Intel32", "Intel64"]),
            requirements.SymbolTableRequirement(
                name="nt_symbols", description="Windows kernel symbols"),
            requirements.PluginRequirement(name='pslist',
                                           plugin=pslist.PsList,
                                           version=(1, 0, 0)),
            requirements.PluginRequirement(name='poolscanner',
                                           plugin=poolscanner.PoolScanner,
                                           version=(1, 0, 0)),
            requirements.PluginRequirement(name='vadyarascan',
                                           plugin=vadyarascan.VadYaraScan,
                                           version=(1, 0, 0))
        ]

    @staticmethod
    def get_record_tuple(service_record: interfaces.objects.ObjectInterface):
        return (format_hints.Hex(service_record.vol.offset),
                service_record.Order, service_record.get_pid(),
                service_record.Start.description,
                service_record.State.description, service_record.get_type(),
                service_record.get_name(), service_record.get_display(),
                service_record.get_binary())

    @staticmethod
    def create_service_table(context: interfaces.context.ContextInterface,
                             symbol_table: str, config_path: str) -> str:
        """Constructs a symbol table containing the symbols for services
        depending upon the operating system in use.

        Args:
            context: The context to retrieve required elements (layers, symbol tables) from
            symbol_table: The name of the table containing the kernel symbols
            config_path: The configuration path for any settings required by the new table

        Returns:
            A symbol table containing the symbols necessary for services
        """
        native_types = context.symbol_space[symbol_table].natives
        is_64bit = symbols.symbol_table_is_64bit(context, symbol_table)

        if SvcScan.is_windows_xp(context=context,
                                 symbol_table=symbol_table) and not is_64bit:
            symbol_filename = "services-xp-x86"
        elif SvcScan.is_xp_or_2003(context=context,
                                   symbol_table=symbol_table) and is_64bit:
            symbol_filename = "services-xp-2003-x64"
        elif poolscanner.PoolScanner.is_windows_8_or_later(
                context=context, symbol_table=symbol_table) and is_64bit:
            symbol_filename = "services-win8-x64"
        elif poolscanner.PoolScanner.is_windows_8_or_later(
                context=context, symbol_table=symbol_table) and not is_64bit:
            symbol_filename = "services-win8-x86"
        elif SvcScan.is_win10_up_to_15063(
                context=context, symbol_table=symbol_table) and is_64bit:
            symbol_filename = "services-win10-15063-x64"
        elif SvcScan.is_win10_up_to_15063(
                context=context, symbol_table=symbol_table) and not is_64bit:
            symbol_filename = "services-win10-15063-x86"
        elif SvcScan.is_win10_16299_or_later(
                context=context, symbol_table=symbol_table) and is_64bit:
            symbol_filename = "services-win10-16299-x64"
        elif SvcScan.is_win10_16299_or_later(
                context=context, symbol_table=symbol_table) and not is_64bit:
            symbol_filename = "services-win10-16299-x86"
        elif SvcScan.is_vista_or_later(context=context,
                                       symbol_table=symbol_table) and is_64bit:
            symbol_filename = "services-vista-x64"
        elif SvcScan.is_vista_or_later(
                context=context, symbol_table=symbol_table) and not is_64bit:
            symbol_filename = "services-vista-x86"
        else:
            raise NotImplementedError(
                "This version of Windows is not supported!")

        return intermed.IntermediateSymbolTable.create(
            context,
            config_path,
            "windows",
            symbol_filename,
            class_types=services.class_types,
            native_types=native_types)

    def _generator(self):

        service_table_name = self.create_service_table(
            self.context, self.config["nt_symbols"], self.config_path)

        relative_tag_offset = self.context.symbol_space.get_type(
            service_table_name + constants.BANG +
            "_SERVICE_RECORD").relative_child_offset("Tag")

        filter_func = pslist.PsList.create_name_filter(["services.exe"])

        is_vista_or_later = SvcScan.is_vista_or_later(
            context=self.context, symbol_table=self.config["nt_symbols"])

        if is_vista_or_later:
            service_tag = b"serH"
        else:
            service_tag = b"sErv"

        seen = []

        for task in pslist.PsList.list_processes(
                context=self.context,
                layer_name=self.config['primary'],
                symbol_table=self.config['nt_symbols'],
                filter_func=filter_func):

            try:
                proc_layer_name = task.add_process_layer()
            except exceptions.InvalidAddressException:
                continue

            layer = self.context.layers[proc_layer_name]

            for offset in layer.scan(
                    context=self.context,
                    scanner=scanners.BytesScanner(needle=service_tag),
                    sections=vadyarascan.VadYaraScan.get_vad_maps(task)):

                if not is_vista_or_later:
                    service_record = self.context.object(
                        service_table_name + constants.BANG +
                        "_SERVICE_RECORD",
                        offset=offset - relative_tag_offset,
                        layer_name=proc_layer_name)

                    if not service_record.is_valid():
                        continue

                    yield (0, self.get_record_tuple(service_record))
                else:
                    service_header = self.context.object(
                        service_table_name + constants.BANG +
                        "_SERVICE_HEADER",
                        offset=offset,
                        layer_name=proc_layer_name)

                    if not service_header.is_valid():
                        continue

                    # since we walk the s-list backwards, if we've seen
                    # an object, then we've also seen all objects that
                    # exist before it, thus we can break at that time.
                    for service_record in service_header.ServiceRecord.traverse(
                    ):
                        if service_record in seen:
                            break
                        seen.append(service_record)
                        yield (0, self.get_record_tuple(service_record))

    def run(self):
        return renderers.TreeGrid([
            ('Offset', format_hints.Hex),
            ('Order', int),
            ('Pid', int),
            ('Start', str),
            ('State', str),
            ('Type', str),
            ('Name', str),
            ('Display', str),
            ('Binary', str),
        ], self._generator())
Example #5
0
class BigPools(interfaces.plugins.PluginInterface):
    """List big page pools."""

    _version = (1, 0, 0)

    is_vista_or_later = poolscanner.os_distinguisher(
        version_check=lambda x: x >= (6, 0),
        fallback_checks=[("KdCopyDataBlock", None, True)])

    is_win10 = poolscanner.os_distinguisher(version_check=lambda x:
                                            (10, 0) <= x,
                                            fallback_checks=[
                                                ("ObHeaderCookie", None, True),
                                                ("_HANDLE_TABLE",
                                                 "HandleCount", False)
                                            ])

    @classmethod
    def get_requirements(
            cls) -> List[interfaces.configuration.RequirementInterface]:
        # Since we're calling the plugin, make sure we have the plugin's requirements
        return [
            requirements.TranslationLayerRequirement(
                name='primary',
                description='Memory layer for the kernel',
                architectures=["Intel32", "Intel64"]),
            requirements.SymbolTableRequirement(
                name="nt_symbols", description="Windows kernel symbols"),
            requirements.StringRequirement(
                name='tags',
                description=
                "Comma separated list of pool tags to filter pools returned",
                optional=True,
                default=None)
        ]

    @classmethod
    def list_big_pools(cls,
                       context: interfaces.context.ContextInterface,
                       layer_name: str,
                       symbol_table: str,
                       tags: Optional[list] = None):
        """Returns the big page pool objects from the kernel PoolBigPageTable array.

        Args:
            context: The context to retrieve required elements (layers, symbol tables) from
            layer_name: The name of the layer on which to operate
            symbol_table: The name of the table containing the kernel symbols
            tags: An optional list of pool tags to filter big page pool tags by

        Yields:
            A big page pool object
        """
        kvo = context.layers[layer_name].config['kernel_virtual_offset']
        ntkrnlmp = context.module(symbol_table,
                                  layer_name=layer_name,
                                  offset=kvo)

        big_page_table_offset = ntkrnlmp.get_symbol("PoolBigPageTable").address
        big_page_table = ntkrnlmp.object(object_type="unsigned long long",
                                         offset=big_page_table_offset)

        big_page_table_size_offset = ntkrnlmp.get_symbol(
            "PoolBigPageTableSize").address
        big_page_table_size = ntkrnlmp.object(
            object_type="unsigned long", offset=big_page_table_size_offset)

        try:
            big_page_table_type = ntkrnlmp.get_type("_POOL_TRACKER_BIG_PAGES")
        except exceptions.SymbolError:
            # We have to manually load a symbol table
            is_vista_or_later = cls.is_vista_or_later(context, symbol_table)
            is_win10 = cls.is_win10(context, symbol_table)
            if is_win10:
                big_pools_json_filename = "bigpools-win10"
            elif is_vista_or_later:
                big_pools_json_filename = "bigpools-vista"
            else:
                big_pools_json_filename = "bigpools"

            if symbols.symbol_table_is_64bit(context, symbol_table):
                big_pools_json_filename += "-x64"
            else:
                big_pools_json_filename += "-x86"

            new_table_name = intermed.IntermediateSymbolTable.create(
                context=context,
                config_path=configuration.path_join(
                    context.symbol_space[symbol_table].config_path,
                    "bigpools"),
                sub_path="windows",
                filename=big_pools_json_filename,
                table_mapping={'nt_symbols': symbol_table},
                class_types={
                    '_POOL_TRACKER_BIG_PAGES':
                    extensions.pool.POOL_TRACKER_BIG_PAGES
                })
            module = context.module(new_table_name, layer_name, offset=0)
            big_page_table_type = module.get_type("_POOL_TRACKER_BIG_PAGES")

        big_pools = ntkrnlmp.object(object_type="array",
                                    offset=big_page_table,
                                    subtype=big_page_table_type,
                                    count=big_page_table_size,
                                    absolute=True)

        for big_pool in big_pools:
            if big_pool.is_valid():
                if tags is None or big_pool.get_key() in tags:
                    yield big_pool

    def _generator(
            self) -> Iterator[Tuple[int, Tuple[int, str]]]:  #, str, int]]]:
        if self.config.get("tags"):
            tags = [tag for tag in self.config["tags"].split(',')]
        else:
            tags = None

        for big_pool in self.list_big_pools(
                context=self.context,
                layer_name=self.config["primary"],
                symbol_table=self.config["nt_symbols"],
                tags=tags):

            num_bytes = big_pool.get_number_of_bytes()
            if not isinstance(num_bytes, interfaces.renderers.BaseAbsentValue):
                num_bytes = format_hints.Hex(num_bytes)

            yield (0, (format_hints.Hex(big_pool.Va), big_pool.get_key(),
                       big_pool.get_pool_type(), num_bytes))

    def run(self):
        return renderers.TreeGrid([
            ('Allocation', format_hints.Hex),
            ('Tag', str),
            ('PoolType', str),
            ('NumberOfBytes', format_hints.Hex),
        ], self._generator())
Example #6
0
class HiveScan(interfaces.plugins.PluginInterface):
    """Scans for registry hives present in a particular windows memory
    image."""

    _version = (1, 0, 0)

    is_windows_8_1_or_later = poolscanner.os_distinguisher(
        version_check=lambda x: x >= (6, 3),
        fallback_checks=[("_KPRCB", "PendingTickFlags", True)])

    @classmethod
    def get_requirements(cls):
        return [
            requirements.TranslationLayerRequirement(
                name='primary',
                description='Memory layer for the kernel',
                architectures=["Intel32", "Intel64"]),
            requirements.SymbolTableRequirement(
                name="nt_symbols", description="Windows kernel symbols"),
            requirements.PluginRequirement(name='poolscanner',
                                           plugin=poolscanner.PoolScanner,
                                           version=(1, 0, 0)),
            requirements.PluginRequirement(name='bigpools',
                                           plugin=bigpools.BigPools,
                                           version=(1, 0, 0)),
        ]

    @classmethod
    def scan_hives(cls,
                   context: interfaces.context.ContextInterface,
                   layer_name: str,
                   symbol_table: str) -> \
            Iterable[interfaces.objects.ObjectInterface]:
        """Scans for hives using the poolscanner module and constraints or bigpools module with tag.

        Args:
            context: The context to retrieve required elements (layers, symbol tables) from
            layer_name: The name of the layer on which to operate
            symbol_table: The name of the table containing the kernel symbols

        Returns:
            A list of Hive objects as found from the `layer_name` layer based on Hive pool signatures
        """

        is_64bit = symbols.symbol_table_is_64bit(context, symbol_table)
        is_windows_8_1_or_later = HiveScan.is_windows_8_1_or_later(
            context=context, symbol_table=symbol_table)

        if is_windows_8_1_or_later and is_64bit:
            kvo = context.layers[layer_name].config['kernel_virtual_offset']
            ntkrnlmp = context.module(symbol_table,
                                      layer_name=layer_name,
                                      offset=kvo)

            for pool in bigpools.BigPools.list_big_pools(
                    context,
                    layer_name=layer_name,
                    symbol_table=symbol_table,
                    tags=["CM10"]):
                cmhive = ntkrnlmp.object(object_type="_CMHIVE",
                                         offset=pool.Va,
                                         absolute=True)
                yield cmhive

        else:
            constraints = poolscanner.PoolScanner.builtin_constraints(
                symbol_table, [b'CM10'])

            for result in poolscanner.PoolScanner.generate_pool_scan(
                    context, layer_name, symbol_table, constraints):
                _constraint, mem_object, _header = result
                yield mem_object

    def _generator(self):
        for hive in self.scan_hives(self.context, self.config['primary'],
                                    self.config['nt_symbols']):

            yield (0, (format_hints.Hex(hive.vol.offset), ))

    def run(self):
        return renderers.TreeGrid([("Offset", format_hints.Hex)],
                                  self._generator())