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

Sign up to get free protection for your applications and to get access to all the features.
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