Exemple #1
0
    def virtual_process_from_physical(cls,
                                      context: interfaces.context.ContextInterface,
                                      layer_name: str,
                                      symbol_table: str,
                                      proc: interfaces.objects.ObjectInterface) -> \
            Iterable[interfaces.objects.ObjectInterface]:
        """ Returns a virtual process from a physical addressed one

        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
            proc: the process object with phisical address

        Returns:
            A process object on virtual address layer

        """

        # We'll use the first thread to bounce back to the virtual process
        kvo = context.layers[layer_name].config['kernel_virtual_offset']
        ntkrnlmp = context.module(symbol_table,
                                  layer_name=layer_name,
                                  offset=kvo)

        tleoffset = ntkrnlmp.get_type("_ETHREAD").relative_child_offset(
            "ThreadListEntry")
        # Start out with the member offset
        offsets = [tleoffset]

        # If (and only if) we're dealing with 64-bit Windows 7 SP1
        # then add the other commonly seen member offset to the list
        kuser = info.Info.get_kuser_structure(context, layer_name,
                                              symbol_table)
        nt_major_version = int(kuser.NtMajorVersion)
        nt_minor_version = int(kuser.NtMinorVersion)
        vers = info.Info.get_version_structure(context, layer_name,
                                               symbol_table)
        build = vers.MinorVersion
        bits = context.layers[layer_name].bits_per_register
        version = (nt_major_version, nt_minor_version, build)
        if version == (6, 1, 7601) and bits == 64:
            offsets.append(tleoffset + 8)

        # Now we can try to bounce back
        for ofs in offsets:
            ethread = ntkrnlmp.object(object_type="_ETHREAD",
                                      offset=proc.ThreadListHead.Flink - ofs,
                                      absolute=True)

            # Ask for the thread's process to get an _EPROCESS with a virtual address layer
            virtual_process = ethread.owning_process()
            # Sanity check the bounce.
            # This compares the original offset with the new one (translated from virtual layer)
            (_, _, ph_offset, _, _) = list(context.layers[layer_name].mapping(
                offset=virtual_process.vol.offset, length=0))[0]
            if virtual_process and \
                    proc.vol.offset == ph_offset:
                return virtual_process
Exemple #2
0
    def get_type_map(cls, context: interfaces.context.ContextInterface,
                     layer_name: str, symbol_table: str) -> Dict[int, str]:
        """List the executive object types (_OBJECT_TYPE) using the
        ObTypeIndexTable or ObpObjectTypes symbol (differs per OS). This method
        will be necessary for determining what type of object we have given an
        object header.

        Note:
            The object type index map was hard coded into profiles in previous versions of volatility.
            It is now generated dynamically.

        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 mapping of type indicies to type names
        """

        type_map = {}

        kvo = context.layers[layer_name].config['kernel_virtual_offset']
        ntkrnlmp = context.module(symbol_table,
                                  layer_name=layer_name,
                                  offset=kvo)

        try:
            table_addr = ntkrnlmp.get_symbol("ObTypeIndexTable").address
        except exceptions.SymbolError:
            table_addr = ntkrnlmp.get_symbol("ObpObjectTypes").address

        ptrs = ntkrnlmp.object(object_type="array",
                               offset=table_addr,
                               subtype=ntkrnlmp.get_type("pointer"),
                               count=100)

        for i, ptr in enumerate(ptrs):  # type: ignore
            # the first entry in the table is always null. break the
            # loop when we encounter the first null entry after that
            if i > 0 and ptr == 0:
                break
            objt = ptr.dereference().cast(symbol_table + constants.BANG +
                                          "_OBJECT_TYPE")

            try:
                type_name = objt.Name.String
            except exceptions.InvalidAddressException:
                vollog.log(
                    constants.LOGLEVEL_VVV,
                    "Cannot access _OBJECT_HEADER.Name at {0:#x}".format(
                        objt.Name.vol.offset))
                continue

            type_map[i] = type_name

        return type_map
