dissect.target 3.19.dev57__py3-none-any.whl → 3.20__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
Files changed (180) hide show
  1. dissect/target/container.py +1 -1
  2. dissect/target/exceptions.py +6 -5
  3. dissect/target/filesystem.py +2 -2
  4. dissect/target/filesystems/btrfs.py +14 -5
  5. dissect/target/filesystems/config.py +5 -1
  6. dissect/target/filesystems/extfs.py +5 -4
  7. dissect/target/filesystems/fat.py +22 -16
  8. dissect/target/filesystems/ffs.py +11 -4
  9. dissect/target/filesystems/jffs.py +12 -7
  10. dissect/target/filesystems/ntfs.py +22 -6
  11. dissect/target/filesystems/overlay.py +14 -4
  12. dissect/target/filesystems/smb.py +3 -3
  13. dissect/target/filesystems/squashfs.py +4 -4
  14. dissect/target/filesystems/vmfs.py +4 -4
  15. dissect/target/filesystems/xfs.py +15 -8
  16. dissect/target/helpers/compat/path_common.py +5 -5
  17. dissect/target/helpers/configutil.py +128 -32
  18. dissect/target/helpers/cyber.py +2 -0
  19. dissect/target/helpers/data/windowsZones.xml +19 -23
  20. dissect/target/helpers/docs.py +1 -1
  21. dissect/target/helpers/keychain.py +2 -0
  22. dissect/target/helpers/mount.py +2 -1
  23. dissect/target/helpers/record.py +29 -2
  24. dissect/target/helpers/record_modifier.py +5 -1
  25. dissect/target/helpers/regutil.py +56 -26
  26. dissect/target/loader.py +1 -1
  27. dissect/target/loaders/mqtt.py +104 -9
  28. dissect/target/loaders/proxmox.py +68 -0
  29. dissect/target/loaders/vma.py +1 -1
  30. dissect/target/loaders/xva.py +1 -1
  31. dissect/target/plugin.py +24 -21
  32. dissect/target/plugins/apps/av/mcafee.py +2 -0
  33. dissect/target/plugins/apps/av/sophos.py +2 -0
  34. dissect/target/plugins/apps/av/trendmicro.py +2 -0
  35. dissect/target/plugins/apps/browser/chromium.py +27 -6
  36. dissect/target/plugins/apps/container/docker.py +48 -32
  37. dissect/target/plugins/apps/editor/__init__.py +0 -0
  38. dissect/target/plugins/apps/editor/editor.py +23 -0
  39. dissect/target/plugins/apps/{texteditor → editor}/windowsnotepad.py +40 -31
  40. dissect/target/plugins/apps/other/__init__.py +0 -0
  41. dissect/target/plugins/apps/other/env.py +56 -0
  42. dissect/target/plugins/apps/shell/powershell.py +6 -2
  43. dissect/target/plugins/apps/shell/wget.py +91 -0
  44. dissect/target/plugins/apps/ssh/openssh.py +2 -0
  45. dissect/target/plugins/apps/ssh/opensshd.py +2 -0
  46. dissect/target/plugins/apps/virtualization/__init__.py +0 -0
  47. dissect/target/plugins/apps/virtualization/vmware_workstation.py +61 -0
  48. dissect/target/plugins/apps/vpn/wireguard.py +9 -9
  49. dissect/target/plugins/apps/webhosting/cpanel.py +2 -0
  50. dissect/target/plugins/apps/webserver/caddy.py +2 -0
  51. dissect/target/plugins/apps/webserver/nginx.py +2 -0
  52. dissect/target/plugins/child/esxi.py +3 -1
  53. dissect/target/plugins/child/parallels.py +68 -0
  54. dissect/target/plugins/child/proxmox.py +23 -0
  55. dissect/target/plugins/child/virtuozzo.py +12 -8
  56. dissect/target/plugins/child/vmware_workstation.py +23 -8
  57. dissect/target/plugins/filesystem/acquire_hash.py +2 -1
  58. dissect/target/plugins/filesystem/icat.py +15 -11
  59. dissect/target/plugins/filesystem/ntfs/mft.py +10 -6
  60. dissect/target/plugins/filesystem/ntfs/mft_timeline.py +3 -1
  61. dissect/target/plugins/filesystem/ntfs/usnjrnl.py +2 -0
  62. dissect/target/plugins/filesystem/ntfs/utils.py +3 -1
  63. dissect/target/plugins/filesystem/unix/suid.py +4 -1
  64. dissect/target/plugins/filesystem/walkfs.py +2 -0
  65. dissect/target/plugins/general/example.py +2 -2
  66. dissect/target/plugins/general/loaders.py +18 -5
  67. dissect/target/plugins/general/network.py +20 -5
  68. dissect/target/plugins/general/osinfo.py +1 -0
  69. dissect/target/plugins/general/plugins.py +53 -10
  70. dissect/target/plugins/os/unix/_os.py +70 -44
  71. dissect/target/plugins/os/unix/applications.py +78 -0
  72. dissect/target/plugins/os/unix/bsd/citrix/history.py +2 -0
  73. dissect/target/plugins/os/unix/bsd/osx/_os.py +4 -21
  74. dissect/target/plugins/os/unix/bsd/osx/network.py +92 -0
  75. dissect/target/plugins/os/unix/bsd/osx/user.py +4 -0
  76. dissect/target/plugins/os/unix/cronjobs.py +8 -4
  77. dissect/target/plugins/os/unix/etc/etc.py +4 -0
  78. dissect/target/plugins/os/unix/generic.py +2 -0
  79. dissect/target/plugins/os/unix/history.py +27 -25
  80. dissect/target/plugins/os/unix/linux/_os.py +8 -10
  81. dissect/target/plugins/os/unix/linux/cmdline.py +2 -0
  82. dissect/target/plugins/os/unix/linux/debian/apt.py +4 -1
  83. dissect/target/plugins/os/unix/linux/debian/dpkg.py +3 -3
  84. dissect/target/plugins/os/unix/linux/debian/proxmox/__init__.py +0 -0
  85. dissect/target/plugins/os/unix/linux/debian/proxmox/_os.py +141 -0
  86. dissect/target/plugins/os/unix/linux/debian/proxmox/vm.py +29 -0
  87. dissect/target/plugins/os/unix/linux/debian/snap.py +79 -0
  88. dissect/target/plugins/os/unix/linux/environ.py +2 -0
  89. dissect/target/plugins/os/unix/linux/fortios/_os.py +74 -63
  90. dissect/target/plugins/os/unix/linux/fortios/generic.py +2 -0
  91. dissect/target/plugins/os/unix/linux/fortios/locale.py +2 -0
  92. dissect/target/plugins/os/unix/linux/modules.py +2 -0
  93. dissect/target/plugins/os/unix/linux/netstat.py +2 -0
  94. dissect/target/{helpers → plugins/os/unix/linux}/network_managers.py +11 -9
  95. dissect/target/plugins/os/unix/linux/processes.py +2 -0
  96. dissect/target/plugins/os/unix/linux/redhat/yum.py +4 -1
  97. dissect/target/plugins/os/unix/linux/services.py +5 -3
  98. dissect/target/plugins/os/unix/linux/sockets.py +2 -0
  99. dissect/target/plugins/os/unix/linux/suse/zypper.py +4 -1
  100. dissect/target/plugins/os/unix/locale.py +2 -0
  101. dissect/target/plugins/os/unix/locate/gnulocate.py +4 -2
  102. dissect/target/plugins/os/unix/locate/mlocate.py +2 -0
  103. dissect/target/plugins/os/unix/locate/plocate.py +3 -1
  104. dissect/target/plugins/os/unix/log/atop.py +2 -0
  105. dissect/target/plugins/os/unix/log/audit.py +3 -1
  106. dissect/target/plugins/os/unix/log/auth.py +351 -38
  107. dissect/target/plugins/os/unix/log/journal.py +123 -101
  108. dissect/target/plugins/os/unix/log/lastlog.py +5 -3
  109. dissect/target/plugins/os/unix/log/messages.py +51 -27
  110. dissect/target/plugins/os/unix/log/utmp.py +52 -71
  111. dissect/target/plugins/os/unix/packagemanager.py +5 -38
  112. dissect/target/plugins/os/unix/shadow.py +3 -1
  113. dissect/target/plugins/os/unix/trash.py +132 -0
  114. dissect/target/plugins/os/windows/_os.py +22 -41
  115. dissect/target/plugins/os/windows/activitiescache.py +9 -4
  116. dissect/target/plugins/os/windows/adpolicy.py +2 -1
  117. dissect/target/plugins/os/windows/amcache.py +16 -13
  118. dissect/target/plugins/os/windows/defender.py +4 -3
  119. dissect/target/plugins/os/windows/dpapi/keyprovider/credhist.py +3 -0
  120. dissect/target/plugins/os/windows/dpapi/keyprovider/empty.py +3 -0
  121. dissect/target/plugins/os/windows/dpapi/keyprovider/keychain.py +3 -0
  122. dissect/target/plugins/os/windows/dpapi/keyprovider/lsa.py +3 -0
  123. dissect/target/plugins/os/windows/env.py +1 -2
  124. dissect/target/plugins/os/windows/exchange/exchange.py +6 -4
  125. dissect/target/plugins/os/windows/generic.py +68 -19
  126. dissect/target/plugins/os/windows/lnk.py +2 -0
  127. dissect/target/plugins/os/windows/locale.py +9 -3
  128. dissect/target/plugins/os/windows/log/etl.py +5 -4
  129. dissect/target/plugins/os/windows/log/evt.py +12 -8
  130. dissect/target/plugins/os/windows/log/evtx.py +9 -7
  131. dissect/target/plugins/os/windows/log/mssql.py +103 -0
  132. dissect/target/plugins/os/windows/log/pfro.py +2 -1
  133. dissect/target/plugins/os/windows/network.py +380 -0
  134. dissect/target/plugins/os/windows/notifications.py +6 -4
  135. dissect/target/plugins/os/windows/prefetch.py +7 -2
  136. dissect/target/plugins/os/windows/regf/7zip.py +9 -1
  137. dissect/target/plugins/os/windows/regf/applications.py +62 -0
  138. dissect/target/plugins/os/windows/regf/auditpol.py +2 -1
  139. dissect/target/plugins/os/windows/regf/bam.py +3 -1
  140. dissect/target/plugins/os/windows/regf/cit.py +14 -12
  141. dissect/target/plugins/os/windows/regf/clsid.py +6 -3
  142. dissect/target/plugins/os/windows/regf/firewall.py +2 -1
  143. dissect/target/plugins/os/windows/regf/mru.py +9 -8
  144. dissect/target/plugins/os/windows/regf/nethist.py +6 -3
  145. dissect/target/plugins/os/windows/regf/recentfilecache.py +3 -1
  146. dissect/target/plugins/os/windows/regf/regf.py +5 -1
  147. dissect/target/plugins/os/windows/regf/shellbags.py +351 -345
  148. dissect/target/plugins/os/windows/regf/shimcache.py +1 -1
  149. dissect/target/plugins/os/windows/regf/usb.py +2 -1
  150. dissect/target/plugins/os/windows/regf/userassist.py +2 -1
  151. dissect/target/plugins/os/windows/registry.py +11 -0
  152. dissect/target/plugins/os/windows/services.py +3 -2
  153. dissect/target/plugins/os/windows/startupinfo.py +7 -2
  154. dissect/target/plugins/os/windows/syscache.py +5 -2
  155. dissect/target/plugins/os/windows/tasks.py +1 -1
  156. dissect/target/plugins/os/windows/thumbcache.py +11 -5
  157. dissect/target/plugins/os/windows/ual.py +12 -9
  158. dissect/target/plugins/os/windows/wer.py +21 -6
  159. dissect/target/plugins/os/windows/wua_history.py +0 -1
  160. dissect/target/target.py +13 -8
  161. dissect/target/tools/dump/utils.py +4 -0
  162. dissect/target/tools/fsutils.py +1 -1
  163. dissect/target/tools/info.py +1 -1
  164. dissect/target/tools/mount.py +15 -5
  165. dissect/target/tools/query.py +15 -9
  166. dissect/target/tools/shell.py +98 -9
  167. dissect/target/tools/utils.py +7 -7
  168. dissect/target/volume.py +4 -4
  169. {dissect.target-3.19.dev57.dist-info → dissect.target-3.20.dist-info}/METADATA +6 -2
  170. {dissect.target-3.19.dev57.dist-info → dissect.target-3.20.dist-info}/RECORD +176 -160
  171. {dissect.target-3.19.dev57.dist-info → dissect.target-3.20.dist-info}/WHEEL +1 -1
  172. dissect/target/helpers/targetd.py +0 -58
  173. dissect/target/loaders/targetd.py +0 -223
  174. dissect/target/plugins/apps/texteditor/texteditor.py +0 -13
  175. dissect/target/plugins/os/unix/etc.py +0 -9
  176. /dissect/target/plugins/apps/{texteditor → database}/__init__.py +0 -0
  177. {dissect.target-3.19.dev57.dist-info → dissect.target-3.20.dist-info}/COPYRIGHT +0 -0
  178. {dissect.target-3.19.dev57.dist-info → dissect.target-3.20.dist-info}/LICENSE +0 -0
  179. {dissect.target-3.19.dev57.dist-info → dissect.target-3.20.dist-info}/entry_points.txt +0 -0
  180. {dissect.target-3.19.dev57.dist-info → dissect.target-3.20.dist-info}/top_level.txt +0 -0
