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.
- dissect/target/helpers/configutil.py +5 -5
- dissect/target/helpers/record.py +12 -6
- dissect/target/helpers/regutil.py +28 -11
- dissect/target/helpers/utils.py +20 -1
- dissect/target/loaders/itunes.py +5 -3
- dissect/target/plugins/apps/browser/iexplore.py +7 -3
- dissect/target/plugins/general/network.py +1 -1
- dissect/target/plugins/general/plugins.py +1 -1
- dissect/target/plugins/os/unix/_os.py +1 -1
- dissect/target/plugins/os/unix/bsd/osx/network.py +2 -1
- dissect/target/plugins/os/unix/esxi/_os.py +34 -32
- dissect/target/plugins/os/unix/etc/etc.py +12 -6
- dissect/target/plugins/os/unix/linux/fortios/_keys.py +7914 -1114
- dissect/target/plugins/os/unix/linux/fortios/_os.py +109 -22
- dissect/target/plugins/os/unix/linux/network.py +338 -0
- dissect/target/plugins/os/unix/linux/network_managers.py +1 -1
- dissect/target/plugins/os/unix/log/auth.py +6 -37
- dissect/target/plugins/os/unix/log/helpers.py +46 -0
- dissect/target/plugins/os/unix/log/messages.py +24 -15
- dissect/target/plugins/os/unix/trash.py +13 -2
- dissect/target/plugins/os/windows/activitiescache.py +32 -30
- dissect/target/plugins/os/windows/catroot.py +14 -5
- dissect/target/plugins/os/windows/lnk.py +13 -7
- dissect/target/plugins/os/windows/network.py +9 -5
- dissect/target/plugins/os/windows/notifications.py +40 -38
- dissect/target/plugins/os/windows/regf/cit.py +20 -7
- dissect/target/tools/diff.py +990 -0
- {dissect.target-3.20.dev64.dist-info → dissect.target-3.20.2.dev11.dist-info}/METADATA +73 -73
- {dissect.target-3.20.dev64.dist-info → dissect.target-3.20.2.dev11.dist-info}/RECORD +34 -31
- {dissect.target-3.20.dev64.dist-info → dissect.target-3.20.2.dev11.dist-info}/WHEEL +1 -1
- {dissect.target-3.20.dev64.dist-info → dissect.target-3.20.2.dev11.dist-info}/entry_points.txt +1 -0
- {dissect.target-3.20.dev64.dist-info → dissect.target-3.20.2.dev11.dist-info}/COPYRIGHT +0 -0
- {dissect.target-3.20.dev64.dist-info → dissect.target-3.20.2.dev11.dist-info}/LICENSE +0 -0
- {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,
|
474
|
-
|
475
|
-
|
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) ->
|
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
|
-
) ->
|
925
|
+
) -> ConfigurationParser:
|
926
926
|
parser_type = _select_parser(entry, hint)
|
927
927
|
|
928
928
|
parser = parser_type.create_parser(options)
|
dissect/target/helpers/record.py
CHANGED
@@ -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
|
-
|
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
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
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:
|
dissect/target/helpers/utils.py
CHANGED
@@ -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
|
|
dissect/target/loaders/itunes.py
CHANGED
@@ -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
|
-
|
167
|
-
|
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
|
-
|
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
|
-
|
46
|
-
|
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.
|
@@ -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
|
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
|
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
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
|
497
|
-
|
498
|
-
|
499
|
-
|
500
|
-
|
501
|
-
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
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(
|
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(
|
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
|
-
|
75
|
-
|
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
|