dissect.target 3.13.dev26__py3-none-any.whl → 3.14__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
Files changed (138) hide show
  1. dissect/target/container.py +9 -1
  2. dissect/target/containers/asdf.py +2 -0
  3. dissect/target/containers/ewf.py +2 -0
  4. dissect/target/containers/hdd.py +2 -0
  5. dissect/target/containers/hds.py +2 -0
  6. dissect/target/containers/qcow2.py +2 -0
  7. dissect/target/containers/raw.py +2 -0
  8. dissect/target/containers/split.py +2 -0
  9. dissect/target/containers/vdi.py +2 -0
  10. dissect/target/containers/vhd.py +2 -0
  11. dissect/target/containers/vhdx.py +2 -0
  12. dissect/target/containers/vmdk.py +2 -0
  13. dissect/target/filesystem.py +108 -15
  14. dissect/target/filesystems/ad1.py +1 -1
  15. dissect/target/filesystems/btrfs.py +180 -0
  16. dissect/target/filesystems/cb.py +4 -4
  17. dissect/target/filesystems/config.py +161 -31
  18. dissect/target/filesystems/dir.py +1 -1
  19. dissect/target/filesystems/exfat.py +1 -1
  20. dissect/target/filesystems/extfs.py +5 -1
  21. dissect/target/filesystems/fat.py +1 -1
  22. dissect/target/filesystems/ffs.py +1 -1
  23. dissect/target/filesystems/itunes.py +1 -1
  24. dissect/target/filesystems/ntfs.py +1 -1
  25. dissect/target/filesystems/smb.py +1 -1
  26. dissect/target/filesystems/squashfs.py +1 -1
  27. dissect/target/filesystems/tar.py +1 -1
  28. dissect/target/filesystems/vmfs.py +1 -1
  29. dissect/target/filesystems/xfs.py +1 -1
  30. dissect/target/filesystems/zip.py +1 -1
  31. dissect/target/helpers/cache.py +2 -2
  32. dissect/target/helpers/configutil.py +283 -83
  33. dissect/target/helpers/fsutil.py +9 -6
  34. dissect/target/helpers/hashutil.py +20 -19
  35. dissect/target/helpers/utils.py +14 -3
  36. dissect/target/loaders/ad1.py +1 -1
  37. dissect/target/loaders/asdf.py +1 -1
  38. dissect/target/loaders/log.py +2 -2
  39. dissect/target/loaders/smb.py +23 -13
  40. dissect/target/loaders/targetd.py +12 -2
  41. dissect/target/loaders/vma.py +1 -1
  42. dissect/target/loaders/xva.py +1 -1
  43. dissect/target/plugin.py +14 -2
  44. dissect/target/plugins/apps/av/sophos.py +1 -2
  45. dissect/target/plugins/apps/av/symantec.py +3 -4
  46. dissect/target/plugins/apps/av/trendmicro.py +2 -3
  47. dissect/target/plugins/{browsers → apps/browser}/chrome.py +6 -3
  48. dissect/target/plugins/{browsers → apps/browser}/chromium.py +18 -13
  49. dissect/target/plugins/{browsers → apps/browser}/edge.py +6 -3
  50. dissect/target/plugins/{browsers → apps/browser}/firefox.py +3 -7
  51. dissect/target/plugins/{browsers → apps/browser}/iexplore.py +14 -4
  52. dissect/target/plugins/apps/remoteaccess/teamviewer.py +55 -27
  53. dissect/target/plugins/apps/ssh/opensshd.py +31 -30
  54. dissect/target/plugins/apps/{webservers → webserver}/apache.py +1 -1
  55. dissect/target/plugins/apps/{webservers → webserver}/caddy.py +1 -1
  56. dissect/target/plugins/apps/{webservers → webserver}/iis.py +1 -1
  57. dissect/target/plugins/apps/{webservers → webserver}/nginx.py +1 -1
  58. dissect/target/plugins/child/hyperv.py +1 -2
  59. dissect/target/plugins/child/vmware_workstation.py +1 -3
  60. dissect/target/plugins/filesystem/acquire_handles.py +2 -0
  61. dissect/target/plugins/filesystem/acquire_hash.py +1 -7
  62. dissect/target/plugins/filesystem/icat.py +5 -5
  63. dissect/target/plugins/filesystem/ntfs/mft.py +2 -2
  64. dissect/target/plugins/filesystem/ntfs/mft_timeline.py +2 -2
  65. dissect/target/plugins/filesystem/ntfs/usnjrnl.py +2 -3
  66. dissect/target/plugins/filesystem/resolver.py +1 -1
  67. dissect/target/plugins/filesystem/unix/capability.py +77 -66
  68. dissect/target/plugins/filesystem/walkfs.py +25 -19
  69. dissect/target/plugins/filesystem/yara.py +20 -19
  70. dissect/target/plugins/general/config.py +28 -11
  71. dissect/target/plugins/os/unix/_os.py +28 -21
  72. dissect/target/plugins/os/unix/bsd/osx/user.py +1 -3
  73. dissect/target/plugins/os/unix/cronjobs.py +4 -16
  74. dissect/target/plugins/os/unix/{linux/esxi → esxi}/_os.py +5 -6
  75. dissect/target/plugins/os/unix/generic.py +5 -1
  76. dissect/target/plugins/os/unix/history.py +2 -1
  77. dissect/target/plugins/os/unix/linux/_os.py +12 -5
  78. dissect/target/plugins/os/unix/linux/services.py +112 -0
  79. dissect/target/plugins/os/unix/linux/suse/zypper.py +4 -4
  80. dissect/target/plugins/os/unix/locale.py +3 -1
  81. dissect/target/plugins/os/unix/log/journal.py +7 -6
  82. dissect/target/plugins/os/unix/packagemanager.py +3 -3
  83. dissect/target/plugins/os/unix/shadow.py +1 -1
  84. dissect/target/plugins/os/windows/_os.py +2 -1
  85. dissect/target/plugins/os/windows/amcache.py +9 -10
  86. dissect/target/plugins/os/windows/catroot.py +2 -2
  87. dissect/target/plugins/os/windows/cim.py +5 -4
  88. dissect/target/plugins/os/windows/datetime.py +4 -1
  89. dissect/target/plugins/os/windows/defender.py +3 -3
  90. dissect/target/plugins/os/windows/generic.py +10 -11
  91. dissect/target/plugins/os/windows/lnk.py +6 -6
  92. dissect/target/plugins/os/windows/log/amcache.py +3 -5
  93. dissect/target/plugins/os/windows/log/pfro.py +1 -3
  94. dissect/target/plugins/os/windows/prefetch.py +5 -6
  95. dissect/target/plugins/os/windows/recyclebin.py +3 -4
  96. dissect/target/plugins/os/windows/regf/7zip.py +2 -4
  97. dissect/target/plugins/os/windows/regf/bam.py +1 -2
  98. dissect/target/plugins/os/windows/regf/cit.py +4 -5
  99. dissect/target/plugins/os/windows/regf/mru.py +6 -2
  100. dissect/target/plugins/os/windows/regf/muicache.py +1 -3
  101. dissect/target/plugins/os/windows/regf/recentfilecache.py +1 -2
  102. dissect/target/plugins/os/windows/regf/shimcache.py +1 -2
  103. dissect/target/plugins/os/windows/regf/trusteddocs.py +1 -1
  104. dissect/target/plugins/os/windows/regf/userassist.py +1 -2
  105. dissect/target/plugins/os/windows/services.py +2 -4
  106. dissect/target/plugins/os/windows/sru.py +4 -4
  107. dissect/target/plugins/os/windows/startupinfo.py +5 -6
  108. dissect/target/plugins/os/windows/syscache.py +2 -3
  109. dissect/target/target.py +65 -32
  110. dissect/target/tools/info.py +2 -1
  111. dissect/target/tools/mount.py +2 -12
  112. dissect/target/tools/shell.py +3 -2
  113. dissect/target/volume.py +10 -9
  114. dissect/target/volumes/bde.py +1 -1
  115. dissect/target/volumes/ddf.py +2 -0
  116. dissect/target/volumes/disk.py +2 -0
  117. dissect/target/volumes/luks.py +1 -1
  118. dissect/target/volumes/lvm.py +2 -0
  119. dissect/target/volumes/md.py +2 -0
  120. dissect/target/volumes/vmfs.py +2 -0
  121. {dissect.target-3.13.dev26.dist-info → dissect.target-3.14.dist-info}/METADATA +2 -1
  122. {dissect.target-3.13.dev26.dist-info → dissect.target-3.14.dist-info}/RECORD +137 -136
  123. {dissect.target-3.13.dev26.dist-info → dissect.target-3.14.dist-info}/WHEEL +1 -1
  124. dissect/target/plugins/os/unix/services.py +0 -151
  125. /dissect/target/plugins/apps/{containers → browser}/__init__.py +0 -0
  126. /dissect/target/plugins/{browsers → apps/browser}/browser.py +0 -0
  127. /dissect/target/plugins/apps/{vpns → container}/__init__.py +0 -0
  128. /dissect/target/plugins/apps/{containers → container}/docker.py +0 -0
  129. /dissect/target/plugins/apps/{webservers → vpn}/__init__.py +0 -0
  130. /dissect/target/plugins/apps/{vpns → vpn}/openvpn.py +0 -0
  131. /dissect/target/plugins/apps/{vpns → vpn}/wireguard.py +0 -0
  132. /dissect/target/plugins/{browsers → apps/webserver}/__init__.py +0 -0
  133. /dissect/target/plugins/apps/{webservers/webservers.py → webserver/webserver.py} +0 -0
  134. /dissect/target/plugins/os/unix/{linux/esxi → esxi}/__init__.py +0 -0
  135. {dissect.target-3.13.dev26.dist-info → dissect.target-3.14.dist-info}/COPYRIGHT +0 -0
  136. {dissect.target-3.13.dev26.dist-info → dissect.target-3.14.dist-info}/LICENSE +0 -0
  137. {dissect.target-3.13.dev26.dist-info → dissect.target-3.14.dist-info}/entry_points.txt +0 -0
  138. {dissect.target-3.13.dev26.dist-info → dissect.target-3.14.dist-info}/top_level.txt +0 -0