@@ -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
@@ -517,6 +517,7 @@ class MicrosoftDefenderPlugin(plugin.Plugin):
517
517
  regf_mtime=exclusion_type_subkey.timestamp,
518
518
  type=exclusion_type,
519
519
  value=exclusion_value,
520
+ _target=self.target,
520
521
  )
521
522
 
522
523
  def _mplog_processimage(self, data: dict) -> Iterator[DefenderMPLogProcessImageRecord]:
@@ -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,6 +1,10 @@
1
+ from __future__ import annotations
2
+
3
+ import struct
1
4
  from datetime import datetime
2
- from typing import Optional
5
+ from typing import Iterator
3
6
 
7
+ from dissect.util.sid import read_sid
4
8
  from dissect.util.ts import from_unix
5
9
 
6
10
  from dissect.target.exceptions import RegistryError, UnsupportedPluginError
@@ -8,7 +12,10 @@ from dissect.target.helpers.descriptor_extensions import (
8
12
  RegistryRecordDescriptorExtension,
9
13
  UserRecordDescriptorExtension,
10
14
  )
11
- from dissect.target.helpers.record import create_extended_descriptor
15
+ from dissect.target.helpers.record import (
16
+ TargetRecordDescriptor,
17
+ create_extended_descriptor,
18
+ )
12
19
  from dissect.target.plugin import Plugin, export
