dissect.target 3.14.dev28__py3-none-any.whl → 3.15__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
Files changed (86) hide show
  1. dissect/target/containers/ewf.py +1 -1
  2. dissect/target/containers/vhd.py +5 -2
  3. dissect/target/filesystem.py +36 -18
  4. dissect/target/filesystems/dir.py +10 -4
  5. dissect/target/filesystems/jffs.py +122 -0
  6. dissect/target/helpers/compat/path_310.py +506 -0
  7. dissect/target/helpers/compat/path_311.py +539 -0
  8. dissect/target/helpers/compat/path_312.py +443 -0
  9. dissect/target/helpers/compat/path_39.py +545 -0
  10. dissect/target/helpers/compat/path_common.py +223 -0
  11. dissect/target/helpers/cyber.py +512 -0
  12. dissect/target/helpers/fsutil.py +128 -666
  13. dissect/target/helpers/hashutil.py +17 -57
  14. dissect/target/helpers/keychain.py +9 -3
  15. dissect/target/helpers/loaderutil.py +1 -1
  16. dissect/target/helpers/mount.py +47 -4
  17. dissect/target/helpers/polypath.py +73 -0
  18. dissect/target/helpers/record_modifier.py +100 -0
  19. dissect/target/loader.py +2 -1
  20. dissect/target/loaders/asdf.py +2 -0
  21. dissect/target/loaders/cyber.py +37 -0
  22. dissect/target/loaders/log.py +14 -3
  23. dissect/target/loaders/raw.py +2 -0
  24. dissect/target/loaders/remote.py +12 -0
  25. dissect/target/loaders/tar.py +13 -0
  26. dissect/target/loaders/targetd.py +2 -0
  27. dissect/target/loaders/velociraptor.py +12 -3
  28. dissect/target/loaders/vmwarevm.py +2 -0
  29. dissect/target/plugin.py +272 -143
  30. dissect/target/plugins/apps/ssh/openssh.py +11 -54
  31. dissect/target/plugins/apps/ssh/opensshd.py +4 -3
  32. dissect/target/plugins/apps/ssh/putty.py +236 -0
  33. dissect/target/plugins/apps/ssh/ssh.py +58 -0
  34. dissect/target/plugins/apps/vpn/openvpn.py +6 -0
  35. dissect/target/plugins/apps/webserver/apache.py +309 -95
  36. dissect/target/plugins/apps/webserver/caddy.py +5 -2
  37. dissect/target/plugins/apps/webserver/citrix.py +82 -0
  38. dissect/target/plugins/apps/webserver/iis.py +9 -12
  39. dissect/target/plugins/apps/webserver/nginx.py +5 -2
  40. dissect/target/plugins/apps/webserver/webserver.py +25 -41
  41. dissect/target/plugins/child/wsl.py +1 -1
  42. dissect/target/plugins/filesystem/ntfs/mft.py +10 -0
  43. dissect/target/plugins/filesystem/ntfs/mft_timeline.py +10 -0
  44. dissect/target/plugins/filesystem/ntfs/usnjrnl.py +10 -0
  45. dissect/target/plugins/filesystem/ntfs/utils.py +28 -5
  46. dissect/target/plugins/filesystem/resolver.py +6 -4
  47. dissect/target/plugins/general/default.py +0 -2
  48. dissect/target/plugins/general/example.py +0 -1
  49. dissect/target/plugins/general/loaders.py +3 -5
  50. dissect/target/plugins/os/unix/_os.py +3 -3
  51. dissect/target/plugins/os/unix/bsd/citrix/_os.py +68 -28
  52. dissect/target/plugins/os/unix/bsd/citrix/history.py +130 -0
  53. dissect/target/plugins/os/unix/generic.py +17 -10
  54. dissect/target/plugins/os/unix/linux/fortios/__init__.py +0 -0
  55. dissect/target/plugins/os/unix/linux/fortios/_os.py +534 -0
  56. dissect/target/plugins/os/unix/linux/fortios/generic.py +30 -0
  57. dissect/target/plugins/os/unix/linux/fortios/locale.py +109 -0
  58. dissect/target/plugins/os/windows/log/evt.py +1 -1
  59. dissect/target/plugins/os/windows/log/schedlgu.py +155 -0
  60. dissect/target/plugins/os/windows/regf/firewall.py +1 -1
  61. dissect/target/plugins/os/windows/regf/shimcache.py +1 -1
  62. dissect/target/plugins/os/windows/regf/trusteddocs.py +1 -1
  63. dissect/target/plugins/os/windows/registry.py +1 -1
  64. dissect/target/plugins/os/windows/sam.py +3 -0
  65. dissect/target/plugins/os/windows/sru.py +41 -28
  66. dissect/target/plugins/os/windows/tasks.py +5 -2
  67. dissect/target/target.py +7 -3
  68. dissect/target/tools/dd.py +7 -1
  69. dissect/target/tools/fs.py +8 -1
  70. dissect/target/tools/info.py +22 -15
  71. dissect/target/tools/mount.py +28 -3
  72. dissect/target/tools/query.py +146 -117
  73. dissect/target/tools/reg.py +21 -16
  74. dissect/target/tools/shell.py +30 -6
  75. dissect/target/tools/utils.py +28 -0
  76. dissect/target/volumes/bde.py +14 -10
  77. dissect/target/volumes/luks.py +18 -10
  78. {dissect.target-3.14.dev28.dist-info → dissect.target-3.15.dist-info}/METADATA +4 -3
  79. {dissect.target-3.14.dev28.dist-info → dissect.target-3.15.dist-info}/RECORD +85 -67
  80. dissect/target/plugins/os/unix/linux/fortigate/_os.py +0 -175
  81. /dissect/target/{plugins/os/unix/linux/fortigate → helpers/compat}/__init__.py +0 -0
  82. {dissect.target-3.14.dev28.dist-info → dissect.target-3.15.dist-info}/COPYRIGHT +0 -0
  83. {dissect.target-3.14.dev28.dist-info → dissect.target-3.15.dist-info}/LICENSE +0 -0
  84. {dissect.target-3.14.dev28.dist-info → dissect.target-3.15.dist-info}/WHEEL +0 -0
  85. {dissect.target-3.14.dev28.dist-info → dissect.target-3.15.dist-info}/entry_points.txt +0 -0
  86. {dissect.target-3.14.dev28.dist-info → dissect.target-3.15.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,155 @@
1
+ from __future__ import annotations
2
+
3
+ import logging
4
+ import re
5
+ import warnings
6
+ from dataclasses import dataclass
7
+ from datetime import datetime
8
+ from typing import Iterator, Optional
9
+
10
+ from dissect.target import Target
11
+ from dissect.target.exceptions import UnsupportedPluginError
12
+ from dissect.target.helpers.record import TargetRecordDescriptor
13
+ from dissect.target.plugin import Plugin, export
14
+
15
+ warnings.simplefilter(action="ignore", category=FutureWarning)
16
+ log = logging.getLogger(__name__)
17
+
18
+ SchedLgURecord = TargetRecordDescriptor(
19
+ "windows/tasks/log/schedlgu",
20
+ [
21
+ ("datetime", "ts"),
22
+ ("string", "job"),
23
+ ("string", "command"),
24
+ ("string", "status"),
25
+ ("uint32", "exit_code"),
26
+ ("string", "version"),
27
+ ],
28
+ )
29
+
30
+ JOB_REGEX_PATTERN = re.compile(r"\"(.*?)\" \((.*?)\)")
31
+ SCHEDLGU_REGEX_PATTERN = re.compile(r"\".+\n.+\n\s{4}.+\n|\".+\n.+", re.MULTILINE)
32
+
33
+
34
+ @dataclass(order=True)
35
+ class SchedLgU:
36
+ ts: datetime = None
37
+ job: str = None
38
+ status: str = None
39
+ command: str = None
40
+ exit_code: int = None
41
+ version: str = None
42
+
43
+ @staticmethod
44
+ def _sanitize_ts(ts: str) -> datetime:
45
+ # sometimes "at" exists before the timestamp
46
+ ts = ts.strip("at ")
47
+ try:
48
+ ts = datetime.strptime(ts, "%m/%d/%Y %I:%M:%S %p")
49
+ except ValueError:
50
+ ts = datetime.strptime(ts, "%d-%m-%Y %H:%M:%S")
51
+
52
+ return ts
53
+
54
+ @staticmethod
55
+ def _parse_job(line: str) -> tuple[str, Optional[str]]:
56
+ matches = JOB_REGEX_PATTERN.match(line)
57
+ if matches:
58
+ return matches.groups()
59
+
60
+ log.warning("SchedLgU failed to parse job and command from line: '%s'. Returning line.", line)
61
+ return line, None
62
+
63
+ @classmethod
64
+ def from_line(cls, line: str) -> SchedLgU:
65
+ """Parse a group of SchedLgU.txt lines."""
66
+ event = cls()
67
+ lines = line.splitlines()
68
+
69
+ # Events can have 2 or 3 lines as a group in total. An example of a complete task job event is:
70
+ # "Symantec NetDetect.job" (NDETECT.EXE)
71
+ # Finished 14-9-2003 13:21:01
72
+ # Result: The task completed with an exit code of (65).
73
+ if len(lines) == 3:
74
+ event.job, event.command = cls._parse_job(lines[0])
75
+ event.status, event.ts = lines[1].split(maxsplit=1)
76
+ event.exit_code = int(lines[2].split("(")[1].rstrip(")."))
77
+
78
+ # Events that have 2 lines as a group can be started task job event or the Task Scheduler Service. Examples:
79
+ # "Symantec NetDetect.job" (NDETECT.EXE)
80
+ # Started at 14-9-2003 13:26:00
81
+ elif len(lines) == 2 and ".job" in lines[0]:
82
+ event.job, event.command = cls._parse_job(lines[0])
83
+ event.status, event.ts = lines[1].split(maxsplit=1)
84
+
85
+ # Events without a task job event are the Task Scheduler Service events. Which can look like this:
86
+ # "Task Scheduler Service"
87
+ # Exited at 14-9-2003 13:40:24
88
+ # OR
89
+ # "Task Scheduler Service"
90
+ # 6.0.6000.16386 (vista_rtm.061101-2205)
91
+ elif len(lines) == 2:
92
+ event.job = lines[0].strip('"')
93
+
94
+ if lines[1].startswith("\t") or lines[1].startswith(" "):
95
+ event.status, event.ts = lines[1].split(maxsplit=1)
96
+ else:
97
+ event.version = lines[1]
98
+
99
+ if event.ts:
100
+ event.ts = cls._sanitize_ts(event.ts)
101
+
102
+ return event
103
+
104
+
105
+ class SchedLgUPlugin(Plugin):
106
+ """Plugin for parsing the Task Scheduler Service transaction log file (SchedLgU.txt)."""
107
+
108
+ PATHS = {
109
+ "sysvol/SchedLgU.txt",
110
+ "sysvol/windows/SchedLgU.txt",
111
+ "sysvol/windows/tasks/SchedLgU.txt",
112
+ "sysvol/winnt/tasks/SchedLgU.txt",
113
+ }
114
+
115
+ def __init__(self, target: Target) -> None:
116
+ self.target = target
117
+ self.paths = [self.target.fs.path(path) for path in self.PATHS if self.target.fs.path(path).exists()]
118
+
119
+ def check_compatible(self) -> None:
120
+ if len(self.paths) == 0:
121
+ raise UnsupportedPluginError("No SchedLgU.txt file found.")
122
+
123
+ @export(record=SchedLgURecord)
124
+ def schedlgu(self) -> Iterator[SchedLgURecord]:
125
+ """Return all events in the Task Scheduler Service transaction log file (SchedLgU.txt).
126
+
127
+ Older Windows systems may log ``.job`` tasks that get started remotely in the SchedLgU.txt file.
128
+ In addition, this log file records when the Task Scheduler service starts and stops.
129
+
130
+ Adversaries may use malicious ``.job`` files to gain persistence on a system.
131
+
132
+ Yield:
133
+ ts (datetime): The timestamp of the event.
134
+ job (str): The name of the ``.job`` file.
135
+ command (str): The command executed.
136
+ status (str): The status of the event (finished, completed, exited, stopped).
137
+ exit_code (int): The exit code of the event.
138
+ version (str): The version of the Task Scheduler service.
139
+ """
140
+
141
+ for path in self.paths:
142
+ content = path.read_text(encoding="UTF-16", errors="surrogateescape")
143
+
144
+ for match in re.findall(SCHEDLGU_REGEX_PATTERN, content):
145
+ event = SchedLgU.from_line(match)
146
+
147
+ yield SchedLgURecord(
148
+ ts=event.ts,
149
+ job=event.job,
150
+ command=event.command,
151
+ status=event.status,
152
+ exit_code=event.exit_code,
153
+ version=event.version,
154
+ _target=self.target,
155
+ )
@@ -11,7 +11,7 @@ class FirewallPlugin(Plugin):
11
11
  """Plugin that parses firewall rules from the registry."""
12
12
 
13
13
  KEY = "HKLM\\SYSTEM\\CurrentControlSet\\Services\\SharedAccess\\Parameters\\FirewallPolicy\\FirewallRules"
14
- FIELD_MAP = {"app": "uri"}
14
+ FIELD_MAP = {"app": "path"}
15
15
  VALUE_MAP = {"active": lambda val: val == "TRUE"}
16
16
 
17
17
  def check_compatible(self) -> None:
@@ -357,6 +357,6 @@ class ShimcachePlugin(Plugin):
357
357
  last_modified=ts,
358
358
  name=name,
359
359
  index=index,
360
- path=self.target.fs.path(self.target.resolve(file_path)),
360
+ path=self.target.resolve(file_path),
361
361
  _target=self.target,
362
362
  )
