dissect.target 3.20.dev36__py3-none-any.whl → 3.20.dev39__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
Files changed (124) hide show
  1. dissect/target/filesystems/config.py +1 -1
  2. dissect/target/helpers/compat/path_common.py +5 -5
  3. dissect/target/helpers/configutil.py +31 -29
  4. dissect/target/helpers/cyber.py +2 -0
  5. dissect/target/helpers/docs.py +1 -1
  6. dissect/target/helpers/keychain.py +2 -0
  7. dissect/target/helpers/mount.py +2 -1
  8. dissect/target/plugin.py +15 -13
  9. dissect/target/plugins/apps/av/mcafee.py +2 -0
  10. dissect/target/plugins/apps/av/sophos.py +2 -0
  11. dissect/target/plugins/apps/av/trendmicro.py +2 -0
  12. dissect/target/plugins/apps/browser/chromium.py +27 -6
  13. dissect/target/plugins/apps/editor/editor.py +23 -0
  14. dissect/target/plugins/apps/{texteditor → editor}/windowsnotepad.py +40 -31
  15. dissect/target/plugins/apps/shell/powershell.py +6 -2
  16. dissect/target/plugins/apps/shell/wget.py +1 -1
  17. dissect/target/plugins/apps/ssh/openssh.py +2 -0
  18. dissect/target/plugins/apps/ssh/opensshd.py +2 -0
  19. dissect/target/plugins/apps/vpn/wireguard.py +9 -9
  20. dissect/target/plugins/apps/webhosting/cpanel.py +2 -0
  21. dissect/target/plugins/apps/webserver/caddy.py +2 -0
  22. dissect/target/plugins/apps/webserver/nginx.py +2 -0
  23. dissect/target/plugins/child/esxi.py +3 -1
  24. dissect/target/plugins/child/virtuozzo.py +12 -8
  25. dissect/target/plugins/filesystem/acquire_hash.py +2 -1
  26. dissect/target/plugins/filesystem/icat.py +15 -11
  27. dissect/target/plugins/filesystem/ntfs/mft.py +2 -0
  28. dissect/target/plugins/filesystem/ntfs/mft_timeline.py +3 -1
  29. dissect/target/plugins/filesystem/ntfs/usnjrnl.py +2 -0
  30. dissect/target/plugins/filesystem/ntfs/utils.py +3 -1
  31. dissect/target/plugins/filesystem/unix/suid.py +4 -1
  32. dissect/target/plugins/filesystem/walkfs.py +2 -0
  33. dissect/target/plugins/general/example.py +2 -2
  34. dissect/target/plugins/general/loaders.py +1 -1
  35. dissect/target/plugins/general/network.py +7 -0
  36. dissect/target/plugins/general/osinfo.py +1 -0
  37. dissect/target/plugins/general/plugins.py +4 -0
  38. dissect/target/plugins/os/unix/_os.py +2 -1
  39. dissect/target/plugins/os/unix/bsd/citrix/history.py +2 -0
  40. dissect/target/plugins/os/unix/bsd/osx/network.py +2 -0
  41. dissect/target/plugins/os/unix/bsd/osx/user.py +4 -0
  42. dissect/target/plugins/os/unix/cronjobs.py +8 -4
  43. dissect/target/plugins/os/unix/etc/etc.py +4 -0
  44. dissect/target/plugins/os/unix/generic.py +2 -0
  45. dissect/target/plugins/os/unix/history.py +27 -25
  46. dissect/target/plugins/os/unix/linux/cmdline.py +2 -0
  47. dissect/target/plugins/os/unix/linux/debian/apt.py +4 -1
  48. dissect/target/plugins/os/unix/linux/debian/dpkg.py +3 -3
  49. dissect/target/plugins/os/unix/linux/environ.py +2 -0
  50. dissect/target/plugins/os/unix/linux/fortios/generic.py +2 -0
  51. dissect/target/plugins/os/unix/linux/fortios/locale.py +2 -0
  52. dissect/target/plugins/os/unix/linux/modules.py +2 -0
  53. dissect/target/plugins/os/unix/linux/netstat.py +2 -0
  54. dissect/target/plugins/os/unix/linux/processes.py +2 -0
  55. dissect/target/plugins/os/unix/linux/redhat/yum.py +4 -1
  56. dissect/target/plugins/os/unix/linux/services.py +5 -3
  57. dissect/target/plugins/os/unix/linux/sockets.py +2 -0
  58. dissect/target/plugins/os/unix/linux/suse/zypper.py +4 -1
  59. dissect/target/plugins/os/unix/locale.py +2 -0
  60. dissect/target/plugins/os/unix/locate/gnulocate.py +4 -2
  61. dissect/target/plugins/os/unix/locate/mlocate.py +2 -0
  62. dissect/target/plugins/os/unix/locate/plocate.py +3 -1
  63. dissect/target/plugins/os/unix/log/atop.py +2 -0
  64. dissect/target/plugins/os/unix/log/audit.py +3 -1
  65. dissect/target/plugins/os/unix/log/auth.py +4 -2
  66. dissect/target/plugins/os/unix/log/lastlog.py +5 -3
  67. dissect/target/plugins/os/unix/log/messages.py +2 -0
  68. dissect/target/plugins/os/unix/log/utmp.py +4 -2
  69. dissect/target/plugins/os/unix/packagemanager.py +4 -37
  70. dissect/target/plugins/os/unix/shadow.py +3 -1
  71. dissect/target/plugins/os/unix/trash.py +1 -1
  72. dissect/target/plugins/os/windows/activitiescache.py +9 -4
  73. dissect/target/plugins/os/windows/adpolicy.py +2 -1
  74. dissect/target/plugins/os/windows/amcache.py +16 -13
  75. dissect/target/plugins/os/windows/defender.py +3 -3
  76. dissect/target/plugins/os/windows/dpapi/keyprovider/credhist.py +3 -0
  77. dissect/target/plugins/os/windows/dpapi/keyprovider/empty.py +3 -0
  78. dissect/target/plugins/os/windows/dpapi/keyprovider/keychain.py +3 -0
  79. dissect/target/plugins/os/windows/dpapi/keyprovider/lsa.py +3 -0
  80. dissect/target/plugins/os/windows/env.py +1 -2
  81. dissect/target/plugins/os/windows/exchange/exchange.py +6 -4
  82. dissect/target/plugins/os/windows/generic.py +20 -18
  83. dissect/target/plugins/os/windows/lnk.py +2 -0
  84. dissect/target/plugins/os/windows/locale.py +9 -3
  85. dissect/target/plugins/os/windows/log/etl.py +5 -4
  86. dissect/target/plugins/os/windows/log/evt.py +12 -8
  87. dissect/target/plugins/os/windows/log/evtx.py +9 -7
  88. dissect/target/plugins/os/windows/log/pfro.py +2 -1
  89. dissect/target/plugins/os/windows/network.py +2 -0
  90. dissect/target/plugins/os/windows/notifications.py +6 -4
  91. dissect/target/plugins/os/windows/prefetch.py +7 -2
  92. dissect/target/plugins/os/windows/regf/7zip.py +9 -1
  93. dissect/target/plugins/os/windows/regf/auditpol.py +2 -1
  94. dissect/target/plugins/os/windows/regf/bam.py +3 -1
  95. dissect/target/plugins/os/windows/regf/cit.py +14 -12
  96. dissect/target/plugins/os/windows/regf/clsid.py +6 -3
  97. dissect/target/plugins/os/windows/regf/firewall.py +2 -1
  98. dissect/target/plugins/os/windows/regf/mru.py +9 -8
  99. dissect/target/plugins/os/windows/regf/nethist.py +6 -3
  100. dissect/target/plugins/os/windows/regf/recentfilecache.py +3 -1
  101. dissect/target/plugins/os/windows/regf/regf.py +5 -1
  102. dissect/target/plugins/os/windows/regf/shimcache.py +1 -1
  103. dissect/target/plugins/os/windows/regf/usb.py +2 -1
  104. dissect/target/plugins/os/windows/regf/userassist.py +2 -1
  105. dissect/target/plugins/os/windows/registry.py +11 -0
  106. dissect/target/plugins/os/windows/services.py +3 -2
  107. dissect/target/plugins/os/windows/startupinfo.py +7 -2
  108. dissect/target/plugins/os/windows/syscache.py +3 -1
  109. dissect/target/plugins/os/windows/tasks.py +1 -1
  110. dissect/target/plugins/os/windows/thumbcache.py +11 -5
  111. dissect/target/plugins/os/windows/ual.py +12 -9
  112. dissect/target/plugins/os/windows/wua_history.py +0 -1
  113. dissect/target/tools/dump/utils.py +4 -0
  114. dissect/target/tools/shell.py +2 -1
  115. {dissect.target-3.20.dev36.dist-info → dissect.target-3.20.dev39.dist-info}/METADATA +1 -1
  116. {dissect.target-3.20.dev36.dist-info → dissect.target-3.20.dev39.dist-info}/RECORD +122 -123
  117. dissect/target/plugins/apps/texteditor/texteditor.py +0 -13
  118. dissect/target/plugins/os/unix/etc.py +0 -9
  119. /dissect/target/plugins/apps/{texteditor → editor}/__init__.py +0 -0
  120. {dissect.target-3.20.dev36.dist-info → dissect.target-3.20.dev39.dist-info}/COPYRIGHT +0 -0
  121. {dissect.target-3.20.dev36.dist-info → dissect.target-3.20.dev39.dist-info}/LICENSE +0 -0
  122. {dissect.target-3.20.dev36.dist-info → dissect.target-3.20.dev39.dist-info}/WHEEL +0 -0
  123. {dissect.target-3.20.dev36.dist-info → dissect.target-3.20.dev39.dist-info}/entry_points.txt +0 -0
  124. {dissect.target-3.20.dev36.dist-info → dissect.target-3.20.dev39.dist-info}/top_level.txt +0 -0