13
20
 
14
21
  UserRegistryRecordDescriptor = create_extended_descriptor(
@@ -111,6 +118,15 @@ WinSockNamespaceProviderRecord = UserRegistryRecordDescriptor(
111
118
  ],
112
119
  )
113
120
 
121
+ ComputerSidRecord = TargetRecordDescriptor(
122
+ "windows/sid/computer",
123
+ [
124
+ ("datetime", "ts"),
125
+ ("string", "sidtype"),
126
+ ("string", "sid"),
127
+ ],
128
+ )
129
+
114
130
 
115
131
  class GenericPlugin(Plugin):
116
132
  """Generic Windows plugin.
@@ -123,12 +139,12 @@ class GenericPlugin(Plugin):
123
139
  raise UnsupportedPluginError("Unsupported Plugin")
124
140
 
125
141
  @export(property=True)
126
- def ntversion(self):
142
+ def ntversion(self) -> str | None:
127
143
  """Return the Windows NT version."""
128
144
  return self.target._os._nt_version()
129
145
 
130
146
  @export(output="yield")
131
- def pathenvironment(self):
147
+ def pathenvironment(self) -> Iterator[str]:
132
148
  """Return the content of the Windows PATH environment variable.
133
149
 
134
150
  PATH is an environment variable on an operating system that specifies a set of directories where executable