@@ -73,7 +73,7 @@ class TrustedDocumentsPlugin(Plugin):
73
73
  ts=key.ts,
74
74
  type=value.type,
75
75
  application=application,
76
- document_path=self.target.fs.path(self.target.resolve(value.name)),
76
+ document_path=self.target.resolve(value.name),
77
77
  value=value.value,
78
78
  _key=key,
79
79
  _user=user,
@@ -329,7 +329,7 @@ class RegistryPlugin(Plugin):
329
329
  @internal
330
330
  def get_user_details(self, key: RegistryKey) -> UserDetails:
331
331
  """Return user details for the user who owns a registry hive that contains the provided key"""
332
- if not key.hive or not key.hive.filepath:
332
+ if not key.hive or not getattr(key.hive, "filepath", None):
333
333
  return
334
334
 
335
335
  return self._hives_to_users.get(key.hive)
@@ -252,6 +252,9 @@ def rid_to_key(rid: int) -> tuple[bytes, bytes]:
252
252
 
253
253
 
254
254
  def decrypt_single_hash(rid: int, samkey: bytes, enc_hash: bytes, apwd: bytes) -> bytes:
255
+ if not enc_hash:
256
+ return b""
257
+
255
258
  sh = c_sam.SAM_HASH(enc_hash)
