From 76a7f1557450970b83ad4b2e106e3e80828fee33 Mon Sep 17 00:00:00 2001 From: JSCU-CNI <121175071+JSCU-CNI@users.noreply.github.com> Date: Wed, 18 Sep 2024 13:04:21 +0200 Subject: [PATCH 1/3] add support for vmware workstation linux children --- .../plugins/apps/virtualization/__init__.py | 0 .../plugins/child/vmware_workstation.py | 28 +++++++++++---- .../child/vmware_workstation/inventory.vmls | 3 ++ tests/plugins/apps/virtualization/__init__.py | 0 .../plugins/child/test_vmware_workstation.py | 35 +++++++++++++++++++ 5 files changed, 59 insertions(+), 7 deletions(-) create mode 100644 dissect/target/plugins/apps/virtualization/__init__.py create mode 100644 tests/_data/plugins/child/vmware_workstation/inventory.vmls create mode 100644 tests/plugins/apps/virtualization/__init__.py create mode 100644 tests/plugins/child/test_vmware_workstation.py diff --git a/dissect/target/plugins/apps/virtualization/__init__.py b/dissect/target/plugins/apps/virtualization/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/dissect/target/plugins/child/vmware_workstation.py b/dissect/target/plugins/child/vmware_workstation.py index 8bcd3fda9..ca0564c10 100644 --- a/dissect/target/plugins/child/vmware_workstation.py +++ b/dissect/target/plugins/child/vmware_workstation.py @@ -1,16 +1,30 @@ +from typing import Iterator + from dissect.target.exceptions import UnsupportedPluginError +from dissect.target.helpers.fsutil import TargetPath from dissect.target.helpers.record import ChildTargetRecord from dissect.target.plugin import ChildTargetPlugin +INVENTORY_PATHS = [ + # Windows + "AppData/Roaming/VMware/inventory.vmls", + # Linux + ".vmware/inventory.vmls", +] + + +def find_vm_inventory(target) -> Iterator[TargetPath]: + """Search for inventory.vmls files in user home folders. + + Does not support older vmAutoStart.xml or vmInventory.xml formats.""" -def find_vm_inventory(target): for user_details in target.user_details.all_with_home(): - inv_file = user_details.home_path.joinpath("AppData/Roaming/VMware/inventory.vmls") - if inv_file.exists(): - yield inv_file + for inv_path in INVENTORY_PATHS: + if (inv_file := user_details.home_path.joinpath(inv_path)).exists(): + yield inv_file -class WorkstationChildTargetPlugin(ChildTargetPlugin): +class VmwareWorkstationChildTargetPlugin(ChildTargetPlugin): """Child target plugin that yields from VMware Workstation VM inventory.""" __type__ = "vmware_workstation" @@ -20,10 +34,10 @@ def __init__(self, target): self.inventories = list(find_vm_inventory(target)) def check_compatible(self) -> None: - if not len(self.inventories): + if not self.inventories: raise UnsupportedPluginError("No VMWare inventories found") - def list_children(self): + def list_children(self) -> Iterator[ChildTargetRecord]: for inv in self.inventories: for line in inv.open("rt"): line = line.strip() diff --git a/tests/_data/plugins/child/vmware_workstation/inventory.vmls b/tests/_data/plugins/child/vmware_workstation/inventory.vmls new file mode 100644 index 000000000..3dcbf348f --- /dev/null +++ b/tests/_data/plugins/child/vmware_workstation/inventory.vmls @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:486e70bef251e4014f5ccf4657ef93334be4bcea94c6d46b591e4737efeda28a +size 1105 diff --git a/tests/plugins/apps/virtualization/__init__.py b/tests/plugins/apps/virtualization/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/plugins/child/test_vmware_workstation.py b/tests/plugins/child/test_vmware_workstation.py new file mode 100644 index 000000000..e601cb955 --- /dev/null +++ b/tests/plugins/child/test_vmware_workstation.py @@ -0,0 +1,35 @@ +import pytest + +from dissect.target.filesystem import VirtualFilesystem +from dissect.target.plugins.child.vmware_workstation import ( + VmwareWorkstationChildTargetPlugin, +) +from dissect.target.target import Target +from tests._utils import absolute_path + + +@pytest.mark.parametrize( + "target,fs,inventory_path", + [ + ("target_win_users", "fs_win", "Users\\John\\AppData\\Roaming\\VMware\\inventory.vmls"), + ("target_unix_users", "fs_unix", "/home/user/.vmware/inventory.vmls"), + ], +) +def test_child_vmware_workstation( + target: Target, fs: VirtualFilesystem, inventory_path: str, request: pytest.FixtureRequest +) -> None: + """test if we detect VMware Workstation children from inventory files correctly on Windows and Unix targets""" + + target = request.getfixturevalue(target) + fs = request.getfixturevalue(fs) + + fs.map_file(inventory_path, absolute_path("_data/plugins/child/vmware_workstation/inventory.vmls")) + target.add_plugin(VmwareWorkstationChildTargetPlugin) + children = list(target.list_children()) + + assert len(children) == 3 + assert [c.path for c in children] == [ + "/path/to/first/vm/vm.vmx", + "/path/to/second/vm/vm.vmx", + "/path/to/third/vm/vm.vmx", + ] From 8869870e3594f19fe874a18bcc935f5947e4e7dc Mon Sep 17 00:00:00 2001 From: JSCU-CNI <121175071+JSCU-CNI@users.noreply.github.com> Date: Wed, 18 Sep 2024 13:34:30 +0200 Subject: [PATCH 2/3] add vmware drag and drop plugin --- .../apps/virtualization/vmware_workstation.py | 61 +++++++++++++++++++ .../virtualization/vmware_workstation/dnd.tar | 3 + .../virtualization/test_vmware_workstation.py | 38 ++++++++++++ 3 files changed, 102 insertions(+) create mode 100644 dissect/target/plugins/apps/virtualization/vmware_workstation.py create mode 100644 tests/_data/plugins/apps/virtualization/vmware_workstation/dnd.tar create mode 100644 tests/plugins/apps/virtualization/test_vmware_workstation.py diff --git a/dissect/target/plugins/apps/virtualization/vmware_workstation.py b/dissect/target/plugins/apps/virtualization/vmware_workstation.py new file mode 100644 index 000000000..411a81b3d --- /dev/null +++ b/dissect/target/plugins/apps/virtualization/vmware_workstation.py @@ -0,0 +1,61 @@ +from typing import Iterator + +from dissect.target.exceptions import UnsupportedPluginError +from dissect.target.helpers.descriptor_extensions import UserRecordDescriptorExtension +from dissect.target.helpers.fsutil import TargetPath +from dissect.target.helpers.record import create_extended_descriptor +from dissect.target.plugin import Plugin, alias, export +from dissect.target.plugins.general.users import UserDetails +from dissect.target.target import Target + +VmwareDragAndDropRecord = create_extended_descriptor([UserRecordDescriptorExtension])( + "virtualization/vmware/clipboard", + [ + ("datetime", "ts"), + ("path", "path"), + ], +) + +VMWARE_DND_PATHS = [ + # Windows + "AppData/Local/Temp/VmwareDND", + # Linux + ".cache/vmware/drag_and_drop", +] + + +class VmwareWorkstationPlugin(Plugin): + """VMware Workstation plugin.""" + + __namespace__ = "vmware" + + def __init__(self, target: Target): + super().__init__(target) + self.dnd_dirs = list(self.find_dnd_dirs()) + + def check_compatible(self) -> None: + if not self.dnd_dirs: + raise UnsupportedPluginError("No VMware Workstation DnD artifact(s) found") + + def find_dnd_dirs(self) -> Iterator[tuple[UserDetails, TargetPath]]: + for user_details in self.target.user_details.all_with_home(): + for dnd_path in VMWARE_DND_PATHS: + if (dnd_dir := user_details.home_path.joinpath(dnd_path)).exists(): + yield user_details, dnd_dir + + @alias("draganddrop") + @export(record=VmwareDragAndDropRecord) + def clipboard(self) -> Iterator[VmwareDragAndDropRecord]: + """Yield cached VMware Workstation drag-and-drop file artifacts.""" + + for user_details, dnd_dir in self.dnd_dirs: + for file in dnd_dir.rglob("*/*"): + if file.is_dir(): + continue + + yield VmwareDragAndDropRecord( + ts=file.lstat().st_mtime, + path=file, + _user=user_details.user, + _target=self.target, + ) diff --git a/tests/_data/plugins/apps/virtualization/vmware_workstation/dnd.tar b/tests/_data/plugins/apps/virtualization/vmware_workstation/dnd.tar new file mode 100644 index 000000000..6ca51a57b --- /dev/null +++ b/tests/_data/plugins/apps/virtualization/vmware_workstation/dnd.tar @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:aec4af743e91af2c8a7a03eb41f5a0aa800c1377a16581d4e4fdbd7ca048a3a6 +size 51200 diff --git a/tests/plugins/apps/virtualization/test_vmware_workstation.py b/tests/plugins/apps/virtualization/test_vmware_workstation.py new file mode 100644 index 000000000..ef833714b --- /dev/null +++ b/tests/plugins/apps/virtualization/test_vmware_workstation.py @@ -0,0 +1,38 @@ +from datetime import datetime, timezone + +import pytest + +from dissect.target.filesystem import VirtualFilesystem +from dissect.target.plugins.apps.virtualization.vmware_workstation import ( + VmwareWorkstationPlugin, +) +from dissect.target.target import Target +from tests._utils import absolute_path + + +@pytest.mark.parametrize( + "target,fs,dnd_path", + [ + ("target_win_users", "fs_win", "Users\\John\\AppData\\Local\\Temp\\VmwareDND"), + ("target_unix_users", "fs_unix", "/home/user/.cache/vmware/drag_and_drop"), + ], +) +def test_vmware_workstation_clipboard_dnd( + target: Target, fs: VirtualFilesystem, dnd_path: str, request: pytest.FixtureRequest +) -> None: + """test if we correctly detect drag and drop artifacts on Windows and Unix targets""" + + target = request.getfixturevalue(target) + fs = request.getfixturevalue(fs) + fs.map_dir_from_tar(dnd_path, absolute_path("_data/plugins/apps/virtualization/vmware_workstation/dnd.tar")) + target.add_plugin(VmwareWorkstationPlugin) + results = sorted(list(target.vmware.clipboard()), key=lambda r: r.path) + dnd_path = dnd_path.replace("\\", "/") + + assert len(results) == 3 + assert [str(r.path).replace("C:\\", "").replace("\\", "/") for r in results] == [ + f"{dnd_path}/8lkBf1/wachter.jpg", + f"{dnd_path}/iUpQHG/credentials.txt", + f"{dnd_path}/x1G9fJ/example.txt", + ] + assert results[0].ts == datetime(2024, 9, 18, 11, 16, 44, tzinfo=timezone.utc) From b6291cc8d5f30596e6456ad41ace4fe393843ef7 Mon Sep 17 00:00:00 2001 From: JSCU-CNI <121175071+JSCU-CNI@users.noreply.github.com> Date: Wed, 18 Sep 2024 13:37:23 +0200 Subject: [PATCH 3/3] typing --- dissect/target/plugins/child/vmware_workstation.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/dissect/target/plugins/child/vmware_workstation.py b/dissect/target/plugins/child/vmware_workstation.py index ca0564c10..f3be28587 100644 --- a/dissect/target/plugins/child/vmware_workstation.py +++ b/dissect/target/plugins/child/vmware_workstation.py @@ -4,6 +4,7 @@ from dissect.target.helpers.fsutil import TargetPath from dissect.target.helpers.record import ChildTargetRecord from dissect.target.plugin import ChildTargetPlugin +from dissect.target.target import Target INVENTORY_PATHS = [ # Windows @@ -13,7 +14,7 @@ ] -def find_vm_inventory(target) -> Iterator[TargetPath]: +def find_vm_inventory(target: Target) -> Iterator[TargetPath]: """Search for inventory.vmls files in user home folders. Does not support older vmAutoStart.xml or vmInventory.xml formats.""" @@ -29,7 +30,7 @@ class VmwareWorkstationChildTargetPlugin(ChildTargetPlugin): __type__ = "vmware_workstation" - def __init__(self, target): + def __init__(self, target: Target): super().__init__(target) self.inventories = list(find_vm_inventory(target))