Exemple #3
0
    def list_notify_routines(
            cls, context: interfaces.context.ContextInterface, layer_name: str,
            symbol_table: str, callback_table_name: str
    ) -> Iterable[Tuple[str, int, Optional[str]]]:
        """Lists all kernel notification routines.

        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
            callback_table_name: The nae of the table containing the callback symbols

        Yields:
            A name, location and optional detail string
        """

        kvo = context.layers[layer_name].config['kernel_virtual_offset']
        ntkrnlmp = context.module(symbol_table,
                                  layer_name=layer_name,
                                  offset=kvo)

        is_vista_or_later = versions.is_vista_or_later(
            context=context, symbol_table=symbol_table)
        full_type_name = callback_table_name + constants.BANG + "_GENERIC_CALLBACK"

        symbol_names = [("PspLoadImageNotifyRoutine", False),
                        ("PspCreateThreadNotifyRoutine", True),
                        ("PspCreateProcessNotifyRoutine", True)]

        for symbol_name, extended_list in symbol_names:

            try:
                symbol_offset = ntkrnlmp.get_symbol(symbol_name).address
            except exceptions.SymbolError:
                vollog.debug("Cannot find {}".format(symbol_name))
                continue

            if is_vista_or_later and extended_list:
                count = 64
            else:
                count = 8

            fast_refs = ntkrnlmp.object(
                object_type="array",
                offset=symbol_offset,
                subtype=ntkrnlmp.get_type("_EX_FAST_REF"),
                count=count)

            for fast_ref in fast_refs:
                try:
                    callback = fast_ref.dereference().cast(full_type_name)
                except exceptions.InvalidAddressException:
                    continue

                if callback.Callback != 0:
                    yield symbol_name, callback.Callback, None
Exemple #4
0
    def get_kernel_module(cls, context: interfaces.context.ContextInterface, layer_name: str, symbol_table: str):
        """Returns the kernel module based on the layer and symbol_table"""
        virtual_layer = context.layers[layer_name]
        if not isinstance(virtual_layer, layers.intel.Intel):
            raise TypeError("Virtual Layer is not an intel layer")

        kvo = virtual_layer.config["kernel_virtual_offset"]

        ntkrnlmp = context.module(symbol_table, layer_name = layer_name, offset = kvo)
        return ntkrnlmp
Exemple #5
0
    def find_aslr(cls,
                  context: interfaces.context.ContextInterface,
                  symbol_table: str,
                  layer_name: str,
                  progress_callback: constants.ProgressCallback = None) \
            -> Tuple[int, int]:
        """Determines the offset of the actual DTB in physical space and its
        symbol offset."""
        init_task_symbol = symbol_table + constants.BANG + 'init_task'
        init_task_json_address = context.symbol_space.get_symbol(
            init_task_symbol).address
        swapper_signature = rb"swapper(\/0|\x00\x00)\x00\x00\x00\x00\x00\x00"
        module = context.module(symbol_table, layer_name, 0)
        address_mask = context.symbol_space[symbol_table].config.get(
            'symbol_mask', None)

        task_symbol = module.get_type('task_struct')
        comm_child_offset = task_symbol.relative_child_offset('comm')

        for offset in context.layers[layer_name].scan(
                scanner=scanners.RegExScanner(swapper_signature),
                context=context,
                progress_callback=progress_callback):
            init_task_address = offset - comm_child_offset
            init_task = module.object(object_type='task_struct',
                                      offset=init_task_address,
                                      absolute=True)
            if init_task.pid != 0:
                continue
            elif init_task.has_member(
                    'state') and init_task.state.cast('unsigned int') != 0:
                continue

            # This we get for free
            aslr_shift = init_task.files.cast(
                'long unsigned int') - module.get_symbol('init_files').address
            kaslr_shift = init_task_address - cls.virtual_to_physical_address(
                init_task_json_address)
            if address_mask:
                aslr_shift = aslr_shift & address_mask

            if aslr_shift & 0xfff != 0 or kaslr_shift & 0xfff != 0:
                continue
            vollog.debug(
                "Linux ASLR shift values determined: physical {:0x} virtual {:0x}"
                .format(kaslr_shift, aslr_shift))
            return kaslr_shift, aslr_shift

        # We don't throw an exception, because we may legitimately not have an ASLR shift, but we report it
        vollog.debug(
            "Scanners could not determine any ASLR shifts, using 0 for both")
        return 0, 0