256
259
 
257
260
  if sh.revision not in [0x01, 0x02]:
@@ -1,3 +1,5 @@
1
+ from typing import Iterator, Optional, Union
2
+
1
3
  from dissect.esedb.exceptions import Error
2
4
  from dissect.esedb.tools import sru
3
5
 
@@ -223,6 +225,22 @@ SdpNetworkProviderRecord = TargetRecordDescriptor(
223
225
  ],
224
226
  )
225
227
 
228
+ SRURecord = Union[
229
+ NetworkDataRecord,
230
+ NetworkConnectivityRecord,
231
+ EnergyEstimatorRecord,
232
+ EnergyUsageRecord,
233
+ EnergyUsageLTRecord,
234
+ ApplicationRecord,
235
+ PushNotificationRecord,
236
+ ApplicationTimelineRecord,
237
+ VfuRecord,
238
+ SdpVolumeProviderRecord,
239
+ SdpPhysicalDiskProviderRecord,
240
+ SdpCpuProviderRecord,
241
+ SdpNetworkProviderRecord,
242
+ ]
243
+
226
244
  FIELD_MAPPINGS = {
227
245
  "ActiveAcTime": "active_ac_time",
228
246
  "ActiveDcTime": "active_dc_time",
@@ -322,7 +340,7 @@ FIELD_MAPPINGS = {
322
340
  }
323
341
 
324
342
 
325
- def transform_app_id(value):
343
+ def transform_app_id(value: Optional[Union[bytes, str]]) -> Optional[str]:
326
344
  if value is not None:
327
345
  if isinstance(value, bytes):
328
346
  value = value.decode()
@@ -364,10 +382,11 @@ class SRUPlugin(Plugin):
364
382
  if not self._sru:
365
383
  raise UnsupportedPluginError("No SRUDB found")
366
384
 
367
- def read_records(self, table_name, record_type):
385
+ def read_records(self, table_name: str, record_type: SRURecord) -> Iterator[SRURecord]:
368
386
  table = self._sru.get_table(table_name=table_name)
369
387
  if not table:
370
- raise ValueError(f"Table not found: {table_name}")
388
+ self.target.log.warning("Table not found: %s", table_name)
389
+ return iter(())
371
390
 
372
391
  columns = [c.name for c in table.columns]
373
392
  if columns[:4] != ["AutoIncId", "TimeStamp", "AppId", "UserId"]:
@@ -392,90 +411,84 @@ class SRUPlugin(Plugin):
392
411
  )
