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
@@ -108,7 +108,7 @@ MAGIC_WIN10 = 0x73743031
108
108
 
109
109
 
110
110
  class SHIMCACHE_WIN_TYPE(IntEnum):
111
- """Specific shimcache versions"""
111
+ """Shimcache version mapping."""
112
112
 
113
113
  VERSION_WIN10_CREATORS = 0x1001
114
114
  VERSION_WIN10 = 0x1000
@@ -54,7 +54,8 @@ class UsbPlugin(Plugin):
54
54
 
55
55
  To get a full picture of the USB history on a Windows machine, you should parse the
56
56
  relevant EventIDs using the evtx plugin. For more research on event log USB forensics, see:
57
- - https://www.researchgate.net/publication/318514858
57
+
58
+ - https://www.researchgate.net/publication/318514858_USB_Storage_Device_Forensics_for_Windows_10
58
59
  - https://dfir.pubpub.org/pub/h78di10n/release/2
59
60
  - https://www.senturean.com/posts/19_08_03_usb_storage_forensics_1/#1-system-events
60
61
 
@@ -1,4 +1,5 @@
1
1
  import codecs
2
+ from typing import Iterator
2
3
 
3
4
  from dissect.cstruct import cstruct
4
5
  from dissect.util.ts import wintimestamp
@@ -60,7 +61,7 @@ class UserAssistPlugin(Plugin):
60
61
  raise UnsupportedPluginError("No UserAssist key found")
61
62
 
62
63
  @export(record=UserAssistRecord)
63
- def userassist(self):
64
+ def userassist(self) -> Iterator[UserAssistRecord]:
64
65
  """Return the UserAssist information for each user.
65
66
 
66
67
  The UserAssist registry keys contain information about programs that were recently executed on the system.
@@ -68,6 +68,10 @@ class RegistryPlugin(Plugin):
68
68
  "COMPONENTS",
69
69
  "DEFAULT",
70
70
  "ELAM",
71
+ "USER.DAT", # Win 95/98/ME
72
+ "SYSTEM.DAT", # Win 95/98/ME
73
+ "CLASSES.DAT", # Win ME
74
+ "REG.DAT", # Win 3.1
71
75
  ]
72
76
 
73
77
  def __init__(self, target: Target) -> None:
@@ -88,7 +92,14 @@ class RegistryPlugin(Plugin):
88
92
 
89
93
  def _init_registry(self) -> None:
90
94
  dirs = [
95
+ # Windows XP or newer
91
96
  ("sysvol/windows/system32/config", False),
97
+ # Windows NT3, NT4, 2k
98
+ ("sysvol/WINNT/system32/config", False),
99
+ # Windows 3.11, 95, 98, ME
100
+ ("sysvol/windows", False),
101
+ # ReactOS (alternative location)
102
+ ("sysvol/reactos", False),
92
103
  # RegBack hives are often empty files
93
104
  ("sysvol/windows/system32/config/RegBack", True),
94
105
  ]
@@ -1,4 +1,5 @@
1
1
  import re
2
+ from typing import Iterator
2
3
 
3
4
  from dissect.target.exceptions import (
4
5
  RegistryError,
@@ -62,8 +63,8 @@ class ServicesPlugin(Plugin):
62
63
  raise UnsupportedPluginError("No services found in the registry")
63
64
 
64
65
  @export(record=ServiceRecord)
65
- def services(self):
66
- """Return information about all installed services.
66
+ def services(self) -> Iterator[ServiceRecord]:
67
+ """Return information about all installed Windows services.
67
68
 
68
69
  The HKLM\\SYSTEM\\CurrentControlSet\\Services registry key contains information about the installed services and
69
70
  drivers on the system.
@@ -1,4 +1,7 @@
1
+ from __future__ import annotations
2
+
1
3
  import datetime
4
+ from typing import Iterator
2
5
 
3
6
  from defusedxml import ElementTree
4
7
 
@@ -34,7 +37,7 @@ StartupInfoRecord = TargetRecordDescriptor(
34
37
  )
35
38
 
36
39
 
37
- def parse_ts(time_string):
40
+ def parse_ts(time_string: str) -> datetime.datetime | None:
38
41
  if not time_string:
39
42
  return None
40
43
 
@@ -42,6 +45,8 @@ def parse_ts(time_string):
42
45
 
43
46
 
44
47
  class StartupInfoPlugin(Plugin):
48
+ """Windows startup info plugin."""
49
+
45
50
  def __init__(self, target):
46
51
  super().__init__(target)
47
52
  self._files = []
@@ -55,7 +60,7 @@ class StartupInfoPlugin(Plugin):
55
60
  raise UnsupportedPluginError("No StartupInfo files found")
56
61
 
57
62
  @export(record=StartupInfoRecord)
58
- def startupinfo(self):
63
+ def startupinfo(self) -> Iterator[StartupInfoRecord]:
59
64
  """Return the contents of StartupInfo files.