@@ -142,7 +158,7 @@ class GenericPlugin(Plugin):
142
158
  yield r.value("Path").value
143
159
 
144
160
  @export(property=True)
145
- def domain(self):
161
+ def domain(self) -> str | None:
146
162
  """Return the domain name.
147
163
 
148
164
  Corporate Windows systems are usually connected to a domain (active directory).
@@ -167,7 +183,7 @@ class GenericPlugin(Plugin):
167
183
  continue
168
184
 
169
185
  @export(property=True)
170
- def activity(self) -> Optional[datetime]:
186
+ def activity(self) -> datetime | None:
171
187
  """Return last seen activity based on filesystem timestamps."""
172
188
  last_seen = 0
173
189
 
@@ -193,7 +209,7 @@ class GenericPlugin(Plugin):
193
209
  return from_unix(last_seen)
194
210
 
195
211
  @export(property=True)
196
- def install_date(self) -> Optional[datetime]:
212
+ def install_date(self) -> datetime | None:
197
213
  """Returns the install date of the system.
198
214
 
199
215
  The value of the registry key is stored as a Unix epoch timestamp.
@@ -211,7 +227,7 @@ class GenericPlugin(Plugin):
211
227
  return
212
228
 
213
229
  @export(record=AppInitRecord)
214
- def appinit(self):
230
+ def appinit(self) -> Iterator[AppInitRecord]:
215
231
  """Return all available Application Initial (AppInit) DLLs registry key values.
216
232
 
217
233
  AppInit_DLLs is a mechanism that allows an arbitrary list of DLLs to be loaded into each user mode process on
@@ -258,7 +274,7 @@ class GenericPlugin(Plugin):
258
274
  continue
259
275
 
260
276
  @export(record=KnownDllRecord)
261
- def knowndlls(self):
277
+ def knowndlls(self) -> Iterator[KnownDllRecord]:
262
278
  """Return all available KnownDLLs registry key values.