393
412
 
394
413
  @export(record=NetworkDataRecord)
395
- def network_data(self):
396
- """
397
- Return the contents of Windows Network Data Usage Monitor table from the SRUDB.dat file.
414
+ def network_data(self) -> Iterator[NetworkDataRecord]:
415
+ """Return the contents of Windows Network Data Usage Monitor table from the SRUDB.dat file.
398
416
 
399
417
  Gives insight into the network usage of the system.
400
418
  """
401
419
  yield from self.read_records("network_data", NetworkDataRecord)
402
420
 
403
421
  @export(record=NetworkConnectivityRecord)
404
- def network_connectivity(self):
405
- """
406
- Return the contents of Windows Network Connectivity Usage Monitor table from the SRUDB.dat file.
422
+ def network_connectivity(self) -> Iterator[NetworkConnectivityRecord]:
423
+ """Return the contents of Windows Network Connectivity Usage Monitor table from the SRUDB.dat file.
407
424
 
408
425
  Gives insight into the network connectivity usage of the system.
409
426
  """
410
427
  yield from self.read_records("network_connectivity", NetworkConnectivityRecord)
411
428
 
412
429
  @export(record=EnergyEstimatorRecord)
413
- def energy_estimator(self):
430
+ def energy_estimator(self) -> Iterator[EnergyEstimatorRecord]:
414
431
  """Return the contents of Energy Estimator table from the SRUDB.dat file."""