@@ -6,11 +6,10 @@ import uuid
6
6
  from struct import unpack
7
7
  from typing import Iterator, Optional, Union
8
8
 
9
- from flow.record.fieldtypes import posix_path
10
-
11
9
  from dissect.target.filesystem import Filesystem
12
10
  from dissect.target.helpers.fsutil import TargetPath
13
11
  from dissect.target.helpers.record import UnixUserRecord
12
+ from dissect.target.helpers.utils import parse_options_string
14
13
  from dissect.target.plugin import OperatingSystem, OSPlugin, arg, export
15
14
  from dissect.target.target import Target
16
15
 
@@ -61,7 +60,7 @@ class UnixPlugin(OSPlugin):
61
60
  uid=pwent.get(2),
62
61
  gid=pwent.get(3),
63
62
  gecos=pwent.get(4),
64
- home=posix_path(pwent.get(5)),
63
+ home=self.target.fs.path(pwent.get(5)),
65
64
  shell=pwent.get(6),
66
65
  source=passwd_file,
67
66
  _target=self.target,
@@ -177,33 +176,41 @@ class UnixPlugin(OSPlugin):
177
176
  def _add_mounts(self) -> None:
178
177
  fstab = self.target.fs.path("/etc/fstab")
179
178
 
180
- volumes_to_mount = [v for v in self.target.volumes if v.fs]
181
-
182
- for dev_id, volume_name, _, mount_point in parse_fstab(fstab, self.target.log):
183
- for volume in volumes_to_mount:
179
+ for dev_id, volume_name, mount_point, _, options in parse_fstab(fstab, self.target.log):
180
+ opts = parse_options_string(options)
181
+ subvol = opts.get("subvol", None)
182
+ subvolid = opts.get("subvolid", None)
183
+ for fs in self.target.filesystems:
184
184
  fs_id = None