60
65
 
61
66
  On a Windows system, the StartupInfo log files contain information about process execution for the first 90
@@ -1,3 +1,5 @@
1
+ from typing import Iterator
2
+
1
3
  from dissect.ntfs import ntfs
2
4
 
3
5
  from dissect.target.exceptions import RegistryValueNotFoundError, UnsupportedPluginError
@@ -41,7 +43,7 @@ class SyscachePlugin(Plugin):
41
43
  raise UnsupportedPluginError("Could not load Syscache.hve")
42
44
 
43
45
  @export(record=SyscacheRecord)
44
- def syscache(self):
46
+ def syscache(self) -> Iterator[SyscacheRecord]:
45
47
  """Parse the objects in the ObjectTable from the Syscache.hve file."""
46
48
 
47
49
  # Try to get the system volume
@@ -75,7 +77,8 @@ class SyscachePlugin(Plugin):
75
77
  full_path = None
76
78
  if mft:
77
79
  try:
78
- full_path = self.target.fs.path("\\".join(["sysvol", mft.mft(file_segment).fullpath()]))
80
+ if path := mft(file_segment).full_path():
81
+ full_path = self.target.fs.path("\\".join(["sysvol", path]))
79
82
  except ntfs.Error:
80
83
  pass
81
84
 
@@ -125,7 +125,7 @@ class TasksPlugin(Plugin):
125
125
  intervals. An adversary may leverage such scheduled tasks to gain persistence on a system.
126
126
 
127
127
  References:
128
- https://en.wikipedia.org/wiki/Windows_Task_Scheduler
128
+ - https://en.wikipedia.org/wiki/Windows_Task_Scheduler
129
129
 
130
130
  Yields:
131
131
  The scheduled tasks found on the target.
@@ -1,5 +1,7 @@
1
+ from __future__ import annotations
2
+
1
3
  from pathlib import Path
2
- from typing import Iterator, Optional, Union
4
+ from typing import Iterator
3
5
 
4
6
  from dissect.thumbcache import Error, Thumbcache
5
7
  from dissect.thumbcache.tools.extract_with_index import dump_entry_data_through_index
@@ -33,6 +35,8 @@ IconcacheRecord = TargetRecordDescriptor("windows/thumbcache/iconcache", GENERIC
33
35
 
34
36
 
35
37
  class ThumbcachePlugin(Plugin):
38
+ """Windows thumbcache plugin."""
39
+
36
40
  __namespace__ = "thumbcache"
37
41
 
38
42
  def get_cache_paths(self) -> Iterator[TargetPath]:
@@ -71,8 +75,8 @@ class ThumbcachePlugin(Plugin):
71
75
  self,
72
76
  record_type: TargetRecordDescriptor,
73
77
  prefix: str,
74
- output_dir: Optional[Path],
75
- ) -> Iterator[Union[ThumbcacheRecord, IconcacheRecord, IndexRecord]]:
78
+ output_dir: Path | None,
79
+ ) -> Iterator[ThumbcacheRecord | IconcacheRecord | IndexRecord]:
76
80
  for cache_path in self.get_cache_paths():
77
81
  try:
78
82
  if output_dir:
@@ -91,10 +95,12 @@ class ThumbcachePlugin(Plugin):
91
95
 
92
96
  @arg("--output", "-o", dest="output_dir", type=Path, help="Path to extract thumbcache thumbnails to.")
93
97
  @export(record=[ThumbcacheRecord, IndexRecord])
94
- def thumbcache(self, output_dir: Optional[Path] = None) -> Iterator[Union[ThumbcacheRecord, IndexRecord]]:
98
+ def thumbcache(self, output_dir: Path | None = None) -> Iterator[ThumbcacheRecord | IndexRecord]:
99
+ """Yield thumbcache thumbnails."""
95
100
  yield from self._parse_thumbcache(ThumbcacheRecord, "thumbcache", output_dir)
