def _generator(self, syshive, sechive): bootkey = hashdump.Hashdump.get_bootkey(syshive) if not bootkey: raise ValueError('Unable to find bootkey') is_vista_or_later = poolscanner.os_distinguisher( version_check=lambda x: x >= (6, 0), fallback_checks=[("KdCopyDataBlock", None, True)]) vista_or_later = is_vista_or_later( context=self.context, symbol_table=self.config['nt_symbols']) lsakey = lsadump.Lsadump.get_lsa_key(sechive, bootkey, vista_or_later) if not lsakey: raise ValueError('Unable to find lsa key') nlkm = self.get_nlkm(sechive, lsakey, vista_or_later) if not nlkm: raise ValueError('Unable to find nlkma key') cache = sechive.get_key("Cache") if not cache: raise ValueError('Unable to find cache key') for cache_item in cache.get_values(): if cache_item.Name == "NL$Control": continue data = sechive.read(cache_item.Data + 4, cache_item.DataLength) if data == None: continue (uname_len, domain_len, domain_name_len, enc_data, ch) = self.parse_cache_entry(data) # Skip if nothing in this cache entry if uname_len == 0 or len(ch) == 0: continue dec_data = self.decrypt_hash(enc_data, nlkm, ch, not vista_or_later) (username, domain, domain_name, hashh) = self.parse_decrypted_cache(dec_data, uname_len, domain_len, domain_name_len) yield (0, (username, domain, domain_name, hashh))
def _generator(self, syshive, sechive): is_vista_or_later = poolscanner.os_distinguisher(version_check = lambda x: x >= (6, 0), fallback_checks = [("KdCopyDataBlock", None, True)]) vista_or_later = is_vista_or_later(context = self.context, symbol_table = self.config['nt_symbols']) bootkey = hashdump.Hashdump.get_bootkey(syshive) lsakey = self.get_lsa_key(sechive, bootkey, vista_or_later) if not bootkey: raise ValueError('Unable to find bootkey') if not lsakey: raise ValueError('Unable to find lsa key') secrets_key = sechive.get_key('Policy\\Secrets') if not secrets_key: raise ValueError('Unable to find secrets key') for key in secrets_key.get_subkeys(): sec_val_key = sechive.get_key('Policy\\Secrets\\' + key.get_key_path().split('\\')[3] + '\\CurrVal') if not sec_val_key: continue enc_secret_value = next(sec_val_key.get_values()) if not enc_secret_value: continue enc_secret = sechive.read(enc_secret_value.Data + 4, enc_secret_value.DataLength) if not enc_secret: continue if not vista_or_later: secret = self.decrypt_secret(enc_secret[0xC:], lsakey) else: secret = self.decrypt_aes(enc_secret, lsakey) yield (0, (key.get_name(), secret.decode('latin1'), secret))
def _generator(self, procs): conhost_pids = [] appinfo_service_pid = 0 suspicious_procs = {} # For unknown reasons, the owner process id will point to the actual parent id + 2 with the exception of conhost which is +1. # OWNER_PROCESS_ID_OFFSET will be used to get the real parent PID from the OwnerProcessId field. OWNER_PROCESS_ID_OFFSET = -2 # CONHOST_PROCESS_ID_OFFSET will be used to check if OwnerProcessId actually points to a console host process. CONHOST_PROCESS_ID_OFFSET = -1 # OwnerProcessId only exists as a union from Windows 8 or later. is_win8_or_later = poolscanner.os_distinguisher( version_check=lambda x: x >= (6, 2), fallback_checks=[("_EPROCESS", "OwnerProcessId", True)]) if not is_win8_or_later(self.context, self.config["nt_symbols"]): vollog.warning( "check_parent_spoof doesn't work in Windows versions prior to Windows 8" ) return for proc in procs: if not proc.has_member("OwnerProcessId"): continue process_name = utility.array_to_string(proc.ImageFileName) inherited_process_pid = proc.InheritedFromUniqueProcessId # Save appinfo's service pid to exclude, this is hosted only by svchost.exe. if (appinfo_service_pid == 0) and ("svchost" in process_name): for entry in proc.load_order_modules(): dll_name = renderers.UnreadableValue() try: dll_name = entry.FullDllName.get_string() if "appinfo.dll" not in dll_name: continue except exceptions.InvalidAddressException: continue appinfo_service_pid = proc.UniqueProcessId # If a process was already flagged as suspicious and it's actually pointing to the appinfo service, it should not be flagged. if proc.UniqueProcessId in suspicious_procs: suspicious_procs.pop(proc.UniqueProcessId) # The Owner process id field is used a union for the owner process id and console host process id # We save the conhost process id's in order to exclude them from the check. if "conhost" in process_name: conhost_pids.append(proc.UniqueProcessId) # If a process was already flagged as suspicious and it's actually pointing to a conhost process, it should not be flagged. # Since suspicious_procs saves everything at OWNER_PROCESS_ID_OFFSET we correct the offset check here by adding the conhost offset. conhost_pid = proc.UniqueProcessId + CONHOST_PROCESS_ID_OFFSET if conhost_pid in suspicious_procs: suspicious_procs.pop(conhost_pid) try: owner_process_pid = proc.OwnerProcessId # There are several checks here: # Most services and system processes are initialized with 0, exclude them and System by checking if the pid is under 0. # AppInfo service is responsible for UAC and could be triggered as a false positive as well. if (owner_process_pid < 10) or (owner_process_pid + OWNER_PROCESS_ID_OFFSET == inherited_process_pid) \ or (owner_process_pid + OWNER_PROCESS_ID_OFFSET == appinfo_service_pid) or (owner_process_pid + CONHOST_PROCESS_ID_OFFSET) in conhost_pids: continue except Exception as e: continue suspicious_procs[owner_process_pid + OWNER_PROCESS_ID_OFFSET] = ( proc.UniqueProcessId, process_name, inherited_process_pid, owner_process_pid + OWNER_PROCESS_ID_OFFSET) for owner_process_id, suspicious_proc in suspicious_procs.items(): yield (0, (suspicious_proc[0], suspicious_proc[1], suspicious_proc[2], suspicious_proc[3]))
class SvcScan(interfaces.plugins.PluginInterface): """Scans for windows services.""" _version = (1, 0, 0) is_vista_or_later = poolscanner.os_distinguisher( version_check=lambda x: x >= (6, 0), fallback_checks=[("KdCopyDataBlock", None, True)]) is_windows_xp = poolscanner.os_distinguisher( version_check=lambda x: (5, 1) <= x < (5, 2), fallback_checks=[("KdCopyDataBlock", None, False), ("_HANDLE_TABLE", "HandleCount", True)]) is_xp_or_2003 = poolscanner.os_distinguisher( version_check=lambda x: (5, 1) <= x < (6, 0), fallback_checks=[("KdCopyDataBlock", None, False), ("_HANDLE_TABLE", "HandleCount", True)]) is_win10_up_to_15063 = poolscanner.os_distinguisher( version_check=lambda x: (10, 0) <= x < (10, 0, 16299), fallback_checks=[("ObHeaderCookie", None, True), ("_HANDLE_TABLE", "HandleCount", False), ("ObHeaderCookie", None, True)]) is_win10_16299_or_later = poolscanner.os_distinguisher( version_check=lambda x: x >= (10, 0, 16299), fallback_checks=[("ObHeaderCookie", None, True), ("_HANDLE_TABLE", "HandleCount", False), ("ObHeaderCookie", None, True)]) @classmethod def get_requirements( cls) -> List[interfaces.configuration.RequirementInterface]: # Since we're calling the plugin, make sure we have the plugin's requirements return [ requirements.TranslationLayerRequirement( name='primary', description='Memory layer for the kernel', architectures=["Intel32", "Intel64"]), requirements.SymbolTableRequirement( name="nt_symbols", description="Windows kernel symbols"), requirements.PluginRequirement(name='pslist', plugin=pslist.PsList, version=(1, 0, 0)), requirements.PluginRequirement(name='poolscanner', plugin=poolscanner.PoolScanner, version=(1, 0, 0)), requirements.PluginRequirement(name='vadyarascan', plugin=vadyarascan.VadYaraScan, version=(1, 0, 0)) ] @staticmethod def get_record_tuple(service_record: interfaces.objects.ObjectInterface): return (format_hints.Hex(service_record.vol.offset), service_record.Order, service_record.get_pid(), service_record.Start.description, service_record.State.description, service_record.get_type(), service_record.get_name(), service_record.get_display(), service_record.get_binary()) @staticmethod def create_service_table(context: interfaces.context.ContextInterface, symbol_table: str, config_path: str) -> str: """Constructs a symbol table containing the symbols for services depending upon the operating system in use. Args: context: The context to retrieve required elements (layers, symbol tables) from symbol_table: The name of the table containing the kernel symbols config_path: The configuration path for any settings required by the new table Returns: A symbol table containing the symbols necessary for services """ native_types = context.symbol_space[symbol_table].natives is_64bit = symbols.symbol_table_is_64bit(context, symbol_table) if SvcScan.is_windows_xp(context=context, symbol_table=symbol_table) and not is_64bit: symbol_filename = "services-xp-x86" elif SvcScan.is_xp_or_2003(context=context, symbol_table=symbol_table) and is_64bit: symbol_filename = "services-xp-2003-x64" elif poolscanner.PoolScanner.is_windows_8_or_later( context=context, symbol_table=symbol_table) and is_64bit: symbol_filename = "services-win8-x64" elif poolscanner.PoolScanner.is_windows_8_or_later( context=context, symbol_table=symbol_table) and not is_64bit: symbol_filename = "services-win8-x86" elif SvcScan.is_win10_up_to_15063( context=context, symbol_table=symbol_table) and is_64bit: symbol_filename = "services-win10-15063-x64" elif SvcScan.is_win10_up_to_15063( context=context, symbol_table=symbol_table) and not is_64bit: symbol_filename = "services-win10-15063-x86" elif SvcScan.is_win10_16299_or_later( context=context, symbol_table=symbol_table) and is_64bit: symbol_filename = "services-win10-16299-x64" elif SvcScan.is_win10_16299_or_later( context=context, symbol_table=symbol_table) and not is_64bit: symbol_filename = "services-win10-16299-x86" elif SvcScan.is_vista_or_later(context=context, symbol_table=symbol_table) and is_64bit: symbol_filename = "services-vista-x64" elif SvcScan.is_vista_or_later( context=context, symbol_table=symbol_table) and not is_64bit: symbol_filename = "services-vista-x86" else: raise NotImplementedError( "This version of Windows is not supported!") return intermed.IntermediateSymbolTable.create( context, config_path, "windows", symbol_filename, class_types=services.class_types, native_types=native_types) def _generator(self): service_table_name = self.create_service_table( self.context, self.config["nt_symbols"], self.config_path) relative_tag_offset = self.context.symbol_space.get_type( service_table_name + constants.BANG + "_SERVICE_RECORD").relative_child_offset("Tag") filter_func = pslist.PsList.create_name_filter(["services.exe"]) is_vista_or_later = SvcScan.is_vista_or_later( context=self.context, symbol_table=self.config["nt_symbols"]) if is_vista_or_later: service_tag = b"serH" else: service_tag = b"sErv" seen = [] for task in pslist.PsList.list_processes( context=self.context, layer_name=self.config['primary'], symbol_table=self.config['nt_symbols'], filter_func=filter_func): try: proc_layer_name = task.add_process_layer() except exceptions.InvalidAddressException: continue layer = self.context.layers[proc_layer_name] for offset in layer.scan( context=self.context, scanner=scanners.BytesScanner(needle=service_tag), sections=vadyarascan.VadYaraScan.get_vad_maps(task)): if not is_vista_or_later: service_record = self.context.object( service_table_name + constants.BANG + "_SERVICE_RECORD", offset=offset - relative_tag_offset, layer_name=proc_layer_name) if not service_record.is_valid(): continue yield (0, self.get_record_tuple(service_record)) else: service_header = self.context.object( service_table_name + constants.BANG + "_SERVICE_HEADER", offset=offset, layer_name=proc_layer_name) if not service_header.is_valid(): continue # since we walk the s-list backwards, if we've seen # an object, then we've also seen all objects that # exist before it, thus we can break at that time. for service_record in service_header.ServiceRecord.traverse( ): if service_record in seen: break seen.append(service_record) yield (0, self.get_record_tuple(service_record)) def run(self): return renderers.TreeGrid([ ('Offset', format_hints.Hex), ('Order', int), ('Pid', int), ('Start', str), ('State', str), ('Type', str), ('Name', str), ('Display', str), ('Binary', str), ], self._generator())
class BigPools(interfaces.plugins.PluginInterface): """List big page pools.""" _version = (1, 0, 0) is_vista_or_later = poolscanner.os_distinguisher( version_check=lambda x: x >= (6, 0), fallback_checks=[("KdCopyDataBlock", None, True)]) is_win10 = poolscanner.os_distinguisher(version_check=lambda x: (10, 0) <= x, fallback_checks=[ ("ObHeaderCookie", None, True), ("_HANDLE_TABLE", "HandleCount", False) ]) @classmethod def get_requirements( cls) -> List[interfaces.configuration.RequirementInterface]: # Since we're calling the plugin, make sure we have the plugin's requirements return [ requirements.TranslationLayerRequirement( name='primary', description='Memory layer for the kernel', architectures=["Intel32", "Intel64"]), requirements.SymbolTableRequirement( name="nt_symbols", description="Windows kernel symbols"), requirements.StringRequirement( name='tags', description= "Comma separated list of pool tags to filter pools returned", optional=True, default=None) ] @classmethod def list_big_pools(cls, context: interfaces.context.ContextInterface, layer_name: str, symbol_table: str, tags: Optional[list] = None): """Returns the big page pool objects from the kernel PoolBigPageTable array. Args: context: The context to retrieve required elements (layers, symbol tables) from layer_name: The name of the layer on which to operate symbol_table: The name of the table containing the kernel symbols tags: An optional list of pool tags to filter big page pool tags by Yields: A big page pool object """ kvo = context.layers[layer_name].config['kernel_virtual_offset'] ntkrnlmp = context.module(symbol_table, layer_name=layer_name, offset=kvo) big_page_table_offset = ntkrnlmp.get_symbol("PoolBigPageTable").address big_page_table = ntkrnlmp.object(object_type="unsigned long long", offset=big_page_table_offset) big_page_table_size_offset = ntkrnlmp.get_symbol( "PoolBigPageTableSize").address big_page_table_size = ntkrnlmp.object( object_type="unsigned long", offset=big_page_table_size_offset) try: big_page_table_type = ntkrnlmp.get_type("_POOL_TRACKER_BIG_PAGES") except exceptions.SymbolError: # We have to manually load a symbol table is_vista_or_later = cls.is_vista_or_later(context, symbol_table) is_win10 = cls.is_win10(context, symbol_table) if is_win10: big_pools_json_filename = "bigpools-win10" elif is_vista_or_later: big_pools_json_filename = "bigpools-vista" else: big_pools_json_filename = "bigpools" if symbols.symbol_table_is_64bit(context, symbol_table): big_pools_json_filename += "-x64" else: big_pools_json_filename += "-x86" new_table_name = intermed.IntermediateSymbolTable.create( context=context, config_path=configuration.path_join( context.symbol_space[symbol_table].config_path, "bigpools"), sub_path="windows", filename=big_pools_json_filename, table_mapping={'nt_symbols': symbol_table}, class_types={ '_POOL_TRACKER_BIG_PAGES': extensions.pool.POOL_TRACKER_BIG_PAGES }) module = context.module(new_table_name, layer_name, offset=0) big_page_table_type = module.get_type("_POOL_TRACKER_BIG_PAGES") big_pools = ntkrnlmp.object(object_type="array", offset=big_page_table, subtype=big_page_table_type, count=big_page_table_size, absolute=True) for big_pool in big_pools: if big_pool.is_valid(): if tags is None or big_pool.get_key() in tags: yield big_pool def _generator( self) -> Iterator[Tuple[int, Tuple[int, str]]]: #, str, int]]]: if self.config.get("tags"): tags = [tag for tag in self.config["tags"].split(',')] else: tags = None for big_pool in self.list_big_pools( context=self.context, layer_name=self.config["primary"], symbol_table=self.config["nt_symbols"], tags=tags): num_bytes = big_pool.get_number_of_bytes() if not isinstance(num_bytes, interfaces.renderers.BaseAbsentValue): num_bytes = format_hints.Hex(num_bytes) yield (0, (format_hints.Hex(big_pool.Va), big_pool.get_key(), big_pool.get_pool_type(), num_bytes)) def run(self): return renderers.TreeGrid([ ('Allocation', format_hints.Hex), ('Tag', str), ('PoolType', str), ('NumberOfBytes', format_hints.Hex), ], self._generator())
class HiveScan(interfaces.plugins.PluginInterface): """Scans for registry hives present in a particular windows memory image.""" _version = (1, 0, 0) is_windows_8_1_or_later = poolscanner.os_distinguisher( version_check=lambda x: x >= (6, 3), fallback_checks=[("_KPRCB", "PendingTickFlags", True)]) @classmethod def get_requirements(cls): return [ requirements.TranslationLayerRequirement( name='primary', description='Memory layer for the kernel', architectures=["Intel32", "Intel64"]), requirements.SymbolTableRequirement( name="nt_symbols", description="Windows kernel symbols"), requirements.PluginRequirement(name='poolscanner', plugin=poolscanner.PoolScanner, version=(1, 0, 0)), requirements.PluginRequirement(name='bigpools', plugin=bigpools.BigPools, version=(1, 0, 0)), ] @classmethod def scan_hives(cls, context: interfaces.context.ContextInterface, layer_name: str, symbol_table: str) -> \ Iterable[interfaces.objects.ObjectInterface]: """Scans for hives using the poolscanner module and constraints or bigpools module with tag. Args: context: The context to retrieve required elements (layers, symbol tables) from layer_name: The name of the layer on which to operate symbol_table: The name of the table containing the kernel symbols Returns: A list of Hive objects as found from the `layer_name` layer based on Hive pool signatures """ is_64bit = symbols.symbol_table_is_64bit(context, symbol_table) is_windows_8_1_or_later = HiveScan.is_windows_8_1_or_later( context=context, symbol_table=symbol_table) if is_windows_8_1_or_later and is_64bit: kvo = context.layers[layer_name].config['kernel_virtual_offset'] ntkrnlmp = context.module(symbol_table, layer_name=layer_name, offset=kvo) for pool in bigpools.BigPools.list_big_pools( context, layer_name=layer_name, symbol_table=symbol_table, tags=["CM10"]): cmhive = ntkrnlmp.object(object_type="_CMHIVE", offset=pool.Va, absolute=True) yield cmhive else: constraints = poolscanner.PoolScanner.builtin_constraints( symbol_table, [b'CM10']) for result in poolscanner.PoolScanner.generate_pool_scan( context, layer_name, symbol_table, constraints): _constraint, mem_object, _header = result yield mem_object def _generator(self): for hive in self.scan_hives(self.context, self.config['primary'], self.config['nt_symbols']): yield (0, (format_hints.Hex(hive.vol.offset), )) def run(self): return renderers.TreeGrid([("Offset", format_hints.Hex)], self._generator())