263
279
 
264
280
  The KnownDLLs registry key values are used to cache frequently used system DLLs. Initially, it was added to
@@ -287,7 +303,7 @@ class GenericPlugin(Plugin):
287
303
  pass
288
304
 
289
305
  @export(record=SessionManagerRecord)
290
- def sessionmanager(self):
306
+ def sessionmanager(self) -> Iterator[SessionManagerRecord]:
291
307
  """Return interesting Session Manager (Smss.exe) registry key entries.
292
308
 
293
309
  Session Manager (Smss.exe) is the first user-mode process started by the kernel and performs several tasks,
@@ -339,7 +355,7 @@ class GenericPlugin(Plugin):
339
355
  )
340
356
 
341
357
  @export(record=NullSessionPipeRecord)
342
- def nullsessionpipes(self):
358
+ def nullsessionpipes(self) -> Iterator[NullSessionPipeRecord]:
343
359
  """Return the NullSessionPipes registry key value.
344
360
 
345
361
  The NullSessionPipes registry key value specifies server pipes and shared folders that are excluded from the
@@ -367,7 +383,7 @@ class GenericPlugin(Plugin):
367
383
  continue
368
384
 
369
385
  @export(record=NdisRecord)
370
- def ndis(self):
386
+ def ndis(self) -> Iterator[NdisRecord]:
371
387
  """Return network registry key entries."""