185
+ fs_subvol = None
186
+ fs_subvolid = None
187
+ fs_volume_name = fs.volume.name if fs.volume and not isinstance(fs.volume, list) else None
185
188
  last_mount = None
186
189
 
187
190
  if dev_id:
188
- if volume.fs.__fstype__ == "xfs":
189
- fs_id = volume.fs.xfs.uuid
190
- elif volume.fs.__fstype__ == "ext":
191
- fs_id = volume.fs.extfs.uuid
192
- last_mount = volume.fs.extfs.last_mount
193
- elif volume.fs.__fstype__ == "fat":
194
- fs_id = volume.fs.fatfs.volume_id
191
+ if fs.__type__ == "xfs":
192
+ fs_id = fs.xfs.uuid
193
+ elif fs.__type__ == "ext":
194
+ fs_id = fs.extfs.uuid
195
+ last_mount = fs.extfs.last_mount
196
+ elif fs.__type__ == "btrfs":
197
+ fs_id = fs.btrfs.uuid
198
+ fs_subvol = fs.subvolume.path
199
+ fs_subvolid = fs.subvolume.objectid
200
+ elif fs.__type__ == "fat":
201
+ fs_id = fs.fatfs.volume_id
195
202
  # This normalizes fs_id to comply with libblkid generated UUIDs
196
203
  # This is needed because FAT filesystems don't have a real UUID,
197
204
  # but instead a volume_id which is not case-sensitive
198
205
  fs_id = fs_id[:4].upper() + "-" + fs_id[4:].upper()
199
206
 
200
207
  if (
201
- (fs_id and (fs_id == dev_id))
202
- or (volume.name and (volume.name == volume_name))
208
+ (fs_id and (fs_id == dev_id and (subvol == fs_subvol or subvolid == fs_subvolid)))
209
+ or (fs_volume_name and (fs_volume_name == volume_name))
203
210
  or (last_mount and (last_mount == mount_point))
204
211
  ):
205
- self.target.log.debug("Mounting %s at %s", volume, mount_point)
206
- self.target.fs.mount(mount_point, volume.fs)
212
+ self.target.log.debug("Mounting %s (%s) at %s", fs, fs.volume, mount_point)
213
+ self.target.fs.mount(mount_point, fs)
207
214
 
208
215
  def _parse_os_release(self, glob: Optional[str] = None) -> dict[str, str]:
209
216
  """Parse files containing Unix version information.
@@ -283,7 +290,7 @@ class UnixPlugin(OSPlugin):
283
290
  def parse_fstab(
284
291
  fstab: TargetPath,
285
292
  log: logging.Logger = log,
286
- ) -> Iterator[tuple[Union[uuid.UUID, str], str, str, str]]:
293
+ ) -> Iterator[tuple[Union[uuid.UUID, str], str, str, str, str]]:
287
294
  """Parse fstab file and return a generator that streams the details of entries,
288
295
  with unsupported FS types and block devices filtered away.