Exemple #6
0
    def list_bugcheck_callbacks(
            cls, context: interfaces.context.ContextInterface, layer_name: str,
            symbol_table: str,
            callback_table_name: str) -> Iterable[Tuple[str, int, str]]:
        """Lists all kernel bugcheck callbacks.

        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
            callback_table_name: The nae of the table containing the callback symbols

        Yields:
            A name, location and optional detail string
        """

        kvo = context.layers[layer_name].config['kernel_virtual_offset']
        ntkrnlmp = context.module(symbol_table,
                                  layer_name=layer_name,
                                  offset=kvo)

        try:
            list_offset = ntkrnlmp.get_symbol(
                "KeBugCheckCallbackListHead").address
        except exceptions.SymbolError:
            vollog.debug("Cannot find KeBugCheckCallbackListHead")
            return

        full_type_name = callback_table_name + constants.BANG + "_KBUGCHECK_CALLBACK_RECORD"
        callback_record = context.object(full_type_name,
                                         offset=kvo + list_offset,
                                         layer_name=layer_name)

        for callback in callback_record.Entry:

            try:
                context.layers[layer_name].is_valid(callback.CallbackRoutine)
            except exceptions.InvalidAddressException:
                continue

            try:
                component = context.object(symbol_table + constants.BANG +
                                           "string",
                                           layer_name=layer_name,
                                           offset=callback.Component,
                                           max_length=64,
                                           errors="replace")
            except exceptions.InvalidAddressException:
                component = renderers.UnreadableValue()

            yield "KeBugCheckCallbackListHead", callback.CallbackRoutine, component
Exemple #7
0
    def list_processes(cls,
                       context: interfaces.context.ContextInterface,
                       layer_name: str,
                       symbol_table: str,
                       filter_func: Callable[[interfaces.objects.ObjectInterface], bool] = lambda _: False) -> \
            Iterable[interfaces.objects.ObjectInterface]:
        """Lists all the processes in the primary layer that are in the pid
        config option.

        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
            filter_func: A function which takes an EPROCESS object and returns True if the process should be ignored/filtered

        Returns:
            The list of EPROCESS objects from the `layer_name` layer's PsActiveProcessHead list after filtering
        """

        # We only use the object factory to demonstrate how to use one
        kvo = context.layers[layer_name].config['kernel_virtual_offset']
        ntkrnlmp = context.module(symbol_table,
                                  layer_name=layer_name,
                                  offset=kvo)

        ps_aph_offset = ntkrnlmp.get_symbol("PsActiveProcessHead").address
        list_entry = ntkrnlmp.object(object_type="_LIST_ENTRY",
                                     offset=ps_aph_offset)

        # This is example code to demonstrate how to use symbol_space directly, rather than through a module:
        #
        # ```
        # reloff = self.context.symbol_space.get_type(
        #          self.config['nt_symbols'] + constants.BANG + "_EPROCESS").relative_child_offset(
        #          "ActiveProcessLinks")
        # ```
        #
        # Note: "nt_symbols!_EPROCESS" could have been used, but would rely on the "nt_symbols" symbol table not already
        # having been present.  Strictly, the value of the requirement should be joined with the BANG character
        # defined in the constants file
        reloff = ntkrnlmp.get_type("_EPROCESS").relative_child_offset(
            "ActiveProcessLinks")
        eproc = ntkrnlmp.object(object_type="_EPROCESS",
                                offset=list_entry.vol.offset - reloff,
                                absolute=True)

        for proc in eproc.ActiveProcessLinks:
            if not filter_func(proc):
                yield proc