415
432
  yield from self.read_records("energy_estimator", EnergyEstimatorRecord)
416
433
 
417
434
  @export(record=EnergyUsageRecord)
418
- def energy_usage(self):
419
- """
420
- Return the contents of Energy Usage Provider table from the SRUDB.dat file.
435
+ def energy_usage(self) -> Iterator[EnergyUsageRecord]:
436
+ """Return the contents of Energy Usage Provider table from the SRUDB.dat file.
421
437
 
422
438
  Gives insight into the energy usage of the system.
423
439
  """
424
440
  yield from self.read_records("energy_usage", EnergyUsageRecord)
425
441
 
426
442
  @export(record=EnergyUsageLTRecord)
427
- def energy_usage_lt(self):
428
- """
429
- Return the contents of Energy Usage Provider Long Term table from the SRUDB.dat file.
443
+ def energy_usage_lt(self) -> Iterator[EnergyUsageLTRecord]:
444
+ """Return the contents of Energy Usage Provider Long Term table from the SRUDB.dat file.
430
445
 
431
446
  Gives insight into the energy usage of the system looking over the long term.
432
447
  """
433
448
  yield from self.read_records("energy_usage_lt", EnergyUsageLTRecord)
434
449
 
435
450
  @export(record=ApplicationRecord)
436
- def application(self):
437
- """
438
- Return the contents of Application Resource Usage table from the SRUDB.dat file.
451
+ def application(self) -> Iterator[ApplicationRecord]:
452
+ """Return the contents of Application Resource Usage table from the SRUDB.dat file.
439
453
 
440
454
  Gives insights into the resource usage of applications on the system.
