dissect.target 3.16.dev44__py3-none-any.whl → 3.17__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- dissect/target/container.py +1 -0
- dissect/target/containers/fortifw.py +190 -0
- dissect/target/filesystem.py +192 -67
- dissect/target/filesystems/dir.py +14 -1
- dissect/target/filesystems/overlay.py +103 -0
- dissect/target/helpers/compat/path_common.py +19 -5
- dissect/target/helpers/configutil.py +30 -7
- dissect/target/helpers/network_managers.py +101 -73
- dissect/target/helpers/record_modifier.py +4 -1
- dissect/target/loader.py +3 -1
- dissect/target/loaders/dir.py +23 -5
- dissect/target/loaders/itunes.py +3 -3
- dissect/target/loaders/mqtt.py +309 -0
- dissect/target/loaders/overlay.py +31 -0
- dissect/target/loaders/target.py +12 -9
- dissect/target/loaders/vb.py +2 -2
- dissect/target/loaders/velociraptor.py +5 -4
- dissect/target/plugin.py +1 -1
- dissect/target/plugins/apps/browser/brave.py +10 -0
- dissect/target/plugins/apps/browser/browser.py +43 -0
- dissect/target/plugins/apps/browser/chrome.py +10 -0
- dissect/target/plugins/apps/browser/chromium.py +234 -12
- dissect/target/plugins/apps/browser/edge.py +10 -0
- dissect/target/plugins/apps/browser/firefox.py +512 -19
- dissect/target/plugins/apps/browser/iexplore.py +2 -2
- dissect/target/plugins/apps/container/docker.py +24 -4
- dissect/target/plugins/apps/ssh/openssh.py +4 -0
- dissect/target/plugins/apps/ssh/putty.py +45 -14
- dissect/target/plugins/apps/ssh/ssh.py +40 -0
- dissect/target/plugins/apps/vpn/openvpn.py +115 -93
- dissect/target/plugins/child/docker.py +24 -0
- dissect/target/plugins/filesystem/ntfs/mft.py +1 -1
- dissect/target/plugins/filesystem/walkfs.py +2 -2
- dissect/target/plugins/general/users.py +6 -0
- dissect/target/plugins/os/unix/bsd/__init__.py +0 -0
- dissect/target/plugins/os/unix/esxi/_os.py +2 -2
- dissect/target/plugins/os/unix/linux/debian/vyos/_os.py +1 -1
- dissect/target/plugins/os/unix/linux/fortios/_os.py +9 -9
- dissect/target/plugins/os/unix/linux/services.py +1 -0
- dissect/target/plugins/os/unix/linux/sockets.py +2 -2
- dissect/target/plugins/os/unix/log/messages.py +53 -8
- dissect/target/plugins/os/windows/_os.py +10 -1
- dissect/target/plugins/os/windows/catroot.py +178 -63
- dissect/target/plugins/os/windows/credhist.py +210 -0
- dissect/target/plugins/os/windows/dpapi/crypto.py +12 -1
- dissect/target/plugins/os/windows/dpapi/dpapi.py +62 -7
- dissect/target/plugins/os/windows/dpapi/master_key.py +22 -2
- dissect/target/plugins/os/windows/regf/runkeys.py +6 -4
- dissect/target/plugins/os/windows/sam.py +10 -1
- dissect/target/target.py +1 -1
- dissect/target/tools/dump/run.py +23 -28
- dissect/target/tools/dump/state.py +11 -8
- dissect/target/tools/dump/utils.py +5 -4
- dissect/target/tools/query.py +3 -15
- dissect/target/tools/shell.py +48 -8
- dissect/target/tools/utils.py +23 -0
- {dissect.target-3.16.dev44.dist-info → dissect.target-3.17.dist-info}/METADATA +7 -3
- {dissect.target-3.16.dev44.dist-info → dissect.target-3.17.dist-info}/RECORD +63 -56
- {dissect.target-3.16.dev44.dist-info → dissect.target-3.17.dist-info}/WHEEL +1 -1
- {dissect.target-3.16.dev44.dist-info → dissect.target-3.17.dist-info}/COPYRIGHT +0 -0
- {dissect.target-3.16.dev44.dist-info → dissect.target-3.17.dist-info}/LICENSE +0 -0
- {dissect.target-3.16.dev44.dist-info → dissect.target-3.17.dist-info}/entry_points.txt +0 -0
- {dissect.target-3.16.dev44.dist-info → dissect.target-3.17.dist-info}/top_level.txt +0 -0
@@ -3,6 +3,7 @@ from __future__ import annotations
|
|
3
3
|
import io
|
4
4
|
import posixpath
|
5
5
|
import stat
|
6
|
+
import sys
|
6
7
|
from typing import IO, TYPE_CHECKING, Iterator, Literal, Optional
|
7
8
|
|
8
9
|
if TYPE_CHECKING:
|
@@ -27,11 +28,24 @@ try:
|
|
27
28
|
self._fs = path._fs
|
28
29
|
self._flavour = path._flavour
|
29
30
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
31
|
+
if sys.version_info >= (3, 10):
|
32
|
+
|
33
|
+
def __getitem__(self, idx: int) -> TargetPath:
|
34
|
+
result = super().__getitem__(idx)
|
35
|
+
result._fs = self._fs
|
36
|
+
result._flavour = self._flavour
|
37
|
+
return result
|
38
|
+
|
39
|
+
else:
|
40
|
+
|
41
|
+
def __getitem__(self, idx: int) -> TargetPath:
|
42
|
+
if idx < 0:
|
43
|
+
idx = len(self) + idx
|
44
|
+
|
45
|
+
result = super().__getitem__(idx)
|
46
|
+
result._fs = self._fs
|
47
|
+
result._flavour = self._flavour
|
48
|
+
return result
|
35
49
|
|
36
50
|
except ImportError:
|
37
51
|
pass
|
@@ -3,6 +3,7 @@ from __future__ import annotations
|
|
3
3
|
import io
|
4
4
|
import json
|
5
5
|
import re
|
6
|
+
import sys
|
6
7
|
from collections import deque
|
7
8
|
from configparser import ConfigParser, MissingSectionHeaderError
|
8
9
|
from dataclasses import dataclass
|
@@ -28,11 +29,22 @@ from dissect.target.filesystem import FilesystemEntry
|
|
28
29
|
from dissect.target.helpers.fsutil import TargetPath
|
29
30
|
|
30
31
|
try:
|
31
|
-
import
|
32
|
+
from ruamel.yaml import YAML
|
32
33
|
|
33
|
-
|
34
|
-
except
|
35
|
-
|
34
|
+
HAS_YAML = True
|
35
|
+
except ImportError:
|
36
|
+
HAS_YAML = False
|
37
|
+
|
38
|
+
try:
|
39
|
+
if sys.version_info < (3, 11):
|
40
|
+
import tomli as toml
|
41
|
+
else:
|
42
|
+
# tomllib is included since python 3.11
|
43
|
+
import tomllib as toml # novermin
|
44
|
+
|
45
|
+
HAS_TOML = True
|
46
|
+
except ImportError:
|
47
|
+
HAS_TOML = False
|
36
48
|
|
37
49
|
|
38
50
|
def _update_dictionary(current: dict[str, Any], key: str, value: Any) -> None:
|
@@ -401,11 +413,21 @@ class Yaml(ConfigurationParser):
|
|
401
413
|
"""Parses a Yaml file."""
|
402
414
|
|
403
415
|
def parse_file(self, fh: TextIO) -> None:
|
404
|
-
if
|
405
|
-
parsed_data =
|
416
|
+
if HAS_YAML:
|
417
|
+
parsed_data = YAML(typ="safe").load(fh)
|
406
418
|
self.parsed_data = ListUnwrapper.unwrap(parsed_data)
|
407
419
|
else:
|
408
|
-
raise ConfigurationParsingError("Failed to parse file, please install
|
420
|
+
raise ConfigurationParsingError("Failed to parse file, please install ruamel.yaml.")
|
421
|
+
|
422
|
+
|
423
|
+
class Toml(ConfigurationParser):
|
424
|
+
"""Parses a Toml file."""
|
425
|
+
|
426
|
+
def parse_file(self, fh: TextIO) -> None:
|
427
|
+
if HAS_TOML:
|
428
|
+
self.parsed_data = toml.loads(fh.read())
|
429
|
+
else:
|
430
|
+
raise ConfigurationParsingError("Failed to parse file, please install tomli.")
|
409
431
|
|
410
432
|
|
411
433
|
class ScopeManager:
|
@@ -696,6 +718,7 @@ CONFIG_MAP: dict[tuple[str, ...], ParserConfig] = {
|
|
696
718
|
"sample": ParserConfig(Txt),
|
697
719
|
"systemd": ParserConfig(SystemD),
|
698
720
|
"template": ParserConfig(Txt),
|
721
|
+
"toml": ParserConfig(Toml),
|
699
722
|
}
|
700
723
|
|
701
724
|
KNOWN_FILES: dict[str, type[ConfigurationParser]] = {
|
@@ -1,9 +1,13 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
import logging
|
1
4
|
import re
|
2
5
|
from collections import defaultdict
|
3
6
|
from configparser import ConfigParser, MissingSectionHeaderError
|
4
7
|
from io import StringIO
|
8
|
+
from itertools import chain
|
5
9
|
from re import compile, sub
|
6
|
-
from typing import Any, Callable, Iterable, Match, Optional
|
10
|
+
from typing import Any, Callable, Iterable, Match, Optional
|
7
11
|
|
8
12
|
from defusedxml import ElementTree
|
9
13
|
|
@@ -11,12 +15,14 @@ from dissect.target.exceptions import PluginError
|
|
11
15
|
from dissect.target.helpers.fsutil import TargetPath
|
12
16
|
from dissect.target.target import Target
|
13
17
|
|
18
|
+
log = logging.getLogger(__name__)
|
19
|
+
|
14
20
|
try:
|
15
|
-
import
|
21
|
+
from ruamel.yaml import YAML
|
16
22
|
|
17
|
-
|
23
|
+
HAS_YAML = True
|
18
24
|
except ImportError:
|
19
|
-
|
25
|
+
HAS_YAML = False
|
20
26
|
|
21
27
|
IGNORED_IPS = [
|
22
28
|
"0.0.0.0",
|
@@ -49,7 +55,7 @@ class Template:
|
|
49
55
|
"""Sets the name of the the used parsing template to the name of the discovered network manager."""
|
50
56
|
self.name = name
|
51
57
|
|
52
|
-
def create_config(self, path: TargetPath) ->
|
58
|
+
def create_config(self, path: TargetPath) -> Optional[dict]:
|
53
59
|
"""Create a network config dictionary based on the configured template and supplied path.
|
54
60
|
|
55
61
|
Args:
|
@@ -60,7 +66,7 @@ class Template:
|
|
60
66
|
"""
|
61
67
|
|
62
68
|
if not path.exists() or path.is_dir():
|
63
|
-
|
69
|
+
log.debug("Failed to get config file %s", path)
|
64
70
|
config = None
|
65
71
|
|
66
72
|
if self.name == "netplan":
|
@@ -73,26 +79,26 @@ class Template:
|
|
73
79
|
config = self._parse_configparser_config(path)
|
74
80
|
return config
|
75
81
|
|
76
|
-
def _parse_netplan_config(self,
|
82
|
+
def _parse_netplan_config(self, path: TargetPath) -> Optional[dict]:
|
77
83
|
"""Internal function to parse a netplan YAML based configuration file into a dict.
|
78
84
|
|
79
85
|
Args:
|
80
|
-
fh: A
|
86
|
+
fh: A path to the configuration file to be parsed.
|
81
87
|
|
82
88
|
Returns:
|
83
89
|
Dictionary containing the parsed YAML based configuration file.
|
84
90
|
"""
|
85
|
-
if
|
86
|
-
return self.parser(
|
91
|
+
if HAS_YAML:
|
92
|
+
return self.parser(path.open("rb"))
|
87
93
|
else:
|
88
|
-
|
94
|
+
log.error("Failed to parse %s. Cannot import ruamel.yaml", self.name)
|
89
95
|
return None
|
90
96
|
|
91
|
-
def _parse_wicked_config(self,
|
97
|
+
def _parse_wicked_config(self, path: TargetPath) -> dict:
|
92
98
|
"""Internal function to parse a wicked XML based configuration file into a dict.
|
93
99
|
|
94
100
|
Args:
|
95
|
-
fh: A
|
101
|
+
fh: A path to the configuration file to be parsed.
|
96
102
|
|
97
103
|
Returns:
|
98
104
|
Dictionary containing the parsed xml based Linux network manager based configuration file.
|
@@ -101,44 +107,43 @@ class Template:
|
|
101
107
|
# we have to replace the ":" for this with "___" (three underscores) to make the xml config non-namespaced.
|
102
108
|
pattern = compile(r"(?<=\n)\s+(<.+?>)")
|
103
109
|
replace_match: Callable[[Match]] = lambda match: match.group(1).replace(":", "___")
|
104
|
-
text = sub(pattern, replace_match,
|
110
|
+
text = sub(pattern, replace_match, path.open("rt").read())
|
105
111
|
|
106
112
|
xml = self.parser.parse(StringIO(text))
|
107
113
|
return self._parse_xml_config(xml, self.sections, self.options)
|
108
114
|
|
109
|
-
def _parse_configparser_config(self,
|
115
|
+
def _parse_configparser_config(self, path: TargetPath) -> dict:
|
110
116
|
"""Internal function to parse ConfigParser compatible configuration files into a dict.
|
111
117
|
|
112
118
|
Args:
|
113
|
-
|
119
|
+
path: A path to the configuration file to be parsed.
|
114
120
|
|
115
121
|
Returns:
|
116
122
|
Dictionary containing the parsed ConfigParser compatible configuration file.
|
117
123
|
"""
|
118
124
|
try:
|
119
|
-
self.parser.read_string(
|
125
|
+
self.parser.read_string(path.open("rt").read(), path.name)
|
120
126
|
return self.parser._sections
|
121
127
|
except MissingSectionHeaderError:
|
122
128
|
# configparser does like config files without headers, so we inject a header to make it work.
|
123
|
-
self.parser.read_string(f"[{self.name}]\n" +
|
129
|
+
self.parser.read_string(f"[{self.name}]\n" + path.open("rt").read(), path.name)
|
124
130
|
return self.parser._sections
|
125
131
|
|
126
|
-
def _parse_text_config(self, comments: str, delim: str,
|
132
|
+
def _parse_text_config(self, comments: str, delim: str, path: TargetPath) -> dict:
|
127
133
|
"""Internal function to parse a basic plain text based configuration file into a dict.
|
128
134
|
|
129
135
|
Args:
|
130
136
|
comments: A string value defining the comment style of the configuration file.
|
131
137
|
delim: A string value defining the delimiters used in the configuration file.
|
132
|
-
|
138
|
+
path: A path to the configuration file to be parsed.
|
133
139
|
|
134
140
|
Returns:
|
135
141
|
Dictionary with a parsed plain text based Linux network manager configuration file.
|
136
142
|
"""
|
137
143
|
config = defaultdict(dict)
|
138
144
|
option_dict = {}
|
139
|
-
fh = fh.open("rt")
|
140
145
|
|
141
|
-
for line in
|
146
|
+
for line in path.open("rt"):
|
142
147
|
line = line.strip()
|
143
148
|
|
144
149
|
if not line or line.startswith(comments):
|
@@ -279,7 +284,7 @@ class Parser:
|
|
279
284
|
if option in translation_values and value:
|
280
285
|
return translation_key
|
281
286
|
|
282
|
-
def _get_option(self, config: dict, option: str, section: Optional[str] = None) ->
|
287
|
+
def _get_option(self, config: dict, option: str, section: Optional[str] = None) -> Optional[str | Callable]:
|
283
288
|
"""Internal function to get arbitrary options values from a parsed (non-translated) dictionary.
|
284
289
|
|
285
290
|
Args:
|
@@ -290,8 +295,14 @@ class Parser:
|
|
290
295
|
Returns:
|
291
296
|
Value(s) corrensponding to that network configuration option.
|
292
297
|
"""
|
298
|
+
if not config:
|
299
|
+
log.error("Cannot get option %s: No config to parse", option)
|
300
|
+
return
|
301
|
+
|
293
302
|
if section:
|
294
|
-
|
303
|
+
# account for values of sections which are None
|
304
|
+
config = config.get(section, {}) or {}
|
305
|
+
|
295
306
|
for key, value in config.items():
|
296
307
|
if key == option:
|
297
308
|
return value
|
@@ -365,7 +376,7 @@ class NetworkManager:
|
|
365
376
|
if self.registered:
|
366
377
|
self.config = self.parser.parse()
|
367
378
|
else:
|
368
|
-
|
379
|
+
log.error("Network manager %s is not registered. Cannot parse config.", self.name)
|
369
380
|
|
370
381
|
@property
|
371
382
|
def interface(self) -> set:
|
@@ -499,7 +510,7 @@ class LinuxNetworkManager:
|
|
499
510
|
|
500
511
|
|
501
512
|
def parse_unix_dhcp_log_messages(target) -> list[str]:
|
502
|
-
"""Parse local syslog and cloud init
|
513
|
+
"""Parse local syslog, journal and cloud init-log files for DHCP lease IPs.
|
503
514
|
|
504
515
|
Args:
|
505
516
|
target: Target to discover and obtain network information from.
|
@@ -507,53 +518,68 @@ def parse_unix_dhcp_log_messages(target) -> list[str]:
|
|
507
518
|
Returns:
|
508
519
|
List of DHCP ip addresses.
|
509
520
|
"""
|
510
|
-
ips =
|
511
|
-
|
512
|
-
# Search through parsed syslogs for DHCP leases.
|
513
|
-
try:
|
514
|
-
messages = target.messages()
|
515
|
-
for record in messages:
|
516
|
-
line = record.message
|
517
|
-
|
518
|
-
# Ubuntu DHCP
|
519
|
-
if ("DHCPv4" in line or "DHCPv6" in line) and " address " in line and " via " in line:
|
520
|
-
ip = line.split(" address ")[1].split(" via ")[0].strip().split("/")[0]
|
521
|
-
if ip not in ips:
|
522
|
-
ips.append(ip)
|
523
|
-
|
524
|
-
# Ubuntu DHCP NetworkManager
|
525
|
-
elif "option ip_address" in line and ("dhcp4" in line or "dhcp6" in line) and "=> '" in line:
|
526
|
-
ip = line.split("=> '")[1].replace("'", "").strip()
|
527
|
-
if ip not in ips:
|
528
|
-
ips.append(ip)
|
529
|
-
|
530
|
-
# Debian and CentOS dhclient
|
531
|
-
elif record.daemon == "dhclient" and "bound to" in line:
|
532
|
-
ip = line.split("bound to")[1].split(" ")[1].strip()
|
533
|
-
if ip not in ips:
|
534
|
-
ips.append(ip)
|
535
|
-
|
536
|
-
# CentOS DHCP and general NetworkManager
|
537
|
-
elif " address " in line and ("dhcp4" in line or "dhcp6" in line):
|
538
|
-
ip = line.split(" address ")[1].strip()
|
539
|
-
if ip not in ips:
|
540
|
-
ips.append(ip)
|
541
|
-
|
542
|
-
except PluginError:
|
543
|
-
target.log.debug("Can not search for DHCP leases in syslog files as they does not exist.")
|
544
|
-
|
545
|
-
# A unix system might be provisioned using Ubuntu's cloud-init (https://cloud-init.io/).
|
546
|
-
if (path := target.fs.path("/var/log/cloud-init.log")).exists():
|
547
|
-
for line in path.open("rt"):
|
548
|
-
# We are interested in the following log line:
|
549
|
-
# YYYY-MM-DD HH:MM:SS,000 - dhcp.py[DEBUG]: Received dhcp lease on IFACE for IP/MASK
|
550
|
-
if "Received dhcp lease on" in line:
|
551
|
-
interface, ip, netmask = re.search(r"Received dhcp lease on (\w{0,}) for (\S+)\/(\S+)", line).groups()
|
552
|
-
if ip not in ips:
|
553
|
-
ips.append(ip)
|
521
|
+
ips = set()
|
522
|
+
messages = set()
|
554
523
|
|
555
|
-
|
556
|
-
|
524
|
+
for log_func in ["messages", "journal"]:
|
525
|
+
try:
|
526
|
+
messages = chain(messages, getattr(target, log_func)())
|
527
|
+
except PluginError:
|
528
|
+
target.log.debug(f"Could not search for DHCP leases in {log_func} files.")
|
529
|
+
|
530
|
+
if not messages:
|
531
|
+
target.log.warning(f"Could not search for DHCP leases using {log_func}: No log entries found.")
|
532
|
+
|
533
|
+
for record in messages:
|
534
|
+
line = record.message
|
535
|
+
|
536
|
+
# Ubuntu cloud-init
|
537
|
+
if "Received dhcp lease on" in line:
|
538
|
+
interface, ip, netmask = re.search(r"Received dhcp lease on (\w{0,}) for (\S+)\/(\S+)", line).groups()
|
539
|
+
ips.add(ip)
|
540
|
+
continue
|
541
|
+
|
542
|
+
# Ubuntu DHCP
|
543
|
+
if ("DHCPv4" in line or "DHCPv6" in line) and " address " in line and " via " in line:
|
544
|
+
ip = line.split(" address ")[1].split(" via ")[0].strip().split("/")[0]
|
545
|
+
ips.add(ip)
|
546
|
+
continue
|
547
|
+
|
548
|
+
# Ubuntu DHCP NetworkManager
|
549
|
+
if "option ip_address" in line and ("dhcp4" in line or "dhcp6" in line) and "=> '" in line:
|
550
|
+
ip = line.split("=> '")[1].replace("'", "").strip()
|
551
|
+
ips.add(ip)
|
552
|
+
continue
|
553
|
+
|
554
|
+
# Debian and CentOS dhclient
|
555
|
+
if hasattr(record, "daemon") and record.daemon == "dhclient" and "bound to" in line:
|
556
|
+
ip = line.split("bound to")[1].split(" ")[1].strip()
|
557
|
+
ips.add(ip)
|
558
|
+
continue
|
559
|
+
|
560
|
+
# CentOS DHCP and general NetworkManager
|
561
|
+
if " address " in line and ("dhcp4" in line or "dhcp6" in line):
|
562
|
+
ip = line.split(" address ")[1].strip()
|
563
|
+
ips.add(ip)
|
564
|
+
continue
|
565
|
+
|
566
|
+
# Ubuntu/Debian DHCP networkd (Journal)
|
567
|
+
if (
|
568
|
+
hasattr(record, "code_func")
|
569
|
+
and record.code_func == "dhcp_lease_acquired"
|
570
|
+
and " address " in line
|
571
|
+
and " via " in line
|
572
|
+
):
|
573
|
+
interface, ip, netmask, gateway = re.search(
|
574
|
+
r"^(\S+): DHCPv[4|6] address (\S+)\/(\S+) via (\S+)", line
|
575
|
+
).groups()
|
576
|
+
ips.add(ip)
|
577
|
+
continue
|
578
|
+
|
579
|
+
# Journals and syslogs can be large and slow to iterate,
|
580
|
+
# so we stop if we have some results and have reached the journal plugin.
|
581
|
+
if len(ips) >= 2 and record._desc.name == "linux/log/journal":
|
582
|
+
break
|
557
583
|
|
558
584
|
return ips
|
559
585
|
|
@@ -631,7 +657,9 @@ TEMPLATES = {
|
|
631
657
|
["netctl"],
|
632
658
|
["address", "gateway", "dns", "ip"],
|
633
659
|
),
|
634
|
-
"netplan": Template(
|
660
|
+
"netplan": Template(
|
661
|
+
"netplan", YAML(typ="safe").load if HAS_YAML else None, ["network"], ["addresses", "dhcp4", "gateway4"]
|
662
|
+
),
|
635
663
|
"NetworkManager": Template(
|
636
664
|
"NetworkManager",
|
637
665
|
ConfigParser(delimiters=("="), comment_prefixes="#", dict_type=dict),
|
@@ -62,13 +62,16 @@ MODIFIER_MAPPING = {
|
|
62
62
|
|
63
63
|
def _resolve_path_types(target: Target, record: Record) -> Iterator[tuple[str, TargetPath]]:
|
64
64
|
for field_name, field_type in record._field_types.items():
|
65
|
-
if not issubclass(field_type, fieldtypes.path):
|
65
|
+
if not issubclass(field_type, (fieldtypes.path, fieldtypes.command)):
|
66
66
|
continue
|
67
67
|
|
68
68
|
path = getattr(record, field_name, None)
|
69
69
|
if path is None:
|
70
70
|
continue
|
71
71
|
|
72
|
+
if isinstance(path, fieldtypes.command):
|
73
|
+
path = path.executable
|
74
|
+
|
72
75
|
yield field_name, target.resolve(str(path))
|
73
76
|
|
74
77
|
|
dissect/target/loader.py
CHANGED
@@ -77,7 +77,7 @@ class Loader:
|
|
77
77
|
raise NotImplementedError()
|
78
78
|
|
79
79
|
@staticmethod
|
80
|
-
def find_all(path: Path) -> Iterator[Path]:
|
80
|
+
def find_all(path: Path, **kwargs) -> Iterator[Path]:
|
81
81
|
"""Finds all targets to load from ``path``.
|
82
82
|
|
83
83
|
This can be used to open multiple targets from a target path that doesn't necessarily map to files on a disk.
|
@@ -176,6 +176,7 @@ def open(item: Union[str, Path], *args, **kwargs) -> Loader:
|
|
176
176
|
|
177
177
|
register("local", "LocalLoader")
|
178
178
|
register("remote", "RemoteLoader")
|
179
|
+
register("mqtt", "MQTTLoader")
|
179
180
|
register("targetd", "TargetdLoader")
|
180
181
|
register("asdf", "AsdfLoader")
|
181
182
|
register("tar", "TarLoader")
|
@@ -198,6 +199,7 @@ register("target", "TargetLoader")
|
|
198
199
|
register("log", "LogLoader")
|
199
200
|
# Disabling ResLoader because of DIS-536
|
200
201
|
# register("res", "ResLoader")
|
202
|
+
register("overlay", "Overlay2Loader")
|
201
203
|
register("phobos", "PhobosLoader")
|
202
204
|
register("velociraptor", "VelociraptorLoader")
|
203
205
|
register("smb", "SmbLoader")
|
dissect/target/loaders/dir.py
CHANGED
@@ -1,9 +1,11 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
3
|
import zipfile
|
4
|
+
from collections import defaultdict
|
4
5
|
from pathlib import Path
|
5
6
|
from typing import TYPE_CHECKING
|
6
7
|
|
8
|
+
from dissect.target.filesystem import LayerFilesystem
|
7
9
|
from dissect.target.filesystems.dir import DirectoryFilesystem
|
8
10
|
from dissect.target.filesystems.zip import ZipFilesystem
|
9
11
|
from dissect.target.helpers import loaderutil
|
@@ -48,6 +50,7 @@ def map_dirs(target: Target, dirs: list[Path | tuple[str, Path]], os_type: str,
|
|
48
50
|
alt_separator = "\\"
|
49
51
|
case_sensitive = False
|
50
52
|
|
53
|
+
drive_letter_map = defaultdict(list)
|
51
54
|
for path in dirs:
|
52
55
|
drive_letter = None
|
53
56
|
if isinstance(path, tuple):
|
@@ -59,13 +62,28 @@ def map_dirs(target: Target, dirs: list[Path | tuple[str, Path]], os_type: str,
|
|
59
62
|
dfs = ZipFilesystem(path.root.fp, path.at, alt_separator=alt_separator, case_sensitive=case_sensitive)
|
60
63
|
else:
|
61
64
|
dfs = DirectoryFilesystem(path, alt_separator=alt_separator, case_sensitive=case_sensitive)
|
62
|
-
target.filesystems.add(dfs)
|
63
65
|
|
64
|
-
|
65
|
-
|
66
|
+
drive_letter_map[drive_letter].append(dfs)
|
67
|
+
|
68
|
+
fs_to_add = []
|
69
|
+
for drive_letter, dfs in drive_letter_map.items():
|
70
|
+
if drive_letter is not None:
|
71
|
+
if len(dfs) > 1:
|
72
|
+
vfs = LayerFilesystem()
|
73
|
+
for fs in dfs:
|
74
|
+
vfs.append_fs_layer(fs)
|
75
|
+
else:
|
76
|
+
vfs = dfs[0]
|
66
77
|
|
67
|
-
|
68
|
-
|
78
|
+
fs_to_add.append(vfs)
|
79
|
+
target.fs.mount(drive_letter.lower() + ":", vfs)
|
80
|
+
else:
|
81
|
+
fs_to_add.extend(dfs)
|
82
|
+
|
83
|
+
for fs in fs_to_add:
|
84
|
+
target.filesystems.add(fs)
|
85
|
+
if os_type == OperatingSystem.WINDOWS:
|
86
|
+
loaderutil.add_virtual_ntfs_filesystem(target, fs, **kwargs)
|
69
87
|
|
70
88
|
|
71
89
|
def find_and_map_dirs(target: Target, path: Path, **kwargs) -> None:
|
dissect/target/loaders/itunes.py
CHANGED
@@ -28,9 +28,9 @@ except ImportError:
|
|
28
28
|
try:
|
29
29
|
from Crypto.Cipher import AES
|
30
30
|
|
31
|
-
|
31
|
+
HAS_CRYPTO = True
|
32
32
|
except ImportError:
|
33
|
-
|
33
|
+
HAS_CRYPTO = False
|
34
34
|
|
35
35
|
|
36
36
|
DOMAIN_TRANSLATION = {
|
@@ -383,7 +383,7 @@ def _create_cipher(key: bytes, iv: bytes = b"\x00" * 16, mode: str = "cbc") -> A
|
|
383
383
|
raise ValueError(f"Invalid key size: {key_size}")
|
384
384
|
|
385
385
|
return _pystandalone.cipher(f"aes-{key_size * 8}-{mode}", key, iv)
|
386
|
-
elif
|
386
|
+
elif HAS_CRYPTO:
|
387
387
|
mode_map = {
|
388
388
|
"cbc": (AES.MODE_CBC, True),
|
389
389
|
"ecb": (AES.MODE_ECB, False),
|