Exemple #8
0
    def list_registry_callbacks(
            cls, context: interfaces.context.ContextInterface, layer_name: str,
            symbol_table: str,
            callback_table_name: str) -> Iterable[Tuple[str, int, None]]:
        """Lists all registry callbacks.

        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
            callback_table_name: The nae of the table containing the callback symbols

        Yields:
            A name, location and optional detail string
        """

        kvo = context.layers[layer_name].config['kernel_virtual_offset']
        ntkrnlmp = context.module(symbol_table,
                                  layer_name=layer_name,
                                  offset=kvo)
        full_type_name = callback_table_name + constants.BANG + "_EX_CALLBACK_ROUTINE_BLOCK"

        try:
            symbol_offset = ntkrnlmp.get_symbol("CmpCallBackVector").address
            symbol_count_offset = ntkrnlmp.get_symbol(
                "CmpCallBackCount").address
        except exceptions.SymbolError:
            vollog.debug("Cannot find CmpCallBackVector or CmpCallBackCount")
            return

        callback_count = ntkrnlmp.object(object_type="unsigned int",
                                         offset=symbol_count_offset)

        if callback_count == 0:
            return

        fast_refs = ntkrnlmp.object(object_type="array",
                                    offset=symbol_offset,
                                    subtype=ntkrnlmp.get_type("_EX_FAST_REF"),
                                    count=callback_count)

        for fast_ref in fast_refs:
            try:
                callback = fast_ref.dereference().cast(full_type_name)
            except exceptions.InvalidAddressException:
                continue

            if callback.Function != 0:
                yield "CmRegisterCallback", callback.Function, None
Exemple #9
0
    def protect_values(cls, context: interfaces.context.ContextInterface, layer_name: str,
                       symbol_table: str) -> Iterable[int]:
        """Look up the array of memory protection constants from the memory
        sample. These don't change often, but if they do in the future, then
        finding them dynamically versus hard-coding here will ensure we parse
        them properly.

        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
        """

        kvo = context.layers[layer_name].config["kernel_virtual_offset"]
        ntkrnlmp = context.module(symbol_table, layer_name = layer_name, offset = kvo)
        addr = ntkrnlmp.get_symbol("MmProtectToValue").address
        values = ntkrnlmp.object(object_type = "array", offset = addr, subtype = ntkrnlmp.get_type("int"), count = 32)
        return values  # type: ignore
Exemple #10
0
    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
Exemple #11
0
    def pool_scan(cls,
                  context: interfaces.context.ContextInterface,
                  layer_name: str,
                  symbol_table: str,
                  pool_constraints: List[PoolConstraint],
                  alignment: int = 8,
                  progress_callback: Optional[constants.ProgressCallback] = None) \
            -> Generator[Tuple[PoolConstraint, interfaces.objects.ObjectInterface], None, None]:
        """Returns the _POOL_HEADER object (based on the symbol_table template)
        after scanning through layer_name returning all headers that match any
        of the constraints provided.  Only one constraint can be provided per
        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
            pool_constraints: List of pool constraints used to limit the scan results
            alignment: An optional value that all pool headers will be aligned to
            progress_callback: An optional function to provide progress feedback whilst scanning

        Returns:
            An Iterable of pool constraints and the pool headers associated with them
        """
        # Setup the pattern
        constraint_lookup = {}  # type: Dict[bytes, PoolConstraint]
        for constraint in pool_constraints:
            if constraint.tag in constraint_lookup:
                raise ValueError(
                    "Constraint tag is used for more than one constraint: {}".
                    format(repr(constraint.tag)))
            constraint_lookup[constraint.tag] = constraint

        pool_header_table_name = cls.get_pool_header_table(
            context, symbol_table)
        module = context.module(pool_header_table_name, layer_name, offset=0)

        # Run the scan locating the offsets of a particular tag
        layer = context.layers[layer_name]
        scanner = PoolHeaderScanner(module, constraint_lookup, alignment)
        yield from layer.scan(context, scanner, progress_callback)