96
101
 
97
102
  @arg("--output", "-o", dest="output_dir", type=Path, help="Path to extract iconcache thumbnails to.")
98
103
  @export(record=[IconcacheRecord, IndexRecord])
99
- def iconcache(self, output_dir: Optional[Path] = None) -> Iterator[Union[IconcacheRecord, IndexRecord]]:
104
+ def iconcache(self, output_dir: Path | None = None) -> Iterator[IconcacheRecord | IndexRecord]:
105
+ """Yield iconcache thumbnails."""
100
106
  yield from self._parse_thumbcache(IconcacheRecord, "iconcache", output_dir)
@@ -1,3 +1,6 @@
1
+ from pathlib import Path
2
+ from typing import Any, Iterator
3
+
1
4
  from dissect.esedb.exceptions import Error
2
5
  from dissect.esedb.tools import ual
3
6
 
@@ -165,14 +168,14 @@ class UalPlugin(Plugin):
165
168
  if not any([path.exists() for path in self.mdb_paths]):
166
169
  raise UnsupportedPluginError("No MDB files found")
167
170
 
168
- def find_mdb_files(self):
171
+ def find_mdb_files(self) -> list[Path]:
169
172
  return [
170
173
  path
171
174
  for path in self.target.fs.path("/").glob(self.LOG_DB_GLOB)
172
175
  if path.exists() and path.name != self.IDENTITY_DB_FILENAME
173
176
  ]
174
177
 
175
- def populate_role_guid_map(self):
178
+ def populate_role_guid_map(self) -> None:
176
179
  identity_db = self.target.fs.path(self.IDENTITY_DB_PATH)
177
180
  if not identity_db.exists():
178
181
  return
@@ -192,13 +195,13 @@ class UalPlugin(Plugin):
192
195
  "role_name": record.get("RoleName"),
193
196
  }
194
197
 
195
- def read_table_records(self, table_name):
198
+ def read_table_records(self, table_name: str) -> Iterator[tuple[Path, dict[str, Any]]]:
196
199
  for mdb_path in self.mdb_paths:
197
200
  fh = mdb_path.open()
198
201
  try:
199
202
  parser = ual.UAL(fh)
200
203
  except Error as e:
201
- self.target.log.warning(f"Error opening {mdb_path} database", exc_info=e)
204
+ self.target.log.warning("Error opening database: %s", mdb_path, exc_info=e)
202
205
  continue
203
206
 
204
207
  for table_record in parser.get_table_records(table_name):
@@ -206,7 +209,7 @@ class UalPlugin(Plugin):
206
209
  yield mdb_path, values
207
210
 
208
211
  @export(record=ClientAccessRecord)
209
- def client_access(self):
212
+ def client_access(self) -> Iterator[ClientAccessRecord]:
210
213
  """Return client access data within the User Access Logs."""
211
214
  for path, client_record in self.read_table_records("CLIENTS"):
212
215
  common_values = {k: v for k, v in client_record.items() if k != "activity_counts"}
@@ -224,7 +227,7 @@ class UalPlugin(Plugin):
224
227
  )
225
228
 
226
229
  @export(record=RoleAccessRecord)
227
- def role_access(self):
230
+ def role_access(self) -> Iterator[RoleAccessRecord]:
228
231
  """Return role access data within the User Access Logs."""
229
232
  for path, record in self.read_table_records("ROLE_ACCESS"):
230
233
  role_guid_data = self.role_guid_map.get(record.get("role_guid"), {})
@@ -237,19 +240,19 @@ class UalPlugin(Plugin):
237
240
  )
238
241
 
239
242
  @export(record=VirtualMachineRecord)
240
- def virtual_machines(self):
243
+ def virtual_machines(self) -> Iterator[VirtualMachineRecord]:
241
244
  """Return virtual machine data within the User Access Logs."""
242
245
  for path, record in self.read_table_records("VIRTUALMACHINES"):
243
246
  yield VirtualMachineRecord(path=path, _target=self.target, **record)
244
247
 
245
248
  @export(record=DomainSeenRecord)
246
- def domains_seen(self):
249
+ def domains_seen(self) -> Iterator[DomainSeenRecord]:
247
250
  """Return DNS data within the User Access Logs."""
248
251
  for path, record in self.read_table_records("DNS"):
249
252
  yield DomainSeenRecord(path=path, _target=self.target, **record)