372
388
  key = "HKLM\\System\\CurrentControlSet\\Control\\Network\\{4D36E972-E325-11CE-BFC1-08002BE10318}"
373
389
  for r in self.target.registry.keys(key):
@@ -401,7 +417,7 @@ class GenericPlugin(Plugin):
401
417
  )
402
418
 
403
419
  @export(record=CommandProcAutoRunRecord)
404
- def commandprocautorun(self):
420
+ def commandprocautorun(self) -> Iterator[CommandProcAutoRunRecord]:
405
421
  """Return all available Command Processor (cmd.exe) AutoRun registry key values.
406
422
 
407
423
  The Command Processor AutoRun registry key values contain commands that are run each time the Command Processor
@@ -435,7 +451,7 @@ class GenericPlugin(Plugin):
435
451
  continue
436
452
 
437
453
  @export(record=AlternateShellRecord)
438
- def alternateshell(self):
454
+ def alternateshell(self) -> Iterator[AlternateShellRecord]:
439
455
  """Return the AlternateShell registry key value.
440
456
 
441
457
  The AlternateShell registry key, HKEY_LOCAL_MACHINE\\System\\CurrentControlSet\\Control\\Safeboot, specifies the
@@ -459,7 +475,7 @@ class GenericPlugin(Plugin):
459
475
  )
460
476
 
461
477
  @export(record=BootShellRecord)
462
- def bootshell(self):
478
+ def bootshell(self) -> Iterator[BootShellRecord]:
463
479
  """Return the BootShell registry key entry.
464
480
 
465
481
  Usually contains a path to bootim.exe which is Windows's recovery menu.
@@ -483,7 +499,7 @@ class GenericPlugin(Plugin):
483
499
  )
484
500
 
485
501
  @export(record=FileRenameOperationRecord)
486
- def filerenameop(self):
502
+ def filerenameop(self) -> Iterator[FileRenameOperationRecord]:
487
503
  """Return all pending file rename operations.
488
504
 
489
505
  The PendingFileRenameOperations registry key value contains information about files that will be renamed on
@@ -513,7 +529,7 @@ class GenericPlugin(Plugin):
513
529
  )
514
530
 
515
531
  @export(record=WinRarRecord)
516
- def winrar(self):
532
+ def winrar(self) -> Iterator[WinRarRecord]:
517
533
  """Return all available WinRAR history registry key values."""
518
534
  keys = [
519
535
  "HKEY_CURRENT_USER\\Software\\WinRAR\\ArcHistory",
@@ -534,7 +550,7 @@ class GenericPlugin(Plugin):
534
550
  )
535
551
 
536
552
  @export(record=WinSockNamespaceProviderRecord)