Exemple #12
0
    def list_modules(
            cls, context: interfaces.context.ContextInterface, layer_name: str,
            symbol_table: str) -> Iterable[interfaces.objects.ObjectInterface]:
        """Lists all the modules in the primary layer.

        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 Modules as retrieved from PsLoadedModuleList
        """

        kvo = context.layers[layer_name].config['kernel_virtual_offset']
        ntkrnlmp = context.module(symbol_table,
                                  layer_name=layer_name,
                                  offset=kvo)

        try:
            # use this type if its available (starting with windows 10)
            ldr_entry_type = ntkrnlmp.get_type("_KLDR_DATA_TABLE_ENTRY")
        except exceptions.SymbolError:
            ldr_entry_type = ntkrnlmp.get_type("_LDR_DATA_TABLE_ENTRY")

        type_name = ldr_entry_type.type_name.split(constants.BANG)[1]

        list_head = ntkrnlmp.get_symbol("PsLoadedModuleList").address
        list_entry = ntkrnlmp.object(object_type="_LIST_ENTRY",
                                     offset=list_head)
        reloff = ldr_entry_type.relative_child_offset("InLoadOrderLinks")
        module = ntkrnlmp.object(object_type=type_name,
                                 offset=list_entry.vol.offset - reloff,
                                 absolute=True)

        for mod in module.InLoadOrderLinks:
            yield mod
Exemple #13
0
    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 = versions.is_vista_or_later(context, symbol_table)
            is_win10 = versions.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