441
455
  """
442
456
  yield from self.read_records("application", ApplicationRecord)
443
457
 
444
458
  @export(record=PushNotificationRecord)
445
- def push_notification(self):
446
- """
447
- Return the contents of Windows Push Notification Data table from the SRUDB.dat file.
459
+ def push_notification(self) -> Iterator[PushNotificationRecord]:
460
+ """Return the contents of Windows Push Notification Data table from the SRUDB.dat file.
448
461
 
449
462
  Gives insight into the notification usage of the system.
450
463
  """
451
464
  yield from self.read_records("push_notifications", PushNotificationRecord)
452
465
 
453
466
  @export(record=ApplicationTimelineRecord)
454
- def application_timeline(self):
467
+ def application_timeline(self) -> Iterator[ApplicationTimelineRecord]:
455
468
  """Return the contents of App Timeline Provider table from the SRUDB.dat file."""
456
469
  yield from self.read_records("application_timeline", ApplicationTimelineRecord)
457
470
 
458
471
  @export(record=VfuRecord)
459
- def vfu(self):
472
+ def vfu(self) -> Iterator[VfuRecord]:
460
473
  """Return the contents of vfuprov table from the SRUDB.dat file."""
461
474
  yield from self.read_records("vfu", VfuRecord)
462
475
 
463
476
  @export(record=SdpVolumeProviderRecord)
464
- def sdp_volume_provider(self):
477
+ def sdp_volume_provider(self) -> Iterator[SdpVolumeProviderRecord]:
465
478
  """Return the contents of SDP Volume Provider table from the SRUDB.dat file."""
466
479
  yield from self.read_records("sdp_volume_provider", SdpVolumeProviderRecord)
467
480
 
468
481
  @export(record=SdpPhysicalDiskProviderRecord)
469
- def sdp_physical_disk_provider(self):
482
+ def sdp_physical_disk_provider(self) -> Iterator[SdpPhysicalDiskProviderRecord]:
470
483
  """Return the contents of SDP Physical Disk Provider table from the SRUDB.dat file."""
471
484
  yield from self.read_records("sdp_physical_disk_provider", SdpPhysicalDiskProviderRecord)
472
485
 
473
486
  @export(record=SdpCpuProviderRecord)
474
- def sdp_cpu_provider(self):
487
+ def sdp_cpu_provider(self) -> Iterator[SdpCpuProviderRecord]:
475
488
  """Return the contents of SDP CPU Provider table from the SRUDB.dat file."""
476
489
  yield from self.read_records("sdp_cpu_provider", SdpCpuProviderRecord)
477
490
 
478
491
  @export(record=SdpNetworkProviderRecord)
479
- def sdp_network_provider(self):
492
+ def sdp_network_provider(self) -> Iterator[SdpNetworkProviderRecord]:
480
493
  """Return the contents of SDP Network Provider table from the SRUDB.dat file."""
481
494
  yield from self.read_records("sdp_network_provider", SdpNetworkProviderRecord)
@@ -1,16 +1,20 @@
1
+ from __future__ import annotations
2
+
3
+ import logging
1
4
  import warnings
2
5
  from typing import Iterator, Union
3
6
 
4
7
  from flow.record import GroupedRecord
5
8
 
9
+ from dissect.target import Target
6
10
  from dissect.target.exceptions import UnsupportedPluginError
7
11
  from dissect.target.helpers.record import DynamicDescriptor, TargetRecordDescriptor
8
12
  from dissect.target.plugin import Plugin, export
9
13
  from dissect.target.plugins.os.windows.task_helpers.tasks_job import AtTask
10
14
  from dissect.target.plugins.os.windows.task_helpers.tasks_xml import ScheduledTasks
11
- from dissect.target.target import Target
12
15
 
13
16
  warnings.simplefilter(action="ignore", category=FutureWarning)
17
+ log = logging.getLogger(__name__)
14
18
 
