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

Sign up to get free protection for your applications and to get access to all the features.
Files changed (120) 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/shell/powershell.py +6 -2
  14. dissect/target/plugins/apps/shell/wget.py +1 -1
  15. dissect/target/plugins/apps/ssh/openssh.py +2 -0
  16. dissect/target/plugins/apps/ssh/opensshd.py +2 -0
  17. dissect/target/plugins/apps/vpn/wireguard.py +9 -9
  18. dissect/target/plugins/apps/webhosting/cpanel.py +2 -0
  19. dissect/target/plugins/apps/webserver/caddy.py +2 -0
  20. dissect/target/plugins/apps/webserver/nginx.py +2 -0
  21. dissect/target/plugins/child/esxi.py +3 -1
  22. dissect/target/plugins/child/virtuozzo.py +12 -8
  23. dissect/target/plugins/filesystem/acquire_hash.py +2 -1
  24. dissect/target/plugins/filesystem/icat.py +15 -11
  25. dissect/target/plugins/filesystem/ntfs/mft.py +2 -0
  26. dissect/target/plugins/filesystem/ntfs/mft_timeline.py +3 -1
  27. dissect/target/plugins/filesystem/ntfs/usnjrnl.py +2 -0
  28. dissect/target/plugins/filesystem/ntfs/utils.py +3 -1
  29. dissect/target/plugins/filesystem/unix/suid.py +4 -1
  30. dissect/target/plugins/filesystem/walkfs.py +2 -0
  31. dissect/target/plugins/general/example.py +2 -2
  32. dissect/target/plugins/general/loaders.py +1 -1
  33. dissect/target/plugins/general/network.py +7 -0
  34. dissect/target/plugins/general/osinfo.py +1 -0
  35. dissect/target/plugins/general/plugins.py +4 -0
  36. dissect/target/plugins/os/unix/_os.py +2 -1
  37. dissect/target/plugins/os/unix/bsd/citrix/history.py +2 -0
  38. dissect/target/plugins/os/unix/bsd/osx/network.py +2 -0
  39. dissect/target/plugins/os/unix/bsd/osx/user.py +4 -0
  40. dissect/target/plugins/os/unix/cronjobs.py +8 -4
  41. dissect/target/plugins/os/unix/etc/etc.py +4 -0
  42. dissect/target/plugins/os/unix/generic.py +2 -0
  43. dissect/target/plugins/os/unix/history.py +27 -25
  44. dissect/target/plugins/os/unix/linux/cmdline.py +2 -0
  45. dissect/target/plugins/os/unix/linux/debian/apt.py +4 -1
  46. dissect/target/plugins/os/unix/linux/debian/dpkg.py +3 -3
  47. dissect/target/plugins/os/unix/linux/environ.py +2 -0
  48. dissect/target/plugins/os/unix/linux/fortios/generic.py +2 -0
  49. dissect/target/plugins/os/unix/linux/fortios/locale.py +2 -0
  50. dissect/target/plugins/os/unix/linux/modules.py +2 -0
  51. dissect/target/plugins/os/unix/linux/netstat.py +2 -0
  52. dissect/target/plugins/os/unix/linux/processes.py +2 -0
  53. dissect/target/plugins/os/unix/linux/redhat/yum.py +4 -1
  54. dissect/target/plugins/os/unix/linux/services.py +5 -3
  55. dissect/target/plugins/os/unix/linux/sockets.py +2 -0
  56. dissect/target/plugins/os/unix/linux/suse/zypper.py +4 -1
  57. dissect/target/plugins/os/unix/locale.py +2 -0
  58. dissect/target/plugins/os/unix/locate/gnulocate.py +4 -2
  59. dissect/target/plugins/os/unix/locate/mlocate.py +2 -0
  60. dissect/target/plugins/os/unix/locate/plocate.py +3 -1
  61. dissect/target/plugins/os/unix/log/atop.py +2 -0
  62. dissect/target/plugins/os/unix/log/audit.py +3 -1
  63. dissect/target/plugins/os/unix/log/auth.py +4 -2
  64. dissect/target/plugins/os/unix/log/lastlog.py +5 -3
  65. dissect/target/plugins/os/unix/log/messages.py +2 -0
  66. dissect/target/plugins/os/unix/log/utmp.py +4 -2
  67. dissect/target/plugins/os/unix/packagemanager.py +4 -37
  68. dissect/target/plugins/os/unix/shadow.py +3 -1
  69. dissect/target/plugins/os/unix/trash.py +1 -1
  70. dissect/target/plugins/os/windows/activitiescache.py +9 -4
  71. dissect/target/plugins/os/windows/adpolicy.py +2 -1
  72. dissect/target/plugins/os/windows/amcache.py +16 -13
  73. dissect/target/plugins/os/windows/defender.py +3 -3
  74. dissect/target/plugins/os/windows/dpapi/keyprovider/credhist.py +3 -0
  75. dissect/target/plugins/os/windows/dpapi/keyprovider/empty.py +3 -0
  76. dissect/target/plugins/os/windows/dpapi/keyprovider/keychain.py +3 -0
  77. dissect/target/plugins/os/windows/dpapi/keyprovider/lsa.py +3 -0
  78. dissect/target/plugins/os/windows/env.py +1 -2
  79. dissect/target/plugins/os/windows/exchange/exchange.py +6 -4
  80. dissect/target/plugins/os/windows/generic.py +20 -18
  81. dissect/target/plugins/os/windows/lnk.py +2 -0
  82. dissect/target/plugins/os/windows/locale.py +9 -3
  83. dissect/target/plugins/os/windows/log/etl.py +5 -4
  84. dissect/target/plugins/os/windows/log/evt.py +12 -8
  85. dissect/target/plugins/os/windows/log/evtx.py +9 -7
  86. dissect/target/plugins/os/windows/log/pfro.py +2 -1
  87. dissect/target/plugins/os/windows/network.py +2 -0
  88. dissect/target/plugins/os/windows/notifications.py +6 -4
  89. dissect/target/plugins/os/windows/prefetch.py +7 -2
  90. dissect/target/plugins/os/windows/regf/7zip.py +9 -1
  91. dissect/target/plugins/os/windows/regf/auditpol.py +2 -1
  92. dissect/target/plugins/os/windows/regf/bam.py +3 -1
  93. dissect/target/plugins/os/windows/regf/cit.py +14 -12
  94. dissect/target/plugins/os/windows/regf/clsid.py +6 -3
  95. dissect/target/plugins/os/windows/regf/firewall.py +2 -1
  96. dissect/target/plugins/os/windows/regf/mru.py +9 -8
  97. dissect/target/plugins/os/windows/regf/nethist.py +6 -3
  98. dissect/target/plugins/os/windows/regf/recentfilecache.py +3 -1
  99. dissect/target/plugins/os/windows/regf/regf.py +5 -1
  100. dissect/target/plugins/os/windows/regf/shimcache.py +1 -1
  101. dissect/target/plugins/os/windows/regf/usb.py +2 -1
  102. dissect/target/plugins/os/windows/regf/userassist.py +2 -1
  103. dissect/target/plugins/os/windows/registry.py +11 -0
  104. dissect/target/plugins/os/windows/services.py +3 -2
  105. dissect/target/plugins/os/windows/startupinfo.py +7 -2
  106. dissect/target/plugins/os/windows/syscache.py +3 -1
  107. dissect/target/plugins/os/windows/tasks.py +1 -1
  108. dissect/target/plugins/os/windows/thumbcache.py +11 -5
  109. dissect/target/plugins/os/windows/ual.py +12 -9
  110. dissect/target/plugins/os/windows/wua_history.py +0 -1
  111. dissect/target/tools/dump/utils.py +4 -0
  112. dissect/target/tools/shell.py +2 -1
  113. {dissect.target-3.20.dev36.dist-info → dissect.target-3.20.dev38.dist-info}/METADATA +1 -1
  114. {dissect.target-3.20.dev36.dist-info → dissect.target-3.20.dev38.dist-info}/RECORD +119 -120
  115. dissect/target/plugins/os/unix/etc.py +0 -9
  116. {dissect.target-3.20.dev36.dist-info → dissect.target-3.20.dev38.dist-info}/COPYRIGHT +0 -0
  117. {dissect.target-3.20.dev36.dist-info → dissect.target-3.20.dev38.dist-info}/LICENSE +0 -0
  118. {dissect.target-3.20.dev36.dist-info → dissect.target-3.20.dev38.dist-info}/WHEEL +0 -0
  119. {dissect.target-3.20.dev36.dist-info → dissect.target-3.20.dev38.dist-info}/entry_points.txt +0 -0
  120. {dissect.target-3.20.dev36.dist-info → dissect.target-3.20.dev38.dist-info}/top_level.txt +0 -0