537
- def winsocknamespaceprovider(self):
553
+ def winsocknamespaceprovider(self) -> Iterator[WinSockNamespaceProviderRecord]:
538
554
  """Return available protocols stored in the Winsock catalog database.
539
555
 
540
556
  References:
@@ -562,7 +578,7 @@ class GenericPlugin(Plugin):
562
578
  )
563
579
 
564
580
  @export(property=True)
565
- def codepage(self) -> Optional[str]:
581
+ def codepage(self) -> str | None:
566
582
  """Returns the current active codepage on the system."""
567
583
 
568
584
  key = "HKLM\\SYSTEM\\CurrentControlSet\\Control\\Nls\\CodePage"
@@ -571,3 +587,36 @@ class GenericPlugin(Plugin):
571
587
  return self.target.registry.key(key).value("ACP").value
572
588
  except RegistryError:
573
589
  pass
590
+
591
+ @export(record=ComputerSidRecord)
592
+ def sid(self) -> Iterator[ComputerSidRecord]:
593
+ """Return the machine- and optional domain SID of the system."""
594
+
595
+ try:
596
+ key = self.target.registry.key("HKLM\\SAM\\SAM\\Domains\\Account")
597
+
598
+ # The machine SID is stored in the last 12 bytes of the V value as little-endian
599
+ # The machine SID differs from a 'normal' binary SID as only holds 3 values and lacks a prefix / Revision
600
+ # NOTE: Consider moving this to dissect.util.sid if we encounter this more often
601
+ sid = struct.unpack_from("<III", key.value("V").value, -12)
602
+
603
+ yield ComputerSidRecord(
604
+ ts=key.timestamp,
605
+ sidtype="Machine",
606
+ sid=f"S-1-5-21-{sid[0]}-{sid[1]}-{sid[2]}",
607
+ _target=self.target,
608
+ )
609
+ except (RegistryError, struct.error):
610
+ pass
611
+
612
+ try:
613
+ key = self.target.registry.key("HKLM\\SECURITY\\Policy\\PolMachineAccountS")
614
+
615
+ yield ComputerSidRecord(
616
+ ts=key.timestamp,
617
+ sidtype="Domain",
618
+ sid=read_sid(key.value("(Default)").value),
619
+ _target=self.target,
620
+ )
621
+ except (RegistryError, struct.error):
622
+ pass
@@ -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)
@@ -1,5 +1,6 @@
1
1
  from functools import lru_cache
2
2
  from pathlib import Path
3
+ from typing import Iterator
3
4
 
4
5
  from dissect.etl.etl import ETL, Event
5
6
 
@@ -65,7 +66,7 @@ class EtlRecordBuilder:
65
66
 
66
67
 
67
68
  class EtlPlugin(Plugin):
68
- """Plugin for fetching and parsing Windows ETL Files (*.etl)"""
69
+ """Plugin for parsing Windows ETL Files (``*.etl``)."""
69
70
 
70
71
  __namespace__ = "etl"
71
72
 
@@ -108,7 +109,7 @@ class EtlPlugin(Plugin):
108
109
  yield from etl_records
109
110
 
110
111
  @export(record=DynamicDescriptor(["datetime"]))
111
- def etl(self):
112
+ def etl(self) -> Iterator[DynamicDescriptor]:
112
113
  """Return the contents of the ETL files generated at last boot and last shutdown.
113
114
 
114
115
  An event trace log (.etl) file, also known as a trace log, stores the trace messages generated during one or
@@ -135,7 +136,7 @@ class EtlPlugin(Plugin):
135
136
  yield from getattr(self, etl_plugin)()
136
137
 
137
138
  @export(record=DynamicDescriptor(["datetime"]))
138
- def shutdown(self):
139
+ def shutdown(self) -> Iterator[DynamicDescriptor]:
139
140
  """Return the contents of the ETL files created at last shutdown.
140
141
 
141
142
  The plugin reads the content from the ShutdownCKCL.etl file or the ShutdownPerfDiagLogger.etl file (depending
@@ -155,7 +156,7 @@ class EtlPlugin(Plugin):
155
156
  yield from self.read_etl_files(self.PATHS["shutdown"])
156
157
 
157
158
  @export(record=DynamicDescriptor(["datetime"]))
158
- def boot(self):
159
+ def boot(self) -> Iterator[DynamicDescriptor]:
159
160
  """Return the contents of the ETL files created at last boot.
160
161
 