250
253
 
251
254
  @export(record=SystemIdentityRecord)
252
- def system_identities(self):
255
+ def system_identities(self) -> Iterator[SystemIdentityRecord]:
253
256
  """Return system identity data within the User Access Logs."""
254
257
  if not self.identity_db_parser:
255
258
  return
@@ -4,6 +4,7 @@ from typing import Iterator
4
4
 
5
5
  from defusedxml import ElementTree
6
6
  from dissect.util.ts import wintimestamp
7
+ from flow.record.base import is_valid_field_name
7
8
 
8
9
  from dissect.target.exceptions import UnsupportedPluginError
9
10
  from dissect.target.helpers.fsutil import Path
@@ -11,7 +12,9 @@ from dissect.target.helpers.record import DynamicDescriptor, TargetRecordDescrip
11
12
  from dissect.target.plugin import Plugin, export
12
13
  from dissect.target.target import Target
13
14
 
14
- camel_case_patterns = [re.compile(r"(\S)([A-Z][a-z]+)"), re.compile(r"([a-z0-9])([A-Z])"), re.compile(r"(\w)[.\s](\w)")]
15
+ CAMEL_CASE_PATTERNS = [re.compile(r"(\S)([A-Z][a-z]+)"), re.compile(r"([a-z0-9])([A-Z])"), re.compile(r"(\w)[.\s](\w)")]
16
+ RE_VALID_KEY_START_CHARS = re.compile(r"[a-zA-Z]")
17
+ RE_VALID_KEY_CHARS = re.compile(r"[a-zA-Z0-9_]")
15
18
 
16
19
 
17
20
  def _create_record_descriptor(record_name: str, record_fields: list[tuple[str, str]]) -> TargetRecordDescriptor:
@@ -49,13 +52,25 @@ class WindowsErrorReportingPlugin(Plugin):
49
52
 
50
53
  def _sanitize_key(self, key: str) -> str:
51
54
  # Convert camel case to snake case
52
- for pattern in camel_case_patterns:
55
+ for pattern in CAMEL_CASE_PATTERNS:
53
56
  key = pattern.sub(r"\1_\2", key)
54
57
 
55
- # Keep only basic characters in key
56
- key = re.sub(r"[^a-zA-Z0-9_]", "", key)
57
-
58
- return key.lower()
58
+ clean_key = ""
59
+ separator = "_"
60
+ prev_encoded = False
61
+ for idx, char in enumerate(key):
62
+ if prev_encoded:
63
+ clean_key += separator
64
+ clean_key += char
65
+ if not is_valid_field_name(clean_key):
66
+ clean_key = clean_key[:-1]
67
+ prefix = f"{separator}x" if (idx != 0 and not prev_encoded) else "x"
68
+ clean_key += prefix + char.encode("utf-8").hex()
69
+ prev_encoded = True
70
+ else:
71
+ prev_encoded = False
72
+
73
+ return clean_key.lower()
59
74
 
60
75
  def _collect_wer_data(self, wer_file: Path) -> tuple[list[tuple[str, str]], dict[str, str]]:
61
76
  """Parse data from a .wer file."""
@@ -994,7 +994,6 @@ class WuaHistoryPlugin(Plugin):
994
994
  - https://learn.microsoft.com/en-us/windows/deployment/update/windows-update-error-reference
995
995
  - https://learn.microsoft.com/en-us/troubleshoot/windows-client/installing-updates-features-roles/common-windows-update-errors
996
996
  - https://learn.microsoft.com/en-us/troubleshoot/windows-client/installing-updates-features-roles/common-windows-update-errors?context=%2Fwindows%2Fdeployment%2Fcontext%2Fcontext
997
-
998
997
  - https://github.com/libyal/esedb-kb/blob/main/documentation/Windows%20Update.asciidoc
999
998
  - https://www.nirsoft.net/articles/extract-windows-updates-list-external-drive.html
1000
999
 
dissect/target/target.py CHANGED
@@ -19,6 +19,7 @@ from dissect.target.exceptions import (
19
19
  VolumeSystemError,
20
20
  )
21
21
  from dissect.target.helpers import config
22
+ from dissect.target.helpers.fsutil import TargetPath
22
23
  from dissect.target.helpers.loaderutil import extract_path_info
23
24
  from dissect.target.helpers.record import ChildTargetRecord
24
25
  from dissect.target.helpers.utils import StrEnum, parse_path_uri, slugify