@@ -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.
@@ -1,5 +1,7 @@
1
+ from __future__ import annotations
2
+
1
3
  from datetime import datetime
2
- from typing import Optional
4
+ from typing import Iterator
3
5
 
4
6
  from dissect.util.ts import from_unix
5
7
 
@@ -123,12 +125,12 @@ class GenericPlugin(Plugin):
123
125
  raise UnsupportedPluginError("Unsupported Plugin")
124
126
 
125
127
  @export(property=True)
126
- def ntversion(self):
128
+ def ntversion(self) -> str | None:
127
129
  """Return the Windows NT version."""
128
130
  return self.target._os._nt_version()
129
131
 
130
132
  @export(output="yield")
131
- def pathenvironment(self):
133
+ def pathenvironment(self) -> Iterator[str]:
132
134
  """Return the content of the Windows PATH environment variable.
133
135
 
134
136
  PATH is an environment variable on an operating system that specifies a set of directories where executable
@@ -142,7 +144,7 @@ class GenericPlugin(Plugin):
142
144
  yield r.value("Path").value
143
145
 
144
146
  @export(property=True)
145
- def domain(self):
147
+ def domain(self) -> str | None:
146
148
  """Return the domain name.
147
149
 
148
150
  Corporate Windows systems are usually connected to a domain (active directory).