15
19
  TaskRecord = TargetRecordDescriptor(
16
20
  "filesystem/windows/task",
@@ -42,7 +46,6 @@ TaskRecord = TargetRecordDescriptor(
42
46
  ("string", "run_level"),
43
47
  ("string", "process_token_sid_type"),
44
48
  ("string", "required_privileges"),
45
- ("boolean", "allow_start_on_demand"),
46
49
  ("string", "restart_on_failure_interval"),
47
50
  ("string", "restart_on_failure_count"),
48
51
  ("string", "mutiple_instances_policy"),
dissect/target/target.py CHANGED
@@ -79,7 +79,7 @@ class Target:
79
79
  self._functions: dict[str, FunctionTuple] = {}
80
80
  self._loader = None
81
81
  self._os = None
82
- self._os_plugin: plugin.OSPlugin = None
82
+ self._os_plugin: type[plugin.OSPlugin] = None
83
83
  self._child_plugins: dict[str, plugin.ChildTargetPlugin] = {}
84
84
  self._cache = dict()
85
85
  self._errors = []
@@ -225,7 +225,10 @@ class Target:
225
225
 
226
226
  loader_cls = loader.find_loader(path, parsed_path=parsed_path)
227
227
  if loader_cls:
228
- loader_instance = loader_cls(path, parsed_path=parsed_path)
228
+ try:
229
+ loader_instance = loader_cls(path, parsed_path=parsed_path)
230
+ except Exception as e:
231
+ raise TargetError(f"Failed to initiate {loader_cls.__name__} for target {path}: {e}", cause=e)
229
232
  return cls._load(path, loader_instance)
230
233
  return cls.open_raw(path)
231
234
 
@@ -281,7 +284,8 @@ class Target:
281
284
  try:
282
285
  ldr = loader_cls(sub_entry, parsed_path=parsed_path)
283
286
  except Exception as e:
284
- getlogger(sub_entry).error("Failed to initiate loader", exc_info=e)
287
+ getlogger(sub_entry).error("Failed to initiate loader: %s", e)
288
+ getlogger(sub_entry).debug("", exc_info=e)
285
289
  continue
286
290
 
287
291
  try:
@@ -9,6 +9,7 @@ import sys
9
9
  from dissect.util.stream import RangeStream
10
10
 
11
11
  from dissect.target import Target
12
+ from dissect.target.exceptions import TargetError
12
13
  from dissect.target.tools.utils import (
13
14
  catch_sigpipe,
14
15
  configure_generic_arguments,
@@ -39,7 +40,12 @@ def main():
39
40
 
40
41
  process_generic_arguments(args)
41
42
 
42
- t = Target.open(args.target)
43
+ try:
44
+ t = Target.open(args.target)
45
+ except TargetError as e:
46
+ log.error(e)
47
+ log.debug("", exc_info=e)
48
+ parser.exit(1)
43
49
 
44
50
  if len(t.disks) > 1:
45
51
  parser.exit("Target has more than one disk")
@@ -10,6 +10,7 @@ import shutil
10
10
  import sys
11
11
 
12
12
  from dissect.target import Target
13
+ from dissect.target.exceptions import TargetError
13
14
  from dissect.target.helpers.fsutil import TargetPath
14
15
  from dissect.target.tools.utils import (
15
16
  catch_sigpipe,
@@ -113,7 +114,13 @@ def main():
113
114
 
114
115
  process_generic_arguments(args)
115
116
 
116
- target = Target.open(args.target)
117
+ try:
118
+ target = Target.open(args.target)
119
+ except TargetError as e:
120
+ log.error(e)
121
+ log.debug("", exc_info=e)
122
+ parser.exit(1)
123
+
117
124
  path = target.fs.path(args.path)
118
125
 
119
126
  if not path.exists():
@@ -8,6 +8,7 @@ from pathlib import Path
8
8
  from typing import Union
9
9
 
10
10
  from dissect.target import Target
11
+ from dissect.target.exceptions import TargetError
11
12
  from dissect.target.helpers.record import TargetRecordDescriptor
12
13
  from dissect.target.tools.query import record_output
13
14
  from dissect.target.tools.utils import (
@@ -72,21 +73,27 @@ def main():
72
73
  targets = targets[:-1]
73
74
  args.targets = targets
74
75
 
75
- for i, target in enumerate(Target.open_all(args.targets)):
76
- try:
77
- if args.jsonlines:
78
- print(json.dumps(get_target_info(target), default=str))
79
- elif args.json:
80
- print(json.dumps(get_target_info(target), indent=4, default=str))
81
- elif args.record:
82
- rs = record_output(args.strings)
83
- rs.write(InfoRecord(**get_target_info(target), _target=target))
84
- else:
85
- if i > 0:
86
- print("-" * 70)
87
- print_target_info(target)
88
- except Exception as e:
89
- target.log.error("Exception in retrieving information for target: `%s`", target, exc_info=e)
76
+ try:
77
+ for i, target in enumerate(Target.open_all(args.targets)):
78
+ try:
79
+ if args.jsonlines:
80
+ print(json.dumps(get_target_info(target), default=str))
81
+ elif args.json:
82
+ print(json.dumps(get_target_info(target), indent=4, default=str))
83
+ elif args.record:
84
+ rs = record_output(args.strings)
85
+ rs.write(InfoRecord(**get_target_info(target), _target=target))
86
+ else:
87
+ if i > 0:
88
+ print("-" * 70)
89
+ print_target_info(target)
90
+ except Exception as e:
91
+ target.log.error("Exception in retrieving information for target: `%s`. Use `-vv` for details.", target)
92
+ target.log.debug("", exc_info=e)
93
+ except TargetError as e:
94
+ log.error(e)
95
+ log.debug("", exc_info=e)
96
+ parser.exit(1)
90
97
 
91
98
 
92
99
  def get_target_info(target: Target) -> dict[str, Union[str, list[str]]]:
@@ -2,7 +2,10 @@ import argparse
2
2
  import logging
3
3
  from typing import Union
4
4
 
5
+ from dissect.util.feature import Feature, feature_enabled
6
+
5
7
  from dissect.target import Target, filesystem
8
+ from dissect.target.exceptions import TargetError
6
9
  from dissect.target.helpers.utils import parse_options_string
7
10
  from dissect.target.tools.utils import (
8
11
  catch_sigpipe,
@@ -10,8 +13,23 @@ from dissect.target.tools.utils import (
10
13
  process_generic_arguments,
11
14
  )
12
15
 
16
+ # Setting logging level to info for startup information.
17
+ logging.basicConfig(level=logging.INFO)
18
+
13
19
  try:
14
- from fuse import FUSE
20
+ if feature_enabled(Feature.BETA):
21
+ from fuse3 import FUSE3 as FUSE
22
+ from fuse3 import util
23
+
24
+ FUSE_VERSION = "3"
25
+ FUSE_LIB_PATH = util.libfuse._name
26
+ else:
27
+ from fuse import FUSE, _libfuse
28
+
29
+ FUSE_VERSION = "2"
30
+ FUSE_LIB_PATH = _libfuse._name
31
+
32
+ logging.info("Using fuse%s library: %s", FUSE_VERSION, FUSE_LIB_PATH)
15
33
 
16
34
  from dissect.target.helpers.mount import DissectMount
17
35
 
@@ -19,6 +37,7 @@ try:
19
37
  except Exception:
20
38
  HAS_FUSE = False
21
39
 
40
+
22
41
  log = logging.getLogger(__name__)
23
42
  logging.lastResort = None
24
43
  logging.raiseExceptions = False
@@ -44,7 +63,13 @@ def main():
44
63
  if not HAS_FUSE:
45
64
  parser.exit("fusepy is not installed: pip install fusepy")
46
65
 
47
- t = Target.open(args.target)
66
+ try:
67
+ t = Target.open(args.target)
68
+ except TargetError as e:
69
+ log.error(e)
70
+ log.debug("", exc_info=e)
71
+ parser.exit(1)
72
+
48
73
  vfs = filesystem.VirtualFilesystem()
49
74
  vfs.mount("fs", t.fs)
50
75
 
@@ -85,7 +110,7 @@ def main():
85
110
 
86
111
 
87
112
  def _format_options(options: dict[str, Union[str, bool]]) -> str:
88
- return ",".join([key if value is True else f"{key}={value}" for key, value in options.items()])
113
+ return ",".join(key if value is True else f"{key}={value}" for key, value in options.items())
89
114
 
90
115
 
91
116
  if __name__ == "__main__":