Skip to content

Commit

Permalink
add application plugins
Browse files Browse the repository at this point in the history
  • Loading branch information
JSCU-CNI committed Sep 17, 2024
1 parent c5bf1ed commit 022ae34
Show file tree
Hide file tree
Showing 8 changed files with 201 additions and 1 deletion.
21 changes: 21 additions & 0 deletions dissect/target/helpers/record.py
Original file line number Diff line number Diff line change
Expand Up @@ -179,3 +179,24 @@ def DynamicDescriptor(types): # noqa
("varint", "interface_service_order"),
],
)


COMMON_APPLICATION_FIELDS = [
("datetime", "ts_modified"),
("datetime", "ts_installed"),
("string", "name"),
("string", "version"),
("string", "author"),
("string", "type"),
("path", "path"),
]

UnixApplicationRecord = TargetRecordDescriptor(
"unix/application",
COMMON_APPLICATION_FIELDS,
)

WindowsApplicationRecord = TargetRecordDescriptor(
"windows/application",
COMMON_APPLICATION_FIELDS,
)
69 changes: 69 additions & 0 deletions dissect/target/plugins/os/unix/applications.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
from typing import Iterator

from dissect.target.exceptions import UnsupportedPluginError
from dissect.target.helpers import configutil
from dissect.target.helpers.fsutil import TargetPath
from dissect.target.helpers.record import UnixApplicationRecord
from dissect.target.plugin import Plugin, export
from dissect.target.target import Target


class UnixApplicationsPlugin(Plugin):
"""Unix Applications plugin."""

SYSTEM_PATHS = [
"/usr/share/applications/",
"/usr/local/share/applications/",
"/var/lib/snapd/desktop/applications/",
"/var/lib/flatpak/exports/share/applications/",
]

USER_PATHS = [
".local/share/applications/",
]

SYSTEM_APPS = [
"org.gnome.",
]

def __init__(self, target: Target):
super().__init__(target)
self.desktop_files = list(self.find_desktop_files())

def find_desktop_files(self) -> Iterator[TargetPath]:
for dir in self.SYSTEM_PATHS:
for file in self.target.fs.path(dir).glob("*.desktop"):
yield file

for user_details in self.target.user_details.all_with_home():
for dir in self.USER_PATHS:
for file in user_details.home_path.joinpath(dir).glob("*.desktop"):
yield file

def check_compatible(self) -> None:
if not self.desktop_files:
raise UnsupportedPluginError("No application .desktop files found")

@export(record=UnixApplicationRecord)
def applications(self) -> Iterator[UnixApplicationRecord]:
"""Yield installed Unix GUI applications from GNOME and XFCE.
Resources:
- https://wiki.archlinux.org/title/Desktop_entries
- https://specifications.freedesktop.org/desktop-entry-spec/latest/
- https://unix.stackexchange.com/questions/582928/where-gnome-apps-are-installed
"""
for file in self.desktop_files:
parser = configutil.parse(file, hint="ini")
config = parser.get("Desktop Entry") or {}
stat = file.lstat()

yield UnixApplicationRecord(
ts_modified=stat.st_mtime,
ts_installed=stat.st_btime if hasattr(stat, "st_btime") else None,
name=config.get("Name"),
version=config.get("Version"),
path=config.get("Exec"),
type="system" if config.get("Icon", "")[0:10] in self.SYSTEM_APPS else "user",
_target=self.target,
)
69 changes: 69 additions & 0 deletions dissect/target/plugins/os/unix/linux/debian/snap.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
from typing import Iterator

from dissect.target.exceptions import UnsupportedPluginError
from dissect.target.filesystems.squashfs import SquashFSFilesystem
from dissect.target.helpers import configutil
from dissect.target.helpers.fsutil import TargetPath
from dissect.target.helpers.record import UnixApplicationRecord
from dissect.target.plugin import Plugin, export
from dissect.target.target import Target


class SnapPlugin(Plugin):
"""Canonical Linux Snapcraft plugin.
Reads information from installed ``*.snap`` files found in ``/var/lib/snapd/snaps``.
Logs of the ``snapd`` daemon can be parsed using the ``journal`` or ``syslog`` plugins.
Resources:
- https://github.com/canonical/snapcraft
- https://en.wikipedia.org/wiki/Snap_(software)
"""

PATHS = [
"/var/lib/snapd/snaps",
]

def __init__(self, target: Target):
super().__init__(target)
self.installs = list(self._find_installs())

def check_compatible(self) -> None:
if not configutil.HAS_YAML:
raise UnsupportedPluginError("Missing required dependency ruamel.yaml")

if not self.installs:
raise UnsupportedPluginError("No snapd install folder(s) found")

def _find_installs(self) -> Iterator[TargetPath]:
for str_path in self.PATHS:
if (path := self.target.fs.path(str_path)).exists():
yield path

@export(record=UnixApplicationRecord)
def snap(self) -> Iterator[UnixApplicationRecord]:
"""Yield installed snap packages."""

for install_path in self.installs:
for snap in install_path.glob("*.snap"):
try:
squashfs = SquashFSFilesystem(snap.open())

except (ValueError, NotImplementedError) as e:
self.target.log.warning("Unable to open snap file %s", snap)
self.target.log.debug("", exc_info=e)
continue

if not (meta := squashfs.path("meta/snap.yaml")).exists():
self.target.log.warning("Snap %s has no meta/snap.yaml file")
continue

meta_data = configutil.parse(meta, hint="yaml")

yield UnixApplicationRecord(
ts_modified=meta.lstat().st_mtime,
name=meta_data.get("name"),
version=meta_data.get("version"),
path=snap,
_target=self.target,
)
2 changes: 1 addition & 1 deletion dissect/target/plugins/os/unix/packagemanager.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from dissect.target.plugin import Plugin, export

PackageManagerLogRecord = TargetRecordDescriptor(
"unix/log/packagemanager",
"unix/packagemanager/log",
[
("datetime", "ts"),
("string", "package_manager"),
Expand Down
41 changes: 41 additions & 0 deletions dissect/target/plugins/os/windows/regf/applications.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
from typing import Iterator

from dissect.target.exceptions import UnsupportedPluginError
from dissect.target.helpers.record import WindowsApplicationRecord
from dissect.target.plugin import Plugin, export
from dissect.target.target import Target


class WindowsApplicationsPlugin(Plugin):
"""Windows Applications plugin."""

KEY = "HKLM\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall"

def __init__(self, target: Target):
super().__init__(target)
self.keys = list(self.target.registry.keys(self.KEY))

def check_compatible(self) -> None:
if not self.target.has_function("registry"):
raise UnsupportedPluginError("No Windows registry found")

if not self.keys:
raise UnsupportedPluginError("No 'Uninstall' registry keys found")

@export(record=WindowsApplicationRecord)
def applications(self) -> Iterator[WindowsApplicationRecord]:
"""Yields installed applications from the Windows registry."""
for uninstall in self.keys:
for app in uninstall.subkeys():
values = {value.name: value.value for value in app.values()}

yield WindowsApplicationRecord(
ts_modified=app.ts,
ts_installed=values.get("InstallDate"),
name=values.get("DisplayName"),
version=values.get("DisplayVersion"),
author=values.get("Publisher"),
type="system" if values.get("SystemComponent") else "user",
path=values.get("DisplayIcon") or values.get("InstallLocation") or values.get("InstallSource"),
_target=self.target,
)
Empty file.
Empty file.
Empty file.

0 comments on commit 022ae34

Please sign in to comment.