@@ -86,8 +87,13 @@ class Target:
86
87
  self._errors = []
87
88
  self._applied = False
88
89
 
90
+ # We do not want to look for config files at the Target path if it is actually a child Target.
91
+ config_paths = [Path.cwd(), Path.home()]
92
+ if not isinstance(path, TargetPath):
93
+ config_paths = [self.path] + config_paths
94
+
89
95
  try:
90
- self._config = config.load([self.path, Path.cwd(), Path.home()])
96
+ self._config = config.load(config_paths)
91
97
  except Exception as e:
92
98
  self.log.warning("Error loading config file: %s", self.path)
93
99
  self.log.debug("", exc_info=e)
@@ -230,7 +236,7 @@ class Target:
230
236
  try:
231
237
  loader_instance = loader_cls(path, parsed_path=parsed_path)
232
238
  except Exception as e:
233
- raise TargetError(f"Failed to initiate {loader_cls.__name__} for target {path}: {e}", cause=e)
239
+ raise TargetError(f"Failed to initiate {loader_cls.__name__} for target {path}: {e}") from e
234
240
  return cls._load(path, loader_instance)
235
241
  return cls.open_raw(path)
236
242
 
@@ -422,7 +428,7 @@ class Target:
422
428
  target.apply()
423
429
  return target
424
430
  except Exception as e:
425
- raise TargetError(f"Failed to load target: {path}", cause=e)
431
+ raise TargetError(f"Failed to load target: {path}") from e
426
432
 
427
433
  def _init_os(self) -> None:
428
434
  """Internal function that attemps to load an OSPlugin for this target."""
@@ -535,7 +541,7 @@ class Target:
535
541
  except PluginError:
536
542
  raise
537
543
  except Exception as e:
538
- raise PluginError(f"An exception occurred while trying to initialize a plugin: {plugin_cls}", cause=e)
544
+ raise PluginError(f"An exception occurred while trying to initialize a plugin: {plugin_cls}") from e
539
545
  else:
540
546
  p = plugin_cls
541
547
 
@@ -550,8 +556,8 @@ class Target:
550
556
  raise
551
557
  except Exception as e:
552
558
  raise UnsupportedPluginError(
553
- f"An exception occurred while checking for plugin compatibility: {plugin_cls}", cause=e
554
- )
559
+ f"An exception occurred while checking for plugin compatibility: {plugin_cls}"
560
+ ) from e
555
561
 
556
562
  self._register_plugin_functions(p)
557
563
 
@@ -608,9 +614,8 @@ class Target:
608
614
  # Just take the last known cause for now
609
615
  raise UnsupportedPluginError(
610
616
  f"Unsupported function `{function}` for target with OS plugin {self._os_plugin}",
611
- cause=causes[0] if causes else None,
612
617
  extra=causes[1:] if len(causes) > 1 else None,
613
- )
618
+ ) from causes[0] if causes else None
614
619
 
615
620
  # We still ended up with no compatible plugins
616
621
  if function not in self._functions:
@@ -38,6 +38,8 @@ log = structlog.get_logger(__name__)
38
38
 
39
39
 
40
40
  class Compression(enum.Enum):
41
+ """Supported compression types."""
42
+
41
43
  BZIP2 = "bzip2"
42
44
  GZIP = "gzip"
43
45
  LZ4 = "lz4"
@@ -46,6 +48,8 @@ class Compression(enum.Enum):
46
48
 
47
49
 
48
50
  class Serialization(enum.Enum):
51
+ """Supported serialization methods."""
52
+
49
53
  JSONLINES = "jsonlines"
50
54
  MSGPACK = "msgpack"
51
55
 