161
162
  The plugin reads the content from the BootCKCL.etl file or the BootPerfDiagLogger.etl file (depending
@@ -1,7 +1,9 @@
1
+ from __future__ import annotations
2
+
1
3
  import fnmatch
2
4
  import re
3
5
  from pathlib import Path
4
- from typing import Any, BinaryIO, Generator, List, Optional
6
+ from typing import Any, BinaryIO, Iterator
5
7
 
6
8
  from dissect.eventlog import evt
7
9
  from flow.record import Record
@@ -48,7 +50,7 @@ class WindowsEventlogsMixin:
48
50
  LOGS_DIR_PATH = None
49
51
 
50
52
  @plugin.internal
51
- def get_logs(self, filename_glob="*") -> List[Path]:
53
+ def get_logs(self, filename_glob="*") -> list[Path]:
52
54
  file_paths = []
53
55
  file_paths.extend(self.get_logs_from_dir(self.LOGS_DIR_PATH, filename_glob=filename_glob))
54
56
 
@@ -66,7 +68,7 @@ class WindowsEventlogsMixin:
66
68
  return file_paths
67
69
 
68
70
  @plugin.internal
69
- def get_logs_from_dir(self, logs_dir: str, filename_glob: str = "*") -> List[Path]:
71
+ def get_logs_from_dir(self, logs_dir: str, filename_glob: str = "*") -> list[Path]:
70
72
  file_paths = []
71
73
  logs_dir = self.target.fs.path(logs_dir)
72
74
  if logs_dir.exists():
@@ -76,7 +78,7 @@ class WindowsEventlogsMixin:
76
78
  return file_paths
77
79
 
78
80
  @plugin.internal
79
- def get_logs_from_registry(self, filename_glob: str = "*") -> List[Path]:
81
+ def get_logs_from_registry(self, filename_glob: str = "*") -> list[Path]:
80
82
  # compile glob into case-insensitive regex
81
83
  filename_regex = re.compile(fnmatch.translate(filename_glob), re.IGNORECASE)
82
84
 
@@ -112,6 +114,8 @@ class WindowsEventlogsMixin:
112
114
 
113
115
 
114
116
  class EvtPlugin(WindowsEventlogsMixin, plugin.Plugin):
117
+ """Windows ``.evt`` event log plugin."""
118
+
115
119
  LOGS_DIR_PATH = "sysvol/windows/system32/config"
116
120
 
117
121
  NEEDLE = b"LfLe"
@@ -120,8 +124,8 @@ class EvtPlugin(WindowsEventlogsMixin, plugin.Plugin):
120
124
  @plugin.arg("--logs-dir", help="logs directory to scan")
121
125
  @plugin.arg("--log-file-glob", default=EVT_GLOB, help="glob pattern to match a log file name")
122
126
  @plugin.export(record=EvtRecordDescriptor)
123
- def evt(self, log_file_glob: str = EVT_GLOB, logs_dir: Optional[str] = None) -> Generator[Record, None, None]:
124
- """Parse Windows Eventlog files (*.evt).
127
+ def evt(self, log_file_glob: str = EVT_GLOB, logs_dir: str | None = None) -> Iterator[EvtRecordDescriptor]:
128
+ """Parse Windows Eventlog files (``*.evt``).
125
129
 
126
130
  Yields dynamically created records based on the fields in the event.
127
131
  At least contains the following fields:
@@ -174,7 +178,7 @@ class EvtPlugin(WindowsEventlogsMixin, plugin.Plugin):
174
178
  )
175
179
 
176
180
  @plugin.export(record=EvtRecordDescriptor)
177
- def scraped_evt(self) -> Generator[Record, None, None]:
181
+ def scraped_evt(self) -> Iterator[EvtRecordDescriptor]:
178
182
  """Yields EVT log file records scraped from target disks"""
179
183
  yield from self.target.scrape.scrape_chunks_from_disks(
180
184
  needle=self.NEEDLE,
@@ -189,6 +193,6 @@ class EvtPlugin(WindowsEventlogsMixin, plugin.Plugin):
189
193
  fh.seek(offset - 4)
190
194
  return fh.read(chunk_size)
191
195
 
192
- def _parse_chunk(self, _, chunk: bytes) -> Generator[Record, None, None]:
196
+ def _parse_chunk(self, _, chunk: bytes) -> Iterator[Record]:
193
197
  for record in evt.parse_chunk(chunk):
194
198
  yield self._build_record(record)