From bd43df9ca3c2d9ad7b3d86cf93d6d90b58937c8e Mon Sep 17 00:00:00 2001 From: JSCU-CNI <121175071+JSCU-CNI@users.noreply.github.com> Date: Wed, 23 Oct 2024 16:40:23 +0200 Subject: [PATCH] restructure editor plugin namespace --- .../apps/{texteditor => editor}/__init__.py | 0 dissect/target/plugins/apps/editor/editor.py | 23 ++++++++++ .../{texteditor => editor}/windowsnotepad.py | 45 ++++++++++++------- .../plugins/apps/texteditor/texteditor.py | 13 ------ .../test_windowsnotepad.py} | 19 +++++--- tests/plugins/apps/texteditor/__init__.py | 0 6 files changed, 64 insertions(+), 36 deletions(-) rename dissect/target/plugins/apps/{texteditor => editor}/__init__.py (100%) create mode 100644 dissect/target/plugins/apps/editor/editor.py rename dissect/target/plugins/apps/{texteditor => editor}/windowsnotepad.py (93%) delete mode 100644 dissect/target/plugins/apps/texteditor/texteditor.py rename tests/plugins/apps/{texteditor/test_texteditor.py => editor/test_windowsnotepad.py} (94%) delete mode 100644 tests/plugins/apps/texteditor/__init__.py diff --git a/dissect/target/plugins/apps/texteditor/__init__.py b/dissect/target/plugins/apps/editor/__init__.py similarity index 100% rename from dissect/target/plugins/apps/texteditor/__init__.py rename to dissect/target/plugins/apps/editor/__init__.py diff --git a/dissect/target/plugins/apps/editor/editor.py b/dissect/target/plugins/apps/editor/editor.py new file mode 100644 index 000000000..25ee0e021 --- /dev/null +++ b/dissect/target/plugins/apps/editor/editor.py @@ -0,0 +1,23 @@ +from dissect.target.plugin import NamespacePlugin, export + +COMMON_EDITOR_FIELDS = [ + ("datetime", "ts"), + ("string", "editor"), + ("path", "source"), +] + + +class EditorPlugin(NamespacePlugin): + """Editor plugin.""" + + __namespace__ = "editor" + + @export + def extensions(self) -> None: + """Yields installed extensions.""" + raise NotImplementedError + + @export + def history(self) -> None: + """Yields history of files.""" + raise NotImplementedError diff --git a/dissect/target/plugins/apps/texteditor/windowsnotepad.py b/dissect/target/plugins/apps/editor/windowsnotepad.py similarity index 93% rename from dissect/target/plugins/apps/texteditor/windowsnotepad.py rename to dissect/target/plugins/apps/editor/windowsnotepad.py index 260bc5b04..2cd2bfe80 100644 --- a/dissect/target/plugins/apps/texteditor/windowsnotepad.py +++ b/dissect/target/plugins/apps/editor/windowsnotepad.py @@ -17,10 +17,7 @@ create_extended_descriptor, ) from dissect.target.plugin import export -from dissect.target.plugins.apps.texteditor.texteditor import ( - GENERIC_TAB_CONTENTS_RECORD_FIELDS, - TexteditorPlugin, -) +from dissect.target.plugins.apps.editor.editor import EditorPlugin from dissect.target.target import Target # Thanks to @Nordgaren, @daddycocoaman, @JustArion and @ogmini for their suggestions and feedback in the PR @@ -94,15 +91,27 @@ }; """ -WINDOWS_SAVED_TABS_EXTRA_FIELDS = [("datetime", "modification_time"), ("digest", "hashes"), ("path", "saved_path")] +GENERIC_TAB_CONTENTS_RECORD_FIELDS = [ + ("string", "editor"), + ("string", "content"), + ("path", "path"), + ("string", "deleted_content"), + ("path", "source"), +] + +WINDOWS_SAVED_TABS_EXTRA_FIELDS = [ + ("datetime", "ts_mtime"), + ("digest", "digest"), + ("path", "saved_path"), +] WindowsNotepadUnsavedTabRecord = create_extended_descriptor([UserRecordDescriptorExtension])( - "texteditor/windowsnotepad/tab/unsaved", + "application/editor/windowsnotepad/tab/unsaved", GENERIC_TAB_CONTENTS_RECORD_FIELDS, ) WindowsNotepadSavedTabRecord = create_extended_descriptor([UserRecordDescriptorExtension])( - "texteditor/windowsnotepad/tab/saved", + "application/editor/windowsnotepad/tab/saved", GENERIC_TAB_CONTENTS_RECORD_FIELDS + WINDOWS_SAVED_TABS_EXTRA_FIELDS, ) @@ -264,7 +273,7 @@ def _process_tab_file(self) -> None: self.deleted_content = deleted_content if deleted_content else None -class WindowsNotepadPlugin(TexteditorPlugin): +class WindowsNotepadPlugin(EditorPlugin): """Windows notepad tab content plugin.""" __namespace__ = "windowsnotepad" @@ -304,16 +313,16 @@ def tabs(self) -> Iterator[WindowsNotepadSavedTabRecord | WindowsNotepadUnsavedT - https://github.com/Nordgaren/tabstate-util/issues/1 - https://medium.com/@mahmoudsoheem/new-digital-forensics-artifact-from-windows-notepad-527645906b7b - Yields a WindowsNotepadSavedTabRecord or WindowsNotepadUnsavedTabRecord. with fields: + Yields a ``WindowsNotepadSavedTabRecord`` or ``WindowsNotepadUnsavedTabRecord`` with fields: .. code-block:: text + ts_mtime (datetime): The modification time of the tab. content (string): The content of the tab. path (path): The path to the tab file. deleted_content (string): The deleted content of the tab, if available. - hashes (digest): A digest of the tab content. + digest (digest): A digest of the tab content. saved_path (path): The path where the tab was saved. - modification_time (datetime): The modification time of the tab. """ for file, user in self.users_tabs: # Parse the file @@ -321,20 +330,24 @@ def tabs(self) -> Iterator[WindowsNotepadSavedTabRecord | WindowsNotepadUnsavedT if tab.is_saved: yield WindowsNotepadSavedTabRecord( + ts_mtime=wintimestamp(tab.tab_header.timestamp), + editor="windowsnotepad", content=tab.content, path=tab.file, deleted_content=tab.deleted_content, - hashes=digest((None, None, tab.tab_header.sha256.hex())), + digest=digest((None, None, tab.tab_header.sha256.hex())), saved_path=tab.tab_header.filePath, - modification_time=wintimestamp(tab.tab_header.timestamp), - _target=self.target, + source=file, _user=user, + _target=self.target, ) else: yield WindowsNotepadUnsavedTabRecord( + editor="windowsnotepad", content=tab.content, + deleted_content=tab.deleted_content, path=tab.file, - _target=self.target, + source=file, _user=user, - deleted_content=tab.deleted_content, + _target=self.target, ) diff --git a/dissect/target/plugins/apps/texteditor/texteditor.py b/dissect/target/plugins/apps/texteditor/texteditor.py deleted file mode 100644 index 1063d4919..000000000 --- a/dissect/target/plugins/apps/texteditor/texteditor.py +++ /dev/null @@ -1,13 +0,0 @@ -from dissect.target.helpers.descriptor_extensions import UserRecordDescriptorExtension -from dissect.target.helpers.record import create_extended_descriptor -from dissect.target.plugin import NamespacePlugin - -GENERIC_TAB_CONTENTS_RECORD_FIELDS = [("string", "content"), ("path", "path"), ("string", "deleted_content")] - -TexteditorTabContentRecord = create_extended_descriptor([UserRecordDescriptorExtension])( - "texteditor/tab", GENERIC_TAB_CONTENTS_RECORD_FIELDS -) - - -class TexteditorPlugin(NamespacePlugin): - __namespace__ = "texteditor" diff --git a/tests/plugins/apps/texteditor/test_texteditor.py b/tests/plugins/apps/editor/test_windowsnotepad.py similarity index 94% rename from tests/plugins/apps/texteditor/test_texteditor.py rename to tests/plugins/apps/editor/test_windowsnotepad.py index e8078d194..79294c075 100644 --- a/tests/plugins/apps/texteditor/test_texteditor.py +++ b/tests/plugins/apps/editor/test_windowsnotepad.py @@ -1,10 +1,9 @@ import os +from datetime import datetime, timezone from pathlib import Path -from flow.record.fieldtypes import datetime as dt - from dissect.target.filesystem import VirtualFilesystem -from dissect.target.plugins.apps.texteditor.windowsnotepad import ( +from dissect.target.plugins.apps.editor.windowsnotepad import ( WindowsNotepadPlugin, WindowsNotepadTab, ) @@ -77,8 +76,10 @@ def test_windows_tab_plugin_deleted_contents( # The recovered content in the records should match the original data, as well as the length for rec in records: + assert rec.editor == "windowsnotepad" assert rec.content == file_text_map[rec.path.name][0] assert rec.deleted_content == file_text_map[rec.path.name][1] + assert rec.source is not None def test_windows_tab_plugin_default( @@ -137,8 +138,10 @@ def test_windows_tab_plugin_default( # The recovered content in the records should match the original data, as well as the length for rec in records: + assert rec.editor == "windowsnotepad" assert rec.content == file_text_map[rec.path.name][0] assert rec.deleted_content == file_text_map[rec.path.name][1] + assert rec.source is not None def test_windows_saved_tab_plugin_extra_fields( @@ -148,13 +151,13 @@ def test_windows_saved_tab_plugin_extra_fields( "saved.bin": ( "Saved!", "C:\\Users\\user\\Desktop\\Saved!.txt", - dt(2024, 3, 28, 13, 7, 55, 482183), + datetime(2024, 3, 28, 13, 7, 55, 482183, tzinfo=timezone.utc), "ed9b760289e614c9dc8776e7280abe870be0a85019a32220b35acc54c0ecfbc1", ), "appclosed_saved_and_deletions.bin": ( text8, "C:\\Users\\user\\Desktop\\Saved.txt", - dt(2024, 3, 28, 13, 16, 21, 158279), + datetime(2024, 3, 28, 13, 16, 21, 158279, tzinfo=timezone.utc), "8d0533144aa42e2d81e7474332bdef6473e42b699041528d55a62e5391e914ce", ), } @@ -183,8 +186,10 @@ def test_windows_saved_tab_plugin_extra_fields( # The recovered content in the records should match the original data, as well as the length and all the # other saved metadata for rec in records: + assert rec.editor == "windowsnotepad" assert len(rec.content) == len(file_text_map[rec.path.name][0]) assert rec.content == file_text_map[rec.path.name][0] assert rec.saved_path == file_text_map[rec.path.name][1] - assert rec.modification_time == file_text_map[rec.path.name][2] - assert rec.hashes.sha256 == file_text_map[rec.path.name][3] + assert rec.ts_mtime == file_text_map[rec.path.name][2] + assert rec.digest.sha256 == file_text_map[rec.path.name][3] + assert rec.source is not None diff --git a/tests/plugins/apps/texteditor/__init__.py b/tests/plugins/apps/texteditor/__init__.py deleted file mode 100644 index e69de29bb..000000000