@@ -64,6 +64,8 @@ PacketSocketRecord = TargetRecordDescriptor(
64
64
 
65
65
 
66
66
  class NetSocketPlugin(Plugin):
67
+ """Linux volatile net sockets plugin."""
68
+
67
69
  __namespace__ = "sockets"
68
70
 
69
71
  def __init__(self, target: Target):
@@ -7,10 +7,13 @@ from dissect.target.helpers.fsutil import open_decompress
7
7
  from dissect.target.plugins.os.unix.packagemanager import (
8
8
  OperationTypes,
9
9
  PackageManagerLogRecord,
10
+ PackageManagerPlugin,
10
11
  )
11
12
 
12
13
 
13
- class ZypperPlugin(plugin.Plugin):
14
+ class ZypperPlugin(PackageManagerPlugin):
15
+ """Zypper package manager plugin."""
16
+
14
17
  __namespace__ = "zypper"
15
18
 
16
19
  LOG_DIR_PATH = "/var/log/zypp"
@@ -29,6 +29,8 @@ def timezone_from_path(path: Path) -> str:
29
29
 
30
30
 
31
31
  class LocalePlugin(Plugin):
32
+ """Unix locale plugin."""
33
+
32
34
  def check_compatible(self) -> None:
33
35
  pass
34
36
 
@@ -1,6 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
- from typing import BinaryIO, Iterable
3
+ from typing import BinaryIO, Iterable, Iterator
4
4
 
5
5
  from dissect.cstruct import cstruct
6
6
 
@@ -69,6 +69,8 @@ class GNULocateFile:
69
69
 
70
70
 
71
71
  class GNULocatePlugin(BaseLocatePlugin):
72
+ """GNU locate plugin."""
73
+
72
74
  __namespace__ = "gnulocate"
73
75
 
74
76
  path = "/var/cache/locate/locatedb"
@@ -78,7 +80,7 @@ class GNULocatePlugin(BaseLocatePlugin):
78
80
  raise UnsupportedPluginError(f"No locatedb file found at {self.path}")
79
81
 
80
82
  @export(record=GNULocateRecord)
81
- def locate(self) -> GNULocateRecord:
83
+ def locate(self) -> Iterator[GNULocateRecord]:
82
84
  """Yield file and directory names from GNU findutils' locatedb file.
83
85
 
84
86
  Resources:
@@ -115,6 +115,8 @@ class MLocateFile:
115
115
 
116
116
 
117
117
  class MLocatePlugin(BaseLocatePlugin):
118
+ """Unix mlocate plugin."""
119
+
118
120
  __namespace__ = "mlocate"
119
121
 
120
122
  path = "/var/lib/mlocate/mlocate.db"
@@ -163,6 +163,8 @@ class PLocateFile:
163
163
 
164
164
 
165
165
  class PLocatePlugin(BaseLocatePlugin):
166
+ """Unix plocate plugin."""
167
+
166
168
  __namespace__ = "plocate"
167
169
 
168
170
  path = "/var/lib/plocate/plocate.db"
@@ -177,7 +179,7 @@ class PLocatePlugin(BaseLocatePlugin):
177
179
  )
178
180
 
179
181
  @export(record=PLocateRecord)
180
- def locate(self) -> PLocateRecord:
182
+ def locate(self) -> Iterator[PLocateRecord]:
181
183
  """Yield file and directory names from the plocate.db.
182
184
 
183
185
  ``plocate`` is the default package on Ubuntu 22 and newer to locate files.
@@ -247,6 +247,8 @@ class AtopFile:
247
247
 
248
248
 
249
249
  class AtopPlugin(Plugin):
250
+ """Unix atop plugin."""
251
+
250
252
  ATOP_GLOB = "atop_*"
251
253
  ATOP_MAGIC = 0xFEEDBEEF
252
254
  ATOP_PATH = "/var/log/atop"
@@ -24,6 +24,8 @@ AUDIT_REGEX = re.compile(r"^type=(?P<audit_type>.*) msg=audit\((?P<ts>.*):(?P<au
24
24
 
25
25
 
26
26
  class AuditPlugin(Plugin):
27
+ """Unix audit log plugin."""
28
+
27
29
  def __init__(self, target):
28
30
  super().__init__(target)
29
31
  self.log_paths = self.get_log_paths()
@@ -51,7 +53,7 @@ class AuditPlugin(Plugin):
51
53
 
52
54
  return log_paths
53
55
 
54
- @export(record=[AuditRecord])
56
+ @export(record=AuditRecord)
55
57
  def audit(self) -> Iterator[AuditRecord]:
56
58
  """Return CentOS and RedHat audit information stored in /var/log/audit*.
57
59
 
@@ -22,17 +22,19 @@ RE_TS_AND_HOSTNAME = re.compile(_TS_REGEX + r"\s\S+\s")
22
22
 
23
23
 
24
24
  class AuthPlugin(Plugin):
25
+ """Unix auth log plugin."""
26
+
25
27
  def check_compatible(self) -> None:
26
28
  var_log = self.target.fs.path("/var/log")
27
29
  if not any(var_log.glob("auth.log*")) and not any(var_log.glob("secure*")):
28
30
  raise UnsupportedPluginError("No auth log files found")
29
31
 
30
- @export(record=[AuthLogRecord])
32
+ @export(record=AuthLogRecord)
31
33
  def securelog(self) -> Iterator[AuthLogRecord]:
32
34
  """Return contents of /var/log/auth.log* and /var/log/secure*."""
33
35
  return self.authlog()
34
36
 
35
- @export(record=[AuthLogRecord])
37
+ @export(record=AuthLogRecord)
36
38
  def authlog(self) -> Iterator[AuthLogRecord]:
37
39
  """Return contents of /var/log/auth.log* and /var/log/secure*."""
38
40
 
@@ -1,4 +1,4 @@
1
- from typing import BinaryIO
1
+ from typing import BinaryIO, Iterator
2
2
 
3
3
  from dissect.cstruct import cstruct
4
4
  from dissect.util import ts
@@ -52,13 +52,15 @@ class LastLogFile:
52
52
 
53
53
 
54
54
  class LastLogPlugin(Plugin):
55
+ """Unix lastlog plugin."""
56
+
55
57
  def check_compatible(self) -> None:
56
58
  lastlog = self.target.fs.path("/var/log/lastlog")
57
59
  if not lastlog.exists():
58
60
  raise UnsupportedPluginError("No lastlog file found")
59
61
 
60
- @export(record=[LastLogRecord])
61
- def lastlog(self):
62
+ @export(record=LastLogRecord)
63
+ def lastlog(self) -> Iterator[LastLogRecord]:
62
64
  """Return last logins information from /var/log/lastlog.
63
65
 
64
66
  The lastlog file contains the most recent logins of all users on a Unix based operating system.
@@ -34,6 +34,8 @@ RE_CLOUD_INIT_LINE = re.compile(
34
34
 
35
35
 
36
36
  class MessagesPlugin(Plugin):
37
+ """Unix messages log plugin."""
38
+
37
39
  def __init__(self, target: Target):
38
40
  super().__init__(target)
39
41
  self.log_files = set(self._find_log_files())
@@ -168,6 +168,8 @@ class UtmpFile:
168
168
 
169
169
 
170
170
  class UtmpPlugin(Plugin):
171
+ """Unix utmp log plugin."""
172
+
171
173
  WTMP_GLOB = "/var/log/wtmp*"
172
174
  BTMP_GLOB = "/var/log/btmp*"
173
175
 
@@ -180,7 +182,7 @@ class UtmpPlugin(Plugin):
180
182
  ):
181
183
  raise UnsupportedPluginError("No WTMP or BTMP log files found")
182
184
 
183
- @export(record=[BtmpRecord])
185
+ @export(record=BtmpRecord)
184
186
  def btmp(self) -> Iterator[BtmpRecord]:
185
187
  """Return failed login attempts stored in the btmp file.
186
188
 
@@ -207,7 +209,7 @@ class UtmpPlugin(Plugin):
207
209
  _target=self.target,
208
210
  )
209
211
 
210
- @export(record=[WtmpRecord])
212
+ @export(record=WtmpRecord)
211
213
  def wtmp(self) -> Iterator[WtmpRecord]:
212
214
  """Return the content of the wtmp log files.
213
215
 
@@ -1,12 +1,9 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from enum import Enum
4
- from typing import Iterator
5
4
 
6
- from dissect.target import Target
7
- from dissect.target.exceptions import UnsupportedPluginError
8
5
  from dissect.target.helpers.record import TargetRecordDescriptor
9
- from dissect.target.plugin import Plugin, export
6
+ from dissect.target.plugin import NamespacePlugin
10
7
 
11
8
  PackageManagerLogRecord = TargetRecordDescriptor(
12
9
  "unix/log/packagemanager",
@@ -22,6 +19,8 @@ PackageManagerLogRecord = TargetRecordDescriptor(
22
19
 
23
20
 
24
21
  class OperationTypes(Enum):
22
+ """Valid operation types."""
23
+
25
24
  Install = "install"
26
25
  Update = "update"
27
26
  Downgrade = "downgrade"
@@ -45,37 +44,5 @@ class OperationTypes(Enum):
45
44
  return OperationTypes.Other
46
45
 
47
46
 
48
- class PackageManagerPlugin(Plugin):
47
+ class PackageManagerPlugin(NamespacePlugin):
49
48
  __namespace__ = "packagemanager"
50
- __findable__ = False
51
-
52
- TOOLS = [
53
- "apt",
54
- "yum",
55
- "zypper",
56
- ]
57
-
58
- def __init__(self, target: Target):
59
- super().__init__(target)
60
- self._plugins = []
61
- for entry in self.TOOLS:
62
- try:
63
- self._plugins.append(getattr(self.target, entry))
64
- except Exception:
65
- target.log.exception(f"Failed to load tool plugin: {entry}")
66
-
67
- def check_compatible(self) -> None:
68
- if not len(self._plugins):
69
- raise UnsupportedPluginError("No compatible plugins found")
70
-
71
- def _func(self, f: str) -> Iterator[PackageManagerLogRecord]:
72
- for p in self._plugins:
73
- try:
74
- yield from getattr(p, f)()
75
- except Exception:
76
- self.target.log.exception("Failed to execute package manager plugin: %s.%s", p._name, f)
77
-
78
- @export(record=PackageManagerLogRecord)
79
- def logs(self) -> Iterator[PackageManagerLogRecord]:
80
- """Returns logs from all available Unix package managers."""
81
- yield from self._func("logs")
@@ -25,6 +25,8 @@ UnixShadowRecord = TargetRecordDescriptor(
25
25
 
26
26
 
27
27
  class ShadowPlugin(Plugin):
28
+ """Unix shadow passwords plugin."""
29
+
28
30
  def check_compatible(self) -> None:
29
31
  if not self.target.fs.path("/etc/shadow").exists():
30
32
  raise UnsupportedPluginError("No shadow file found")
@@ -36,7 +38,7 @@ class ShadowPlugin(Plugin):
36
38
  """Yield shadow records from /etc/shadow files.
37
39
 
38
40
  Resources:
39
- - https://manpages.ubuntu.com/manpages/oracular/en/man5/passwd.5.html#file:/etc/shadow
41
+ - https://manpages.ubuntu.com/manpages/oracular/en/man5/passwd.5.html
40
42
  """
41
43
 
42
44
  seen_hashes = set()
@@ -59,7 +59,7 @@ class GnomeTrashPlugin(Plugin):
59
59
  - https://github.com/GNOME/glib/blob/main/gio/glocalfile.c
60
60
  - https://specifications.freedesktop.org/basedir-spec/latest/
61
61
 
62
- Yields ``TrashRecord``s with the following fields:
62
+ Yields ``TrashRecord`` records with the following fields:
63
63
 
64
64
  .. code-block:: text
65
65
 
@@ -1,3 +1,8 @@
1
+ from __future__ import annotations
2
+
3
+ from datetime import datetime
4
+ from typing import Iterator
5
+
1
6
  from dissect.sql import sqlite3
2
7
  from dissect.util.ts import from_unix
3
8
 
@@ -42,8 +47,8 @@ class ActivitiesCachePlugin(Plugin):
42
47
  """Plugin that parses the ActivitiesCache.db on newer Windows 10 machines.
43
48
 
44
49
  References:
45
- https://www.cclsolutionsgroup.com/resources/technical-papers
46
- https://salt4n6.com/2018/05/03/windows-10-timeline-forensic-artefacts/
50
+ - https://www.cclsolutionsgroup.com/resources/technical-papers
51
+ - https://salt4n6.com/2018/05/03/windows-10-timeline-forensic-artefacts/
47
52
  """
48
53
 
49
54
  def __init__(self, target):
@@ -62,7 +67,7 @@ class ActivitiesCachePlugin(Plugin):
62
67
  raise UnsupportedPluginError("No ActiviesCache.db files found")
63
68
 
64
69
  @export(record=ActivitiesCacheRecord)
65
- def activitiescache(self):
70
+ def activitiescache(self) -> Iterator[ActivitiesCacheRecord]:
66
71
  """Return ActivitiesCache.db database content.
67
72
 
68
73
  The Windows Activities Cache database keeps track of activity on a device, such as application and services
@@ -143,7 +148,7 @@ class ActivitiesCachePlugin(Plugin):
143
148
  )
144
149
 
145
150
 
146
- def mkts(ts):
151
+ def mkts(ts: int) -> datetime | None:
147
152
  """Timestamps inside ActivitiesCache.db are stored in a Unix-like format.
148
153
 
149
154
  Source: https://salt4n6.com/2018/05/03/windows-10-timeline-forensic-artefacts/
@@ -1,4 +1,5 @@
1
1
  from struct import unpack
2
+ from typing import Iterator
2
3
 
3
4
  from defusedxml import ElementTree
4
5
  from dissect.cstruct import cstruct
@@ -90,7 +91,7 @@ class ADPolicyPlugin(Plugin):
90
91
  self.target.log.warning("Unable to read XML policy file: %s", error)
91
92
 
92
93
  @export(record=ADPolicyRecord)
93
- def adpolicy(self):
94
+ def adpolicy(self) -> Iterator[ADPolicyRecord]:
94
95
  """Return all AD policies (also known as GPOs or Group Policy Objects).
95
96
 
96
97
  An Active Directory (AD) maintains global policies that should be adhered by all systems in the domain.
@@ -1,4 +1,7 @@
1
+ from __future__ import annotations
2
+
1
3
  from datetime import datetime, timezone
4
+ from typing import Iterator
2
5
 
3
6
  from dissect.util.ts import wintimestamp
4
7
 
@@ -292,13 +295,13 @@ class AmcachePluginOldMixin:
292
295
  )
293
296
 
294
297
  @export(record=ProgramsAppcompatRecord)
295
- def programs(self):
298
+ def programs(self) -> Iterator[ProgramsAppcompatRecord]:
296
299
  """Return Programs records from Amcache hive."""
297
300
  if self.amcache:
298
301
  yield from self.parse_programs()
299
302
 
300
303
  @export(record=FileAppcompatRecord)
301
- def files(self):
304
+ def files(self) -> Iterator[FileAppcompatRecord]:
302
305
  """Return File records from Amcache hive."""
303
306
  if self.amcache:
304
307
  yield from self.parse_file()
@@ -321,9 +324,9 @@ class AmcachePlugin(AmcachePluginOldMixin, Plugin):
321
324
  * InventoryApplicationShortcut
322
325
 
323
326
  References:
324
- https://binaryforay.blogspot.com/2015/04/appcompatcache-changes-in-windows-10.html
325
- https://www.ssi.gouv.fr/uploads/2019/01/anssi-coriin_2019-analysis_amcache.pdf
326
- https://aboutdfir.com/new-windows-11-pro-22h2-evidence-of-execution-artifact/
327
+ - https://binaryforay.blogspot.com/2015/04/appcompatcache-changes-in-windows-10.html
328
+ - https://cyber.gouv.fr/sites/default/files/2019/01/anssi-coriin_2019-analysis_amcache.pdf
329
+ - https://aboutdfir.com/new-windows-11-pro-22h2-evidence-of-execution-artifact/
327
330
 
328
331
  """
329
332
 
@@ -545,7 +548,7 @@ class AmcachePlugin(AmcachePluginOldMixin, Plugin):
545
548
  )
546
549
 
547
550
  @export(record=ApplicationAppcompatRecord)
548
- def applications(self):
551
+ def applications(self) -> Iterator[ApplicationAppcompatRecord]:
549
552
  """Return InventoryApplication records from Amcache hive.
550
553
 
551
554
  Amcache is a registry hive that stores information about executed programs. The InventoryApplication key holds
@@ -559,7 +562,7 @@ class AmcachePlugin(AmcachePluginOldMixin, Plugin):
559
562
  yield from self.parse_inventory_application()
560
563
 
561
564
  @export(record=ApplicationFileAppcompatRecord)
562
- def application_files(self):
565
+ def application_files(self) -> Iterator[ApplicationFileAppcompatRecord]:
563
566
  """Return InventoryApplicationFile records from Amcache hive.
564
567
 
565
568
  Amcache is a registry hive that stores information about executed programs. The InventoryApplicationFile key
@@ -573,7 +576,7 @@ class AmcachePlugin(AmcachePluginOldMixin, Plugin):
573
576
  yield from self.parse_inventory_application_file()
574
577
 
575
578
  @export(record=BinaryAppcompatRecord)
576
- def drivers(self):
579
+ def drivers(self) -> Iterator[BinaryAppcompatRecord]:
577
580
  """Return InventoryDriverBinary records from Amcache hive.
578
581
 
579
582
  Amcache is a registry hive that stores information about executed programs. The InventoryDriverBinary key holds
@@ -588,7 +591,7 @@ class AmcachePlugin(AmcachePluginOldMixin, Plugin):
588
591
  yield from self.parse_inventory_driver_binary()
589
592
 
590
593
  @export(record=ShortcutAppcompatRecord)
591
- def shortcuts(self):
594
+ def shortcuts(self) -> Iterator[ShortcutAppcompatRecord]:
592
595
  """Return InventoryApplicationShortcut records from Amcache hive.
593
596
 
594
597
  Amcache is a registry hive that stores information about executed programs. The InventoryApplicationShortcut
@@ -604,7 +607,7 @@ class AmcachePlugin(AmcachePluginOldMixin, Plugin):
604
607
  yield from self.parse_inventory_application_shortcut()
605
608
 
606
609
  @export(record=ContainerAppcompatRecord)
607
- def device_containers(self):
610
+ def device_containers(self) -> Iterator[ContainerAppcompatRecord]:
608
611
  """Return InventoryDeviceContainer records from Amcache hive.
609
612
 
610
613
  Amcache is a registry hive that stores information about executed programs. The InventoryDeviceContainer key
@@ -619,7 +622,7 @@ class AmcachePlugin(AmcachePluginOldMixin, Plugin):
619
622
  yield from self.parse_inventory_device_container()
620
623
 
621
624
  @export(record=AppLaunchAppcompatRecord)
622
- def applaunches(self):
625
+ def applaunches(self) -> Iterator[AppLaunchAppcompatRecord]:
623
626
  """Return AppLaunchAppcompatRecord records from Amcache applaunch files (Windows 11 22H2 or later).
624
627
 
625
628
  TODO: Research C:\\Windows\\appcompat\\pca\\PcaGeneralDb0.txt and
@@ -641,11 +644,11 @@ class AmcachePlugin(AmcachePluginOldMixin, Plugin):
641
644
  )
642
645
 
643
646
 
644
- def parse_win_datetime(value: str):
647
+ def parse_win_datetime(value: str) -> datetime | None:
645
648
  if value:
646
649
  return datetime.strptime(value, "%m/%d/%Y %H:%M:%S")
647
650
 
648
651
 
649
- def parse_win_timestamp(value: str):
652
+ def parse_win_timestamp(value: str) -> datetime | None:
650
653
  if value:
651
654
  return wintimestamp(value)
@@ -4,7 +4,7 @@ import re
4
4
  from datetime import datetime, timezone
5
5
  from io import BytesIO
6
6
  from pathlib import Path
7
- from typing import Any, BinaryIO, Generator, Iterable, Iterator, TextIO, Union
7
+ from typing import Any, BinaryIO, Iterable, Iterator, TextIO
8
8
 
9
9
  import dissect.util.ts as ts
10
10
  from dissect.cstruct import cstruct
@@ -434,7 +434,7 @@ class MicrosoftDefenderPlugin(plugin.Plugin):
434
434
  raise UnsupportedPluginError("No Defender objects found")
435
435
 
436
436
  @plugin.export(record=DefenderLogRecord)
437
- def evtx(self) -> Generator[Record, None, None]:
437
+ def evtx(self) -> Iterator[DefenderLogRecord]:
438
438
  """Parse Microsoft Defender evtx log files"""
439
439
 
440
440
  defender_evtx_field_names = [field_name for _, field_name in DEFENDER_EVTX_FIELDS]
@@ -458,7 +458,7 @@ class MicrosoftDefenderPlugin(plugin.Plugin):
458
458
  yield DefenderLogRecord(**record_fields, _target=self.target)
459
459
 
460
460
  @plugin.export(record=[DefenderQuarantineRecord, DefenderFileQuarantineRecord])
461
- def quarantine(self) -> Iterator[Union[DefenderQuarantineRecord, DefenderFileQuarantineRecord]]:
461
+ def quarantine(self) -> Iterator[DefenderQuarantineRecord | DefenderFileQuarantineRecord]:
462
462
  """Parse the quarantine folder of Microsoft Defender for quarantine entry resources.
463
463
 
464
464
  Quarantine entry resources contain metadata about detected threats that Microsoft Defender has placed in
@@ -8,6 +8,8 @@ from dissect.target.plugins.os.windows.dpapi.keyprovider.keyprovider import (
8
8
 
9
9
 
10
10
  class CredHistKeyProviderPlugin(KeyProviderPlugin):
11
+ """Windows CREDHIST SHA1-hash key provider plugin."""
12
+
11
13
  __namespace__ = "_dpapi_keyprovider_credhist"
12
14
 
13
15
  def check_compatible(self) -> None:
@@ -16,6 +18,7 @@ class CredHistKeyProviderPlugin(KeyProviderPlugin):
16
18
 
17
19
  @export(output="yield")
18
20
  def keys(self) -> Iterator[tuple[str, str]]:
21
+ """Yield Windows CREDHIST SHA1 hashes."""
19
22
  for credhist in self.target.credhist():
20
23
  if value := getattr(credhist, "sha1"):
21
24
  yield self.__namespace__, value
@@ -7,6 +7,8 @@ from dissect.target.plugins.os.windows.dpapi.keyprovider.keyprovider import (
7
7
 
8
8
 
9
9
  class EmptyKeyProviderPlugin(KeyProviderPlugin):
10
+ """Empty key provider plugin."""
11
+
10
12
  __namespace__ = "_dpapi_keyprovider_empty"
11
13
 
12
14
  def check_compatible(self) -> None:
@@ -14,4 +16,5 @@ class EmptyKeyProviderPlugin(KeyProviderPlugin):
14
16
 
15
17
  @export(output="yield")
16
18
  def keys(self) -> Iterator[tuple[str, str]]:
19
+ """Yield an empty string."""
17
20
  yield self.__namespace__, ""
@@ -8,6 +8,8 @@ from dissect.target.plugins.os.windows.dpapi.keyprovider.keyprovider import (
8
8
 
9
9
 
10
10
  class KeychainKeyProviderPlugin(KeyProviderPlugin):
11
+ """Keychain key provider plugin."""
12
+
11
13
  __namespace__ = "_dpapi_keyprovider_keychain"
12
14
 
13
15
  def check_compatible(self) -> None:
@@ -15,6 +17,7 @@ class KeychainKeyProviderPlugin(KeyProviderPlugin):
15
17
 
16
18
  @export(output="yield")
17
19
  def keys(self) -> Iterator[tuple[str, str]]:
20
+ """Yield keychain passphrases."""
18
21
  for key in keychain.get_keys_for_provider("user") + keychain.get_keys_without_provider():
19
22
  if key.key_type == keychain.KeyType.PASSPHRASE:
20
23
  yield self.__namespace__, key.value
@@ -21,6 +21,8 @@ c_defaultpassword = cstruct().load(defaultpassword_def)
21
21
 
22
22
 
23
23
  class LSADefaultPasswordKeyProviderPlugin(KeyProviderPlugin):
24
+ """Windows LSA DefaultPassword key provider plugin."""
25
+
24
26
  __namespace__ = "_dpapi_keyprovider_lsa_defaultpassword"
25
27
 
26
28
  def check_compatible(self) -> None:
@@ -29,6 +31,7 @@ class LSADefaultPasswordKeyProviderPlugin(KeyProviderPlugin):
29
31
 
30
32
  @export(output="yield")
31
33
  def keys(self) -> Iterator[tuple[str, str]]:
34
+ """Yield Windows LSA DefaultPassword strings."""
32
35
  if default_pass := self.target.lsa._secrets.get("DefaultPassword"):
33
36
  try:
34
37
  value = c_defaultpassword.DefaultPassword(default_pass).data
@@ -314,8 +314,7 @@ class EnvironmentVariablePlugin(Plugin):
314
314
  def user_env(self, user_sid: Optional[str] = None) -> OrderedDict[str, str]:
315
315
  """Return a dict of all found (user) environment variables.
316
316
 
317
- If no ``user_sid` is provided, this function will return just the
318
- system environment variables.
317
+ If no ``user_sid`` is provided, this function will return just the system environment variables.
319
318
  """
320
319
  return self._get_user_env_vars(user_sid)
321
320
 
@@ -3,13 +3,15 @@ from dissect.target.plugin import Plugin, export
3
3
 
4
4
 
5
5
  class ExchangePlugin(Plugin):
6
+ """Microsoft Exchange Server plugin."""
7
+
6
8
  __namespace__ = "exchange"
7
9
 
8
10
  def check_compatible(self) -> None:
9
11
  if not len(self.install_paths()):
10
12
  raise UnsupportedPluginError("No Exchange install path found")
11
13
 
12
- def install_paths(self):
14
+ def install_paths(self) -> list[str]:
13
15
  paths = []
14
16
  key = "HKLM\\SOFTWARE\\Microsoft\\ExchangeServer"
15
17
  for reg_key in self.target.registry.keys(key):
@@ -23,9 +25,9 @@ class ExchangePlugin(Plugin):
23
25
 
24
26
  return paths
25
27
 
26
- @export
27
- def transport_agents(self):
28
- """Return the content of the config file for Transport Agents for Microsoft Exchange.
28
+ @export(output="none")
29
+ def transport_agents(self) -> None:
30
+ """Print the content of the config file for Transport Agents for Microsoft Exchange.
29
31
 
30
32
  A Transport Agent is additional software on a Microsoft Exchange server that allows for custom processing of
31
33
  email messages that go through the transport pipeline.