@@ -167,7 +169,7 @@ class GenericPlugin(Plugin):
167
169
  continue
168
170
 
169
171
  @export(property=True)
170
- def activity(self) -> Optional[datetime]:
172
+ def activity(self) -> datetime | None:
171
173
  """Return last seen activity based on filesystem timestamps."""
172
174
  last_seen = 0
173
175
 
@@ -193,7 +195,7 @@ class GenericPlugin(Plugin):
193
195
  return from_unix(last_seen)
194
196
 
195
197
  @export(property=True)
196
- def install_date(self) -> Optional[datetime]:
198
+ def install_date(self) -> datetime | None:
197
199
  """Returns the install date of the system.
198
200
 
199
201
  The value of the registry key is stored as a Unix epoch timestamp.
@@ -211,7 +213,7 @@ class GenericPlugin(Plugin):
211
213
  return
212
214
 
213
215
  @export(record=AppInitRecord)
214
- def appinit(self):
216
+ def appinit(self) -> Iterator[AppInitRecord]:
215
217
  """Return all available Application Initial (AppInit) DLLs registry key values.
216
218
 
217
219
  AppInit_DLLs is a mechanism that allows an arbitrary list of DLLs to be loaded into each user mode process on
@@ -258,7 +260,7 @@ class GenericPlugin(Plugin):
258
260
  continue
259
261
 
260
262
  @export(record=KnownDllRecord)
261
- def knowndlls(self):
263
+ def knowndlls(self) -> Iterator[KnownDllRecord]:
262
264
  """Return all available KnownDLLs registry key values.
263
265
 
264
266
  The KnownDLLs registry key values are used to cache frequently used system DLLs. Initially, it was added to
@@ -287,7 +289,7 @@ class GenericPlugin(Plugin):
287
289
  pass
288
290
 
289
291
  @export(record=SessionManagerRecord)
290
- def sessionmanager(self):
292
+ def sessionmanager(self) -> Iterator[SessionManagerRecord]:
291
293
  """Return interesting Session Manager (Smss.exe) registry key entries.
292
294
 
293
295
  Session Manager (Smss.exe) is the first user-mode process started by the kernel and performs several tasks,
@@ -339,7 +341,7 @@ class GenericPlugin(Plugin):
339
341
  )
340
342
 
341
343
  @export(record=NullSessionPipeRecord)
342
- def nullsessionpipes(self):
344
+ def nullsessionpipes(self) -> Iterator[NullSessionPipeRecord]:
343
345
  """Return the NullSessionPipes registry key value.
344
346
 
345
347
  The NullSessionPipes registry key value specifies server pipes and shared folders that are excluded from the
@@ -367,7 +369,7 @@ class GenericPlugin(Plugin):
367
369
  continue
368
370
 
369
371
  @export(record=NdisRecord)
370
- def ndis(self):
372
+ def ndis(self) -> Iterator[NdisRecord]:
371
373
  """Return network registry key entries."""
372
374
  key = "HKLM\\System\\CurrentControlSet\\Control\\Network\\{4D36E972-E325-11CE-BFC1-08002BE10318}"
373
375
  for r in self.target.registry.keys(key):
@@ -401,7 +403,7 @@ class GenericPlugin(Plugin):
401
403
  )
402
404
 
403
405
  @export(record=CommandProcAutoRunRecord)
404
- def commandprocautorun(self):
406
+ def commandprocautorun(self) -> Iterator[CommandProcAutoRunRecord]:
405
407
  """Return all available Command Processor (cmd.exe) AutoRun registry key values.
406
408
 
407
409
  The Command Processor AutoRun registry key values contain commands that are run each time the Command Processor
@@ -435,7 +437,7 @@ class GenericPlugin(Plugin):
435
437
  continue
436
438
 
437
439
  @export(record=AlternateShellRecord)
