Skip to content
Open
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
23 changes: 23 additions & 0 deletions dissect/target/plugins/os/windows/registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,29 @@ def get_user(self, key: RegistryKey | RegistryValue) -> WindowsUserRecord | None
return details.user
return None

@internal
def get_hive_name(self, key: RegistryKey | RegistryValue | RegistryHive) -> str | None:
"""Return the hive name for the given key or value."""

if isinstance(key, (RegistryKey, RegistryValue)):
wanted_hive = key.hive
elif isinstance(key, (RegistryHive)):
wanted_hive = key
else:
raise TypeError(f"Unexpected type for {key!r}")

for name, hive, _ in self._hive_paths:
if hive is wanted_hive:
return name
return None

@internal
def get_hive_shortname(self, key: RegistryKey | RegistryValue | RegistryHive) -> str | None:
"""Return the hive shortname for the given key or hive."""

reversed_shortnames = {value: key for key, value in self.target.registry.SHORTNAMES.items()}
return reversed_shortnames.get(self.get_hive_name(key))

@internal
def glob_ext(self, pattern: str) -> Iterator[KeyCollection]:
key_path, pattern = glob_split(pattern)
Expand Down
135 changes: 135 additions & 0 deletions dissect/target/plugins/os/windows/startup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
from __future__ import annotations

from typing import TYPE_CHECKING

from dissect.target.exceptions import RegistryValueNotFoundError, UnsupportedPluginError
from dissect.target.helpers.descriptor_extensions import UserRecordDescriptorExtension
from dissect.target.helpers.record import create_extended_descriptor
from dissect.target.plugin import Plugin, export

if TYPE_CHECKING:
from collections.abc import Iterator
from pathlib import Path

from dissect.target.helpers.regutil import RegistryKey
from dissect.target.plugins.general.users import UserDetails
from dissect.target.target import Target


StartupRecord = create_extended_descriptor([UserRecordDescriptorExtension])(
"windows/startup",
[
("datetime", "ts_mtime"),
("datetime", "ts_btime"),
("command", "command"),
("path", "source"),
],
)


class StartupPlugin(Plugin):
"""Windows startup plugin.

Extracts entries from Windows Startup directories and registry folders. Location can be customized with registry key
``(HKLM|HKCU)\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\(Shell Folders|User Shell Folders)\\Startup``

References:
- https://support.microsoft.com/en-us/windows/configure-startup-applications-in-windows-115a420a-0bff-4a6f-90e0-1934c844e473
"""

SYSTEM_PATH = "/sysvol/ProgramData/Microsoft/Windows/Start Menu/Programs/Startup/"
USER_PATH = "AppData/Roaming/Microsoft/Windows/Start Menu/Programs/Startup/"

SYSTEM_KEYS = (
"HKLM\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders",
"HKLM\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\User Shell Folders",
)
USER_KEYS = (
"HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders",
"HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\User Shell Folders",
)

def __init__(self, target: Target):
super().__init__(target)
self.startup_files = set(self.find_startup_files())

def find_startup_files(self) -> Iterator[tuple[Path, UserDetails | None, RegistryKey | None]]:
"""Yields found configured startup files which may or may not exist on the target."""

seen = set()

for path in self.target.fs.path(self.SYSTEM_PATH).glob("*"):
if path.is_file() and path.name != "desktop.ini":
seen.add(path)
yield path, None, None

for user_details in self.target.user_details.all_with_home():
for path in user_details.home_path.joinpath(self.USER_PATH).glob("*"):
if path.is_file() and path.name != "desktop.ini":
seen.add(path)
yield path, user_details, None

# The ``Shell Folders\\Startup`` and ``User Shell Folders\\Startup`` can point towards an alternative folder
# with files, or to a specific file.
if self.target.has_function("registry"):
for key_path in self.SYSTEM_KEYS + self.USER_KEYS:
for key in self.target.registry.keys(key_path):
user_details = self.target.registry.get_user_details(key)

for name in ("Startup", "Common Startup"):
try:
value: str = key.value(name).value
except RegistryValueNotFoundError:
continue

path: Path = self.target.resolve(value, user_details.user.sid if user_details else None) # type: ignore
if path in seen:
continue

