dissect.target 3.18.dev15__py3-none-any.whl → 3.19__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- dissect/target/filesystem.py +44 -25
- dissect/target/filesystems/config.py +32 -21
- dissect/target/filesystems/extfs.py +4 -0
- dissect/target/filesystems/itunes.py +1 -1
- dissect/target/filesystems/tar.py +1 -1
- dissect/target/filesystems/zip.py +81 -46
- dissect/target/helpers/config.py +22 -7
- dissect/target/helpers/configutil.py +69 -5
- dissect/target/helpers/cyber.py +4 -2
- dissect/target/helpers/fsutil.py +32 -4
- dissect/target/helpers/loaderutil.py +26 -7
- dissect/target/helpers/network_managers.py +22 -7
- dissect/target/helpers/record.py +37 -0
- dissect/target/helpers/record_modifier.py +23 -4
- dissect/target/helpers/shell_application_ids.py +732 -0
- dissect/target/helpers/utils.py +11 -0
- dissect/target/loader.py +1 -0
- dissect/target/loaders/ab.py +285 -0
- dissect/target/loaders/libvirt.py +40 -0
- dissect/target/loaders/mqtt.py +14 -1
- dissect/target/loaders/tar.py +8 -4
- dissect/target/loaders/utm.py +3 -0
- dissect/target/loaders/velociraptor.py +6 -6
- dissect/target/plugin.py +60 -3
- dissect/target/plugins/apps/browser/chrome.py +1 -0
- dissect/target/plugins/apps/browser/chromium.py +7 -5
- dissect/target/plugins/apps/browser/edge.py +1 -0
- dissect/target/plugins/apps/browser/firefox.py +82 -36
- dissect/target/plugins/apps/remoteaccess/anydesk.py +70 -50
- dissect/target/plugins/apps/remoteaccess/remoteaccess.py +8 -8
- dissect/target/plugins/apps/remoteaccess/teamviewer.py +46 -31
- dissect/target/plugins/apps/ssh/openssh.py +1 -1
- dissect/target/plugins/apps/ssh/ssh.py +177 -0
- dissect/target/plugins/apps/texteditor/__init__.py +0 -0
- dissect/target/plugins/apps/texteditor/texteditor.py +13 -0
- dissect/target/plugins/apps/texteditor/windowsnotepad.py +340 -0
- dissect/target/plugins/child/qemu.py +21 -0
- dissect/target/plugins/filesystem/ntfs/mft.py +132 -45
- dissect/target/plugins/filesystem/unix/capability.py +102 -87
- dissect/target/plugins/filesystem/walkfs.py +32 -21
- dissect/target/plugins/filesystem/yara.py +144 -23
- dissect/target/plugins/general/network.py +82 -0
- dissect/target/plugins/general/users.py +14 -10
- dissect/target/plugins/os/unix/_os.py +19 -5
- dissect/target/plugins/os/unix/bsd/freebsd/_os.py +3 -5
- dissect/target/plugins/os/unix/esxi/_os.py +29 -23
- dissect/target/plugins/os/unix/etc/etc.py +5 -8
- dissect/target/plugins/os/unix/history.py +3 -7
- dissect/target/plugins/os/unix/linux/_os.py +15 -14
- dissect/target/plugins/os/unix/linux/android/_os.py +15 -24
- dissect/target/plugins/os/unix/linux/redhat/_os.py +1 -1
- dissect/target/plugins/os/unix/locale.py +17 -6
- dissect/target/plugins/os/unix/shadow.py +47 -31
- dissect/target/plugins/os/windows/_os.py +4 -4
- dissect/target/plugins/os/windows/adpolicy.py +4 -1
- dissect/target/plugins/os/windows/catroot.py +1 -11
- dissect/target/plugins/os/windows/credential/__init__.py +0 -0
- dissect/target/plugins/os/windows/credential/lsa.py +174 -0
- dissect/target/plugins/os/windows/{sam.py → credential/sam.py} +5 -2
- dissect/target/plugins/os/windows/defender.py +6 -3
- dissect/target/plugins/os/windows/dpapi/blob.py +3 -0
- dissect/target/plugins/os/windows/dpapi/crypto.py +61 -23
- dissect/target/plugins/os/windows/dpapi/dpapi.py +127 -133
- dissect/target/plugins/os/windows/dpapi/keyprovider/__init__.py +0 -0
- dissect/target/plugins/os/windows/dpapi/keyprovider/credhist.py +21 -0
- dissect/target/plugins/os/windows/dpapi/keyprovider/empty.py +17 -0
- dissect/target/plugins/os/windows/dpapi/keyprovider/keychain.py +20 -0
- dissect/target/plugins/os/windows/dpapi/keyprovider/keyprovider.py +8 -0
- dissect/target/plugins/os/windows/dpapi/keyprovider/lsa.py +38 -0
- dissect/target/plugins/os/windows/dpapi/master_key.py +3 -0
- dissect/target/plugins/os/windows/jumplist.py +292 -0
- dissect/target/plugins/os/windows/lnk.py +96 -93
- dissect/target/plugins/os/windows/regf/shellbags.py +8 -5
- dissect/target/plugins/os/windows/regf/shimcache.py +2 -2
- dissect/target/plugins/os/windows/regf/usb.py +179 -114
- dissect/target/plugins/os/windows/task_helpers/tasks_xml.py +1 -1
- dissect/target/plugins/os/windows/wua_history.py +1073 -0
- dissect/target/target.py +4 -3
- dissect/target/tools/fs.py +53 -15
- dissect/target/tools/fsutils.py +243 -0
- dissect/target/tools/info.py +11 -4
- dissect/target/tools/query.py +2 -2
- dissect/target/tools/shell.py +505 -333
- dissect/target/tools/utils.py +23 -2
- dissect/target/tools/yara.py +65 -0
- dissect/target/volumes/md.py +2 -2
- {dissect.target-3.18.dev15.dist-info → dissect.target-3.19.dist-info}/METADATA +11 -7
- {dissect.target-3.18.dev15.dist-info → dissect.target-3.19.dist-info}/RECORD +94 -75
- {dissect.target-3.18.dev15.dist-info → dissect.target-3.19.dist-info}/WHEEL +1 -1
- {dissect.target-3.18.dev15.dist-info → dissect.target-3.19.dist-info}/entry_points.txt +1 -0
- dissect/target/helpers/ssh.py +0 -177
- /dissect/target/plugins/os/windows/{credhist.py → credential/credhist.py} +0 -0
- {dissect.target-3.18.dev15.dist-info → dissect.target-3.19.dist-info}/COPYRIGHT +0 -0
- {dissect.target-3.18.dev15.dist-info → dissect.target-3.19.dist-info}/LICENSE +0 -0
- {dissect.target-3.18.dev15.dist-info → dissect.target-3.19.dist-info}/top_level.txt +0 -0
@@ -171,7 +171,7 @@ class ConfigurationParser:
|
|
171
171
|
try:
|
172
172
|
self.parse_file(fh)
|
173
173
|
except Exception as e:
|
174
|
-
raise ConfigurationParsingError(
|
174
|
+
raise ConfigurationParsingError(e.args) from e
|
175
175
|
|
176
176
|
if self.collapse_all or self.collapse:
|
177
177
|
self.parsed_data = self._collapse_dict(self.parsed_data)
|
@@ -240,6 +240,33 @@ class Default(ConfigurationParser):
|
|
240
240
|
self.parsed_data = information_dict
|
241
241
|
|
242
242
|
|
243
|
+
class CSVish(Default):
|
244
|
+
"""Parses CSV-ish config files (does not confirm to CSV standard!)"""
|
245
|
+
|
246
|
+
def __init__(self, *args, fields: tuple[str], **kwargs) -> None:
|
247
|
+
self.fields = fields
|
248
|
+
self.num_fields = len(self.fields)
|
249
|
+
self.maxsplit = self.num_fields - 1
|
250
|
+
super().__init__(*args, **kwargs)
|
251
|
+
|
252
|
+
def parse_file(self, fh: TextIO) -> None:
|
253
|
+
information_dict = {}
|
254
|
+
|
255
|
+
for i, raw_line in enumerate(self.line_reader(fh, strip_comments=True)):
|
256
|
+
line = raw_line.strip()
|
257
|
+
columns = re.split(self.SEPARATOR, line, maxsplit=self.maxsplit)
|
258
|
+
|
259
|
+
if len(columns) < self.num_fields:
|
260
|
+
# keep unparsed lines separate (often env vars)
|
261
|
+
data = {"line": line}
|
262
|
+
else:
|
263
|
+
data = dict(zip(self.fields, columns))
|
264
|
+
|
265
|
+
information_dict[str(i)] = data
|
266
|
+
|
267
|
+
self.parsed_data = information_dict
|
268
|
+
|
269
|
+
|
243
270
|
class Ini(ConfigurationParser):
|
244
271
|
"""Parses an ini file according using the built-in python ConfigParser"""
|
245
272
|
|
@@ -688,11 +715,12 @@ class ParserConfig:
|
|
688
715
|
collapse_inverse: Optional[bool] = None
|
689
716
|
separator: Optional[tuple[str]] = None
|
690
717
|
comment_prefixes: Optional[tuple[str]] = None
|
718
|
+
fields: Optional[tuple[str]] = None
|
691
719
|
|
692
720
|
def create_parser(self, options: Optional[ParserOptions] = None) -> ConfigurationParser:
|
693
721
|
kwargs = {}
|
694
722
|
|
695
|
-
for field_name in ["collapse", "collapse_inverse", "separator", "comment_prefixes"]:
|
723
|
+
for field_name in ["collapse", "collapse_inverse", "separator", "comment_prefixes", "fields"]:
|
696
724
|
value = getattr(options, field_name, None) or getattr(self, field_name)
|
697
725
|
if value:
|
698
726
|
kwargs.update({field_name: value})
|
@@ -721,6 +749,7 @@ CONFIG_MAP: dict[tuple[str, ...], ParserConfig] = {
|
|
721
749
|
"toml": ParserConfig(Toml),
|
722
750
|
}
|
723
751
|
|
752
|
+
|
724
753
|
KNOWN_FILES: dict[str, type[ConfigurationParser]] = {
|
725
754
|
"ulogd.conf": ParserConfig(Ini),
|
726
755
|
"sshd_config": ParserConfig(Indentation, separator=(r"\s",)),
|
@@ -730,6 +759,41 @@ KNOWN_FILES: dict[str, type[ConfigurationParser]] = {
|
|
730
759
|
"nsswitch.conf": ParserConfig(Default, separator=(":",)),
|
731
760
|
"lsb-release": ParserConfig(Default),
|
732
761
|
"catalog": ParserConfig(Xml),
|
762
|
+
"fstab": ParserConfig(
|
763
|
+
CSVish,
|
764
|
+
separator=(r"\s",),
|
765
|
+
comment_prefixes=("#",),
|
766
|
+
fields=("device", "mount", "type", "options", "dump", "pass"),
|
767
|
+
),
|
768
|
+
"crontab": ParserConfig(
|
769
|
+
CSVish,
|
770
|
+
separator=(r"\s",),
|
771
|
+
comment_prefixes=("#",),
|
772
|
+
fields=("minute", "hour", "day", "month", "weekday", "user", "command"),
|
773
|
+
),
|
774
|
+
"shadow": ParserConfig(
|
775
|
+
CSVish,
|
776
|
+
separator=(r"\:",),
|
777
|
+
comment_prefixes=("#",),
|
778
|
+
fields=(
|
779
|
+
"username",
|
780
|
+
"password",
|
781
|
+
"lastchange",
|
782
|
+
"minpassage",
|
783
|
+
"maxpassage",
|
784
|
+
"warning",
|
785
|
+
"inactive",
|
786
|
+
"expire",
|
787
|
+
"rest",
|
788
|
+
),
|
789
|
+
),
|
790
|
+
"passwd": ParserConfig(
|
791
|
+
CSVish,
|
792
|
+
separator=(r"\:",),
|
793
|
+
comment_prefixes=("#",),
|
794
|
+
fields=("username", "password", "uid", "gid", "gecos", "homedir", "shell"),
|
795
|
+
),
|
796
|
+
"mime.types": ParserConfig(CSVish, separator=(r"\s+",), comment_prefixes=("#",), fields=("name", "extensions")),
|
733
797
|
}
|
734
798
|
|
735
799
|
|
@@ -748,13 +812,13 @@ def parse(path: Union[FilesystemEntry, TargetPath], hint: Optional[str] = None,
|
|
748
812
|
FileNotFoundError: If the ``path`` is not a file.
|
749
813
|
"""
|
750
814
|
|
751
|
-
if not path.is_file(follow_symlinks=True):
|
752
|
-
raise FileNotFoundError(f"Could not parse {path} as a dictionary.")
|
753
|
-
|
754
815
|
entry = path
|
755
816
|
if isinstance(path, TargetPath):
|
756
817
|
entry = path.get()
|
757
818
|
|
819
|
+
if not entry.is_file(follow_symlinks=True):
|
820
|
+
raise FileNotFoundError(f"Could not parse {path} as a dictionary.")
|
821
|
+
|
758
822
|
options = ParserOptions(*args, **kwargs)
|
759
823
|
|
760
824
|
return parse_config(entry, hint, options)
|
dissect/target/helpers/cyber.py
CHANGED
@@ -137,7 +137,7 @@ def nms(buf: str, color: Optional[Color] = None, mask_space: bool = False, mask_
|
|
137
137
|
|
138
138
|
if (
|
139
139
|
("\n" in char or "\r\n" in char)
|
140
|
-
or (not mask_space and char == " " and not is_indent
|
140
|
+
or (not mask_space and char == " " and not is_indent)
|
141
141
|
or (not mask_indent and is_indent)
|
142
142
|
):
|
143
143
|
if "\n" in char:
|
@@ -189,7 +189,7 @@ def nms(buf: str, color: Optional[Color] = None, mask_space: bool = False, mask_
|
|
189
189
|
|
190
190
|
if (
|
191
191
|
("\n" in char or "\r\n" in char)
|
192
|
-
or (not mask_space and char == " " and not is_indent
|
192
|
+
or (not mask_space and char == " " and not is_indent)
|
193
193
|
or (not mask_indent and is_indent)
|
194
194
|
):
|
195
195
|
if "\n" in char:
|
@@ -268,6 +268,8 @@ def matrix(buf: str, color: Optional[Color] = None, **kwargs) -> None:
|
|
268
268
|
|
269
269
|
if cur_ansi:
|
270
270
|
char = cur_ansi + char + "\033[0m"
|
271
|
+
if end_ansi:
|
272
|
+
cur_ansi = ""
|
271
273
|
|
272
274
|
if "\n" in char or "\r\n" in char:
|
273
275
|
char = " " + char
|
dissect/target/helpers/fsutil.py
CHANGED
@@ -96,6 +96,7 @@ __all__ = [
|
|
96
96
|
"TargetPath",
|
97
97
|
"walk_ext",
|
98
98
|
"walk",
|
99
|
+
"recurse",
|
99
100
|
]
|
100
101
|
|
101
102
|
|
@@ -144,6 +145,7 @@ class stat_result: # noqa
|
|
144
145
|
"st_file_attributes": "Windows file attribute bits",
|
145
146
|
"st_fstype": "Type of filesystem",
|
146
147
|
"st_reparse_tag": "Windows reparse tag",
|
148
|
+
"st_birthtime_ns": "time of creation in nanoseconds",
|
147
149
|
# Internal fields
|
148
150
|
"_s": "internal tuple",
|
149
151
|
}
|
@@ -193,6 +195,7 @@ class stat_result: # noqa
|
|
193
195
|
self.st_file_attributes = s[22]
|
194
196
|
self.st_fstype = s[23]
|
195
197
|
self.st_reparse_tag = s[24]
|
198
|
+
self.st_birthtime_ns = s[25]
|
196
199
|
|
197
200
|
# stat_result behaves like a tuple, but only with the first 10 fields
|
198
201
|
# Note that this means it specifically uses the integer variants of the timestamps
|
@@ -289,6 +292,20 @@ def walk_ext(path_entry, topdown=True, onerror=None, followlinks=False):
|
|
289
292
|
yield [path_entry], dirs, files
|
290
293
|
|
291
294
|
|
295
|
+
def recurse(path_entry: filesystem.FilesystemEntry) -> Iterator[filesystem.FilesystemEntry]:
|
296
|
+
"""Recursively walk the given :class:`FilesystemEntry`, yields :class:`FilesystemEntry` instances."""
|
297
|
+
yield path_entry
|
298
|
+
|
299
|
+
if not path_entry.is_dir():
|
300
|
+
return
|
301
|
+
|
302
|
+
for child_entry in path_entry.scandir():
|
303
|
+
if child_entry.is_dir() and not child_entry.is_symlink():
|
304
|
+
yield from recurse(child_entry)
|
305
|
+
else:
|
306
|
+
yield child_entry
|
307
|
+
|
308
|
+
|
292
309
|
def glob_split(pattern: str, alt_separator: str = "") -> tuple[str, str]:
|
293
310
|
"""Split a pattern on path part boundaries on the first path part with a glob pattern.
|
294
311
|
|
@@ -423,15 +440,20 @@ def has_glob_magic(s) -> bool:
|
|
423
440
|
|
424
441
|
|
425
442
|
def resolve_link(
|
426
|
-
fs: filesystem.Filesystem,
|
443
|
+
fs: filesystem.Filesystem,
|
444
|
+
link: str,
|
445
|
+
path: str,
|
446
|
+
*,
|
447
|
+
alt_separator: str = "",
|
448
|
+
previous_links: set[str] | None = None,
|
427
449
|
) -> filesystem.FilesystemEntry:
|
428
450
|
"""Resolves a symlink to its actual path.
|
429
451
|
|
430
452
|
It stops resolving once it detects an infinite recursion loop.
|
431
453
|
"""
|
432
454
|
|
433
|
-
link = normalize(
|
434
|
-
path = normalize(
|
455
|
+
link = normalize(link, alt_separator=alt_separator)
|
456
|
+
path = normalize(path, alt_separator=alt_separator)
|
435
457
|
|
436
458
|
# Create hash for entry based on path and link
|
437
459
|
link_id = f"{path}{link}"
|
@@ -454,7 +476,13 @@ def resolve_link(
|
|
454
476
|
entry = fs.get(link)
|
455
477
|
|
456
478
|
if entry.is_symlink():
|
457
|
-
entry = resolve_link(
|
479
|
+
entry = resolve_link(
|
480
|
+
fs,
|
481
|
+
entry.readlink(),
|
482
|
+
link,
|
483
|
+
alt_separator=entry.fs.alt_separator,
|
484
|
+
previous_links=previous_links,
|
485
|
+
)
|
458
486
|
|
459
487
|
return entry
|
460
488
|
|
@@ -5,7 +5,7 @@ import re
|
|
5
5
|
import urllib
|
6
6
|
from os import PathLike
|
7
7
|
from pathlib import Path
|
8
|
-
from typing import TYPE_CHECKING, BinaryIO
|
8
|
+
from typing import TYPE_CHECKING, BinaryIO
|
9
9
|
|
10
10
|
from dissect.target.exceptions import FileNotFoundError
|
11
11
|
from dissect.target.filesystem import Filesystem
|
@@ -42,12 +42,31 @@ def add_virtual_ntfs_filesystem(
|
|
42
42
|
fh_sds = _try_open(fs, sds_path)
|
43
43
|
|
44
44
|
if any([fh_boot, fh_mft]):
|
45
|
-
ntfs =
|
46
|
-
target.filesystems.add(ntfs)
|
47
|
-
fs.ntfs = ntfs.ntfs
|
45
|
+
ntfs = None
|
48
46
|
|
49
|
-
|
50
|
-
|
47
|
+
try:
|
48
|
+
ntfs = NtfsFilesystem(boot=fh_boot, mft=fh_mft, usnjrnl=fh_usnjrnl, sds=fh_sds)
|
49
|
+
except Exception as e:
|
50
|
+
if fh_boot:
|
51
|
+
log.warning("Failed to load NTFS filesystem from %s, retrying without $Boot file", fs)
|
52
|
+
log.debug("", exc_info=e)
|
53
|
+
|
54
|
+
try:
|
55
|
+
# Try once more without the $Boot file
|
56
|
+
ntfs = NtfsFilesystem(mft=fh_mft, usnjrnl=fh_usnjrnl, sds=fh_sds)
|
57
|
+
except Exception:
|
58
|
+
log.warning("Failed to load NTFS filesystem from %s without $Boot file, skipping", fs)
|
59
|
+
return
|
60
|
+
|
61
|
+
# Only add it if we have a valid NTFS with an MFT
|
62
|
+
if ntfs and ntfs.ntfs.mft:
|
63
|
+
target.filesystems.add(ntfs)
|
64
|
+
fs.ntfs = ntfs.ntfs
|
65
|
+
else:
|
66
|
+
log.warning("Opened NTFS filesystem from %s but could not find $MFT, skipping", fs)
|
67
|
+
|
68
|
+
|
69
|
+
def _try_open(fs: Filesystem, path: str) -> BinaryIO | None:
|
51
70
|
paths = [path] if not isinstance(path, list) else path
|
52
71
|
|
53
72
|
for path in paths:
|
@@ -61,7 +80,7 @@ def _try_open(fs: Filesystem, path: str) -> BinaryIO:
|
|
61
80
|
pass
|
62
81
|
|
63
82
|
|
64
|
-
def extract_path_info(path:
|
83
|
+
def extract_path_info(path: str | Path) -> tuple[Path, urllib.parse.ParseResult | None]:
|
65
84
|
"""
|
66
85
|
Extracts a ParseResult from a path if it has
|
67
86
|
a scheme and adjusts the path if necessary.
|
@@ -7,12 +7,14 @@ from configparser import ConfigParser, MissingSectionHeaderError
|
|
7
7
|
from io import StringIO
|
8
8
|
from itertools import chain
|
9
9
|
from re import compile, sub
|
10
|
-
from typing import Any, Callable, Iterable, Match, Optional
|
10
|
+
from typing import Any, Callable, Iterable, Iterator, Match, Optional
|
11
11
|
|
12
12
|
from defusedxml import ElementTree
|
13
13
|
|
14
14
|
from dissect.target.exceptions import PluginError
|
15
15
|
from dissect.target.helpers.fsutil import TargetPath
|
16
|
+
from dissect.target.plugins.os.unix.log.journal import JournalRecord
|
17
|
+
from dissect.target.plugins.os.unix.log.messages import MessagesRecord
|
16
18
|
from dissect.target.target import Target
|
17
19
|
|
18
20
|
log = logging.getLogger(__name__)
|
@@ -509,14 +511,15 @@ class LinuxNetworkManager:
|
|
509
511
|
return values
|
510
512
|
|
511
513
|
|
512
|
-
def parse_unix_dhcp_log_messages(target) ->
|
514
|
+
def parse_unix_dhcp_log_messages(target: Target, iter_all: bool = False) -> set[str]:
|
513
515
|
"""Parse local syslog, journal and cloud init-log files for DHCP lease IPs.
|
514
516
|
|
515
517
|
Args:
|
516
518
|
target: Target to discover and obtain network information from.
|
519
|
+
iter_all: Parse limited amount of journal messages (first 10000) or all of them.
|
517
520
|
|
518
521
|
Returns:
|
519
|
-
|
522
|
+
A set of found DHCP IP addresses.
|
520
523
|
"""
|
521
524
|
ips = set()
|
522
525
|
messages = set()
|
@@ -530,9 +533,19 @@ def parse_unix_dhcp_log_messages(target) -> list[str]:
|
|
530
533
|
if not messages:
|
531
534
|
target.log.warning(f"Could not search for DHCP leases using {log_func}: No log entries found.")
|
532
535
|
|
533
|
-
|
536
|
+
def records_enumerate(iterable: Iterable) -> Iterator[tuple[int, JournalRecord | MessagesRecord]]:
|
537
|
+
count = 0
|
538
|
+
for rec in iterable:
|
539
|
+
if rec._desc.name == "linux/log/journal":
|
540
|
+
count += 1
|
541
|
+
yield count, rec
|
542
|
+
|
543
|
+
for count, record in records_enumerate(messages):
|
534
544
|
line = record.message
|
535
545
|
|
546
|
+
if not line:
|
547
|
+
continue
|
548
|
+
|
536
549
|
# Ubuntu cloud-init
|
537
550
|
if "Received dhcp lease on" in line:
|
538
551
|
interface, ip, netmask = re.search(r"Received dhcp lease on (\w{0,}) for (\S+)\/(\S+)", line).groups()
|
@@ -576,9 +589,11 @@ def parse_unix_dhcp_log_messages(target) -> list[str]:
|
|
576
589
|
ips.add(ip)
|
577
590
|
continue
|
578
591
|
|
579
|
-
#
|
580
|
-
#
|
581
|
-
if
|
592
|
+
# The journal parser is relatively slow, so we stop when we have read 10000 journal entries,
|
593
|
+
# or if we have found at least one ip address. When `iter_all` is `True` we continue searching.
|
594
|
+
if not iter_all and (ips or count > 10_000):
|
595
|
+
if not ips:
|
596
|
+
target.log.warning("No DHCP IP addresses found in first 10000 journal entries.")
|
582
597
|
break
|
583
598
|
|
584
599
|
return ips
|
dissect/target/helpers/record.py
CHANGED
@@ -142,3 +142,40 @@ EmptyRecord = RecordDescriptor(
|
|
142
142
|
"empty",
|
143
143
|
[],
|
144
144
|
)
|
145
|
+
|
146
|
+
COMMON_INTERFACE_ELEMENTS = [
|
147
|
+
("string", "name"),
|
148
|
+
("string", "type"),
|
149
|
+
("boolean", "enabled"),
|
150
|
+
("string", "mac"),
|
151
|
+
("net.ipaddress[]", "dns"),
|
152
|
+
("net.ipaddress[]", "ip"),
|
153
|
+
("net.ipaddress[]", "gateway"),
|
154
|
+
("string", "source"),
|
155
|
+
]
|
156
|
+
|
157
|
+
|
158
|
+
UnixInterfaceRecord = TargetRecordDescriptor(
|
159
|
+
"unix/network/interface",
|
160
|
+
COMMON_INTERFACE_ELEMENTS,
|
161
|
+
)
|
162
|
+
|
163
|
+
WindowsInterfaceRecord = TargetRecordDescriptor(
|
164
|
+
"windows/network/interface",
|
165
|
+
[
|
166
|
+
*COMMON_INTERFACE_ELEMENTS,
|
167
|
+
("varint", "vlan"),
|
168
|
+
("string", "metric"),
|
169
|
+
("datetime", "last_connected"),
|
170
|
+
],
|
171
|
+
)
|
172
|
+
|
173
|
+
MacInterfaceRecord = TargetRecordDescriptor(
|
174
|
+
"macos/network/interface",
|
175
|
+
[
|
176
|
+
*COMMON_INTERFACE_ELEMENTS,
|
177
|
+
("varint", "vlan"),
|
178
|
+
("string", "proxy"),
|
179
|
+
("varint", "interface_service_order"),
|
180
|
+
],
|
181
|
+
)
|
@@ -4,7 +4,7 @@ from typing import Callable, Iterable, Iterator
|
|
4
4
|
from flow.record import GroupedRecord, Record, RecordDescriptor, fieldtypes
|
5
5
|
|
6
6
|
from dissect.target import Target
|
7
|
-
from dissect.target.exceptions import FilesystemError
|
7
|
+
from dissect.target.exceptions import FileNotFoundError, FilesystemError
|
8
8
|
from dissect.target.helpers.fsutil import TargetPath
|
9
9
|
from dissect.target.helpers.hashutil import common
|
10
10
|
from dissect.target.helpers.utils import StrEnum
|
@@ -44,7 +44,20 @@ def _resolve_path_records(field_name: str, resolved_path: TargetPath) -> Record:
|
|
44
44
|
|
45
45
|
|
46
46
|
def _hash_path_records(field_name: str, resolved_path: TargetPath) -> Record:
|
47
|
-
"""Hash files from path fields inside the record.
|
47
|
+
"""Hash files from path fields inside the record.
|
48
|
+
|
49
|
+
Args:
|
50
|
+
field_name: Name of the field.
|
51
|
+
resolved_path: Path to the file we should hash.
|
52
|
+
|
53
|
+
Raises:
|
54
|
+
FileNotFoundError: Raised if the provided ``resolved_path`` does not exist or is not a file on the target.
|
55
|
+
|
56
|
+
Returns: Modified record with digests of path field types.
|
57
|
+
"""
|
58
|
+
|
59
|
+
if not resolved_path.exists() or not resolved_path.is_file():
|
60
|
+
raise FileNotFoundError(f"Path not found or is not a file: '{resolved_path}'")
|
48
61
|
|
49
62
|
with resolved_path.open() as fh:
|
50
63
|
path_hash = common(fh)
|
@@ -81,8 +94,14 @@ def modify_record(target: Target, record: Record, modifier_function: ModifierFun
|
|
81
94
|
for field_name, resolved_path in _resolve_path_types(target, record):
|
82
95
|
try:
|
83
96
|
_record = modifier_function(field_name, resolved_path)
|
84
|
-
except FilesystemError:
|
85
|
-
|
97
|
+
except FilesystemError as e:
|
98
|
+
target.log.warning(
|
99
|
+
"Unable to modify record '%s' with function '%s': %s",
|
100
|
+
record._desc.name,
|
101
|
+
modifier_function.__name__,
|
102
|
+
e,
|
103
|
+
)
|
104
|
+
target.log.debug("", exc_info=e)
|
86
105
|
else:
|
87
106
|
additional_records.append(_record)
|
88
107
|
|