def run(self): self.entries = {} logger.info('Started Services enumeration Plugin...') for control_set_services_path in self.registry_hive.get_control_sets(SERVICES_PATH): try: subkey = self.registry_hive.get_key(control_set_services_path) except RegistryKeyNotFoundException as ex: logger.error(ex) continue self.entries[control_set_services_path] = { 'timestamp': convert_wintime(subkey.header.last_modified, as_json=self.as_json) } services = [] for service in subkey.iter_subkeys(): values = None if service.values_count > 0: values = [attr.asdict(x) for x in service.iter_values(as_json=True)] entry = { 'name': service.name, 'last_modified': convert_wintime(service.header.last_modified, as_json=self.as_json), 'values': values, 'parameters': [attr.asdict(x) for x in self.registry_hive.recurse_subkeys(nk_record=service, path=r'{}\{}'.format( control_set_services_path, service.name), as_json=True)] if service.subkey_count else None } services.append(entry) self.entries[control_set_services_path]['services'] = services
def recurse_subkeys(self, nk_record=None, path=None, as_json=False): """ Recurse over a subkey, and yield all of its subkeys and values :param nk_record: an instance of NKRecord from which to start iterating, if None, will start from Root :param path: The current registry path :param as_json: Whether to normalize the data as JSON or not """ # If None, will start iterating from Root NK entry if not nk_record: nk_record = self.root # Iterate over subkeys for subkey in nk_record.iter_subkeys(): if subkey.subkey_count: if path: yield from self.recurse_subkeys(nk_record=subkey, path=r'{}\{}'.format( path, subkey.name), as_json=as_json) else: yield from self.recurse_subkeys(nk_record=subkey, path=r'\{}'.format( subkey.name), as_json=as_json) values = [] if subkey.values_count: if as_json: values = [ attr.asdict(x) for x in subkey.iter_values(as_json=as_json) ] else: values = list(subkey.iter_values(as_json=as_json)) ts = convert_wintime(subkey.header.last_modified) yield Subkey( subkey_name=subkey.name, path=r'{}\{}'.format(path, subkey.name) if path else '\\', timestamp=ts.isoformat() if as_json else ts, values=values, values_count=len(values)) # Get the values of the subkey values = [] if nk_record.values_count: if as_json: values = [ attr.asdict(x) for x in nk_record.iter_values(as_json=as_json) ] else: values = list(nk_record.iter_values(as_json=as_json)) ts = convert_wintime(nk_record.header.last_modified) yield Subkey(subkey_name=nk_record.name, path=path, timestamp=ts.isoformat() if as_json else ts, values=values, values_count=len(values))
def run(self): logger.info('Started AmCache Plugin...') is_win_7_hive = False entries = [] try: amcache_subkey = self.registry_hive.get_key(r'\Root\File') except RegistryKeyNotFoundException: amcache_subkey = self.registry_hive.get_key( r'\Root\InventoryApplicationFile') is_win_7_hive = True if is_win_7_hive: for subkey in amcache_subkey.iter_subkeys(): entry = { underscore(x.name): x.value for x in subkey.iter_values(as_json=self.as_json) } entry['program_id'] = entry['program_id'][4:] entry['file_id'] = entry['file_id'][4:] entry['sha1'] = entry['file_id'] entry['timestamp'] = convert_wintime( subkey.header.last_modified, as_json=self.as_json) entry['size'] = int(entry['size'], 16) if isinstance( entry['size'], str) else entry['size'] entry['is_pe_file'] = bool(entry['is_pe_file']) entry['is_os_component'] = bool(entry['is_os_component']) if entry['link_date'] == 0: entry.pop('link_date') entry['type'] = 'win_7_amcache' entries.append(entry) else: for subkey in amcache_subkey.iter_subkeys(): for file_subkey in subkey.iter_subkeys(): entry = { x.name: x.value for x in file_subkey.iter_values(as_json=self.as_json) } entry['timestamp'] = convert_wintime( file_subkey.header.last_modified, as_json=self.as_json) for k, v in WIN8_AMCACHE_MAPPINGS.items(): content = entry.pop(k, None) if content: entry[v] = content entry['sha1'] = entry['sha1'][4:] entry['program_id'] = entry['program_id'][4:] entry['type'] = 'win_8+_amcache' for ts_field_name in WIN8_TS_FIELDS: ts = entry.pop(ts_field_name, None) if ts: entry[ts_field_name] = convert_wintime( ts, as_json=self.as_json) entries.append(entry) return entries
def run(self): for guid in GUIDS: try: subkey = self.registry_hive.get_key(r'{}\{}'.format(USER_ASSIST_KEY_PATH, guid)) count_subkey = subkey.get_subkey('Count') if not count_subkey.values_count: logger.debug('Skipping {}'.format(guid)) continue for value in count_subkey.iter_values(): name = codecs.decode(value.name, encoding='rot-13') if name in WHITELISTED_NAMES: continue for k, v in GUID_TO_PATH_MAPPINGS.items(): if k in name: name = name.replace(k, v) break entry = None data = value.value if len(data) == 72: try: parsed_entry = WIN7_USER_ASSIST.parse(data) except ConstError as ex: logger.error(f'Could not parse user assist entry named {name}: {ex}') continue entry = { 'name': name, 'timestamp': convert_wintime(parsed_entry.last_execution_timestamp, as_json=self.as_json), 'run_counter': parsed_entry.run_counter, 'focus_count': parsed_entry.focus_count, 'total_focus_time_ms': parsed_entry.total_focus_time_ms, 'session_id': parsed_entry.session_id } elif len(data) == 16: try: parsed_entry = WIN_XP_USER_ASSIST.parse(data) except ConstError as ex: logger.error(f'Could not parse user assist entry named {name}: {ex}') continue entry = { 'name': name, 'timestamp': convert_wintime(parsed_entry.last_execution_timestamp, as_json=self.as_json), 'session_id': parsed_entry.session_id, 'run_counter': parsed_entry.run_counter - 5 } if entry: self.entries.append(entry) except RegistryKeyNotFoundException: continue
def parse_amcache_file_entry(self, subkey): entry = { underscore(x.name): x.value for x in subkey.iter_values(as_json=self.as_json) } # Sometimes the value names might be numeric instead. Translate them: for k, v in AMCACHE_FIELD_NUMERIC_MAPPINGS.items(): content = entry.pop(k, None) if content: entry[v] = content if 'sha1' in entry: entry['sha1'] = entry['sha1'][4:] if 'file_id' in entry and entry['file_id'] != 0: entry['file_id'] = entry['file_id'][4:] if 'sha1' not in entry: entry['sha1'] = entry['file_id'] if 'program_id' in entry: entry['program_id'] = entry['program_id'][4:] entry['timestamp'] = convert_wintime(subkey.header.last_modified, as_json=self.as_json) if 'size' in entry: entry['size'] = int(entry['size'], 16) if isinstance( entry['size'], str) else entry['size'] is_pefile = entry.get('is_pe_file') if is_pefile is not None: entry['is_pe_file'] = bool(is_pefile) is_os_component = entry.get('is_os_component') if is_os_component is not None: entry['is_os_component'] = bool(is_os_component) if entry.get('link_date') == 0: entry.pop('link_date') for ts_field_name in WIN8_TS_FIELDS: ts = entry.pop(ts_field_name, None) if ts: entry[ts_field_name] = convert_wintime(ts, as_json=self.as_json) self.entries.append(entry)
def run(self): try: subkey = self.registry_hive.get_key(WORD_WHEEL_QUERY_KEY_PATH) except RegistryKeyNotFoundException as ex: logger.error( f'Could not find {self.NAME} plugin data at: {WORD_WHEEL_QUERY_KEY_PATH}: {ex}' ) return None timestamp = convert_wintime(subkey.header.last_modified, as_json=self.as_json) mru_list_order = subkey.get_value('MRUListEx') # If this is the value, the list is empty if mru_list_order == 0xffffffff: return None for i, entry_name in enumerate( GreedyRange(Int32ul).parse(mru_list_order)): entry_value = subkey.get_value(str(entry_name)) if not entry_value: continue self.entries.append({ 'last_write': timestamp, 'mru_id': entry_name, 'order': i, 'name': CString('utf-16').parse(entry_value) })
def run(self): logger.info('Started profile list plugin...') try: subkey = self.registry_hive.get_key(PROFILE_LIST_KEY_PATH) except RegistryKeyNotFoundException as ex: logger.error(ex) for profile in subkey.iter_subkeys(): self.entries.append({ 'last_write': convert_wintime(profile.header.last_modified, as_json=self.as_json), 'path': profile.get_value('ProfileImagePath'), 'flags': profile.get_value('Flags'), 'full_profile': profile.get_value('FullProfile'), 'state': profile.get_value('State'), 'sid': profile.name, 'load_time': convert_filetime(profile.get_value('ProfileLoadTimeLow'), profile.get_value('ProfileLoadTimeHigh')), 'local_load_time': convert_filetime(profile.get_value('LocalProfileLoadTimeLow'), profile.get_value('LocalProfileLoadTimeHigh')) })
def get_timestamp_for_subkeys(registry_hive, subkey_list): for subkey_path in subkey_list: try: subkey = registry_hive.get_key(subkey_path) except RegistryKeyNotFoundException as ex: logger.exception(f'Could not obtain timestamp for subkey {subkey_path}') continue yield subkey_path, convert_wintime(subkey.header.last_modified, as_json=True)
def run(self): try: open_subkey = self.registry_hive.get_key(WINRAR_ARCHIVE_OPEN_HIST) timestamp = convert_wintime(open_subkey.header.last_modified, as_json=self.as_json) opened_archives = [value.value for value in open_subkey.iter_values(as_json=self.as_json)] for archive in opened_archives: self.entries.append({ 'last_write': timestamp, 'archive_path': archive, 'operation': 'archive_opened' }) except RegistryKeyNotFoundException as ex: logger.error(f'Could not find {self.NAME} plugin data at: {WINRAR_ARCHIVE_OPEN_HIST}: {ex}') try: create_subkey = self.registry_hive.get_key(WINRAR_ARCHIVE_CREATION_HIST) timestamp = convert_wintime(create_subkey.header.last_modified, as_json=self.as_json) created_archives = [value.value for value in create_subkey.iter_values(as_json=self.as_json)] for archive in created_archives: self.entries.append({ 'last_write': timestamp, 'archive_name': archive, 'operation': 'archive_created' }) except RegistryKeyNotFoundException as ex: logger.error(f'Could not find {self.NAME} plugin data at: {WINRAR_ARCHIVE_CREATION_HIST}: {ex}') try: extract_subkey = self.registry_hive.get_key(WINRAR_ARCHIVE_EXTRACT_HIST) timestamp = convert_wintime(extract_subkey.header.last_modified, as_json=self.as_json) extracted_archives = [value.value for value in extract_subkey.iter_values(as_json=self.as_json)] for location in extracted_archives: self.entries.append({ 'last_write': timestamp, 'destination_folder': location, 'operation': 'archive_extracted' }) except RegistryKeyNotFoundException as ex: logger.error(f'Could not find {self.NAME} plugin data at: {WINRAR_ARCHIVE_EXTRACT_HIST}: {ex}')
def __dict__(self): return { 'name': self.name, 'subkey_count': self.subkey_count, 'value_count': self.values_count, 'values': {x['name']: x['value'] for x in self.iter_values()} if self.values_count else None, 'subkeys': {x['name'] for x in self.iter_subkeys()} if self.subkey_count else None, 'timestamp': convert_wintime(self.header.last_modified, as_json=True), 'volatile_subkeys': self.volatile_subkeys_count }
def run(self): uninstall_sk = self.registry_hive.get_key(INSTALLED_SOFTWARE_PATH) for installed_program in uninstall_sk.iter_subkeys(): values = { x.name: x.value for x in installed_program.iter_values(as_json=self.as_json) } if installed_program.values_count else {} self.entries.append({ 'service_name': installed_program.name, 'timestamp': convert_wintime(installed_program.header.last_modified, as_json=self.as_json), **values })
def run(self): logger.info("Started BootKey Plugin...") for subkey_path in self.registry_hive.get_control_sets(LSA_KEY_PATH): lsa_key = self.registry_hive.get_key(subkey_path) bootkey = _descramble_bootkey(_collect_bootkey(lsa_key)) self.entries.append({ "key": bootkey.hex() if self.as_json else bootkey, 'timestamp': convert_wintime(lsa_key.header.last_modified, as_json=self.as_json) })
def run(self) -> None: logger.info("Started Boot Entry List Plugin...") objects_key = self.registry_hive.get_key(BCD_OBJECTS_PATH) for obj_key in objects_key.iter_subkeys(): desc_key = obj_key.get_subkey("Description") # Object type defines the boot entry features desc_type = desc_key.get_value("Type") # The remaining boot entry attributes are stored as object elements desc_name = _get_element_by_type(obj_key, ELEM_TYPE_DESCRIPTION) path_name = _get_element_by_type(obj_key, ELEM_TYPE_APPLICATION_PATH) device_data = _get_element_by_type(obj_key, ELEM_TYPE_APPLICATION_DEVICE) # Filter out objects that do not look like boot entries if desc_name is None or path_name is None or device_data is None: continue # TODO: Figure out the device data blob format if not isinstance(device_data, bytes) or len(device_data) < 72: continue # TODO: Figure out how non-GPT partitions are encoded gpt_part_guid = str(uuid.UUID(bytes_le=device_data[32:48])) gpt_disk_guid = str(uuid.UUID(bytes_le=device_data[56:72])) entry_type = "0x%08X" % desc_type if self.as_json else desc_type self.entries.append({ "guid": obj_key.name, "type": entry_type, "name": desc_name, "gpt_disk": gpt_disk_guid, "gpt_partition": gpt_part_guid, "image_path": path_name, "timestamp": convert_wintime(obj_key.header.last_modified, as_json=self.as_json), })
def run(self): logger.info('Started Computer Name Plugin...') for subkey_path in self.registry_hive.get_control_sets( COMPUTER_NAME_PATH): subkey = self.registry_hive.get_key(subkey_path) try: self.entries.append({ 'name': subkey.get_value('ComputerName', as_json=self.as_json), 'timestamp': convert_wintime(subkey.header.last_modified, as_json=self.as_json) }) except RegistryValueNotFoundException as ex: continue
def run(self): image_file_execution_options = self.registry_hive.get_key( IMAGE_FILE_EXECUTION_OPTIONS) if image_file_execution_options.subkey_count: for subkey in image_file_execution_options.iter_subkeys(): values = { x.name: x.value for x in subkey.iter_values(as_json=self.as_json) } if subkey.values_count else {} self.entries.append({ 'name': subkey.name, 'timestamp': convert_wintime(subkey.header.last_modified, as_json=self.as_json), **values })
def _get_installed_software(self, subkey_path): try: uninstall_sk = self.registry_hive.get_key(subkey_path) except RegistryKeyNotFoundException as ex: logger.error(ex) return for installed_program in uninstall_sk.iter_subkeys(): values = {underscore(x.name): x.value for x in installed_program.iter_values(as_json=self.as_json)} if installed_program.values_count else {} self.entries.append({ 'service_name': installed_program.name, 'timestamp': convert_wintime(installed_program.header.last_modified, as_json=self.as_json), 'registry_path': subkey_path, **values })
def run(self): logger.info('Started WDIGEST Plugin...') for subkey_path in self.registry_hive.get_control_sets(WDIGEST_PATH): subkey = self.registry_hive.get_key(subkey_path) try: self.entries.append({ 'subkey': subkey_path, 'use_logon_credential': subkey.get_value('UseLogonCredential', as_json=self.as_json), 'timestamp': convert_wintime(subkey.header.last_modified, as_json=self.as_json) }) except RegistryValueNotFoundException as ex: continue
def run(self): logger.info('Started Host and Domain Name Plugin...') for subkey_path in self.registry_hive.get_control_sets(HOST_PARAMETERS_PATH): subkey = self.registry_hive.get_key(subkey_path) hostname = subkey.get_value('Hostname', as_json=self.as_json) domain = subkey.get_value('Domain', as_json=self.as_json) # The default key value is 0x00000000 (REG_DWORD) when # the Windows machine is not in an AD domain. if not isinstance(domain, str): domain = None self.entries.append({ 'hostname': hostname, 'domain': domain, 'timestamp': convert_wintime(subkey.header.last_modified, as_json=self.as_json) })
def run(self) -> None: logger.info("Started Machine Domain SID Plugin...") name_key = self.registry_hive.get_key(DOMAIN_NAME_PATH) # Primary Domain Name or Workgroup Name (binary-encoded and length-prefixed) name_value = name_key.get_value() # Skip UNICODE_STRING struct header and strip trailing \x0000 domain_name = name_value[8:].decode('utf-16-le', errors='replace').rstrip('\x00') sid_key = self.registry_hive.get_key(DOMAIN_SID_PATH) # Domain SID value (binary-encoded) sid_value = sid_key.get_value() domain_sid: Optional[str] = None machine_sid: Optional[str] = None # The default key value is 0x00000000 (REG_DWORD) when # the Windows machine is not in an AD domain. # Otherwise, it contains the domain machine SID data # in the standard binary format (REG_BINARY). if isinstance(sid_value, bytes): parsed_sid = SID.parse(sid_value) domain_sid = convert_sid(parsed_sid, strip_rid=True) machine_sid = convert_sid(parsed_sid) self.entries.append({ "domain_name": domain_name, "domain_sid": domain_sid, "machine_sid": machine_sid, "timestamp": convert_wintime(sid_key.header.last_modified, as_json=self.as_json), })
def run(self) -> None: logger.info("Started Machine Local SID Plugin...") account_key = self.registry_hive.get_key(ACCOUNT_PATH) # A computer's SID is stored in the SECURITY hive # under 'SECURITY\SAM\Domains\Account'. # This key has a value named 'F' and a value named 'V'. v_value = account_key.get_value("V") # The 'V' value is a binary value that has the computer SID embedded # within it at the end of its data. sid_value = v_value[-24:] parsed_sid = SID.parse(sid_value) self.entries.append({ "machine_sid": convert_sid(parsed_sid), "timestamp": convert_wintime(account_key.header.last_modified, as_json=self.as_json), })
def run(self): logger.info('Started Computer Name Plugin...') try: for subkey_path in self.registry_hive.get_control_sets(BAM_PATH): subkey = self.registry_hive.get_key(subkey_path) for sid_subkey in subkey.iter_subkeys(): sid = sid_subkey.name logger.info(f'Parsing BAM for {sid}') sequence_number = None version = None entries = [] for value in sid_subkey.get_values(): if value.name == 'SequenceNumber': sequence_number = value.value elif value.name == 'Version': version = value.value else: entries.append({ 'executable': value.name, 'timestamp': convert_wintime(Int64ul.parse(value.value), as_json=self.as_json) }) self.entries.extend([{ 'sequence_number': sequence_number, 'version': version, 'sid': sid, **x } for x in entries]) except RegistryKeyNotFoundException as ex: logger.error(ex)
def get_timestamp_for_subkeys(registry_hive, subkey_list): for subkey_path in subkey_list: subkey = registry_hive.get_key(subkey_path) yield subkey_path, convert_wintime(subkey.header.last_modified, as_json=True)
def recurse_subkeys(self, nk_record=None, path=None, as_json=False, is_init=True): """ Recurse over a subkey, and yield all of its subkeys and values :param nk_record: an instance of NKRecord from which to start iterating, if None, will start from Root :param path: The current registry path :param as_json: Whether to normalize the data as JSON or not """ # If None, will start iterating from Root NK entry if not nk_record: nk_record = self.root # Iterate over subkeys if nk_record.header.subkey_count: for subkey in nk_record.iter_subkeys(): if path: subkey_path = r'{}\{}'.format( path, subkey.name) if path else r'\{}'.format( subkey.name) else: subkey_path = f'\\{subkey.name}' # Leaf Index records do not contain subkeys if isinstance(subkey, LIRecord): continue if subkey.subkey_count: yield from self.recurse_subkeys(nk_record=subkey, path=subkey_path, as_json=as_json, is_init=False) values = [] if subkey.values_count: try: if as_json: values = [ attr.asdict(x) for x in subkey.iter_values(as_json=as_json) ] else: values = list(subkey.iter_values(as_json=as_json)) except RegistryParsingException as ex: logger.exception( f'Failed to parse hive value at path: {path}') values = [] ts = convert_wintime(subkey.header.last_modified) yield Subkey( subkey_name=subkey.name, path=subkey_path, timestamp=ts.isoformat() if as_json else ts, values=values, values_count=len(values), actual_path=f'{self.partial_hive_path}{subkey_path}' if self.partial_hive_path else None) if is_init: # Get the values of the subkey values = [] if nk_record.values_count: try: if as_json: values = [ attr.asdict(x) for x in nk_record.iter_values(as_json=as_json) ] else: values = list(nk_record.iter_values(as_json=as_json)) except RegistryParsingException as ex: logger.exception( f'Failed to parse hive value at path: {path}') values = [] ts = convert_wintime(nk_record.header.last_modified) subkey_path = path or '\\' yield Subkey(subkey_name=nk_record.name, path=subkey_path, timestamp=ts.isoformat() if as_json else ts, values=values, values_count=len(values), actual_path=f'{self.partial_hive_path}\\{subkey_path}' if self.partial_hive_path else None)