289
296
  """
@@ -310,7 +317,7 @@ def parse_fstab(
310
317
  if len(entry_parts) != 6:
311
318
  continue
312
319
 
313
- dev, mount_point, fs_type, _, _, _ = entry_parts
320
+ dev, mount_point, fs_type, options, _, _ = entry_parts
314
321
 
315
322
  if fs_type in SKIP_FS_TYPES:
316
323
  log.warning("Skipped FS type: %s, %s, %s", fs_type, dev, mount_point)
@@ -341,4 +348,4 @@ def parse_fstab(
341
348
  except ValueError:
342
349
  pass
343
350
 
344
- yield dev_id, volume_name, fs_type, mount_point
351
+ yield dev_id, volume_name, mount_point, fs_type, options
@@ -1,8 +1,6 @@
1
1
  import plistlib
2
2
  from typing import Iterator
3
3
 
4
- from flow.record.fieldtypes import posix_path
5
-
6
4
  from dissect.target.exceptions import UnsupportedPluginError
7
5
  from dissect.target.helpers.descriptor_extensions import UserRecordDescriptorExtension
8
6
  from dissect.target.helpers.record import create_extended_descriptor
@@ -49,7 +47,7 @@ class UserPlugin(Plugin):
49
47
  password_last_time=account_policy.get("passwordLastSetTime"),
50
48
  failed_login_count=account_policy.get("failedLoginCount"),
51
49
  failed_login_time=account_policy.get("failedLoginTimestamp"),
52
- source=posix_path(user_details.user.source),
50
+ source=self.target.fs.path(user_details.user.source),
53
51
  _user=user_details.user,
54
52
  _target=self.target,
55
53
  )
@@ -4,7 +4,7 @@ from dissect.target.helpers.record import TargetRecordDescriptor
4
4
  from dissect.target.plugin import Plugin, export
5
5
 
6
6
  CronjobRecord = TargetRecordDescriptor(
7
- "linux/cronjob",
7
+ "unix/cronjob",
8
8
  [
9
9
  ("string", "minute"),
10
10
  ("string", "hour"),
@@ -12,13 +12,13 @@ CronjobRecord = TargetRecordDescriptor(
12
12
  ("string", "month"),
13
13
  ("string", "weekday"),
14
14
  ("string", "user"),
15
- ("wstring", "command"),
15
+ ("string", "command"),
16
16
  ("path", "source"),
17
17
  ],
18
18
  )
19
19
 
20
20
  EnvironmentVariableRecord = TargetRecordDescriptor(
21
- "linux/environmentvariable",
21
+ "unix/environmentvariable",
22
22
  [
23
23
  ("string", "key"),
24
24
  ("string", "value"),
@@ -31,19 +31,6 @@ class CronjobPlugin(Plugin):
31
31
  def check_compatible(self) -> None:
32
32
  pass
33
33
 
34
- def get_record(self, minute, hour, day, month, weekday, usr, cmd, path):
35
- return CronjobRecord(
36
- minute=minute,
37
- hour=hour,
38
- day=day,
39
- month=month,
40
- weekday=weekday,
41
- user=usr,
42
- command=cmd,
43
- source=self.resolver.resolve(path),
44
- _target=self.target,
45
- )
46
-
47
34
  def parse_crontab(self, file_path):
48
35
  for line in file_path.open("rt"):
49
36
  line = line.strip()
@@ -93,6 +80,7 @@ class CronjobPlugin(Plugin):
93
80
  "/var/spool/cron",
94
81
  "/var/spool/cron/crontabs",
95
82
  "/etc/cron.d",
83
+ "/usr/local/etc/cron.d", # FreeBSD
96
84
  ]
97
85
  for path in crontab_dirs:
98
86
  fspath = self.target.fs.path(path)
@@ -12,7 +12,6 @@ from typing import Any, BinaryIO, Iterator, Optional, TextIO
12
12
  from defusedxml import ElementTree
13
13
  from dissect.hypervisor.util import vmtar
14
14
  from dissect.sql import sqlite3
15
- from flow.record.fieldtypes import path
16
15
 
17
16
  try:
18
17
  from dissect.hypervisor.util.envelope import Envelope, KeyStore
@@ -25,7 +24,7 @@ from dissect.target.filesystem import Filesystem, VirtualFilesystem
25
24
  from dissect.target.filesystems import tar
26
25
  from dissect.target.helpers.record import TargetRecordDescriptor
27
26
  from dissect.target.plugin import OperatingSystem, arg, export, internal
28
- from dissect.target.plugins.os.unix.linux._os import LinuxPlugin
27
+ from dissect.target.plugins.os.unix._os import UnixPlugin
29
28
  from dissect.target.target import Target
30
29
 
31
30
  VirtualMachineRecord = TargetRecordDescriptor(
@@ -36,7 +35,7 @@ VirtualMachineRecord = TargetRecordDescriptor(
36
35
  )
37
36
 
38
37
 
39
- class ESXiPlugin(LinuxPlugin):
38
+ class ESXiPlugin(UnixPlugin):
40
39
  """ESXi OS plugin
41
40
 
42
41
  ESXi partitioning varies between versions. Generally, specific partition numbers have special meaning.
@@ -159,7 +158,7 @@ class ESXiPlugin(LinuxPlugin):
159
158
  root = ElementTree.fromstring(inv_file.read_text("utf-8"))
160
159
  for entry in root.iter("ConfigEntry"):
161
160
  yield VirtualMachineRecord(
162
- path=path.from_posix(entry.findtext("vmxCfgPath")),
161
+ path=self.target.fs.path(entry.findtext("vmxCfgPath")),
163
162
  _target=self.target,
164
163
  )
165
164
 
@@ -241,7 +240,7 @@ def _mount_filesystems(target: Target, sysvol: Filesystem, cfg: dict[str, str]):
241
240
  osdata_fs = None
242
241
  locker_fs = None
243
242
  for fs in target.filesystems:
244
- if fs.__fstype__ == "fat":
243
+ if fs.__type__ == "fat":
245
244
  fs.volume.seek(512)
246
245
  magic, uuid1, uuid2, uuid3, uuid4 = struct.unpack("<16sIIH6s", fs.volume.read(32))
247
246
  if magic != b"VMWARE FAT16 ":
@@ -273,7 +272,7 @@ def _mount_filesystems(target: Target, sysvol: Filesystem, cfg: dict[str, str]):
273
272
  target.fs.symlink(f"/vmfs/volumes/{fs_uuid}", "/store")
274
273
  target.fs.symlink("/store", "/locker")
275
274
 
276
- elif fs.__fstype__ == "vmfs":
275
+ elif fs.__type__ == "vmfs":
277
276
  target.fs.mount(f"/vmfs/volumes/{fs.vmfs.uuid}", fs)
278
277
  target.fs.symlink(f"/vmfs/volumes/{fs.vmfs.uuid}", f"/vmfs/volumes/{fs.vmfs.label}")
279
278
 
@@ -20,6 +20,8 @@ class GenericPlugin(Plugin):
20
20
 
21
21
  last_seen = 0
22
22
  for f in var_log.iterdir():
