Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add improvements for VMware Workstation #854

Merged
merged 5 commits into from
Oct 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file.
61 changes: 61 additions & 0 deletions dissect/target/plugins/apps/virtualization/vmware_workstation.py
Original file line number Diff line number Diff line change
@@ -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")

Check warning on line 38 in dissect/target/plugins/apps/virtualization/vmware_workstation.py

View check run for this annotation

Codecov / codecov/patch

dissect/target/plugins/apps/virtualization/vmware_workstation.py#L38

Added line #L38 was not covered by tests

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

Check warning on line 54 in dissect/target/plugins/apps/virtualization/vmware_workstation.py

View check run for this annotation

Codecov / codecov/patch

dissect/target/plugins/apps/virtualization/vmware_workstation.py#L54

Added line #L54 was not covered by tests

yield VmwareDragAndDropRecord(
ts=file.lstat().st_mtime,
path=file,
_user=user_details.user,
_target=self.target,
)
31 changes: 23 additions & 8 deletions dissect/target/plugins/child/vmware_workstation.py
Original file line number Diff line number Diff line change
@@ -1,29 +1,44 @@
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
from dissect.target.target import Target

INVENTORY_PATHS = [
# Windows
"AppData/Roaming/VMware/inventory.vmls",
# Linux
".vmware/inventory.vmls",
]


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."""

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"

def __init__(self, target):
def __init__(self, target: Target):
super().__init__(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()
Expand Down
Git LFS file not shown
3 changes: 3 additions & 0 deletions tests/_data/plugins/child/vmware_workstation/inventory.vmls
Git LFS file not shown
Empty file.
38 changes: 38 additions & 0 deletions tests/plugins/apps/virtualization/test_vmware_workstation.py
Original file line number Diff line number Diff line change
@@ -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)
35 changes: 35 additions & 0 deletions tests/plugins/child/test_vmware_workstation.py
Original file line number Diff line number Diff line change
@@ -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",
]