From 8691c68604e5720fc2c38b86edbce25fbbf80053 Mon Sep 17 00:00:00 2001 From: Mike Auty Date: Sat, 9 Nov 2019 14:31:59 +0000 Subject: [PATCH] Pool: Refactor pool extension to its own file. --- .../framework/plugins/windows/poolscanner.py | 3 +- .../framework/symbols/windows/__init__.py | 8 +- .../symbols/windows/extensions/__init__.py | 282 +----------------- .../symbols/windows/extensions/pool.py | 270 +++++++++++++++++ 4 files changed, 287 insertions(+), 276 deletions(-) create mode 100644 volatility/framework/symbols/windows/extensions/pool.py diff --git a/volatility/framework/plugins/windows/poolscanner.py b/volatility/framework/plugins/windows/poolscanner.py index 663331106..cfb9637e8 100644 --- a/volatility/framework/plugins/windows/poolscanner.py +++ b/volatility/framework/plugins/windows/poolscanner.py @@ -13,6 +13,7 @@ from volatility.framework.layers import scanners from volatility.framework.renderers import format_hints from volatility.framework.symbols import intermed from volatility.framework.symbols.windows import extensions +from volatility.framework.symbols.windows.extensions import pool from volatility.plugins.windows import handles vollog = logging.getLogger(__name__) @@ -433,7 +434,7 @@ class PoolScanner(plugins.PluginInterface): sub_path = "windows", filename = pool_header_json_filename, table_mapping = {'nt_symbols': symbol_table}, - class_types = {'_POOL_HEADER': extensions.POOL_HEADER}) + class_types = {'_POOL_HEADER': extensions.pool.POOL_HEADER}) module = context.module(new_table_name, layer_name, offset = 0) return module diff --git a/volatility/framework/symbols/windows/__init__.py b/volatility/framework/symbols/windows/__init__.py index 7b5687ce6..edf1793b1 100644 --- a/volatility/framework/symbols/windows/__init__.py +++ b/volatility/framework/symbols/windows/__init__.py @@ -1,11 +1,11 @@ # This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0 # which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 # - +import volatility.framework.symbols.windows.extensions.pool from volatility.framework import interfaces from volatility.framework.symbols import intermed from volatility.framework.symbols.windows import extensions -from volatility.framework.symbols.windows.extensions import registry +from volatility.framework.symbols.windows.extensions import registry, pool class WindowsKernelIntermedSymbols(intermed.IntermediateSymbolTable): @@ -19,7 +19,7 @@ class WindowsKernelIntermedSymbols(intermed.IntermediateSymbolTable): self.set_type_class('_EPROCESS', extensions.EPROCESS) self.set_type_class('_UNICODE_STRING', extensions.UNICODE_STRING) self.set_type_class('_EX_FAST_REF', extensions.EX_FAST_REF) - self.set_type_class('_OBJECT_HEADER', extensions.OBJECT_HEADER) + self.set_type_class('_OBJECT_HEADER', pool.OBJECT_HEADER) self.set_type_class('_FILE_OBJECT', extensions.FILE_OBJECT) self.set_type_class('_DEVICE_OBJECT', extensions.DEVICE_OBJECT) self.set_type_class('_CM_KEY_BODY', registry.CM_KEY_BODY) @@ -36,7 +36,7 @@ class WindowsKernelIntermedSymbols(intermed.IntermediateSymbolTable): # This doesn't exist in very specific versions of windows try: - self.set_type_class('_POOL_HEADER', extensions.POOL_HEADER) + self.set_type_class('_POOL_HEADER', pool.POOL_HEADER) except ValueError: pass diff --git a/volatility/framework/symbols/windows/extensions/__init__.py b/volatility/framework/symbols/windows/extensions/__init__.py index b3ef22936..813fe5f76 100644 --- a/volatility/framework/symbols/windows/extensions/__init__.py +++ b/volatility/framework/symbols/windows/extensions/__init__.py @@ -13,183 +13,13 @@ from volatility.framework import constants, exceptions, interfaces, objects, ren from volatility.framework.layers import intel from volatility.framework.renderers import conversion from volatility.framework.symbols import generic +from volatility.framework.symbols.windows.extensions import pool vollog = logging.getLogger(__name__) # Keep these in a basic module, to prevent import cycles when symbol providers require them -class POOL_HEADER(objects.StructType): - """A kernel pool allocation header. - - Exists at the base of the allocation and provides a tag that we can - scan for. - """ - - def get_object(self, - type_name: str, - type_map: dict, - use_top_down: bool, - native_layer_name: Optional[str] = None, - object_type: Optional[str] = None, - cookie: Optional[int] = None) -> Optional[interfaces.objects.ObjectInterface]: - """Carve an object or data structure from a kernel pool allocation. - - :param type_name: the data structure type name - :param native_layer_name: the name of the layer where the data originally lived - :param object_type: the object type (executive kernel objects only) - :return: - """ - - symbol_table_name = self.vol.type_name.split(constants.BANG)[0] - if constants.BANG in type_name: - symbol_table_name, type_name = type_name.split(constants.BANG)[0:2] - - object_header_type = self._context.symbol_space.get_type(symbol_table_name + constants.BANG + "_OBJECT_HEADER") - pool_header_size = self.vol.size - - # if there is no object type, then just instantiate a structure - if object_type is None: - mem_object = self._context.object(symbol_table_name + constants.BANG + type_name, - layer_name = self.vol.layer_name, - offset = self.vol.offset + pool_header_size, - native_layer_name = native_layer_name) - return mem_object - - # otherwise we have an executive object in the pool - else: - if symbols.symbol_table_is_64bit(self._context, symbol_table_name): - alignment = 16 - else: - alignment = 8 - - # use the top down approach for windows 8 and later - if use_top_down: - infomask_offset = object_header_type.relative_child_offset('InfoMask') - optional_headers, lengths_of_optional_headers = self._calculate_optional_header_lengths( - self._context, symbol_table_name) - padding_available = None if 'PADDING_INFO' not in optional_headers else optional_headers.index( - 'PADDING_INFO') - max_optional_headers_length = sum(lengths_of_optional_headers) - - # define the starting and ending bounds for the scan - start_offset = self.vol.offset + pool_header_size - addr_limit = min(max_optional_headers_length, self.BlockSize * alignment) - - # A single read is better than lots of little one-byte reads. - # We're ok padding this, because the byte we'd check would be 0 which would only be valid if there - # were no optional headers in the first place (ie, if we read too much for headers that don't exist, - # but the bit we could read were valid) - infomask_data = self._context.layers[self.vol.layer_name].read(start_offset, - addr_limit + infomask_offset, - pad = True) - - # Addr stores the offset to the potential start of the OBJECT_HEADER from just after the POOL_HEADER - # It will always be aligned to a particular alignment - for addr in range(0, addr_limit, alignment): - infomask_value = infomask_data[addr + infomask_offset] - - padding_present = False - optional_headers_length = 0 - for i in range(len(lengths_of_optional_headers)): - if infomask_value & (1 << i): - optional_headers_length += lengths_of_optional_headers[i] - if i == padding_available: - padding_present = True - - # PADDING_INFO is a special case (4 bytes that contain the total padding length) - padding_length = 0 - if padding_present and padding_available is not None: - # Read the four bytes from just before the next optional_headers_length minus the padding_info size - # - # --------------- - # POOL_HEADER - # --------------- - # - # start of PADDING_INFO - # --------------- - # End of other optional headers - # --------------- - # OBJECT_HEADER - # --------------- - if addr - optional_headers_length < 0: - continue - padding_length = struct.unpack( - "= padding_length > addr: - continue - - try: - - object_header = self._context.object(symbol_table_name + constants.BANG + "_OBJECT_HEADER", - layer_name = self.vol.layer_name, - offset = addr + start_offset, - native_layer_name = native_layer_name) - - if not object_header.is_valid(): - continue - - object_type_string = object_header.get_object_type(type_map, cookie) - if object_type_string == object_type: - - mem_object = object_header.Body.cast(symbol_table_name + constants.BANG + type_name) - if mem_object.is_valid(): - return mem_object - - except (TypeError, exceptions.InvalidAddressException): - pass - - # use the bottom up approach for windows 7 and earlier - else: - type_size = self._context.symbol_space.get_type(symbol_table_name + constants.BANG + type_name).size - rounded_size = conversion.round(type_size, alignment, up = True) - - mem_object = self._context.object(symbol_table_name + constants.BANG + type_name, - layer_name = self.vol.layer_name, - offset = self.vol.offset + self.BlockSize * alignment - rounded_size, - native_layer_name = native_layer_name) - - object_header = mem_object.object_header() - - try: - object_type_string = object_header.get_object_type(type_map, cookie) - if object_type_string == object_type: - return mem_object - else: - return None - except (TypeError, exceptions.InvalidAddressException): - return None - return None - - @classmethod - @functools.lru_cache() - def _calculate_optional_header_lengths(cls, context: interfaces.context.ContextInterface, - symbol_table_name: str) -> Tuple[List[str], List[int]]: - headers = [] - sizes = [] - for header in [ - 'CREATOR_INFO', 'NAME_INFO', 'HANDLE_INFO', 'QUOTA_INFO', 'PROCESS_INFO', 'AUDIT_INFO', 'EXTENDED_INFO', - 'HANDLE_REVOCATION_INFO', 'PADDING_INFO' - ]: - try: - type_name = "{}{}_OBJECT_HEADER_{}".format(symbol_table_name, constants.BANG, header) - header_type = context.symbol_space.get_type(type_name) - headers.append(header) - sizes.append(header_type.size) - except: - # Some of these may not exist, for example: - # if build < 9200: PADDING_INFO else: AUDIT_INFO - # if build == 10586: HANDLE_REVOCATION_INFO else EXTENDED_INFO - # based on what's present and what's not, this list should be the right order and the right length - pass - return headers, sizes - - class KSYSTEM_TIME(objects.StructType): """A system time structure that stores a high and low part.""" @@ -480,23 +310,7 @@ class EX_FAST_REF(objects.StructType): native_layer_name = self.vol.native_layer_name) -class ExecutiveObject(interfaces.objects.ObjectInterface): - """This is used as a "mixin" that provides all kernel executive objects - with a means of finding their own object header.""" - - def object_header(self) -> 'OBJECT_HEADER': - if constants.BANG not in self.vol.type_name: - raise ValueError("Invalid symbol table name syntax (no {} found)".format(constants.BANG)) - symbol_table_name = self.vol.type_name.split(constants.BANG)[0] - body_offset = self._context.symbol_space.get_type(symbol_table_name + constants.BANG + - "_OBJECT_HEADER").relative_child_offset("Body") - return self._context.object(symbol_table_name + constants.BANG + "_OBJECT_HEADER", - layer_name = self.vol.layer_name, - offset = self.vol.offset - body_offset, - native_layer_name = self.vol.native_layer_name) - - -class DEVICE_OBJECT(objects.StructType, ExecutiveObject): +class DEVICE_OBJECT(objects.StructType, pool.ExecutiveObject): """A class for kernel device objects.""" def get_device_name(self) -> str: @@ -504,7 +318,7 @@ class DEVICE_OBJECT(objects.StructType, ExecutiveObject): return header.NameInfo.Name.String # type: ignore -class DRIVER_OBJECT(objects.StructType, ExecutiveObject): +class DRIVER_OBJECT(objects.StructType, pool.ExecutiveObject): """A class for kernel driver objects.""" def get_driver_name(self) -> str: @@ -516,7 +330,7 @@ class DRIVER_OBJECT(objects.StructType, ExecutiveObject): return True -class OBJECT_SYMBOLIC_LINK(objects.StructType, ExecutiveObject): +class OBJECT_SYMBOLIC_LINK(objects.StructType, pool.ExecutiveObject): """A class for kernel link objects.""" def get_link_name(self) -> str: @@ -531,7 +345,7 @@ class OBJECT_SYMBOLIC_LINK(objects.StructType, ExecutiveObject): return conversion.wintime_to_datetime(self.CreationTime.QuadPart) -class FILE_OBJECT(objects.StructType, ExecutiveObject): +class FILE_OBJECT(objects.StructType, pool.ExecutiveObject): """A class for windows file objects.""" def is_valid(self) -> bool: @@ -552,7 +366,7 @@ class FILE_OBJECT(objects.StructType, ExecutiveObject): return name -class KMUTANT(objects.StructType, ExecutiveObject): +class KMUTANT(objects.StructType, pool.ExecutiveObject): """A class for windows mutant objects.""" def is_valid(self) -> bool: @@ -565,83 +379,6 @@ class KMUTANT(objects.StructType, ExecutiveObject): return header.NameInfo.Name.String # type: ignore -class OBJECT_HEADER(objects.StructType): - """A class for the headers for executive kernel objects, which contains - quota information, ownership details, naming data, and ACLs.""" - - def is_valid(self) -> bool: - """Determine if the object is valid.""" - - # if self.InfoMask > 0x48: - # return False - - try: - if self.PointerCount > 0x1000000 or self.PointerCount < 0: - return False - except exceptions.InvalidAddressException: - return False - - return True - - def get_object_type(self, type_map: Dict[int, str], cookie: int = None) -> Optional[str]: - """Across all Windows versions, the _OBJECT_HEADER embeds details on - the type of object (i.e. process, file) but the way its embedded - differs between versions. - - This API abstracts away those details. - """ - - if self.vol.get('object_header_object_type', None) is not None: - return self.vol.object_header_object_type - - try: - # vista and earlier have a Type member - self._vol['object_header_object_type'] = self.Type.Name.String - except AttributeError: - # windows 7 and later have a TypeIndex, but windows 10 - # further encodes the index value with nt1!ObHeaderCookie - try: - type_index = ((self.vol.offset >> 8) ^ cookie ^ self.TypeIndex) & 0xFF - except (AttributeError, TypeError): - type_index = self.TypeIndex - - self._vol['object_header_object_type'] = type_map.get(type_index) - return self.vol.object_header_object_type - - @property - def NameInfo(self) -> interfaces.objects.ObjectInterface: - if constants.BANG not in self.vol.type_name: - raise ValueError("Invalid symbol table name syntax (no {} found)".format(constants.BANG)) - - symbol_table_name = self.vol.type_name.split(constants.BANG)[0] - - try: - header_offset = self.NameInfoOffset - except AttributeError: - # http://codemachine.com/article_objectheader.html (Windows 7 and later) - name_info_bit = 0x2 - - layer = self._context.layers[self.vol.native_layer_name] - kvo = layer.config.get("kernel_virtual_offset", None) - - if kvo is None: - raise AttributeError("Could not find kernel_virtual_offset for layer: {}".format(self.vol.layer_name)) - - ntkrnlmp = self._context.module(symbol_table_name, layer_name = self.vol.layer_name, offset = kvo) - address = ntkrnlmp.get_symbol("ObpInfoMaskToOffset").address - calculated_index = self.InfoMask & (name_info_bit | (name_info_bit - 1)) - - header_offset = self._context.object(symbol_table_name + constants.BANG + "unsigned char", - layer_name = self.vol.native_layer_name, - offset = kvo + address + calculated_index) - - header = self._context.object(symbol_table_name + constants.BANG + "_OBJECT_HEADER_NAME_INFO", - layer_name = self.vol.layer_name, - offset = self.vol.offset - header_offset, - native_layer_name = self.vol.native_layer_name) - return header - - class ETHREAD(objects.StructType): """A class for executive thread objects.""" @@ -665,7 +402,7 @@ class UNICODE_STRING(objects.StructType): String = property(get_string) -class EPROCESS(generic.GenericIntelProcess, ExecutiveObject): +class EPROCESS(generic.GenericIntelProcess, pool.ExecutiveObject): """A class for executive kernel processes objects.""" def is_valid(self) -> bool: @@ -886,6 +623,7 @@ class LIST_ENTRY(objects.StructType, collections.abc.Iterable): trans_layer = self._context.layers[layer] try: + trans_layer.is_valid(self.vol.offset) link = getattr(self, direction).dereference() except exceptions.InvalidAddressException: return @@ -900,7 +638,9 @@ class LIST_ENTRY(objects.StructType, collections.abc.Iterable): while link.vol.offset not in seen: obj_offset = link.vol.offset - relative_offset - if not trans_layer.is_valid(obj_offset): + try: + trans_layer.is_valid(obj_offset) + except exceptions.InvalidAddressException: return obj = self._context.object(symbol_type, diff --git a/volatility/framework/symbols/windows/extensions/pool.py b/volatility/framework/symbols/windows/extensions/pool.py new file mode 100644 index 000000000..0b83a33dd --- /dev/null +++ b/volatility/framework/symbols/windows/extensions/pool.py @@ -0,0 +1,270 @@ +import functools +import struct +from typing import Optional, Tuple, List, Dict + +from volatility.framework import objects, interfaces, constants, symbols, exceptions +from volatility.framework.renderers import conversion + + +class POOL_HEADER(objects.StructType): + """A kernel pool allocation header. + + Exists at the base of the allocation and provides a tag that we can + scan for. + """ + + def get_object(self, + type_name: str, + type_map: dict, + use_top_down: bool, + native_layer_name: Optional[str] = None, + object_type: Optional[str] = None, + cookie: Optional[int] = None) -> Optional[interfaces.objects.ObjectInterface]: + """Carve an object or data structure from a kernel pool allocation. + + :param type_name: the data structure type name + :param native_layer_name: the name of the layer where the data originally lived + :param object_type: the object type (executive kernel objects only) + :return: + """ + + symbol_table_name = self.vol.type_name.split(constants.BANG)[0] + if constants.BANG in type_name: + symbol_table_name, type_name = type_name.split(constants.BANG)[0:2] + + object_header_type = self._context.symbol_space.get_type(symbol_table_name + constants.BANG + "_OBJECT_HEADER") + pool_header_size = self.vol.size + + # if there is no object type, then just instantiate a structure + if object_type is None: + mem_object = self._context.object(symbol_table_name + constants.BANG + type_name, + layer_name = self.vol.layer_name, + offset = self.vol.offset + pool_header_size, + native_layer_name = native_layer_name) + return mem_object + + # otherwise we have an executive object in the pool + else: + if symbols.symbol_table_is_64bit(self._context, symbol_table_name): + alignment = 16 + else: + alignment = 8 + + # use the top down approach for windows 8 and later + if use_top_down: + infomask_offset = object_header_type.relative_child_offset('InfoMask') + optional_headers, lengths_of_optional_headers = self._calculate_optional_header_lengths( + self._context, symbol_table_name) + padding_available = None if 'PADDING_INFO' not in optional_headers else optional_headers.index( + 'PADDING_INFO') + max_optional_headers_length = sum(lengths_of_optional_headers) + + # define the starting and ending bounds for the scan + start_offset = self.vol.offset + pool_header_size + addr_limit = min(max_optional_headers_length, self.BlockSize * alignment) + + # A single read is better than lots of little one-byte reads. + # We're ok padding this, because the byte we'd check would be 0 which would only be valid if there + # were no optional headers in the first place (ie, if we read too much for headers that don't exist, + # but the bit we could read were valid) + infomask_data = self._context.layers[self.vol.layer_name].read(start_offset, + addr_limit + infomask_offset, + pad = True) + + # Addr stores the offset to the potential start of the OBJECT_HEADER from just after the POOL_HEADER + # It will always be aligned to a particular alignment + for addr in range(0, addr_limit, alignment): + infomask_value = infomask_data[addr + infomask_offset] + + padding_present = False + optional_headers_length = 0 + for i in range(len(lengths_of_optional_headers)): + if infomask_value & (1 << i): + optional_headers_length += lengths_of_optional_headers[i] + if i == padding_available: + padding_present = True + + # PADDING_INFO is a special case (4 bytes that contain the total padding length) + padding_length = 0 + if padding_present: + # Read the four bytes from just before the next optional_headers_length minus the padding_info size + # + # --------------- + # POOL_HEADER + # --------------- + # + # start of PADDING_INFO + # --------------- + # End of other optional headers + # --------------- + # OBJECT_HEADER + # --------------- + if addr - optional_headers_length < 0: + continue + padding_length = struct.unpack( + "= padding_length > addr: + continue + + try: + + object_header = self._context.object(symbol_table_name + constants.BANG + "_OBJECT_HEADER", + layer_name = self.vol.layer_name, + offset = addr + start_offset, + native_layer_name = native_layer_name) + + if not object_header.is_valid(): + continue + + object_type_string = object_header.get_object_type(type_map, cookie) + if object_type_string == object_type: + + mem_object = object_header.Body.cast(symbol_table_name + constants.BANG + type_name) + if mem_object.is_valid(): + return mem_object + + except (TypeError, exceptions.InvalidAddressException): + pass + + # use the bottom up approach for windows 7 and earlier + else: + type_size = self._context.symbol_space.get_type(symbol_table_name + constants.BANG + type_name).size + rounded_size = conversion.round(type_size, alignment, up = True) + + mem_object = self._context.object(symbol_table_name + constants.BANG + type_name, + layer_name = self.vol.layer_name, + offset = self.vol.offset + self.BlockSize * alignment - rounded_size, + native_layer_name = native_layer_name) + + object_header = mem_object.object_header() + + try: + object_type_string = object_header.get_object_type(type_map, cookie) + if object_type_string == object_type: + return mem_object + else: + return None + except (TypeError, exceptions.InvalidAddressException): + return None + return None + + @classmethod + @functools.lru_cache() + def _calculate_optional_header_lengths(cls, context: interfaces.context.ContextInterface, + symbol_table_name: str) -> Tuple[List[str], List[int]]: + headers = [] + sizes = [] + for header in [ + 'CREATOR_INFO', 'NAME_INFO', 'HANDLE_INFO', 'QUOTA_INFO', 'PROCESS_INFO', 'AUDIT_INFO', 'EXTENDED_INFO', + 'HANDLE_REVOCATION_INFO', 'PADDING_INFO' + ]: + try: + type_name = "{}{}_OBJECT_HEADER_{}".format(symbol_table_name, constants.BANG, header) + header_type = context.symbol_space.get_type(type_name) + headers.append(header) + sizes.append(header_type.size) + except: + # Some of these may not exist, for example: + # if build < 9200: PADDING_INFO else: AUDIT_INFO + # if build == 10586: HANDLE_REVOCATION_INFO else EXTENDED_INFO + # based on what's present and what's not, this list should be the right order and the right length + pass + return headers, sizes + + +class ExecutiveObject(interfaces.objects.ObjectInterface): + """This is used as a "mixin" that provides all kernel executive objects + with a means of finding their own object header.""" + + def object_header(self) -> 'OBJECT_HEADER': + if constants.BANG not in self.vol.type_name: + raise ValueError("Invalid symbol table name syntax (no {} found)".format(constants.BANG)) + symbol_table_name = self.vol.type_name.split(constants.BANG)[0] + body_offset = self._context.symbol_space.get_type(symbol_table_name + constants.BANG + + "_OBJECT_HEADER").relative_child_offset("Body") + return self._context.object(symbol_table_name + constants.BANG + "_OBJECT_HEADER", + layer_name = self.vol.layer_name, + offset = self.vol.offset - body_offset, + native_layer_name = self.vol.native_layer_name) + + +class OBJECT_HEADER(objects.StructType): + """A class for the headers for executive kernel objects, which contains + quota information, ownership details, naming data, and ACLs.""" + + def is_valid(self) -> bool: + """Determine if the object is valid.""" + + # if self.InfoMask > 0x48: + # return False + + try: + if self.PointerCount > 0x1000000 or self.PointerCount < 0: + return False + except exceptions.InvalidAddressException: + return False + + return True + + def get_object_type(self, type_map: Dict[int, str], cookie: int = None) -> Optional[str]: + """Across all Windows versions, the _OBJECT_HEADER embeds details on + the type of object (i.e. process, file) but the way its embedded + differs between versions. + + This API abstracts away those details. + """ + + if self.vol.get('object_header_object_type', None) is not None: + return self.vol.object_header_object_type + + try: + # vista and earlier have a Type member + self._vol['object_header_object_type'] = self.Type.Name.String + except AttributeError: + # windows 7 and later have a TypeIndex, but windows 10 + # further encodes the index value with nt1!ObHeaderCookie + try: + type_index = ((self.vol.offset >> 8) ^ cookie ^ self.TypeIndex) & 0xFF + except (AttributeError, TypeError): + type_index = self.TypeIndex + + self._vol['object_header_object_type'] = type_map.get(type_index) + return self.vol.object_header_object_type + + @property + def NameInfo(self) -> interfaces.objects.ObjectInterface: + if constants.BANG not in self.vol.type_name: + raise ValueError("Invalid symbol table name syntax (no {} found)".format(constants.BANG)) + + symbol_table_name = self.vol.type_name.split(constants.BANG)[0] + + try: + header_offset = self.NameInfoOffset + except AttributeError: + # http://codemachine.com/article_objectheader.html (Windows 7 and later) + name_info_bit = 0x2 + + layer = self._context.layers[self.vol.native_layer_name] + kvo = layer.config.get("kernel_virtual_offset", None) + + if kvo is None: + raise AttributeError("Could not find kernel_virtual_offset for layer: {}".format(self.vol.layer_name)) + + ntkrnlmp = self._context.module(symbol_table_name, layer_name = self.vol.layer_name, offset = kvo) + address = ntkrnlmp.get_symbol("ObpInfoMaskToOffset").address + calculated_index = self.InfoMask & (name_info_bit | (name_info_bit - 1)) + + header_offset = self._context.object(symbol_table_name + constants.BANG + "unsigned char", + layer_name = self.vol.native_layer_name, + offset = kvo + address + calculated_index) + + header = self._context.object(symbol_table_name + constants.BANG + "_OBJECT_HEADER_NAME_INFO", + layer_name = self.vol.layer_name, + offset = self.vol.offset - header_offset, + native_layer_name = self.vol.native_layer_name) + return header