23
+ if not f.exists():
24
+ continue
23
25
  if f.stat().st_mtime > last_seen:
24
26
  last_seen = f.stat().st_mtime
25
27
 
@@ -30,16 +32,18 @@ class GenericPlugin(Plugin):
30
32
  def install_date(self) -> Optional[datetime]:
31
33
  """Return the likely install date of the operating system."""
32
34
 
35
+ # Although this purports to be a generic function for Unix targets,
36
+ # these paths are Linux specific.
33
37
  files = [
34
38
  # Debian
35
39
  "/var/log/installer/install-journal.txt",
36
40
  "/var/log/installer/syslog",
41
+ "/var/lib/dpkg/arch",
37
42
  # RedHat
38
43
  "/root/anaconda-ks.cfg",
39
44
  # Generic
40
45
  "/etc/hostname",
41
46
  "/etc/machine-id",
42
- "/var/lib/dpkg/arch",
43
47
  ]
44
48
  dates = []
45
49
 
@@ -11,7 +11,7 @@ from dissect.target.helpers.record import UnixUserRecord, create_extended_descri
11
11
  from dissect.target.plugin import Plugin, export, internal
12
12
 
13
13
  CommandHistoryRecord = create_extended_descriptor([UserRecordDescriptorExtension])(
14
- "linux/history",
14
+ "unix/history",
15
15
  [
16
16
  ("datetime", "ts"),
17
17
  ("string", "command"),
@@ -35,6 +35,7 @@ class CommandHistoryPlugin(Plugin):
35
35
  ("python", ".python_history"),
36
36
  ("sqlite", ".sqlite_history"),
37
37
  ("zsh", ".zsh_history"),
38
+ ("ash", ".ash_history"),
38
39
  )
39
40
 
40
41
  def __init__(self, target: Target):
@@ -69,13 +69,20 @@ class LinuxPlugin(UnixPlugin, LinuxNetworkManager):
69
69
 
70
70
  @export(property=True)
71
71
  def version(self) -> str:
72
- if distrib_description := self._os_release.get("DISTRIB_DESCRIPTION"):
72
+ distrib_description = self._os_release.get("DISTRIB_DESCRIPTION", "")
73
+ name = self._os_release.get("NAME", "") or self._os_release.get("DISTRIB_ID", "")
74
+ version = (
75
+ self._os_release.get("VERSION", "")
76
+ or self._os_release.get("VERSION_ID", "")
77
+ or self._os_release.get("DISTRIB_RELEASE", "")
78
+ )
79
+
80
+ if len(f"{name} {version}") > len(distrib_description):
81
+ return f"{name} {version}"
82
+
83
+ else:
73
84
  return distrib_description
74
85
 
75
- name = self._os_release.get("NAME") or self._os_release.get("DISTRIB_ID")
76
- version = self._os_release.get("VERSION") or self._os_release.get("DISTRIB_RELEASE")
77
- return f"{name} {version}"
78
-
79
86
  @export(property=True)
80
87
  def os(self) -> str:
81
88
  return OperatingSystem.LINUX.value
@@ -0,0 +1,112 @@
1
+ from itertools import chain
2
+ from typing import Iterator
3
+
4
+ from dissect.target.exceptions import FileNotFoundError, UnsupportedPluginError
5
+ from dissect.target.helpers.record import TargetRecordDescriptor
6
+ from dissect.target.plugin import Plugin, export, internal
7
+
8
+ RECORD_NAME = "linux/service"
9
+
10
+ DEFAULT_ELEMENTS = [
11
+ ("datetime", "ts"),
12
+ ("string", "type"),
13
+ ("string", "name"),
14
+ ("path", "source"),
15
+ ]
16
+
17
+ LinuxServiceRecord = TargetRecordDescriptor(RECORD_NAME, DEFAULT_ELEMENTS)
18
+
19
+
20
+ class ServicesPlugin(Plugin):
21
+ SYSTEMD_PATHS = [
22
+ "/etc/systemd/system",
23
+ "/lib/systemd/system",
24
+ "/usr/lib/systemd/system",
25
+ ]
26
+
27
+ INITD_PATHS = ["/etc/rc.d/init.d", "/etc/init.d"]
28
+
29
+ def check_compatible(self) -> None:
30
+ if not any([self.target.fs.path(p).exists() for p in self.SYSTEMD_PATHS + self.INITD_PATHS]):
31
+ raise UnsupportedPluginError("No supported service directories found")
32
+
33
+ @export(record=LinuxServiceRecord)
34
+ def services(self) -> Iterator[LinuxServiceRecord]:
35
+ """Return information about all installed systemd and init.d services.
36
+
37
+ References:
38
+ - https://geeksforgeeks.org/what-is-init-d-in-linux-service-management
39
+ - http://0pointer.de/blog/projects/systemd-for-admins-3.html
40
+ - https://www.freedesktop.org/software/systemd/man/systemd.syntax.html
41
+ """
42
+
43
+ return chain(self.systemd(), self.initd())
44
+
45
+ @internal
46
+ def systemd(self) -> Iterator[LinuxServiceRecord]:
47
+ ignored_suffixes = [".wants", ".requires", ".d"]
48
+
49
+ for systemd_path in self.SYSTEMD_PATHS:
50
+ path = self.target.fs.path(systemd_path)
51
+ if not path.exists() or not path.is_dir():
52
+ continue
53
+
54
+ for service_file in path.iterdir():
55
+ if should_ignore_file(service_file.name, ignored_suffixes):
56
+ continue
57
+
58
+ try:
59
+ parsed_file = self.target.config_tree(service_file, as_dict=True)
60
+ config = {}
61
+ types = []
62
+ for segment, configuration in parsed_file.items():
63
+ for key, value in configuration.items():
64
+ _value = value or None
65
+ _key = f"{segment}_{key}"
66
+ types.append(("string", _key))
67
+ config.update({_key: _value})
68
+ except FileNotFoundError:
69
+ # The service is registered but the symlink is broken.
70
+ yield LinuxServiceRecord(
71
+ ts=service_file.stat(follow_symlinks=False).st_mtime,
72
+ type="systemd",
73
+ name=service_file.name,
74
+ source=service_file,
75
+ _target=self.target,
76
+ )
77
+ continue
78
+
79
+ record = TargetRecordDescriptor(RECORD_NAME, DEFAULT_ELEMENTS + types)
80
+ yield record(
81
+ ts=service_file.stat().st_mtime,
82
+ type="systemd",
83
+ name=service_file.name,
84
+ source=service_file,
85
+ **config,
86
+ _target=self.target,
87
+ )
88
+
89
+ @internal
90
+ def initd(self) -> Iterator[LinuxServiceRecord]:
91
+ ignored_suffixes = ["README"]
92
+
93
+ for initd_path in self.INITD_PATHS:
94
+ path = self.target.fs.path(initd_path)
95
+
96
+ if not path.exists():
97
+ continue
98
+ for file_ in path.iterdir():
99
+ if should_ignore_file(file_.name, ignored_suffixes):
100
+ continue
101
+
102
+ yield LinuxServiceRecord(
103
+ ts=file_.stat().st_mtime,
104
+ type="initd",
105
+ name=file_.name,
106
+ source=file_,
107
+ _target=self.target,
108
+ )
109
+
110
+
111
+ def should_ignore_file(needle: str, haystack: list) -> bool:
112
+ return needle.endswith(tuple(haystack))
@@ -28,17 +28,17 @@ class ZypperPlugin(plugin.Plugin):
28
28
  Example log format::
29
29
 
30
30
  2022-12-16 12:56:23|command|root@ec9fa6d67dda|'zypper' 'install' 'unzip'|
31
- 2022-12-16 12:56:23|install|update-alternatives|1.21.8-1.4|x86_64||repo-oss|b4d6389437e306d6104559c82d09fce15c4486fbc7fd215cc33d265ff729aaf1| # noqa
31
+ 2022-12-16 12:56:23|install|update-alternatives|1.21.8-1.4|x86_64||repo-oss|b4d6389437e306d6104559c82d09fce15c4486fbc7fd215cc33d265ff729aaf1|
32
32
  # 2022-12-16 12:56:23 unzip-6.00-41.1.x86_64.rpm installed ok
33
33
  # Additional rpm output:
34
34
  # update-alternatives: using /usr/bin/unzip-plain to provide /usr/bin/unzip (unzip) in auto mode
35
35
  #
36
- 2022-12-16 12:56:23|install|unzip|6.00-41.1|x86_64|root@ec9fa6d67dda|repo-oss|d7e42c9d83f97cf3b7eceb4d3fa64e445a33a7a33f387366734c444d5571cb3a| # noqa
36
+ 2022-12-16 12:56:23|install|unzip|6.00-41.1|x86_64|root@ec9fa6d67dda|repo-oss|d7e42c9d83f97cf3b7eceb4d3fa64e445a33a7a33f387366734c444d5571cb3a|
37
37
  2022-12-16 12:57:50|command|root@ec9fa6d67dda|'zypper' 'remove' 'unzip'|
38
38
  # 2022-12-16 12:57:50 unzip-6.00-41.1.x86_64 removed ok
39
39
  # Additional rpm output:
40
- # update-alternatives: warning: alternative /usr/bin/unzipsfx-plain (part of link group unzipsfx) doesn't exist; removing from list of alternatives # noqa
41
- # update-alternatives: warning: alternative /usr/bin/zipgrep-plain (part of link group zipgrep) doesn't exist; removing from list of alternatives # noqa
40
+ # update-alternatives: warning: alternative /usr/bin/unzipsfx-plain (part of link group unzipsfx) doesn't exist; removing from list of alternatives
41
+ # update-alternatives: warning: alternative /usr/bin/zipgrep-plain (part of link group zipgrep) doesn't exist; removing from list of alternatives
42
42
  #
43
43
  2022-12-16 12:57:50|remove |unzip|6.00-41.1|x86_64|root@ec9fa6d67dda|
44
44
  2022-12-16 12:58:49|command|root@ec9fa6d67dda|'zypper' 'install' 'unzip'|
@@ -5,7 +5,7 @@ from dissect.target.helpers.record import TargetRecordDescriptor
5
5
  from dissect.target.plugin import Plugin, export
6
6
 
7
7
  UnixKeyboardRecord = TargetRecordDescriptor(
8
- "linux/keyboard",
8
+ "unix/keyboard",
9
9
  [
10
10
  ("string", "layout"),
11
11
  ("string", "model"),
@@ -64,6 +64,8 @@ class LocalePlugin(Plugin):
64
64
  @export(property=True)
65
65
  def language(self):
66
66
  """Get the configured locale(s) of the system."""
67
+ # Although this purports to be a generic function for Unix targets,
68
+ # these paths are Linux specific.
67
69
  locale_paths = ["/etc/default/locale", "/etc/locale.conf"]
68
70
 
69
71
  found_languages = []
@@ -5,7 +5,6 @@ import zstandard
5
5
  from dissect.cstruct import Instance, cstruct
6
6
  from dissect.util import ts
7
7
  from dissect.util.compression import lz4
8
- from flow.record.fieldtypes import path
9
8
 
10
9
  from dissect.target import Target
11
10
  from dissect.target.exceptions import UnsupportedPluginError
@@ -394,6 +393,8 @@ class JournalPlugin(Plugin):
394
393
  - https://github.com/systemd/systemd/blob/9203abf79f1d05fdef9b039e7addf9fc5a27752d/man/systemd.journal-fields.xml
395
394
  """ # noqa: E501
396
395
 
396
+ path_function = self.target.fs.path
397
+
397
398
  for _path in self.journal_paths:
398
399
  fh = _path.open()
399
400
 
@@ -409,7 +410,7 @@ class JournalPlugin(Plugin):
409
410
  message=entry.get("message"),
410
411
  message_id=entry.get("message_id"),
411
412
  priority=get_optional(entry.get("priority"), int),
412
- code_file=get_optional(entry.get("code_file"), path.from_posix),
413
+ code_file=get_optional(entry.get("code_file"), path_function),
413
414
  code_line=get_optional(entry.get("code_line"), int),
414
415
  code_func=entry.get("code_func"),
415
416
  errno=get_optional(entry.get("errno"), int),
@@ -427,12 +428,12 @@ class JournalPlugin(Plugin):
427
428
  uid=get_optional(entry.get("uid"), int),
428
429
  gid=get_optional(entry.get("gid"), int),
429
430
  comm=entry.get("comm"),
430
- exe=get_optional(entry.get("exe"), path.from_posix),
431
+ exe=get_optional(entry.get("exe"), path_function),
431
432
  cmdline=entry.get("cmdline"),
432
433
  cap_effective=entry.get("cap_effective"),
433
434
  audit_session=get_optional(entry.get("audit_session"), int),
434
435
  audit_loginuid=get_optional(entry.get("audit_loginuid"), int),
435
- systemd_cgroup=get_optional(entry.get("systemd_cgroup"), path.from_posix),
436
+ systemd_cgroup=get_optional(entry.get("systemd_cgroup"), path_function),
436
437
  systemd_slice=entry.get("systemd_slice"),
437
438
  systemd_unit=entry.get("systemd_unit"),
438
439
  systemd_user_unit=entry.get("systemd_user_unit"),
@@ -451,8 +452,8 @@ class JournalPlugin(Plugin):
451
452
  kernel_device=entry.get("kernel_device"),
452
453
  kernel_subsystem=entry.get("kernel_subsystem"),
453
454
  udev_sysname=entry.get("udev_sysname"),
454
- udev_devnode=get_optional(entry.get("udev_devnode"), path.from_posix),
455
- udev_devlink=get_optional(entry.get("udev_devlink"), path.from_posix),
455
+ udev_devnode=get_optional(entry.get("udev_devnode"), path_function),
456
+ udev_devlink=get_optional(entry.get("udev_devlink"), path_function),
456
457
  journal_hostname=entry.get("hostname"),
457
458
  filepath=_path,
458
459
  _target=self.target,
@@ -9,7 +9,7 @@ from dissect.target.helpers.record import TargetRecordDescriptor
9
9
  from dissect.target.plugin import Plugin, export
10
10
 
11
11
  PackageManagerLogRecord = TargetRecordDescriptor(
12
- "linux/log/packagemanager",
12
+ "unix/log/packagemanager",
13
13
  [
14
14
  ("datetime", "ts"),
15
15
  ("string", "package_manager"),
@@ -61,7 +61,7 @@ class PackageManagerPlugin(Plugin):
61
61
  for entry in self.TOOLS:
62
62
  try:
63
63
  self._plugins.append(getattr(self.target, entry))
64
- except Exception: # noqa
64
+ except Exception:
65
65
  target.log.exception(f"Failed to load tool plugin: {entry}")
66
66
 
67
67
  def check_compatible(self) -> None:
@@ -77,5 +77,5 @@ class PackageManagerPlugin(Plugin):
77
77
 
78
78
  @export(record=PackageManagerLogRecord)
79
79
  def logs(self) -> Iterator[PackageManagerLogRecord]:
80
- """Returns logs from apt, yum and zypper package managers."""
80
+ """Returns logs from all available Unix package managers."""
81
81
  yield from self._func("logs")
@@ -5,7 +5,7 @@ from dissect.target.helpers.record import TargetRecordDescriptor
5
5
  from dissect.target.plugin import Plugin, export
6
6
 
7
7
  UnixShadowRecord = TargetRecordDescriptor(
8
- "linux/shadow",
8
+ "unix/shadow",
9
9
  [
10
10
  ("string", "name"),
11
11
  ("string", "crypt"),
@@ -77,7 +77,8 @@ class WindowsPlugin(OSPlugin):
77
77
  self.target.fs.mount(drive, volume.fs)
78
78
  break
79
79
  except Exception as e:
80
- self.target.log.warning("Failed to map drive letters", exc_info=e)
80
+ self.target.log.warning("Failed to map drive letters")
81
+ self.target.log.debug("", exc_info=e)
81
82
 
82
83
  @export(property=True)
83
84
  def hostname(self) -> Optional[str]:
@@ -1,7 +1,6 @@
1
1
  from datetime import datetime, timezone
2
2
 
3
3
  from dissect.util.ts import wintimestamp
4
- from flow.record.fieldtypes import path
5
4
 
6
5
  from dissect.target.exceptions import RegistryKeyNotFoundError, UnsupportedPluginError
7
6
  from dissect.target.helpers import regutil
@@ -220,7 +219,7 @@ class AmcachePluginOldMixin:
220
219
  created_timestamp=parse_win_timestamp(subkey_data.get("created_timestamp")),
221
220
  mtime_regf=subkey.timestamp,
222
221
  reference=int(subkey.name, 16),
223
- path=path.from_windows(subkey_data["full_path"]) if subkey_data.get("full_path") else None,
222
+ path=self.target.fs.path(subkey_data["full_path"]) if subkey_data.get("full_path") else None,
224
223
  language_code=subkey_data.get("language_code"),
225
224
  digests=[None, subkey_data["sha1"][-40:] if subkey_data.get("sha1") else None, None],
226
225
  program_id=subkey_data.get("program_id"),
@@ -265,7 +264,7 @@ class AmcachePluginOldMixin:
265
264
  language_code=entry_data.get("LanguageCode"),
266
265
  entry_type=entry_data.get("EntryType"),
267
266
  uninstall_key=entry_data.get("UninstallKey"),
268
- path=path.from_windows(file_path_entry),
267
+ path=self.target.fs.path(file_path_entry),
269
268
  product_code=entry_data.get("ProductCode"),
270
269
  package_code=entry_data.get("PackageCode"),
271
270
  msi_package_code=entry_data.get("MsiPackageCode"),
@@ -284,7 +283,7 @@ class AmcachePluginOldMixin:
284
283
  language_code=entry_data.get("LanguageCode"),
285
284
  entry_type=entry_data.get("EntryType"),
286
285
  uninstall_key=entry_data.get("UninstallKey"),
287
- path=path.from_windows(file_entry),
286
+ path=self.target.fs.path(file_entry),
288
287
  product_code=entry_data.get("ProductCode"),
289
288
  package_code=entry_data.get("PackageCode"),
290
289
  msi_package_code=entry_data.get("MsiPackageCode"),
@@ -416,7 +415,7 @@ class AmcachePlugin(AmcachePluginOldMixin, Plugin):
416
415
  program_instance_id=entry_data.get("ProgramInstanceId"),
417
416
  publisher=entry_data.get("Publisher"),
418
417
  registry_key_path=entry_data.get("RegistryKeyPath"),
419
- root_dir_path=path.from_windows(entry_data.get("RootDirPath")),
418
+ root_dir_path=self.target.fs.path(entry_data.get("RootDirPath")),
420
419
  source=entry_data.get("Source"),
421
420
  uninstall_string=entry_data.get("UninstallString"),
422
421
  type=entry_data.get("Type"),
@@ -467,7 +466,7 @@ class AmcachePlugin(AmcachePluginOldMixin, Plugin):
467
466
  mtime_regf=entry.timestamp,
468
467
  program_id=entry_data.get("ProgramId"),
469
468
  digests=[None, sha1_digest, None],
470
- path=path.from_windows(entry_data.get("LowerCaseLongPath")),
469
+ path=self.target.fs.path(entry_data.get("LowerCaseLongPath")),
471
470
  link_date=parse_win_datetime(entry_data.get("LinkDate")),
472
471
  hash_path=entry_data.get("LongPathHash"),
473
472
  name=entry_data.get("Name"),
@@ -492,8 +491,8 @@ class AmcachePlugin(AmcachePluginOldMixin, Plugin):
492
491
 
493
492
  yield BinaryAppcompatRecord(
494
493
  mtime_regf=entry.timestamp,
495
- driver_name=path.from_windows(entry_data.get("DriverName")),
496
- inf=path.from_windows(entry_data.get("Inf")),
494
+ driver_name=self.target.fs.path(entry_data.get("DriverName")),
495
+ inf=self.target.fs.path(entry_data.get("Inf")),
497
496
  driver_version=entry_data.get("DriverVersion"),
498
497
  product=entry_data.get("Product"),
499
498
  product_version=entry_data.get("ProductVersion"),
@@ -515,7 +514,7 @@ class AmcachePlugin(AmcachePluginOldMixin, Plugin):
515
514
  for entry in self.read_key_subkeys(key):
516
515
  yield ShortcutAppcompatRecord(
517
516
  mtime_regf=entry.timestamp,
518
- path=path.from_windows(entry.value("ShortCutPath").value),
517
+ path=self.target.fs.path(entry.value("ShortCutPath").value),
519
518
  _target=self.target,
520
519
  )
521
520
 
@@ -637,7 +636,7 @@ class AmcachePlugin(AmcachePluginOldMixin, Plugin):
637
636
  parts = line.rstrip().split("|")
638
637
  yield AppLaunchAppcompatRecord(
639
638
  ts=datetime.strptime(parts[-1], "%Y-%m-%d %H:%M:%S.%f").replace(tzinfo=timezone.utc),
640
- path=path.from_windows(parts[0]),
639
+ path=self.target.fs.path(parts[0]),
641
640
  _target=self.target,
642
641
  )
643
642
 
@@ -1,5 +1,5 @@
1
1
  from asn1crypto import algos, core
2
- from flow.record.fieldtypes import digest, path
2
+ from flow.record.fieldtypes import digest
3
3
 
4
4
  from dissect.target.exceptions import UnsupportedPluginError
5
5
  from dissect.target.helpers.record import TargetRecordDescriptor
@@ -118,7 +118,7 @@ class CatrootPlugin(Plugin):
118
118
 
119
119
  yield CatrootRecord(
120
120
  digest=fdigest,
121
- hint=path.from_windows(filehint) if filehint else None,
121
+ hint=self.target.fs.path(filehint) if filehint else None,
122
122
  source=f,
123
123
  _target=self.target,
124
124
  )