From b980a5edff144cb4277ccb13441c02702b214bd3 Mon Sep 17 00:00:00 2001 From: JSCU-CNI <121175071+JSCU-CNI@users.noreply.github.com> Date: Tue, 26 Nov 2024 16:53:48 +0100 Subject: [PATCH 1/6] check if NotificationHandler table exists --- .../plugins/os/windows/notifications.py | 29 ++++++++++--------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/dissect/target/plugins/os/windows/notifications.py b/dissect/target/plugins/os/windows/notifications.py index fa10526f5..96a2ee8ef 100644 --- a/dissect/target/plugins/os/windows/notifications.py +++ b/dissect/target/plugins/os/windows/notifications.py @@ -442,21 +442,22 @@ def wpndatabase(self) -> Iterator[WpnDatabaseNotificationRecord | WpnDatabaseNot """ for user, wpndatabase in self.wpndb_files: db = sqlite3.SQLite3(wpndatabase.open()) - handlers = {} - for row in db.table("NotificationHandler").rows(): - handlers[row["[RecordId]"]] = WpnDatabaseNotificationHandlerRecord( - created_time=datetime.datetime.strptime(row["[CreatedTime]"], "%Y-%m-%d %H:%M:%S"), - modified_time=datetime.datetime.strptime(row["[ModifiedTime]"], "%Y-%m-%d %H:%M:%S"), - id=row["[RecordId]"], - primary_id=row["[PrimaryId]"], - wns_id=row["[WNSId]"], - handler_type=row["[HandlerType]"], - wnf_event_name=row["[WNFEventName]"], - system_data_property_set=row["[SystemDataPropertySet]"], - _target=self.target, - _user=user, - ) + + if "NotificationHandler" in [table.name for table in db.tables()]: + for row in db.table("NotificationHandler").rows(): + handlers[row["[RecordId]"]] = WpnDatabaseNotificationHandlerRecord( + created_time=datetime.datetime.strptime(row["[CreatedTime]"], "%Y-%m-%d %H:%M:%S"), + modified_time=datetime.datetime.strptime(row["[ModifiedTime]"], "%Y-%m-%d %H:%M:%S"), + id=row["[RecordId]"], + primary_id=row["[PrimaryId]"], + wns_id=row["[WNSId]"], + handler_type=row["[HandlerType]"], + wnf_event_name=row["[WNFEventName]"], + system_data_property_set=row["[SystemDataPropertySet]"], + _target=self.target, + _user=user, + ) for row in db.table("Notification").rows(): record = WpnDatabaseNotificationRecord( From ab6914bc24f68af65c77f2d0ea3e16e149e9f4d1 Mon Sep 17 00:00:00 2001 From: JSCU-CNI <121175071+JSCU-CNI@users.noreply.github.com> Date: Wed, 27 Nov 2024 11:39:31 +0100 Subject: [PATCH 2/6] replace most \.table\(".+"\)\. calls with proper checks --- dissect/target/loaders/itunes.py | 6 ++++-- dissect/target/plugins/apps/browser/iexplore.py | 13 +++++++------ dissect/target/plugins/os/unix/esxi/_os.py | 6 +++++- .../target/plugins/os/windows/activitiescache.py | 6 +++++- dissect/target/plugins/os/windows/notifications.py | 9 ++++++--- 5 files changed, 27 insertions(+), 13 deletions(-) diff --git a/dissect/target/loaders/itunes.py b/dissect/target/loaders/itunes.py index 7a96a4630..52e600dce 100644 --- a/dissect/target/loaders/itunes.py +++ b/dissect/target/loaders/itunes.py @@ -163,8 +163,10 @@ def derive_key(self, password: str) -> bytes: def files(self) -> Iterator[FileInfo]: """Iterate all the files in this backup.""" - for row in self.manifest_db.table("Files").rows(): - yield FileInfo(self, row.fileID, row.domain, row.relativePath, row.flags, row.file) + + if table := self.manifest_db.table("Files"): + for row in table.rows(): + yield FileInfo(self, row.fileID, row.domain, row.relativePath, row.flags, row.file) class FileInfo: diff --git a/dissect/target/plugins/apps/browser/iexplore.py b/dissect/target/plugins/apps/browser/iexplore.py index 352614850..09b6c8048 100644 --- a/dissect/target/plugins/apps/browser/iexplore.py +++ b/dissect/target/plugins/apps/browser/iexplore.py @@ -36,12 +36,13 @@ def find_containers(self, name: str) -> Iterator[table.Table]: All ``ContainerId`` values for the requested container name. """ try: - for container_record in self.db.table("Containers").records(): - if record_name := container_record.get("Name"): - record_name = record_name.rstrip("\00").lower() - if record_name == name.lower(): - container_id = container_record.get("ContainerId") - yield self.db.table(f"Container_{container_id}") + if table := self.db.table("Containers"): + for container_record in table.records(): + if record_name := container_record.get("Name"): + record_name = record_name.rstrip("\00").lower() + if record_name == name.lower(): + container_id = container_record.get("ContainerId") + yield self.db.table(f"Container_{container_id}") except KeyError: pass diff --git a/dissect/target/plugins/os/unix/esxi/_os.py b/dissect/target/plugins/os/unix/esxi/_os.py index c86a48a74..e4fe4ec1f 100644 --- a/dissect/target/plugins/os/unix/esxi/_os.py +++ b/dissect/target/plugins/os/unix/esxi/_os.py @@ -472,7 +472,11 @@ def parse_config_store(fh: BinaryIO) -> dict[str, Any]: db = sqlite3.SQLite3(fh) store = {} - for row in db.table("Config").rows(): + + if not (table := db.table("Config")): + return store + + for row in table.rows(): component_name = row.Component config_group_name = row.ConfigGroup value_group_name = row.Name diff --git a/dissect/target/plugins/os/windows/activitiescache.py b/dissect/target/plugins/os/windows/activitiescache.py index fcf106cf6..54a7f001a 100644 --- a/dissect/target/plugins/os/windows/activitiescache.py +++ b/dissect/target/plugins/os/windows/activitiescache.py @@ -116,7 +116,11 @@ def activitiescache(self) -> Iterator[ActivitiesCacheRecord]: for user, cache_file in self.cachefiles: fh = cache_file.open() db = sqlite3.SQLite3(fh) - for r in db.table("Activity").rows(): + + if not (table := db.table("Activity")): + return + + for r in table.rows(): yield ActivitiesCacheRecord( start_time=mkts(r["[StartTime]"]), end_time=mkts(r["[EndTime]"]), diff --git a/dissect/target/plugins/os/windows/notifications.py b/dissect/target/plugins/os/windows/notifications.py index 96a2ee8ef..bc5d7ec6c 100644 --- a/dissect/target/plugins/os/windows/notifications.py +++ b/dissect/target/plugins/os/windows/notifications.py @@ -444,8 +444,8 @@ def wpndatabase(self) -> Iterator[WpnDatabaseNotificationRecord | WpnDatabaseNot db = sqlite3.SQLite3(wpndatabase.open()) handlers = {} - if "NotificationHandler" in [table.name for table in db.tables()]: - for row in db.table("NotificationHandler").rows(): + if table := db.table("NotificationHandler"): + for row in table.rows(): handlers[row["[RecordId]"]] = WpnDatabaseNotificationHandlerRecord( created_time=datetime.datetime.strptime(row["[CreatedTime]"], "%Y-%m-%d %H:%M:%S"), modified_time=datetime.datetime.strptime(row["[ModifiedTime]"], "%Y-%m-%d %H:%M:%S"), @@ -459,7 +459,10 @@ def wpndatabase(self) -> Iterator[WpnDatabaseNotificationRecord | WpnDatabaseNot _user=user, ) - for row in db.table("Notification").rows(): + if not (table := db.table("Notification")): + return + + for row in table.rows(): record = WpnDatabaseNotificationRecord( arrival_time=wintimestamp(row["[ArrivalTime]"]), expiry_time=wintimestamp(row["[ExpiryTime]"]), From 02d17fe48303deb4486fec898c559cf985b64958 Mon Sep 17 00:00:00 2001 From: JSCU-CNI <121175071+JSCU-CNI@users.noreply.github.com> Date: Wed, 27 Nov 2024 11:56:00 +0100 Subject: [PATCH 3/6] remove table iter --- dissect/target/plugins/os/windows/catroot.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/dissect/target/plugins/os/windows/catroot.py b/dissect/target/plugins/os/windows/catroot.py index 9fc38df38..1aa8ac367 100644 --- a/dissect/target/plugins/os/windows/catroot.py +++ b/dissect/target/plugins/os/windows/catroot.py @@ -217,12 +217,11 @@ def catdb(self) -> Iterator[CatrootRecord]: with ese_file.open("rb") as fh: ese_db = EseDB(fh) - tables = [table.name for table in ese_db.tables()] for hash_type, table_name in [("sha256", "HashCatNameTableSHA256"), ("sha1", "HashCatNameTableSHA1")]: - if table_name not in tables: + if not (table := ese_db.table(table_name)): continue - for record in ese_db.table(table_name).records(): + for record in table.records(): file_digest = digest() setattr(file_digest, hash_type, record.get("HashCatNameTable_HashCol").hex()) catroot_names = record.get("HashCatNameTable_CatNameCol").decode().rstrip("|").split("|") From 651b00b09a67cfbad0104d7e38e8c45abed56b52 Mon Sep 17 00:00:00 2001 From: JSCU-CNI <121175071+JSCU-CNI@users.noreply.github.com> Date: Thu, 28 Nov 2024 10:52:07 +0100 Subject: [PATCH 4/6] implement review comments --- .../target/plugins/apps/browser/iexplore.py | 22 +++--- dissect/target/plugins/os/unix/esxi/_os.py | 68 +++++++++---------- dissect/target/plugins/os/windows/catroot.py | 7 +- 3 files changed, 52 insertions(+), 45 deletions(-) diff --git a/dissect/target/plugins/apps/browser/iexplore.py b/dissect/target/plugins/apps/browser/iexplore.py index 09b6c8048..bdc230148 100644 --- a/dissect/target/plugins/apps/browser/iexplore.py +++ b/dissect/target/plugins/apps/browser/iexplore.py @@ -36,15 +36,19 @@ def find_containers(self, name: str) -> Iterator[table.Table]: All ``ContainerId`` values for the requested container name. """ try: - if table := self.db.table("Containers"): - for container_record in table.records(): - if record_name := container_record.get("Name"): - record_name = record_name.rstrip("\00").lower() - if record_name == name.lower(): - container_id = container_record.get("ContainerId") - yield self.db.table(f"Container_{container_id}") - except KeyError: - pass + table = self.db.table("Containers") + + for container_record in table.records(): + if record_name := container_record.get("Name"): + record_name = record_name.rstrip("\00").lower() + if record_name == name.lower(): + container_id = container_record.get("ContainerId") + yield self.db.table(f"Container_{container_id}") + + except KeyError as e: + self.target.log.warning("Exception while parsing EseDB Containers table") + self.target.log.debug("", exc_info=e) + return def _iter_records(self, name: str) -> Iterator[record.Record]: """Yield records from a Webcache container. diff --git a/dissect/target/plugins/os/unix/esxi/_os.py b/dissect/target/plugins/os/unix/esxi/_os.py index e4fe4ec1f..fcacdb9a3 100644 --- a/dissect/target/plugins/os/unix/esxi/_os.py +++ b/dissect/target/plugins/os/unix/esxi/_os.py @@ -473,40 +473,38 @@ def parse_config_store(fh: BinaryIO) -> dict[str, Any]: store = {} - if not (table := db.table("Config")): - return store - - for row in table.rows(): - component_name = row.Component - config_group_name = row.ConfigGroup - value_group_name = row.Name - identifier_name = row.Identifier - - if component_name not in store: - store[component_name] = {} - component = store[component_name] - - if config_group_name not in component: - component[config_group_name] = {} - config_group = component[config_group_name] - - if value_group_name not in config_group: - config_group[value_group_name] = {} - value_group = config_group[value_group_name] - - if identifier_name not in value_group: - value_group[identifier_name] = {} - identifier = value_group[identifier_name] - - identifier["modified_time"] = row.ModifiedTime - identifier["creation_time"] = row.CreationTime - identifier["version"] = row.Version - identifier["success"] = row.Success - identifier["auto_conf_value"] = json.loads(row.AutoConfValue) if row.AutoConfValue else None - identifier["user_value"] = json.loads(row.UserValue) if row.UserValue else None - identifier["vital_value"] = json.loads(row.VitalValue) if row.VitalValue else None - identifier["cached_value"] = json.loads(row.CachedValue) if row.CachedValue else None - identifier["desired_value"] = json.loads(row.DesiredValue) if row.DesiredValue else None - identifier["revision"] = row.Revision + if table := db.table("Config"): + for row in table.rows(): + component_name = row.Component + config_group_name = row.ConfigGroup + value_group_name = row.Name + identifier_name = row.Identifier + + if component_name not in store: + store[component_name] = {} + component = store[component_name] + + if config_group_name not in component: + component[config_group_name] = {} + config_group = component[config_group_name] + + if value_group_name not in config_group: + config_group[value_group_name] = {} + value_group = config_group[value_group_name] + + if identifier_name not in value_group: + value_group[identifier_name] = {} + identifier = value_group[identifier_name] + + identifier["modified_time"] = row.ModifiedTime + identifier["creation_time"] = row.CreationTime + identifier["version"] = row.Version + identifier["success"] = row.Success + identifier["auto_conf_value"] = json.loads(row.AutoConfValue) if row.AutoConfValue else None + identifier["user_value"] = json.loads(row.UserValue) if row.UserValue else None + identifier["vital_value"] = json.loads(row.VitalValue) if row.VitalValue else None + identifier["cached_value"] = json.loads(row.CachedValue) if row.CachedValue else None + identifier["desired_value"] = json.loads(row.DesiredValue) if row.DesiredValue else None + identifier["revision"] = row.Revision return store diff --git a/dissect/target/plugins/os/windows/catroot.py b/dissect/target/plugins/os/windows/catroot.py index 1aa8ac367..a054a327a 100644 --- a/dissect/target/plugins/os/windows/catroot.py +++ b/dissect/target/plugins/os/windows/catroot.py @@ -218,7 +218,12 @@ def catdb(self) -> Iterator[CatrootRecord]: ese_db = EseDB(fh) for hash_type, table_name in [("sha256", "HashCatNameTableSHA256"), ("sha1", "HashCatNameTableSHA1")]: - if not (table := ese_db.table(table_name)): + try: + table = ese_db.table(table_name) + + except KeyError as e: + self.target.log.warning("EseDB %s has no table %s", ese_file, table_name) + self.target.log.debug("", exc_info=e) continue for record in table.records(): From c374b52e63c48e3fdb6060585111de77d83cb113 Mon Sep 17 00:00:00 2001 From: Computer Network Investigation <121175071+JSCU-CNI@users.noreply.github.com> Date: Thu, 28 Nov 2024 14:45:34 +0100 Subject: [PATCH 5/6] Apply suggestions from code review Co-authored-by: Erik Schamper <1254028+Schamper@users.noreply.github.com> --- dissect/target/plugins/apps/browser/iexplore.py | 1 - dissect/target/plugins/os/windows/catroot.py | 1 - 2 files changed, 2 deletions(-) diff --git a/dissect/target/plugins/apps/browser/iexplore.py b/dissect/target/plugins/apps/browser/iexplore.py index bdc230148..8806524ea 100644 --- a/dissect/target/plugins/apps/browser/iexplore.py +++ b/dissect/target/plugins/apps/browser/iexplore.py @@ -48,7 +48,6 @@ def find_containers(self, name: str) -> Iterator[table.Table]: except KeyError as e: self.target.log.warning("Exception while parsing EseDB Containers table") self.target.log.debug("", exc_info=e) - return def _iter_records(self, name: str) -> Iterator[record.Record]: """Yield records from a Webcache container. diff --git a/dissect/target/plugins/os/windows/catroot.py b/dissect/target/plugins/os/windows/catroot.py index a054a327a..6de792100 100644 --- a/dissect/target/plugins/os/windows/catroot.py +++ b/dissect/target/plugins/os/windows/catroot.py @@ -220,7 +220,6 @@ def catdb(self) -> Iterator[CatrootRecord]: for hash_type, table_name in [("sha256", "HashCatNameTableSHA256"), ("sha1", "HashCatNameTableSHA1")]: try: table = ese_db.table(table_name) - except KeyError as e: self.target.log.warning("EseDB %s has no table %s", ese_file, table_name) self.target.log.debug("", exc_info=e) From 0e6d5da71c70ed7cef27bd6d099b2e2df80a8d4f Mon Sep 17 00:00:00 2001 From: JSCU-CNI <121175071+JSCU-CNI@users.noreply.github.com> Date: Thu, 28 Nov 2024 14:47:10 +0100 Subject: [PATCH 6/6] implement review feedback --- .../plugins/os/windows/activitiescache.py | 64 +++++++++---------- .../plugins/os/windows/notifications.py | 50 +++++++-------- 2 files changed, 55 insertions(+), 59 deletions(-) diff --git a/dissect/target/plugins/os/windows/activitiescache.py b/dissect/target/plugins/os/windows/activitiescache.py index 54a7f001a..c945f423a 100644 --- a/dissect/target/plugins/os/windows/activitiescache.py +++ b/dissect/target/plugins/os/windows/activitiescache.py @@ -117,39 +117,37 @@ def activitiescache(self) -> Iterator[ActivitiesCacheRecord]: fh = cache_file.open() db = sqlite3.SQLite3(fh) - if not (table := db.table("Activity")): - return - - for r in table.rows(): - yield ActivitiesCacheRecord( - start_time=mkts(r["[StartTime]"]), - end_time=mkts(r["[EndTime]"]), - last_modified_time=mkts(r["[LastModifiedTime]"]), - last_modified_on_client=mkts(r["[LastModifiedOnClient]"]), - original_last_modified_on_client=mkts(r["[OriginalLastModifiedOnClient]"]), - expiration_time=mkts(r["[ExpirationTime]"]), - app_id=r["[AppId]"], - enterprise_id=r["[EnterpriseId]"], - app_activity_id=r["[AppActivityId]"], - group_app_activity_id=r["[GroupAppActivityId]"], - group=r["[Group]"], - activity_type=r["[ActivityType]"], - activity_status=r["[ActivityStatus]"], - priority=r["[Priority]"], - match_id=r["[MatchId]"], - etag=r["[ETag]"], - tag=r["[Tag]"], - is_local_only=r["[IsLocalOnly]"], - created_in_cloud=r["[CreatedInCloud]"], - platform_device_id=r["[PlatformDeviceId]"], - package_id_hash=r["[PackageIdHash]"], - id=r["[Id]"], - payload=r["[Payload]"], - original_payload=r["[OriginalPayload]"], - clipboard_payload=r["[ClipboardPayload]"], - _target=self.target, - _user=user, - ) + if table := db.table("Activity"): + for r in table.rows(): + yield ActivitiesCacheRecord( + start_time=mkts(r["[StartTime]"]), + end_time=mkts(r["[EndTime]"]), + last_modified_time=mkts(r["[LastModifiedTime]"]), + last_modified_on_client=mkts(r["[LastModifiedOnClient]"]), + original_last_modified_on_client=mkts(r["[OriginalLastModifiedOnClient]"]), + expiration_time=mkts(r["[ExpirationTime]"]), + app_id=r["[AppId]"], + enterprise_id=r["[EnterpriseId]"], + app_activity_id=r["[AppActivityId]"], + group_app_activity_id=r["[GroupAppActivityId]"], + group=r["[Group]"], + activity_type=r["[ActivityType]"], + activity_status=r["[ActivityStatus]"], + priority=r["[Priority]"], + match_id=r["[MatchId]"], + etag=r["[ETag]"], + tag=r["[Tag]"], + is_local_only=r["[IsLocalOnly]"], + created_in_cloud=r["[CreatedInCloud]"], + platform_device_id=r["[PlatformDeviceId]"], + package_id_hash=r["[PackageIdHash]"], + id=r["[Id]"], + payload=r["[Payload]"], + original_payload=r["[OriginalPayload]"], + clipboard_payload=r["[ClipboardPayload]"], + _target=self.target, + _user=user, + ) def mkts(ts: int) -> datetime | None: diff --git a/dissect/target/plugins/os/windows/notifications.py b/dissect/target/plugins/os/windows/notifications.py index bc5d7ec6c..27f2bdbc7 100644 --- a/dissect/target/plugins/os/windows/notifications.py +++ b/dissect/target/plugins/os/windows/notifications.py @@ -459,30 +459,28 @@ def wpndatabase(self) -> Iterator[WpnDatabaseNotificationRecord | WpnDatabaseNot _user=user, ) - if not (table := db.table("Notification")): - return - - for row in table.rows(): - record = WpnDatabaseNotificationRecord( - arrival_time=wintimestamp(row["[ArrivalTime]"]), - expiry_time=wintimestamp(row["[ExpiryTime]"]), - order=row["[Order]"], - id=row["[Id]"], - handler_id=row["[HandlerId]"], - activity_id=UUID(bytes=row["[ActivityId]"]), - type=row["[Type]"], - payload=row["[Payload]"], - payload_type=row["[PayloadType]"], - tag=row["[Tag]"], - group=row["[Group]"], - boot_id=row["[BootId]"], - expires_on_reboot=row["[ExpiresOnReboot]"] != "FALSE", - _target=self.target, - _user=user, - ) - handler = handlers.get(row["[HandlerId]"]) + if table := db.table("Notification"): + for row in table.rows(): + record = WpnDatabaseNotificationRecord( + arrival_time=wintimestamp(row["[ArrivalTime]"]), + expiry_time=wintimestamp(row["[ExpiryTime]"]), + order=row["[Order]"], + id=row["[Id]"], + handler_id=row["[HandlerId]"], + activity_id=UUID(bytes=row["[ActivityId]"]), + type=row["[Type]"], + payload=row["[Payload]"], + payload_type=row["[PayloadType]"], + tag=row["[Tag]"], + group=row["[Group]"], + boot_id=row["[BootId]"], + expires_on_reboot=row["[ExpiresOnReboot]"] != "FALSE", + _target=self.target, + _user=user, + ) + handler = handlers.get(row["[HandlerId]"]) - if handler: - yield GroupedRecord("windows/notification/wpndatabase/grouped", [record, handler]) - else: - yield record + if handler: + yield GroupedRecord("windows/notification/wpndatabase/grouped", [record, handler]) + else: + yield record