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 = versions.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 get_tag(self): vad_address = self.vol.offset # the offset is different on 32 and 64 bits symbol_table_name = self.vol.type_name.split(constants.BANG)[0] if not symbols.symbol_table_is_64bit(self._context, symbol_table_name): vad_address -= 4 else: vad_address -= 12 try: # TODO: instantiate a _POOL_HEADER and return PoolTag bytesobj = self._context.object( symbol_table_name + constants.BANG + "bytes", layer_name=self.vol.layer_name, offset=vad_address, native_layer_name=self.vol.native_layer_name, length=4) return bytesobj.decode() except exceptions.InvalidAddressException: return None except UnicodeDecodeError: return None
def parse_partitions( cls, context: interfaces.context.ContextInterface, layer_name: str, net_symbol_table: str, tcpip_symbol_table: str, tcpip_module_offset: int ) -> Iterable[interfaces.objects.ObjectInterface]: """Parses tcpip.sys's PartitionTable containing established TCP connections. The amount of Partition depends on the value of the symbol `PartitionCount` and correlates with the maximum processor count (refer to Art of Memory Forensics, chapter 11). Args: context: The context to retrieve required elements (layers, symbol tables) from layer_name: The name of the layer on which to operate net_symbol_table: The name of the table containing the tcpip types tcpip_symbol_table: The name of the table containing the tcpip driver symbols tcpip_module_offset: The offset of the tcpip module Returns: The list of TCP endpoint objects from the `layer_name` layer's `PartitionTable` """ if symbols.symbol_table_is_64bit(context, net_symbol_table): alignment = 0x10 else: alignment = 8 obj_name = net_symbol_table + constants.BANG + "_TCP_ENDPOINT" # part_table_symbol is the offset within tcpip.sys which contains the address of the partition table itself part_table_symbol = context.symbol_space.get_symbol( tcpip_symbol_table + constants.BANG + "PartitionTable").address part_count_symbol = context.symbol_space.get_symbol( tcpip_symbol_table + constants.BANG + "PartitionCount").address part_table_addr = context.object( net_symbol_table + constants.BANG + "pointer", layer_name=layer_name, offset=tcpip_module_offset + part_table_symbol) # part_table is the actual partition table offset and consists out of a dynamic amount of _PARTITION objects part_table = context.object(net_symbol_table + constants.BANG + "_PARTITION_TABLE", layer_name=layer_name, offset=part_table_addr) part_count = int.from_bytes( context.layers[layer_name].read( tcpip_module_offset + part_count_symbol, 1), "little") part_table.Partitions.count = part_count vollog.debug( "Found TCP connection PartitionTable @ 0x{:x} (partition count: {})" .format(part_table_addr, part_count)) entry_offset = context.symbol_space.get_type( obj_name).relative_child_offset("ListEntry") for ctr, partition in enumerate(part_table.Partitions): vollog.debug(f"Parsing partition {ctr}") if partition.Endpoints.NumEntries > 0: for endpoint_entry in cls.parse_hashtable( context, layer_name, partition.Endpoints.Directory, partition.Endpoints.TableSize, alignment, net_symbol_table): endpoint = context.object(obj_name, layer_name=layer_name, offset=endpoint_entry - entry_offset) yield endpoint
def _generator(self, procs): """ Finds instances of the RC4 HMAC CSystem structure Returns whether the instances are hooked as well as the function handler addresses Args: procs: the process list filtered to lsass.exe instances """ kernel = self.context.modules[self.config['kernel']] if not symbols.symbol_table_is_64bit(self.context, kernel.symbol_table_name): vollog.info( "This plugin only supports 64bit Windows memory samples") return lsass_proc, proc_layer_name = self._find_lsass_proc(procs) if not lsass_proc: vollog.info( "Unable to find a valid lsass.exe process in the process list. This should never happen. Analysis cannot proceed." ) return cryptdll_base, cryptdll_size = self._find_cryptdll(lsass_proc) if not cryptdll_base: vollog.info( "Unable to find the location of cryptdll.dll inside of lsass.exe. Analysis cannot proceed." ) return # the custom type information from binary analysis cryptdll_types = self._get_cryptdll_types(self.context, self.config, self.config_path, proc_layer_name, cryptdll_base) # attempt to find the array and symbols directly from the PDB csystems, rc4HmacInitialize, rc4HmacDecrypt = \ self._find_csystems_with_symbols(proc_layer_name, cryptdll_types, cryptdll_base, cryptdll_size) # if we can't find cSystems through the PDB then # we fall back to export analysis and scanning # we keep the address of the rc4 functions from the PDB # though as its our only source to get them if csystems is None: fallback_sources = [ self._find_csystems_with_export, self._find_csystems_with_scanning ] for source in fallback_sources: csystems = source(proc_layer_name, cryptdll_types, cryptdll_base, cryptdll_size) if csystems is not None: break if csystems is None: vollog.info( "Unable to find CSystems inside of cryptdll.dll. Analysis cannot proceed." ) return for csystem in csystems: if not self.context.layers[proc_layer_name].is_valid( csystem.vol.offset, csystem.vol.size): continue # filter for RC4 HMAC if csystem.EncryptionType != 0x17: continue # use the specific symbols if present, otherwise use the vad start and size if rc4HmacInitialize and rc4HmacDecrypt: skeleton_key_present = self._check_for_skeleton_key_symbols( csystem, rc4HmacInitialize, rc4HmacDecrypt) else: skeleton_key_present = self._check_for_skeleton_key_vad( csystem, cryptdll_base, cryptdll_size) yield 0, (lsass_proc.UniqueProcessId, "lsass.exe", skeleton_key_present, \ format_hints.Hex(csystem.Initialize), format_hints.Hex(csystem.Decrypt))
def get_object( self, constraint: PoolConstraint, use_top_down: bool, kernel_symbol_table: Optional[str] = None, native_layer_name: Optional[str] = None ) -> Optional[interfaces.objects.ObjectInterface]: """Carve an object or data structure from a kernel pool allocation Args: constraint: a PoolConstraint object used to get the pool allocation header object use_top_down: for delineating how a windows version finds the size of the object body kernel_symbol_table: in case objects of a different symbol table are scanned for native_layer_name: the name of the layer where the data originally lived Returns: An object as found from a POOL_HEADER """ type_name = constraint.type_name executive = constraint.object_type is not None 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] # when checking for symbols from a table other than nt_symbols grab _OBJECT_HEADER from the kernel # because symbol_table_name will be different from kernel_symbol_table. if kernel_symbol_table: object_header_type = self._context.symbol_space.get_type( kernel_symbol_table + constants.BANG + "_OBJECT_HEADER") else: # otherwise symbol_table_name *is* the kernel symbol table, so just use that. 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 not executive: 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) yield 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: body_offset = object_header_type.relative_child_offset('Body') infomask_offset = object_header_type.relative_child_offset( 'InfoMask') pointercount_offset = object_header_type.relative_child_offset( 'PointerCount') pointercount_size = object_header_type.members['PointerCount'][ 1].size 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] pointercount_value = int.from_bytes( infomask_data[addr + pointercount_offset:addr + pointercount_offset + pointercount_size], byteorder='little', signed=True) if not 0x1000000 > pointercount_value >= 0: continue 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( "<I", infomask_data[addr - optional_headers_length:addr - optional_headers_length + 4]) padding_length -= lengths_of_optional_headers[ padding_available or 0] # Certain versions of windows have PADDING_INFO lengths that are too long # So we now check that the padding length is at a minimum the right length # and that it doesn't go beyond the entirety of the data if addr - optional_headers_length >= padding_length > addr: continue try: mem_object = self._context.object( symbol_table_name + constants.BANG + type_name, layer_name=self.vol.layer_name, offset=addr + body_offset + start_offset, native_layer_name=native_layer_name) if mem_object.is_valid(): yield 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 if constraint.additional_structures: for additional_structure in constraint.additional_structures: type_size += self._context.symbol_space.get_type( symbol_table_name + constants.BANG + additional_structure).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) try: if mem_object.is_valid(): yield mem_object except (TypeError, exceptions.InvalidAddressException): pass
def determine_tcpip_version(cls, context: interfaces.context.ContextInterface, layer_name: str, nt_symbol_table: str) -> Tuple[str, Type]: """Tries to determine which symbol filename to use for the image's tcpip driver. The logic is partially taken from the info plugin. Args: context: The context to retrieve required elements (layers, symbol tables) from layer_name: The name of the layer on which to operate nt_symbol_table: The name of the table containing the kernel symbols Returns: The filename of the symbol table to use. """ # while the failsafe way to determine the version of tcpip.sys would be to # extract the driver and parse its PE header containing the versionstring, # unfortunately that header is not guaranteed to persist within memory. # therefore we determine the version based on the kernel version as testing # with several windows versions has showed this to work out correctly. is_64bit = symbols.symbol_table_is_64bit(context, nt_symbol_table) is_18363_or_later = versions.is_win10_18363_or_later( context=context, symbol_table=nt_symbol_table) if is_64bit: arch = "x64" else: arch = "x86" vers = info.Info.get_version_structure(context, layer_name, nt_symbol_table) kuser = info.Info.get_kuser_structure(context, layer_name, nt_symbol_table) try: vers_minor_version = int(vers.MinorVersion) nt_major_version = int(kuser.NtMajorVersion) nt_minor_version = int(kuser.NtMinorVersion) except ValueError: # vers struct exists, but is not an int anymore? raise NotImplementedError( "Kernel Debug Structure version format not supported!") except: # unsure what to raise here. Also, it might be useful to add some kind of fallback, # either to a user-provided version or to another method to determine tcpip.sys's version raise exceptions.VolatilityException( "Kernel Debug Structure missing VERSION/KUSER structure, unable to determine Windows version!" ) vollog.debug("Determined OS Version: {}.{} {}.{}".format( kuser.NtMajorVersion, kuser.NtMinorVersion, vers.MajorVersion, vers.MinorVersion)) if nt_major_version == 10 and arch == "x64": # win10 x64 has an additional class type we have to include. class_types = network.win10_x64_class_types else: # default to general class types class_types = network.class_types # these versions are listed explicitly because symbol files differ based on # version *and* architecture. this is currently the clearest way to show # the differences, even if it introduces a fair bit of redundancy. # furthermore, it is easy to append new versions. if arch == "x86": version_dict = { (6, 0, 6000, 0): "netscan-vista-x86", (6, 0, 6001, 0): "netscan-vista-x86", (6, 0, 6002, 0): "netscan-vista-x86", (6, 0, 6003, 0): "netscan-vista-x86", (6, 1, 7600, 0): "netscan-win7-x86", (6, 1, 7601, 0): "netscan-win7-x86", (6, 1, 8400, 0): "netscan-win7-x86", (6, 2, 9200, 0): "netscan-win8-x86", (6, 3, 9600, 0): "netscan-win81-x86", (10, 0, 10240, 0): "netscan-win10-10240-x86", (10, 0, 10586, 0): "netscan-win10-10586-x86", (10, 0, 14393, 0): "netscan-win10-14393-x86", (10, 0, 15063, 0): "netscan-win10-15063-x86", (10, 0, 16299, 0): "netscan-win10-15063-x86", (10, 0, 17134, 0): "netscan-win10-17134-x86", (10, 0, 17763, 0): "netscan-win10-17134-x86", (10, 0, 18362, 0): "netscan-win10-17134-x86", (10, 0, 18363, 0): "netscan-win10-17134-x86" } else: version_dict = { (6, 0, 6000, 0): "netscan-vista-x64", (6, 0, 6001, 0): "netscan-vista-sp12-x64", (6, 0, 6002, 0): "netscan-vista-sp12-x64", (6, 0, 6003, 0): "netscan-vista-sp12-x64", (6, 1, 7600, 0): "netscan-win7-x64", (6, 1, 7601, 0): "netscan-win7-x64", (6, 1, 8400, 0): "netscan-win7-x64", (6, 2, 9200, 0): "netscan-win8-x64", (6, 3, 9600, 0): "netscan-win81-x64", (6, 3, 9600, 19935): "netscan-win81-19935-x64", (10, 0, 10240, 0): "netscan-win10-x64", (10, 0, 10586, 0): "netscan-win10-x64", (10, 0, 14393, 0): "netscan-win10-x64", (10, 0, 15063, 0): "netscan-win10-15063-x64", (10, 0, 16299, 0): "netscan-win10-16299-x64", (10, 0, 17134, 0): "netscan-win10-17134-x64", (10, 0, 17763, 0): "netscan-win10-17763-x64", (10, 0, 18362, 0): "netscan-win10-18362-x64", (10, 0, 18363, 0): "netscan-win10-18363-x64", (10, 0, 19041, 0): "netscan-win10-19041-x64" } # we do not need to check for tcpip's specific FileVersion in every case tcpip_mod_version = 0 # keep it 0 as a default # special use cases # Win10_18363 is not recognized by windows.info as 18363 # because all kernel file headers and debug structures report 18363 as # "10.0.18362.1198" with the last part being incremented. However, we can use # os_distinguisher to differentiate between 18362 and 18363 if vers_minor_version == 18362 and is_18363_or_later: vollog.debug( "Detected 18363 data structures: working with 18363 symbol table." ) vers_minor_version = 18363 # we need to define additional version numbers (which are then found via tcpip.sys's FileVersion header) in case there is # ambiguity _within_ an OS version. If such a version number (last number of the tuple) is defined for the current OS # we need to inspect tcpip.sys's headers to see if we can grab the precise version if [(a, b, c, d) for a, b, c, d in version_dict if (a, b, c) == (nt_major_version, nt_minor_version, vers_minor_version) and d != 0]: vollog.debug( "Requiring further version inspection due to OS version by checking tcpip.sys's FileVersion header" ) # the following is IntelLayer specific and might need to be adapted to other architectures. physical_layer_name = context.layers[layer_name].config.get( 'memory_layer', None) if physical_layer_name: ver = verinfo.VerInfo.find_version_info( context, physical_layer_name, "tcpip.sys") if ver: tcpip_mod_version = ver[3] vollog.debug( "Determined tcpip.sys's FileVersion: {}".format( tcpip_mod_version)) else: vollog.debug( "Could not determine tcpip.sys's FileVersion.") else: vollog.debug( "Unable to retrieve physical memory layer, skipping FileVersion check." ) # when determining the symbol file we have to consider the following cases: # the determined version's symbol file is found by intermed.create -> proceed # the determined version's symbol file is not found by intermed -> intermed will throw an exc and abort # the determined version has no mapped symbol file -> if win10 use latest, otherwise throw exc # windows version cannot be determined -> throw exc filename = version_dict.get((nt_major_version, nt_minor_version, vers_minor_version, tcpip_mod_version)) if not filename: # no match on filename means that we possibly have a version newer than those listed here. # try to grab the latest supported version of the current image NT version. If that symbol # version does not work, support has to be added manually. current_versions = [ (nt_maj, nt_min, vers_min, tcpip_ver) for nt_maj, nt_min, vers_min, tcpip_ver in version_dict if nt_maj == nt_major_version and nt_min == nt_minor_version and tcpip_ver <= tcpip_mod_version ] current_versions.sort() if current_versions: latest_version = current_versions[-1] filename = version_dict.get(latest_version) vollog.debug( f"Unable to find exact matching symbol file, going with latest: {filename}" ) else: raise NotImplementedError( "This version of Windows is not supported: {}.{} {}.{}!". format(nt_major_version, nt_minor_version, vers.MajorVersion, vers_minor_version)) vollog.debug(f"Determined symbol filename: {filename}") return filename, class_types
def _generator(self) -> Iterator[Tuple[int, Tuple[int, int, Any, Any]]]: kernel = self.context.modules[self.config['kernel']] layer_name = kernel.layer_name collection = self.build_module_collection(self.context, layer_name, kernel.symbol_table_name) kvo = self.context.layers[layer_name].config['kernel_virtual_offset'] ntkrnlmp = self.context.module(kernel.symbol_table_name, layer_name=layer_name, offset=kvo) # this is just one way to enumerate the native (NT) service table. # to do the same thing for the Win32K service table, we would need Win32K.sys symbol support ## we could also find nt!KeServiceDescriptorTable (NT) and KeServiceDescriptorTableShadow (NT, Win32K) service_table_address = ntkrnlmp.get_symbol("KiServiceTable").address service_limit_address = ntkrnlmp.get_symbol("KiServiceLimit").address service_limit = ntkrnlmp.object(object_type="int", offset=service_limit_address) # on 32-bit systems the table indexes are 32-bits and contain pointers (unsigned) # on 64-bit systems the indexes are also 32-bits but they're offsets from the # base address of the table and can be negative, so we need a signed data type is_kernel_64 = symbols.symbol_table_is_64bit(self.context, kernel.symbol_table_name) if is_kernel_64: array_subtype = "long" def kvo_calulator(func: int) -> int: return kvo + service_table_address + (func >> 4) find_address = kvo_calulator else: array_subtype = "unsigned long" def passthrough(func: int) -> int: return func find_address = passthrough functions = ntkrnlmp.object(object_type="array", offset=service_table_address, subtype=ntkrnlmp.get_type(array_subtype), count=service_limit) for idx, function_obj in enumerate(functions): function = find_address(function_obj) module_symbols = collection.get_module_symbols_by_absolute_location( function) for module_name, symbol_generator in module_symbols: symbols_found = False for symbol in symbol_generator: symbols_found = True yield (0, (idx, format_hints.Hex(function), module_name, symbol.split(constants.BANG)[1])) if not symbols_found: yield (0, (idx, format_hints.Hex(function), module_name, renderers.NotAvailableValue()))
def get_available_pages(self) -> Iterable[Tuple[int, int, int]]: """Get the available pages that correspond to a cached file. The tuples generated are (physical_offset, file_offset, page_size). """ symbol_table_name = self.get_symbol_table_name() mmpte_type = self._context.symbol_space.get_type(symbol_table_name + constants.BANG + "_MMPTE") mmpte_size = mmpte_type.size subsection = self.get_subsection() is_64bit = symbols.symbol_table_is_64bit(self._context, symbol_table_name) is_pae = self._context.layers[self.vol.layer_name].metadata.get( "pae", False) # This is a null-terminated single-linked list. while subsection != 0: try: if subsection.ControlArea != self.vol.offset: break except exceptions.InvalidAddressException: break # The offset into the file is stored implicitly based on the PTE location within the Subsection. starting_sector = subsection.StartingSector subsection_offset = starting_sector * 0x200 # Similar to the check in is_valid(), make sure the SubsectionBase is not page aligned. # if subsection.SubsectionBase & self.PAGE_MASK == 0: # break ptecount = 0 while ptecount < subsection.PtesInSubsection: pte_offset = subsection.SubsectionBase + (mmpte_size * ptecount) file_offset = subsection_offset + ptecount * 0x1000 try: mmpte = self.get_pte(pte_offset) except exceptions.InvalidAddressException: ptecount += 1 continue # First we check if the entry is valid. If so, then we get the physical offset. # The valid entries are actually handled by the hardware. if mmpte.u.Hard.Valid == 1: physoffset = mmpte.u.Hard.PageFrameNumber << 12 yield physoffset, file_offset, self.PAGE_SIZE elif mmpte.u.Soft.Prototype == 1: if not is_64bit and not is_pae: subsection_offset = ( (mmpte.u.Subsect.SubsectionAddressHigh << 7) | (mmpte.u.Subsect.SubsectionAddressLow << 3)) # If the entry is not a valid physical address then see if it is in transition. elif mmpte.u.Trans.Transition == 1: # TODO: Fix appropriately in a future release. # Currently just a temporary workaround to deal with custom bit flag # in the PFN field for pages in transition state. # See https://github.com/volatilityfoundation/volatility3/pull/475 physoffset = (mmpte.u.Trans.PageFrameNumber & ((1 << 33) - 1)) << 12 yield physoffset, file_offset, self.PAGE_SIZE # Go to the next PTE entry ptecount += 1 # Go to the next Subsection in the single-linked list subsection = subsection.NextSubsection
def _generator(self): vmlinux = contexts.Module(self.context, self.config['vmlinux'], self.config['primary'], 0) modules = lsmod.Lsmod.list_modules(self.context, self.config['primary'], self.config['vmlinux']) handlers = linux.LinuxUtilities.generate_kernel_handler_info(self.context, self.config['primary'], self.config['vmlinux'], modules) is_32bit = not symbols.symbol_table_is_64bit(self.context, self.config["vmlinux"]) idt_table_size = 256 address_mask = self.context.layers[self.config['primary']].address_mask # hw handlers + system call check_idxs = list(range(0, 20)) + [128] if is_32bit: if vmlinux.has_type("gate_struct"): idt_type = "gate_struct" else: idt_type = "desc_struct" else: if vmlinux.has_type("gate_struct64"): idt_type = "gate_struct64" elif vmlinux.has_type("gate_struct"): idt_type = "gate_struct" else: idt_type = "idt_desc" addrs = vmlinux.object_from_symbol("idt_table") table = vmlinux.object(object_type = 'array', offset = addrs.vol.offset, subtype = vmlinux.get_type(idt_type), count = idt_table_size) for i in check_idxs: ent = table[i] if not ent: continue if hasattr(ent, "Address"): idt_addr = ent.Address else: low = ent.offset_low middle = ent.offset_middle if hasattr(ent, "offset_high"): high = ent.offset_high else: high = 0 idt_addr = (high << 32) | (middle << 16) | low idt_addr = idt_addr & address_mask module_name, symbol_name = linux.LinuxUtilities.lookup_module_address(self.context, handlers, idt_addr) yield (0, [format_hints.Hex(i), format_hints.Hex(idt_addr), module_name, symbol_name])
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 versions.is_windows_xp(context=context, symbol_table=symbol_table) and not is_64bit: symbol_filename = "services-xp-x86" elif versions.is_xp_or_2003(context=context, symbol_table=symbol_table) and is_64bit: symbol_filename = "services-xp-2003-x64" elif versions.is_win10_16299_or_later( context=context, symbol_table=symbol_table) and is_64bit: symbol_filename = "services-win10-16299-x64" elif versions.is_win10_16299_or_later( context=context, symbol_table=symbol_table) and not is_64bit: symbol_filename = "services-win10-16299-x86" elif versions.is_win10_up_to_15063( context=context, symbol_table=symbol_table) and is_64bit: symbol_filename = "services-win8-x64" elif versions.is_win10_up_to_15063( context=context, symbol_table=symbol_table) and not is_64bit: symbol_filename = "services-win8-x86" elif versions.is_win10_15063(context=context, symbol_table=symbol_table) and is_64bit: symbol_filename = "services-win10-15063-x64" elif versions.is_win10_15063( context=context, symbol_table=symbol_table) and not is_64bit: symbol_filename = "services-win10-15063-x86" elif versions.is_windows_8_or_later( context=context, symbol_table=symbol_table) and is_64bit: symbol_filename = "services-win8-x64" elif versions.is_windows_8_or_later( context=context, symbol_table=symbol_table) and not is_64bit: symbol_filename = "services-win8-x86" elif versions.is_vista_or_later( context=context, symbol_table=symbol_table) and is_64bit: symbol_filename = "services-vista-x64" elif versions.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, os.path.join("windows", "services"), symbol_filename, class_types=services.class_types, native_types=native_types)
def generate_pool_scan(cls, context: interfaces.context.ContextInterface, layer_name: str, symbol_table: str, constraints: List[PoolConstraint]) \ -> Generator[Tuple[ PoolConstraint, interfaces.objects.ObjectInterface, interfaces.objects.ObjectInterface], None, None]: """ 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 constraints: List of pool constraints used to limit the scan results Returns: Iterable of tuples, containing the constraint that matched, the object from memory, the object header used to determine the object """ # get the object type map type_map = handles.Handles.get_type_map(context = context, layer_name = layer_name, symbol_table = symbol_table) cookie = handles.Handles.find_cookie(context = context, layer_name = layer_name, symbol_table = symbol_table) is_windows_10 = versions.is_windows_10(context, symbol_table) is_windows_8_or_later = versions.is_windows_8_or_later(context, symbol_table) # start off with the primary virtual layer scan_layer = layer_name # switch to a non-virtual layer if necessary if not is_windows_10: scan_layer = context.layers[scan_layer].config['memory_layer'] if symbols.symbol_table_is_64bit(context, symbol_table): alignment = 0x10 else: alignment = 8 for constraint, header in cls.pool_scan(context, scan_layer, symbol_table, constraints, alignment = alignment): mem_objects = header.get_object(constraint = constraint, use_top_down = is_windows_8_or_later, native_layer_name = layer_name, kernel_symbol_table = symbol_table) for mem_object in mem_objects: if mem_object is None: vollog.log(constants.LOGLEVEL_VVV, f"Cannot create an instance of {constraint.type_name}") continue if constraint.object_type is not None and not constraint.skip_type_test: try: if mem_object.get_object_header().get_object_type(type_map, cookie) != constraint.object_type: continue except exceptions.InvalidAddressException: vollog.log(constants.LOGLEVEL_VVV, f"Cannot test instance type check for {constraint.type_name}") continue yield constraint, mem_object, header
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=os.path.join("windows", "bigpools"), 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]: kernel = self.context.modules[self.config['kernel']] physical_layer_name = self.context.layers[ kernel.layer_name].config.get('memory_layer', None) # Decide of Memory Dump Architecture layer = self.context.layers[physical_layer_name] architecture = "intel" if not symbols.symbol_table_is_64bit( self.context, kernel.symbol_table_name) else "intel64" # Read in the Symbol File symbol_table = intermed.IntermediateSymbolTable.create( context=self.context, config_path=self.config_path, sub_path="windows", filename="mbr", class_types={ 'PARTITION_TABLE': mbr.PARTITION_TABLE, 'PARTITION_ENTRY': mbr.PARTITION_ENTRY }) partition_table_object = symbol_table + constants.BANG + "PARTITION_TABLE" # Define Signature and Data Length mbr_signature = b"\x55\xAA" mbr_length = 0x200 bootcode_length = 0x1B8 # Scan the Layer for Raw Master Boot Record (MBR) and parse the fields for offset, _value in layer.scan( context=self.context, scanner=scanners.MultiStringScanner(patterns=[mbr_signature])): try: mbr_start_offset = offset - (mbr_length - len(mbr_signature)) partition_table = self.context.object(partition_table_object, offset=mbr_start_offset, layer_name=layer.name) # Extract only BootCode full_mbr = layer.read(mbr_start_offset, mbr_length, pad=True) bootcode = full_mbr[:bootcode_length] all_zeros = None if bootcode: all_zeros = bootcode.count(b"\x00") == len(bootcode) if not all_zeros: partition_entries = [ partition_table.FirstEntry, partition_table.SecondEntry, partition_table.ThirdEntry, partition_table.FourthEntry ] if not self.config.get("full", True): yield (0, (format_hints.Hex(offset), partition_table.get_disk_signature(), self.get_hash(bootcode), self.get_hash(full_mbr), renderers.NotApplicableValue(), renderers.NotApplicableValue(), renderers.NotApplicableValue(), renderers.NotApplicableValue(), interfaces.renderers.Disassembly( bootcode, 0, architecture))) else: yield (0, (format_hints.Hex(offset), partition_table.get_disk_signature(), self.get_hash(bootcode), self.get_hash(full_mbr), renderers.NotApplicableValue(), renderers.NotApplicableValue(), renderers.NotApplicableValue(), renderers.NotApplicableValue(), renderers.NotApplicableValue(), renderers.NotApplicableValue(), renderers.NotApplicableValue(), renderers.NotApplicableValue(), renderers.NotApplicableValue(), renderers.NotApplicableValue(), renderers.NotApplicableValue(), renderers.NotApplicableValue(), renderers.NotApplicableValue(), interfaces.renderers.Disassembly( bootcode, 0, architecture), format_hints.HexBytes(bootcode))) for partition_index, partition_entry_object in enumerate( partition_entries, start=1): if not self.config.get("full", True): yield (1, ( format_hints.Hex(offset), partition_table.get_disk_signature(), self.get_hash(bootcode), self.get_hash(full_mbr), partition_index, partition_entry_object.is_bootable(), partition_entry_object.get_partition_type(), format_hints.Hex(partition_entry_object. get_size_in_sectors()), renderers.NotApplicableValue())) else: yield (1, ( format_hints.Hex(offset), partition_table.get_disk_signature(), self.get_hash(bootcode), self.get_hash(full_mbr), partition_index, partition_entry_object.is_bootable(), format_hints.Hex(partition_entry_object. get_bootable_flag()), partition_entry_object.get_partition_type(), format_hints.Hex( partition_entry_object.PartitionType), format_hints.Hex( partition_entry_object.get_starting_lba()), partition_entry_object.get_starting_cylinder(), partition_entry_object.get_starting_chs(), partition_entry_object.get_starting_sector(), partition_entry_object.get_ending_cylinder(), partition_entry_object.get_ending_chs(), partition_entry_object.get_ending_sector(), format_hints.Hex(partition_entry_object. get_size_in_sectors()), renderers.NotApplicableValue(), renderers.NotApplicableValue())) else: vollog.log( constants.LOGLEVEL_VVVV, f"Not a valid MBR: Data all zeroed out : {format_hints.Hex(offset)}" ) continue except exceptions.PagedInvalidAddressException as excp: vollog.log( constants.LOGLEVEL_VVVV, f"Invalid address identified in guessed MBR: {hex(excp.invalid_address)}" ) continue
def populate_processes(self): """Populate the tree view with processes""" root = self.model.invisibleRootItem() for proc in self.window.runtime.get_procs(): root = self.model.invisibleRootItem() # This will hold the export symbol table for an entire process exports = {} # System process? if proc.UniqueProcessId == 4: image_base = self.window.runtime.context.config[ "plugins.PsList.PsList.primary.kernel_virtual_offset"] # Create internal filename filename = "{} ({}) - {:#08x}".format("System", proc.UniqueProcessId, image_base) # Create process tree view item process_item = QtGui.QStandardItem("System") process_item.setData( { "filename": filename, "proc": proc, "entry": None, "exports": exports, "image_base": image_base }, QtCore.Qt.UserRole) process_item.setUserTristate(True) process_item.setCheckable(True) # Determine kernel architecture specifics if symbols.symbol_table_is_64bit( self.window.runtime.context, self.window.runtime. context.config["plugins.PsList.PsList.nt_symbols"]): ptr_mask = 0xffffffffffffffff ptr_width = 16 else: ptr_mask = 0xffffffff ptr_width = 8 root.appendRow([ process_item, QtGui.QStandardItem("{}".format(proc.UniqueProcessId)), QtGui.QStandardItem("{}".format( proc.InheritedFromUniqueProcessId)), QtGui.QStandardItem("0x{:0{w}x}".format(image_base & ptr_mask, w=ptr_width)), QtGui.QStandardItem(""), QtGui.QStandardItem(""), QtGui.QStandardItem(""), QtGui.QStandardItem("") ]) root = process_item # Iterate over kernel modules for module in self.window.runtime.get_modules(): try: process_item = self.make_process_item( proc, module, exports) root.appendRow([ process_item, QtGui.QStandardItem("{}".format( proc.UniqueProcessId)), QtGui.QStandardItem(""), QtGui.QStandardItem("0x{:0{w}x}".format( int(module.DllBase) & ptr_mask, w=ptr_width)), QtGui.QStandardItem(""), QtGui.QStandardItem(""), QtGui.QStandardItem(""), QtGui.QStandardItem("") ]) except framework.exceptions.PagedInvalidAddressException: pass # Iterate over load order modules for entry in proc.load_order_modules(): try: process_item = self.make_process_item( proc, entry, exports, expandable=bool( root == self.model.invisibleRootItem())) except framework.exceptions.PagedInvalidAddressException: continue # Determine process architecture specifics if proc.get_is_wow64(): ptr_mask = 0xffffffffffffffff ptr_width = 16 else: ptr_mask = 0xffffffff ptr_width = 8 if root == self.model.invisibleRootItem(): # Include full details for the main process root.appendRow([ process_item, QtGui.QStandardItem("{}".format(proc.UniqueProcessId)), QtGui.QStandardItem("{}".format( proc.InheritedFromUniqueProcessId)), QtGui.QStandardItem("0x{:0{w}x}".format( int(entry.DllBase) & ptr_mask, w=ptr_width)), QtGui.QStandardItem("{}".format(proc.ActiveThreads)), QtGui.QStandardItem("{}".format( proc.get_handle_count())), QtGui.QStandardItem("{}".format( proc.get_create_time())), QtGui.QStandardItem("{}".format(proc.get_is_wow64())) ]) root = process_item else: # Add details for the DLL root.appendRow([ process_item, QtGui.QStandardItem("{}".format(proc.UniqueProcessId)), QtGui.QStandardItem("{}".format( proc.InheritedFromUniqueProcessId)), QtGui.QStandardItem("0x{:0{w}x}".format( int(entry.DllBase) & ptr_mask, w=ptr_width)), QtGui.QStandardItem(""), QtGui.QStandardItem(""), QtGui.QStandardItem(""), QtGui.QStandardItem("") ]) for i in range(0, 7): self.treeview.resizeColumnToContents(i)
def determine_tcpip_version(cls, context: interfaces.context.ContextInterface, layer_name: str, nt_symbol_table: str) -> str: """Tries to determine which symbol filename to use for the image's tcpip driver. The logic is partially taken from the info plugin. Args: context: The context to retrieve required elements (layers, symbol tables) from layer_name: The name of the layer on which to operate nt_symbol_table: The name of the table containing the kernel symbols Returns: The filename of the symbol table to use. """ # while the failsafe way to determine the version of tcpip.sys would be to # extract the driver and parse its PE header containing the versionstring, # unfortunately that header is not guaranteed to persist within memory. # therefore we determine the version based on the kernel version as testing # with several windows versions has showed this to work out correctly. is_64bit = symbols.symbol_table_is_64bit(context, nt_symbol_table) is_18363_or_later = versions.is_win10_18363_or_later( context=context, symbol_table=nt_symbol_table) if is_64bit: arch = "x64" else: arch = "x86" vers = info.Info.get_version_structure(context, layer_name, nt_symbol_table) kuser = info.Info.get_kuser_structure(context, layer_name, nt_symbol_table) try: vers_minor_version = int(vers.MinorVersion) nt_major_version = int(kuser.NtMajorVersion) nt_minor_version = int(kuser.NtMinorVersion) except ValueError: # vers struct exists, but is not an int anymore? raise NotImplementedError( "Kernel Debug Structure version format not supported!") except: # unsure what to raise here. Also, it might be useful to add some kind of fallback, # either to a user-provided version or to another method to determine tcpip.sys's version raise exceptions.VolatilityException( "Kernel Debug Structure missing VERSION/KUSER structure, unable to determine Windows version!" ) vollog.debug("Determined OS Version: {}.{} {}.{}".format( kuser.NtMajorVersion, kuser.NtMinorVersion, vers.MajorVersion, vers.MinorVersion)) if nt_major_version == 10 and arch == "x64": # win10 x64 has an additional class type we have to include. class_types = network.win10_x64_class_types else: # default to general class types class_types = network.class_types # these versions are listed explicitly because symbol files differ based on # version *and* architecture. this is currently the clearest way to show # the differences, even if it introduces a fair bit of redundancy. # furthermore, it is easy to append new versions. if arch == "x86": version_dict = { (6, 0, 6000): "netscan-vista-x86", (6, 0, 6001): "netscan-vista-x86", (6, 0, 6002): "netscan-vista-x86", (6, 0, 6003): "netscan-vista-x86", (6, 1, 7600): "netscan-win7-x86", (6, 1, 7601): "netscan-win7-x86", (6, 1, 8400): "netscan-win7-x86", (6, 2, 9200): "netscan-win8-x86", (6, 3, 9600): "netscan-win81-x86", (10, 0, 10240): "netscan-win10-10240-x86", (10, 0, 10586): "netscan-win10-10586-x86", (10, 0, 14393): "netscan-win10-14393-x86", (10, 0, 15063): "netscan-win10-15063-x86", (10, 0, 16299): "netscan-win10-15063-x86", (10, 0, 17134): "netscan-win10-17134-x86", (10, 0, 17763): "netscan-win10-17134-x86", (10, 0, 18362): "netscan-win10-17134-x86", (10, 0, 18363): "netscan-win10-17134-x86" } else: version_dict = { (6, 0, 6000): "netscan-vista-x64", (6, 0, 6001): "netscan-vista-sp12-x64", (6, 0, 6002): "netscan-vista-sp12-x64", (6, 0, 6003): "netscan-vista-sp12-x64", (6, 1, 7600): "netscan-win7-x64", (6, 1, 7601): "netscan-win7-x64", (6, 1, 8400): "netscan-win7-x64", (6, 2, 9200): "netscan-win8-x64", (6, 3, 9600): "netscan-win81-x64", (10, 0, 10240): "netscan-win10-x64", (10, 0, 10586): "netscan-win10-x64", (10, 0, 14393): "netscan-win10-x64", (10, 0, 15063): "netscan-win10-15063-x64", (10, 0, 16299): "netscan-win10-16299-x64", (10, 0, 17134): "netscan-win10-17134-x64", (10, 0, 17763): "netscan-win10-17763-x64", (10, 0, 18362): "netscan-win10-18362-x64", (10, 0, 18363): "netscan-win10-18363-x64", (10, 0, 19041): "netscan-win10-19041-x64" } # special use case: Win10_18363 is not recognized by windows.info as 18363 # because all kernel file headers and debug structures report 18363 as # "10.0.18362.1198" with the last part being incremented. However, we can use # os_distinguisher to differentiate between 18362 and 18363 if vers_minor_version == 18362 and is_18363_or_later: vollog.debug( "Detected 18363 data structures: working with 18363 symbol table." ) vers_minor_version = 18363 # when determining the symbol file we have to consider the following cases: # the determined version's symbol file is found by intermed.create -> proceed # the determined version's symbol file is not found by intermed -> intermed will throw an exc and abort # the determined version has no mapped symbol file -> if win10 use latest, otherwise throw exc # windows version cannot be determined -> throw exc filename = version_dict.get( (nt_major_version, nt_minor_version, vers_minor_version)) if not filename: # no match on filename means that we possibly have a version newer than those listed here. # try to grab the latest supported version of the current image NT version. If that symbol # version does not work, support has to be added manually. current_versions = [ key for key in list(version_dict.keys()) if key[0] == nt_major_version and key[1] == nt_minor_version ] current_versions.sort() if current_versions: latest_version = current_versions[-1] filename = version_dict.get(latest_version) vollog.debug( "Unable to find exact matching symbol file, going with latest: {}" .format(filename)) else: raise NotImplementedError( "This version of Windows is not supported: {}.{} {}.{}!". format(nt_major_version, nt_minor_version, vers.MajorVersion, vers_minor_version)) vollog.debug("Determined symbol filename: {}".format(filename)) return filename, class_types
def _generator(self): kernel = self.context.modules[self.config['kernel']] layer_name = kernel.layer_name symbol_table = kernel.symbol_table_name layer = self.context.layers[layer_name] table = self.context.symbol_space[symbol_table] kdbg = self.get_kdbg_structure(self.context, self.config_path, layer_name, symbol_table) yield (0, ("Kernel Base", hex(layer.config["kernel_virtual_offset"]))) yield (0, ("DTB", hex(layer.config["page_map_offset"]))) yield (0, ("Symbols", table.config["isf_url"])) yield (0, ("Is64Bit", str(symbols.symbol_table_is_64bit(self.context, symbol_table)))) yield (0, ("IsPAE", str(self.context.layers[layer_name].metadata.get("pae", False)))) for i, layer in self.get_depends(self.context, layer_name): yield (0, (layer.name, f"{i} {layer.__class__.__name__}")) if kdbg.Header.OwnerTag == 0x4742444B: yield (0, ("KdDebuggerDataBlock", hex(kdbg.vol.offset))) yield (0, ("NTBuildLab", kdbg.get_build_lab())) yield (0, ("CSDVersion", str(kdbg.get_csdversion()))) vers = self.get_version_structure(self.context, layer_name, symbol_table) yield (0, ("KdVersionBlock", hex(vers.vol.offset))) yield (0, ("Major/Minor", f"{vers.MajorVersion}.{vers.MinorVersion}")) yield (0, ("MachineType", str(vers.MachineType))) ntkrnlmp = self.get_kernel_module(self.context, layer_name, symbol_table) cpu_count_offset = ntkrnlmp.get_symbol("KeNumberProcessors").address cpu_count = ntkrnlmp.object(object_type="unsigned int", layer_name=layer_name, offset=cpu_count_offset) yield (0, ("KeNumberProcessors", str(cpu_count))) kuser = self.get_kuser_structure(self.context, layer_name, symbol_table) yield (0, ("SystemTime", str(kuser.SystemTime.get_time()))) yield (0, ("NtSystemRoot", str( kuser.NtSystemRoot.cast("string", encoding="utf-16", errors="replace", max_length=260)))) yield (0, ("NtProductType", str(kuser.NtProductType.description))) yield (0, ("NtMajorVersion", str(kuser.NtMajorVersion))) yield (0, ("NtMinorVersion", str(kuser.NtMinorVersion))) # yield (0, ("KdDebuggerEnabled", "True" if kuser.KdDebuggerEnabled else "False")) # yield (0, ("SafeBootMode", "True" if kuser.SafeBootMode else "False")) nt_header = self.get_ntheader_structure(self.context, self.config_path, layer_name) yield (0, ("PE MajorOperatingSystemVersion", str(nt_header.OptionalHeader.MajorOperatingSystemVersion))) yield (0, ("PE MinorOperatingSystemVersion", str(nt_header.OptionalHeader.MinorOperatingSystemVersion))) yield (0, ("PE Machine", str(nt_header.FileHeader.Machine))) yield (0, ("PE TimeDateStamp", time.asctime(time.gmtime(nt_header.FileHeader.TimeDateStamp))))