Skip to content

Commit

Permalink
Add improvements for VMware Workstation (#854)
Browse files Browse the repository at this point in the history
  • Loading branch information
JSCU-CNI authored Oct 7, 2024
1 parent b96029a commit d0eba16
Show file tree
Hide file tree
Showing 8 changed files with 163 additions and 8 deletions.
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")

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,
)
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",
]

0 comments on commit d0eba16

Please sign in to comment.