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