438
- def alternateshell(self):
440
+ def alternateshell(self) -> Iterator[AlternateShellRecord]:
439
441
  """Return the AlternateShell registry key value.
440
442
 
441
443
  The AlternateShell registry key, HKEY_LOCAL_MACHINE\\System\\CurrentControlSet\\Control\\Safeboot, specifies the
@@ -459,7 +461,7 @@ class GenericPlugin(Plugin):
459
461
  )
460
462
 
461
463
  @export(record=BootShellRecord)
462
- def bootshell(self):
464
+ def bootshell(self) -> Iterator[BootShellRecord]:
463
465
  """Return the BootShell registry key entry.
464
466
 
465
467
  Usually contains a path to bootim.exe which is Windows's recovery menu.
@@ -483,7 +485,7 @@ class GenericPlugin(Plugin):
483
485
  )
484
486
 
485
487
  @export(record=FileRenameOperationRecord)
486
- def filerenameop(self):
488
+ def filerenameop(self) -> Iterator[FileRenameOperationRecord]:
487
489
  """Return all pending file rename operations.
488
490
 
489
491
  The PendingFileRenameOperations registry key value contains information about files that will be renamed on
@@ -513,7 +515,7 @@ class GenericPlugin(Plugin):
513
515
  )
514
516
 
515
517
  @export(record=WinRarRecord)
516
- def winrar(self):
518
+ def winrar(self) -> Iterator[WinRarRecord]:
517
519
  """Return all available WinRAR history registry key values."""
518
520
  keys = [
519
521
  "HKEY_CURRENT_USER\\Software\\WinRAR\\ArcHistory",
@@ -534,7 +536,7 @@ class GenericPlugin(Plugin):
534
536
  )
535
537
 
536
538
  @export(record=WinSockNamespaceProviderRecord)
537
- def winsocknamespaceprovider(self):
539
+ def winsocknamespaceprovider(self) -> Iterator[WinSockNamespaceProviderRecord]:
538
540
  """Return available protocols stored in the Winsock catalog database.
539
541
 
540
542
  References:
@@ -562,7 +564,7 @@ class GenericPlugin(Plugin):
562
564
  )
563
565
 
564
566
  @export(property=True)
565
- def codepage(self) -> Optional[str]:
567
+ def codepage(self) -> str | None:
566
568
  """Returns the current active codepage on the system."""
567
569
 
568
570
  key = "HKLM\\SYSTEM\\CurrentControlSet\\Control\\Nls\\CodePage"
@@ -118,6 +118,8 @@ def parse_lnk_file(target: Target, lnk_file: Lnk, lnk_path: TargetPath) -> Itera
118
118
 
119
119
 
120
120
  class LnkPlugin(Plugin):
121
+ """Windows lnk plugin."""
122
+
121
123
  def __init__(self, target: Target) -> None:
122
124
  super().__init__(target)
123
125
  self.folders = ["programdata", "users", "windows"]
@@ -1,3 +1,7 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Iterator
4
+
1
5
  from dissect.target.exceptions import UnsupportedPluginError
2
6
  from dissect.target.helpers.localeutil import normalize_language, normalize_timezone
3
7
  from dissect.target.helpers.record import TargetRecordDescriptor
@@ -13,6 +17,8 @@ WindowsKeyboardRecord = TargetRecordDescriptor(
13
17
 
14
18
 
15
19
  class LocalePlugin(Plugin):
20
+ """Windows locale plugin."""
21
+
16
22
  def __init__(self, target):
17
23
  super().__init__(target)
18
24
  self.LANG_DICT = {
@@ -26,7 +32,7 @@ class LocalePlugin(Plugin):
26
32
  raise UnsupportedPluginError("Unsupported Plugin")
27
33
 
28
34
  @export(record=WindowsKeyboardRecord)
29
- def keyboard(self):
35
+ def keyboard(self) -> Iterator[WindowsKeyboardRecord]:
30
36
  """Yield records of installed keyboards on the system."""
31
37
  found_keyboards = []
32
38
  for key in self.target.registry.keys("HKCU\\Keyboard Layout\\Preload"):
@@ -41,7 +47,7 @@ class LocalePlugin(Plugin):
41
47
  )
42
48
 
43
49
  @export(property=True)
44
- def language(self):
50
+ def language(self) -> str | None:
45
51
  """Get a list of installed languages on the system."""
46
52
  # HKCU\\Control Panel\\International\\User Profile" Languages
47
53
  found_languages = []
@@ -53,6 +59,6 @@ class LocalePlugin(Plugin):
53
59
  return found_languages
54
60
 
55
61
  @export(property=True)
56
- def timezone(self):
62
+ def timezone(self) -> str | None:
57
63
  """Get the configured timezone of the system in IANA TZ standard format."""
58
64
  return normalize_timezone(self.target.datetime.tzinfo.name)