Exemple #14
0
    def list_hive_objects(
        cls,
        context: interfaces.context.ContextInterface,
        layer_name: str,
        symbol_table: str,
        filter_string: str = None
    ) -> Iterator[interfaces.objects.ObjectInterface]:
        """Lists all the hives in the primary layer.

        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
            filter_string: A string which must be present in the hive name if specified

        Returns:
            The list of registry hives from the `layer_name` layer as filtered against using the `filter_string`
        """

        # We only use the object factory to demonstrate how to use one
        kvo = context.layers[layer_name].config['kernel_virtual_offset']
        ntkrnlmp = context.module(symbol_table,
                                  layer_name=layer_name,
                                  offset=kvo)

        list_head = ntkrnlmp.get_symbol("CmpHiveListHead").address
        list_entry = ntkrnlmp.object(object_type="_LIST_ENTRY",
                                     offset=list_head)
        reloff = ntkrnlmp.get_type("_CMHIVE").relative_child_offset("HiveList")
        cmhive = ntkrnlmp.object(object_type="_CMHIVE",
                                 offset=list_entry.vol.offset - reloff,
                                 absolute=True)

        # Run through the list forwards
        seen = set()

        hg = HiveGenerator(cmhive, forward=True)
        for hive in hg:
            if hive.vol.offset in seen:
                vollog.debug("Hivelist found an already seen offset {} while " \
                             "traversing forwards, this should not occur".format(hex(hive.vol.offset)))
                break
            seen.add(hive.vol.offset)
            if filter_string is None or filter_string.lower() in str(
                    hive.get_name() or "").lower():
                if context.layers[layer_name].is_valid(hive.vol.offset):
                    yield hive

        forward_invalid = hg.invalid
        if forward_invalid:
            vollog.debug(
                "Hivelist failed traversing the list forwards at {}, traversing backwards"
                .format(hex(forward_invalid)))
            hg = HiveGenerator(cmhive, forward=False)
            for hive in hg:
                if hive.vol.offset in seen:
                    vollog.debug("Hivelist found an already seen offset {} while " \
                                 "traversing backwards, list walking met in the middle".format(hex(hive.vol.offset)))
                    break
                seen.add(hive.vol.offset)
                if filter_string is None or filter_string.lower() in str(
                        hive.get_name() or "").lower():
                    if context.layers[layer_name].is_valid(hive.vol.offset):
                        yield hive

            backward_invalid = hg.invalid

            if backward_invalid and forward_invalid != backward_invalid:
                # walking forward and backward did not stop at the same offset. they should if:
                #  1) there are no invalid hives, walking forwards would reach the end and backwards is not necessary
                #  2) there is one invalid hive, walking backwards would stop at the same place as forwards
                # therefore, there must be more 2 or more invalid hives, so the middle of the list is not reachable
                # by walking the list, so revert to scanning, and walk the list forwards and backwards from each
                # found hive
                vollog.debug("Hivelist failed traversing backwards at {}, a different " \
                             "location from forwards, revert to scanning".format(hex(backward_invalid)))
                for hive in hivescan.HiveScan.scan_hives(
                        context, layer_name, symbol_table):
                    try:
                        if hive.HiveList.Flink:
                            start_hive_offset = hive.HiveList.Flink - reloff

                            ## Now instantiate the first hive in virtual address space as normal
                            start_hive = ntkrnlmp.object(
                                object_type="_CMHIVE",
                                offset=start_hive_offset,
                                absolute=True)
                            for forward in (True, False):
                                for linked_hive in start_hive.HiveList.to_list(
                                        hive.vol.type_name, "HiveList",
                                        forward):
                                    if not linked_hive.is_valid(
                                    ) or linked_hive.vol.offset in seen:
                                        continue
                                    seen.add(linked_hive.vol.offset)
                                    if filter_string is None or filter_string.lower(
                                    ) in str(linked_hive.get_name()
                                             or "").lower():
                                        if context.layers[layer_name].is_valid(
                                                linked_hive.vol.offset):
                                            yield linked_hive
                    except exceptions.InvalidAddressException:
                        vollog.debug(
                            "InvalidAddressException when traversing hive {} found from scan, skipping"
                            .format(hex(hive.vol.offset)))
    def list_hive_objects(
        cls,
        context: interfaces.context.ContextInterface,
        layer_name: str,
        symbol_table: str,
        filter_string: str = None
    ) -> Iterator[interfaces.objects.ObjectInterface]:
        """Lists all the hives in the primary layer.

        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
            filter_string: A string which must be present in the hive name if specified

        Returns:
            The list of registry hives from the `layer_name` layer as filtered against using the `filter_string`
        """

        # We only use the object factory to demonstrate how to use one
        kvo = context.layers[layer_name].config['kernel_virtual_offset']
        ntkrnlmp = context.module(symbol_table,
                                  layer_name=layer_name,
                                  offset=kvo)

        list_head = ntkrnlmp.get_symbol("CmpHiveListHead").address
        list_entry = ntkrnlmp.object(object_type="_LIST_ENTRY",
                                     offset=list_head)
        reloff = ntkrnlmp.get_type("_CMHIVE").relative_child_offset("HiveList")
        cmhive = ntkrnlmp.object(object_type="_CMHIVE",
                                 offset=list_entry.vol.offset - reloff,
                                 absolute=True)

        # Run through the list forwards
        seen = set()
        traverse_backwards = False
        try:
            for hive in cmhive.HiveList:
                if filter_string is None or filter_string.lower() in str(
                        hive.get_name() or "").lower():
                    if context.layers[layer_name].is_valid(hive.vol.offset):
                        seen.add(hive.vol.offset)
                        yield hive
        except exceptions.InvalidAddressException:
            vollog.warning(
                "Hivelist failed traversing the list forwards, traversing backwards"
            )
            traverse_backwards = True

        if traverse_backwards:
            try:
                for hive in cmhive.HiveList.to_list(cmhive.vol.type_name,
                                                    "HiveList",
                                                    forward=False):
                    if filter_string is None or filter_string.lower() in str(
                            hive.get_name()
                            or "").lower() and hive.vol.offset not in seen:
                        if context.layers[layer_name].is_valid(
                                hive.vol.offset):
                            yield hive
            except exceptions.InvalidAddressException:
                vollog.warning(
                    "Hivelist failed traversing the list backwards, giving up")