# Yield if the path does not exist (could be dir or file leftover artifact, we don't know).
# We also yield if the path is a file.
if not path.exists() or path.is_file():
# Some values can not be resolved by the resolver plugin.
if value.endswith("Microsoft\\Windows\\Start Menu\\Programs\\Startup"):
continue
seen.add(path)
yield path, user_details, key

if path.is_dir():
for child in path.glob("*"):
if child.name == "desktop.ini" or child in seen:
continue
seen.add(child)
yield child, user_details, key

def check_compatible(self) -> None:
if not self.startup_files:
raise UnsupportedPluginError("No Startup files found on target")

@export(record=StartupRecord)
def startup(self) -> Iterator[StartupRecord]:
"""Return the contents of Startup folders."""

for file_path, user_details, reg_key in self.startup_files:
source = None
ts_mtime = None
ts_btime = None

if file_path.exists():
stat = file_path.lstat()
ts_mtime = stat.st_mtime
ts_btime = getattr(stat, "st_birthtime", None)
source = file_path.parent.resolve()

elif reg_key:
ts_mtime = reg_key.ts
source = f"{self.target.registry.get_hive_shortname(reg_key)}\\{reg_key.path}"

yield StartupRecord(
ts_mtime=ts_mtime,
ts_btime=ts_btime,
command=f"'{file_path.resolve()}'",
source=source,
_user=user_details.user if user_details else None,
_target=self.target,
)
58 changes: 58 additions & 0 deletions tests/plugins/os/windows/test_startup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
from __future__ import annotations

from io import BytesIO
from typing import TYPE_CHECKING

from dissect.target.helpers.regutil import VirtualHive, VirtualKey
from dissect.target.plugins.os.windows.startup import StartupPlugin

if TYPE_CHECKING:
from dissect.target.filesystem import VirtualFilesystem
from dissect.target.target import Target


def test_windows_startup(
target_win_users: Target, fs_win: VirtualFilesystem, hive_hklm: VirtualHive, hive_hku: VirtualHive
) -> None:
"""Test Windows Startp plugin."""

# File persistency
fs_win.map_file_fh("ProgramData/Microsoft/Windows/Start Menu/Programs/Startup/SystemFoo.exe", BytesIO(b""))
fs_win.map_file_fh("ProgramData/Microsoft/Windows/Start Menu/Programs/Startup/SystemBar.exe", BytesIO(b""))
fs_win.map_file_fh(
"Users/John/AppData/Roaming/Microsoft/Windows/Start Menu/Programs/Startup/UserFoo.exe", BytesIO(b"")
)
fs_win.map_file_fh(
"Users/John/AppData/Roaming/Microsoft/Windows/Start Menu/Programs/Startup/desktop.ini", BytesIO(b"")
)

# Registry overrides
key_name = "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\User Shell Folders"
key = VirtualKey(hive_hku, key_name)
key.add_value("Startup", "C:\\Users\\John\\Downloads\\User.exe")
hive_hku.map_key(key_name, key)

key_name = "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders"
key = VirtualKey(hive_hklm, key_name)
key.add_value("Startup", "C:\\Temp\\System.exe")
hive_hklm.map_key(key_name, key)

target_win_users.add_plugin(StartupPlugin)
records = sorted(target_win_users.startup(), key=lambda r: str(r.command.executable))
assert len(records) == 5

assert [r.command.executable for r in records] == [
"/C:/Temp/System.exe",
"/C:/Users/John/AppData/Roaming/Microsoft/Windows/Start Menu/Programs/Startup/UserFoo.exe",
"/C:/Users/John/Downloads/User.exe",
"/sysvol/ProgramData/Microsoft/Windows/Start Menu/Programs/Startup/SystemBar.exe",
"/sysvol/ProgramData/Microsoft/Windows/Start Menu/Programs/Startup/SystemFoo.exe",
]

assert [r.source for r in records] == [
"HKLM\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders",
"\\C:\\Users\\John\\AppData\\Roaming\\Microsoft\\Windows\\Start Menu\\Programs\\Startup",
"HKU\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\User Shell Folders",
"\\sysvol\\ProgramData\\Microsoft\\Windows\\Start Menu\\Programs\\Startup",
"\\sysvol\\ProgramData\\Microsoft\\Windows\\Start Menu\\Programs\\Startup",
]
Loading