@@ -207,7 +207,7 @@ def print_stat(path: fsutil.TargetPath, stdout: TextIO, dereference: bool = Fals
207
207
  filetype=filetype(path),
208
208
  device="?",
209
209
  inode=s.st_ino,
210
- blocks=s.st_blocks or "?",
210
+ blocks=s.st_blocks if s.st_blocks is not None else "?",
211
211
  blksize=s.st_blksize or "?",
212
212
  nlink=s.st_nlink,
213
213
  modeord=oct(stat.S_IMODE(s.st_mode)),
@@ -137,7 +137,7 @@ def print_target_info(target: Target) -> None:
137
137
  continue
138
138
 
139
139
  if isinstance(value, list):
140
- value = ", ".join(value)
140
+ value = ", ".join(map(str, value))
141
141
 
142
142
  if isinstance(value, datetime):
143
143
  value = value.isoformat(timespec="microseconds")
@@ -99,14 +99,24 @@ def main():
99
99
  options = parse_options_string(args.options) if args.options else {}
100
100
 
101
101
  options["nothreads"] = True
102
- options["allow_other"] = True
103
102
  options["ro"] = True
104
-
105
- print(f"Mounting to {args.mount} with options: {_format_options(options)}")
103
+ # Check if the allow other option is either not set (None) or set to True with -o allow_other=True
104
+ if (allow_other := options.get("allow_other")) is None or str(allow_other).lower() == "true":
105
+ options["allow_other"] = True
106
+ # If allow_other was not set, warn the user that it will be set by default
107
+ if allow_other is None:
108
+ log.warning("Using option 'allow_other' by default, please use '-o allow_other=False' to unset")
109
+ # Let the user be able to unset the allow_other option by supplying -o allow_other=False
110
+ elif str(allow_other).lower() == "false":
111
+ options["allow_other"] = False
112
+
113
+ log.info("Mounting to %s with options: %s", args.mount, _format_options(options))
106
114
  try:
107
115
  FUSE(DissectMount(vfs), args.mount, **options)
108
- except RuntimeError:
109
- parser.exit("FUSE error")
116
+ except RuntimeError as e:
117
+ log.error("Mounting target %s failed", t)
118
+ log.debug("", exc_info=e)
119
+ parser.exit(1)
110
120
 
111
121
 
112
122
  def _format_options(options: dict[str, Union[str, bool]]) -> str:
@@ -18,7 +18,6 @@ from dissect.target.exceptions import (
18
18
  UnsupportedPluginError,
19
19
  )
20
20
  from dissect.target.helpers import cache, record_modifier
21
- from dissect.target.loaders.targetd import ProxyLoader
22
21
  from dissect.target.plugin import PLUGINS, OSPlugin, Plugin, find_plugin_functions
23
22
  from dissect.target.report import ExecutionReport
24
23
  from dissect.target.tools.utils import (
@@ -170,33 +169,40 @@ def main():
170
169
  # Show the list of available plugins for the given optional target and optional
171
170
  # search pattern, only display plugins that can be applied to ANY targets
172
171
  if args.list:
173
- collected_plugins = {}
172
+ collected_plugins = []
174
173
 
175
174
  if targets:
176
175
  for plugin_target in Target.open_all(targets, args.children):
177
- if isinstance(plugin_target._loader, ProxyLoader):
178
- parser.error("can't list compatible plugins for remote targets.")
179
176
  funcs, _ = find_plugin_functions(plugin_target, args.list, compatibility=True, show_hidden=True)
180
177
  for func in funcs:
181
- collected_plugins[func.path] = func.plugin_desc
178
+ collected_plugins.append(func)
182
179
  else:
183
180
  funcs, _ = find_plugin_functions(Target(), args.list, compatibility=False, show_hidden=True)
184
181
  for func in funcs:
185
- collected_plugins[func.path] = func.plugin_desc
182
+ collected_plugins.append(func)
186
183
 
187
- # Display in a user friendly manner
188
184
  target = Target()
189
185
  fparser = generate_argparse_for_bound_method(target.plugins, usage_tmpl=USAGE_FORMAT_TMPL)
190
186
  fargs, rest = fparser.parse_known_args(rest)
191
187
 
188
+ # Display in a user friendly manner
192
189
  if collected_plugins:
193
- target.plugins(list(collected_plugins.values()))
190
+ if args.json:
191
+ print('{"plugins": ', end="")
192
+ target.plugins(collected_plugins, as_json=args.json)
194
193
 
195
194
  # No real targets specified, show the available loaders
196
195
  if not targets:
197
196
  fparser = generate_argparse_for_bound_method(target.loaders, usage_tmpl=USAGE_FORMAT_TMPL)
198
197
  fargs, rest = fparser.parse_known_args(rest)
199
- target.loaders(**vars(fargs))
198
+ del fargs.as_json
199
+ if args.json:
200
+ print(', "loaders": ', end="")
201
+ target.loaders(**vars(fargs), as_json=args.json)
202
+
203
+ if args.json:
204
+ print("}")
205
+
200
206
  parser.exit()
201
207
 
202
208
  if not targets: