dissect.target 3.20.dev64__py3-none-any.whl → 3.20.2.dev11__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. dissect/target/helpers/configutil.py +5 -5
  2. dissect/target/helpers/record.py +12 -6
  3. dissect/target/helpers/regutil.py +28 -11
  4. dissect/target/helpers/utils.py +20 -1
  5. dissect/target/loaders/itunes.py +5 -3
  6. dissect/target/plugins/apps/browser/iexplore.py +7 -3
  7. dissect/target/plugins/general/network.py +1 -1
  8. dissect/target/plugins/general/plugins.py +1 -1
  9. dissect/target/plugins/os/unix/_os.py +1 -1
  10. dissect/target/plugins/os/unix/bsd/osx/network.py +2 -1
  11. dissect/target/plugins/os/unix/esxi/_os.py +34 -32
  12. dissect/target/plugins/os/unix/etc/etc.py +12 -6
  13. dissect/target/plugins/os/unix/linux/fortios/_keys.py +7914 -1114
  14. dissect/target/plugins/os/unix/linux/fortios/_os.py +109 -22
  15. dissect/target/plugins/os/unix/linux/network.py +338 -0
  16. dissect/target/plugins/os/unix/linux/network_managers.py +1 -1
  17. dissect/target/plugins/os/unix/log/auth.py +6 -37
  18. dissect/target/plugins/os/unix/log/helpers.py +46 -0
  19. dissect/target/plugins/os/unix/log/messages.py +24 -15
  20. dissect/target/plugins/os/unix/trash.py +13 -2
  21. dissect/target/plugins/os/windows/activitiescache.py +32 -30
  22. dissect/target/plugins/os/windows/catroot.py +14 -5
  23. dissect/target/plugins/os/windows/lnk.py +13 -7
  24. dissect/target/plugins/os/windows/network.py +9 -5
  25. dissect/target/plugins/os/windows/notifications.py +40 -38
  26. dissect/target/plugins/os/windows/regf/cit.py +20 -7
  27. dissect/target/tools/diff.py +990 -0
  28. {dissect.target-3.20.dev64.dist-info → dissect.target-3.20.2.dev11.dist-info}/METADATA +73 -73
  29. {dissect.target-3.20.dev64.dist-info → dissect.target-3.20.2.dev11.dist-info}/RECORD +34 -31
  30. {dissect.target-3.20.dev64.dist-info → dissect.target-3.20.2.dev11.dist-info}/WHEEL +1 -1
  31. {dissect.target-3.20.dev64.dist-info → dissect.target-3.20.2.dev11.dist-info}/entry_points.txt +1 -0
  32. {dissect.target-3.20.dev64.dist-info → dissect.target-3.20.2.dev11.dist-info}/COPYRIGHT +0 -0
  33. {dissect.target-3.20.dev64.dist-info → dissect.target-3.20.2.dev11.dist-info}/LICENSE +0 -0
  34. {dissect.target-3.20.dev64.dist-info → dissect.target-3.20.2.dev11.dist-info}/top_level.txt +0 -0
@@ -470,9 +470,9 @@ class Toml(ConfigurationParser):
470
470
  class Env(ConfigurationParser):
471
471
  """Parses ``.env`` file contents according to Docker and bash specification.
472
472
 
473
- Does not apply interpolation of substituted values, eg. ``foo=${bar}`` and does not attempt
474
- to parse list or dict strings. Does not support dynamic env files, eg. `` foo=`bar` ``. Also
475
- does not support multi-line key/value assignments (yet).
473
+ Does not apply interpolation of substituted values, e.g. ``foo=${bar}`` and does not attempt to parse list or dict
474
+ strings. Does not support dynamic env files, e.g. ``foo=`bar```. Also does not support multi-line key/value
475
+ assignments (yet).
476
476
 
477
477
  Resources:
478
478
  - https://docs.docker.com/compose/environment-variables/variable-interpolation/#env-file-syntax
@@ -891,7 +891,7 @@ KNOWN_FILES: dict[str, type[ConfigurationParser]] = {
891
891
  }
892
892
 
893
893
 
894
- def parse(path: Union[FilesystemEntry, TargetPath], hint: Optional[str] = None, *args, **kwargs) -> ConfigParser:
894
+ def parse(path: Union[FilesystemEntry, TargetPath], hint: Optional[str] = None, *args, **kwargs) -> ConfigurationParser:
895
895
  """Parses the content of an ``path`` or ``entry`` to a dictionary.
896
896
 
897
897
  Args:
@@ -922,7 +922,7 @@ def parse_config(
922
922
  entry: FilesystemEntry,
923
923
  hint: Optional[str] = None,
924
924
  options: Optional[ParserOptions] = None,
925
- ) -> ConfigParser:
925
+ ) -> ConfigurationParser:
926
926
  parser_type = _select_parser(entry, hint)
927
927
 
928
928
  parser = parser_type.create_parser(options)
@@ -145,33 +145,40 @@ EmptyRecord = RecordDescriptor(
145
145
 
146
146
  COMMON_INTERFACE_ELEMENTS = [
147
147
  ("string", "name"),
148
+ ("string[]", "mac"),
148
149
  ("string", "type"),
149
150
  ("boolean", "enabled"),
150
- ("string", "mac"),
151
151
  ("net.ipaddress[]", "dns"),
152
152
  ("net.ipaddress[]", "ip"),
153
153
  ("net.ipaddress[]", "gateway"),
154
+ ("net.ipnetwork[]", "network"),
154
155
  ("string", "source"),
155
156
  ]
156
157
 
157
158
 
158
159
  UnixInterfaceRecord = TargetRecordDescriptor(
159
160
  "unix/network/interface",
160
- COMMON_INTERFACE_ELEMENTS,
161
+ [
162
+ *COMMON_INTERFACE_ELEMENTS,
163
+ ("boolean", "dhcp_ipv4"), # NetworkManager allows for dual-stack configurations.
164
+ ("boolean", "dhcp_ipv6"),
165
+ ("datetime", "last_connected"),
166
+ ("varint[]", "vlan"),
167
+ ("string", "configurator"),
168
+ ],
161
169
  )
162
170
 
163
171
  WindowsInterfaceRecord = TargetRecordDescriptor(
164
172
  "windows/network/interface",
165
173
  [
166
174
  *COMMON_INTERFACE_ELEMENTS,
167
- ("varint", "vlan"),
168
- ("net.ipnetwork[]", "network"),
169
175
  ("varint", "metric"),
170
176
  ("stringlist", "search_domain"),
171
177
  ("datetime", "first_connected"),
172
178
  ("datetime", "last_connected"),
173
179
  ("net.ipaddress[]", "subnetmask"),
174
180
  ("boolean", "dhcp"),
181
+ ("varint", "vlan"),
175
182
  ],
176
183
  )
177
184
 
@@ -179,10 +186,9 @@ MacInterfaceRecord = TargetRecordDescriptor(
179
186
  "macos/network/interface",
180
187
  [
181
188
  *COMMON_INTERFACE_ELEMENTS,
182
- ("varint", "vlan"),
183
- ("net.ipnetwork[]", "network"),
184
189
  ("varint", "interface_service_order"),
185
190
  ("boolean", "dhcp"),
191
+ ("varint", "vlan"),
186
192
  ],
187
193
  )
188
194
 
@@ -12,7 +12,7 @@ from io import BytesIO
12
12
  from pathlib import Path
13
13
  from typing import BinaryIO, Iterator, Optional, TextIO, Union
14
14
 
15
- from dissect.regf import regf
15
+ from dissect.regf import c_regf, regf
16
16
 
17
17
  from dissect.target.exceptions import (
18
18
  RegistryError,
@@ -31,16 +31,33 @@ ValueType = Union[int, str, bytes, list[str]]
31
31
 
32
32
 
33
33
  class RegistryValueType(IntEnum):
34
- NONE = regf.REG_NONE
35
- SZ = regf.REG_SZ
36
- EXPAND_SZ = regf.REG_EXPAND_SZ
37
- BINARY = regf.REG_BINARY
38
- DWORD = regf.REG_DWORD
39
- DWORD_BIG_ENDIAN = regf.REG_DWORD_BIG_ENDIAN
40
- MULTI_SZ = regf.REG_MULTI_SZ
41
- FULL_RESOURCE_DESCRIPTOR = regf.REG_FULL_RESOURCE_DESCRIPTOR
42
- RESOURCE_REQUIREMENTS_LIST = regf.REG_RESOURCE_REQUIREMENTS_LIST
43
- QWORD = regf.REG_QWORD
34
+ """Registry value types as defined in ``winnt.h``.
35
+
36
+ Resources:
37
+ - https://learn.microsoft.com/en-us/windows/win32/sysinfo/registry-value-types
38
+ - https://github.com/fox-it/dissect.regf/blob/main/dissect/regf/c_regf.py
39
+ """
40
+
41
+ NONE = c_regf.REG_NONE
42
+ SZ = c_regf.REG_SZ
43
+ EXPAND_SZ = c_regf.REG_EXPAND_SZ
44
+ BINARY = c_regf.REG_BINARY
45
+ DWORD = c_regf.REG_DWORD
46
+ DWORD_BIG_ENDIAN = c_regf.REG_DWORD_BIG_ENDIAN
47
+ LINK = c_regf.REG_LINK
48
+ MULTI_SZ = c_regf.REG_MULTI_SZ
49
+ RESOURCE_LIST = c_regf.REG_RESOURCE_LIST
50
+ FULL_RESOURCE_DESCRIPTOR = c_regf.REG_FULL_RESOURCE_DESCRIPTOR
51
+ RESOURCE_REQUIREMENTS_LIST = c_regf.REG_RESOURCE_REQUIREMENTS_LIST
52
+ QWORD = c_regf.REG_QWORD
53
+
54
+ @classmethod
55
+ def _missing_(cls, value: int) -> IntEnum:
56
+ # Allow values other than defined members
57
+ member = int.__new__(cls, value)
58
+ member._name_ = None
59
+ member._value_ = value
60
+ return member
44
61
 
45
62
 
46
63
  class RegistryHive:
@@ -1,10 +1,12 @@
1
+ from __future__ import annotations
2
+
1
3
  import logging
2
4
  import re
3
5
  import urllib.parse
4
6
  from datetime import datetime, timezone, tzinfo
5
7
  from enum import Enum
6
8
  from pathlib import Path
7
- from typing import BinaryIO, Callable, Iterator, Optional, Union
9
+ from typing import BinaryIO, Callable, Iterator, Optional, TypeVar, Union
8
10
 
9
11
  from dissect.util.ts import from_unix
10
12
 
@@ -24,6 +26,23 @@ def findall(buf: bytes, needle: bytes) -> Iterator[int]:
24
26
  offset += 1
25
27
 
26
28
 
29
+ T = TypeVar("T")
30
+
31
+
32
+ def to_list(value: T | list[T]) -> list[T]:
33
+ """Convert a single value or a list of values to a list.
34
+
35
+ Args:
36
+ value: The value to convert.
37
+
38
+ Returns:
39
+ A list of values.
40
+ """
41
+ if not isinstance(value, list):
42
+ return [value]
43
+ return value
44
+
45
+
27
46
  class StrEnum(str, Enum):
28
47
  """Sortable and serializible string-based enum"""
29
48
 
@@ -163,8 +163,10 @@ class ITunesBackup:
163
163
 
164
164
  def files(self) -> Iterator[FileInfo]:
165
165
  """Iterate all the files in this backup."""
166
- for row in self.manifest_db.table("Files").rows():
167
- yield FileInfo(self, row.fileID, row.domain, row.relativePath, row.flags, row.file)
166
+
167
+ if table := self.manifest_db.table("Files"):
168
+ for row in table.rows():
169
+ yield FileInfo(self, row.fileID, row.domain, row.relativePath, row.flags, row.file)
168
170
 
169
171
 
170
172
  class FileInfo:
@@ -288,7 +290,7 @@ def translate_file_path(domain: str, relative_path: str) -> str:
288
290
  package_name = ""
289
291
 
290
292
  domain_path = fsutil.join(DOMAIN_TRANSLATION.get(domain, domain), package_name)
291
- return fsutil.join(domain_path, relative_path)
293
+ return fsutil.join(domain_path, relative_path).rstrip("/")
292
294
 
293
295
 
294
296
  def parse_key_bag(buf: bytes) -> tuple[dict[str, bytes, int], dict[str, ClassKey]]:
@@ -36,14 +36,18 @@ class WebCache:
36
36
  All ``ContainerId`` values for the requested container name.
37
37
  """
38
38
  try:
39
- for container_record in self.db.table("Containers").records():
39
+ table = self.db.table("Containers")
40
+
41
+ for container_record in table.records():
40
42
  if record_name := container_record.get("Name"):
41
43
  record_name = record_name.rstrip("\00").lower()
42
44
  if record_name == name.lower():
43
45
  container_id = container_record.get("ContainerId")
44
46
  yield self.db.table(f"Container_{container_id}")
45
- except KeyError:
46
- pass
47
+
48
+ except KeyError as e:
49
+ self.target.log.warning("Exception while parsing EseDB Containers table")
50
+ self.target.log.debug("", exc_info=e)
47
51
 
48
52
  def _iter_records(self, name: str) -> Iterator[record.Record]:
49
53
  """Yield records from a Webcache container.
@@ -79,7 +79,7 @@ class NetworkPlugin(Plugin):
79
79
  @internal
80
80
  def with_mac(self, mac: str) -> Iterator[InterfaceRecord]:
81
81
  for interface in self.interfaces():
82
- if interface.mac == mac:
82
+ if mac in interface.mac:
83
83
  yield interface
84
84
 
85
85
  @internal
@@ -169,7 +169,7 @@ class PluginListPlugin(Plugin):
169
169
 
170
170
 
171
171
  def generate_plugins_json(plugins: list[Plugin]) -> Iterator[dict]:
172
- """Generates JSON output of a list of :class:`Plugin`s."""
172
+ """Generates JSON output of a list of :class:`Plugin`."""
173
173
 
174
174
  for p in plugins:
175
175
  func = getattr(p.class_object, p.method_name)
@@ -182,7 +182,7 @@ class UnixPlugin(OSPlugin):
182
182
  paths (list): list of paths
183
183
  """
184
184
  redhat_legacy_path = "/etc/sysconfig/network"
185
- paths = paths or ["/etc/hostname", "/etc/HOSTNAME", redhat_legacy_path]
185
+ paths = paths or ["/etc/hostname", "/etc/HOSTNAME", "/proc/sys/kernel/hostname", redhat_legacy_path]
186
186
  hostname_dict = {"hostname": None, "domain": None}
187
187
 
188
188
  for path in paths:
@@ -84,9 +84,10 @@ class MacNetworkPlugin(NetworkPlugin):
84
84
  network=network,
85
85
  interface_service_order=interface_service_order,
86
86
  dhcp=dhcp,
87
+ mac=[],
87
88
  _target=self.target,
88
89
  )
89
90
 
90
91
  except Exception as e:
91
- self.target.log.warning("Error reading configuration for network device %s: %s", name, e)
92
+ self.target.log.warning("Error reading configuration for network device %s", name, exc_info=e)
92
93
  continue
@@ -472,37 +472,39 @@ def parse_config_store(fh: BinaryIO) -> dict[str, Any]:
472
472
  db = sqlite3.SQLite3(fh)
473
473
 
474
474
  store = {}
475
- for row in db.table("Config").rows():
476
- component_name = row.Component
477
- config_group_name = row.ConfigGroup
478
- value_group_name = row.Name
479
- identifier_name = row.Identifier
480
-
481
- if component_name not in store:
482
- store[component_name] = {}
483
- component = store[component_name]
484
-
485
- if config_group_name not in component:
486
- component[config_group_name] = {}
487
- config_group = component[config_group_name]
488
-
489
- if value_group_name not in config_group:
490
- config_group[value_group_name] = {}
491
- value_group = config_group[value_group_name]
492
-
493
- if identifier_name not in value_group:
494
- value_group[identifier_name] = {}
495
- identifier = value_group[identifier_name]
496
-
497
- identifier["modified_time"] = row.ModifiedTime
498
- identifier["creation_time"] = row.CreationTime
499
- identifier["version"] = row.Version
500
- identifier["success"] = row.Success
501
- identifier["auto_conf_value"] = json.loads(row.AutoConfValue) if row.AutoConfValue else None
502
- identifier["user_value"] = json.loads(row.UserValue) if row.UserValue else None
503
- identifier["vital_value"] = json.loads(row.VitalValue) if row.VitalValue else None
504
- identifier["cached_value"] = json.loads(row.CachedValue) if row.CachedValue else None
505
- identifier["desired_value"] = json.loads(row.DesiredValue) if row.DesiredValue else None
506
- identifier["revision"] = row.Revision
475
+
476
+ if table := db.table("Config"):
477
+ for row in table.rows():
478
+ component_name = row.Component
479
+ config_group_name = row.ConfigGroup
480
+ value_group_name = row.Name
481
+ identifier_name = row.Identifier
482
+
483
+ if component_name not in store:
484
+ store[component_name] = {}
485
+ component = store[component_name]
486
+
487
+ if config_group_name not in component:
488
+ component[config_group_name] = {}
489
+ config_group = component[config_group_name]
490
+
491
+ if value_group_name not in config_group:
492
+ config_group[value_group_name] = {}
493
+ value_group = config_group[value_group_name]
494
+
495
+ if identifier_name not in value_group:
496
+ value_group[identifier_name] = {}
497
+ identifier = value_group[identifier_name]
498
+
499
+ identifier["modified_time"] = row.ModifiedTime
500
+ identifier["creation_time"] = row.CreationTime
501
+ identifier["version"] = row.Version
502
+ identifier["success"] = row.Success
503
+ identifier["auto_conf_value"] = json.loads(row.AutoConfValue) if row.AutoConfValue else None
504
+ identifier["user_value"] = json.loads(row.UserValue) if row.UserValue else None
505
+ identifier["vital_value"] = json.loads(row.VitalValue) if row.VitalValue else None
506
+ identifier["cached_value"] = json.loads(row.CachedValue) if row.CachedValue else None
507
+ identifier["desired_value"] = json.loads(row.DesiredValue) if row.DesiredValue else None
508
+ identifier["revision"] = row.Revision
507
509
 
508
510
  return store
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  import fnmatch
2
4
  import re
3
5
  from pathlib import Path
@@ -30,9 +32,10 @@ class EtcTree(ConfigurationTreePlugin):
30
32
  def __init__(self, target: Target):
31
33
  super().__init__(target, "/etc")
32
34
 
33
- def _sub(self, items: ConfigurationEntry, entry: Path, pattern: str) -> Iterator[UnixConfigTreeRecord]:
35
+ def _sub(
36
+ self, items: ConfigurationEntry | dict, entry: Path, orig_path: Path, pattern: str
37
+ ) -> Iterator[UnixConfigTreeRecord]:
34
38
  index = 0
35
- config_entry = items
36
39
  if not isinstance(items, dict):
37
40
  items = items.as_dict()
38
41
 
@@ -41,7 +44,7 @@ class EtcTree(ConfigurationTreePlugin):
41
44
  path = Path(entry) / Path(key)
42
45
 
43
46
  if isinstance(value, dict):
44
- yield from self._sub(value, path, pattern)
47
+ yield from self._sub(value, path, orig_path, pattern)
45
48
  continue
46
49
 
47
50
  if not isinstance(value, list):
@@ -50,7 +53,7 @@ class EtcTree(ConfigurationTreePlugin):
50
53
  if fnmatch.fnmatch(path, pattern):
51
54
  data = {
52
55
  "_target": self.target,
53
- "source": self.target.fs.path(config_entry.entry.path),
56
+ "source": self.target.fs.path(orig_path),
54
57
  "path": path,
55
58
  "key": key,
56
59
  "value": value,
@@ -71,8 +74,11 @@ class EtcTree(ConfigurationTreePlugin):
71
74
  for entry, subs, items in self.config_fs.walk(root):
72
75
  for item in items:
73
76
  try:
74
- config_object = self.get(str(Path(entry) / Path(item)))
75
- yield from self._sub(config_object, Path(entry) / Path(item), pattern)
77
+ path = Path(entry) / item
78
+ config_object = self.get(str(path))
79
+
80
+ if isinstance(config_object, ConfigurationEntry):
81
+ yield from self._sub(config_object, path, orig_path=path, pattern=pattern)
76
82
  except Exception:
77
83
  self.target.log.warning("Could not open configuration item: %s", item)
78
84
  pass