dissect.target 3.20.dev9__py3-none-any.whl → 3.20.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/record.py +2 -1
- dissect/target/plugins/os/unix/bsd/osx/_os.py +2 -18
- dissect/target/plugins/os/unix/bsd/osx/network.py +90 -0
- dissect/target/plugins/os/unix/trash.py +132 -0
- dissect/target/tools/utils.py +4 -0
- {dissect.target-3.20.dev9.dist-info → dissect.target-3.20.dev11.dist-info}/METADATA +1 -1
- {dissect.target-3.20.dev9.dist-info → dissect.target-3.20.dev11.dist-info}/RECORD +12 -10
- {dissect.target-3.20.dev9.dist-info → dissect.target-3.20.dev11.dist-info}/COPYRIGHT +0 -0
- {dissect.target-3.20.dev9.dist-info → dissect.target-3.20.dev11.dist-info}/LICENSE +0 -0
- {dissect.target-3.20.dev9.dist-info → dissect.target-3.20.dev11.dist-info}/WHEEL +0 -0
- {dissect.target-3.20.dev9.dist-info → dissect.target-3.20.dev11.dist-info}/entry_points.txt +0 -0
- {dissect.target-3.20.dev9.dist-info → dissect.target-3.20.dev11.dist-info}/top_level.txt +0 -0
dissect/target/helpers/record.py
CHANGED
@@ -40,24 +40,8 @@ class MacPlugin(BsdPlugin):
|
|
40
40
|
@export(property=True)
|
41
41
|
def ips(self) -> Optional[list[str]]:
|
42
42
|
ips = set()
|
43
|
-
|
44
|
-
|
45
|
-
if (preferences := self.target.fs.path(self.SYSTEM)).exists():
|
46
|
-
network = plistlib.load(preferences.open()).get("NetworkServices")
|
47
|
-
|
48
|
-
for interface in network.values():
|
49
|
-
for addresses in [interface.get("IPv4"), interface.get("IPv6")]:
|
50
|
-
ips.update(addresses.get("Addresses", []))
|
51
|
-
|
52
|
-
# IP-addresses configured by DHCP
|
53
|
-
if (dhcp := self.target.fs.path("/private/var/db/dhcpclient/leases")).exists():
|
54
|
-
for lease in dhcp.iterdir():
|
55
|
-
if lease.is_file():
|
56
|
-
lease = plistlib.load(lease.open())
|
57
|
-
|
58
|
-
if ip := lease.get("IPAddress"):
|
59
|
-
ips.add(ip)
|
60
|
-
|
43
|
+
for ip in self.target.network.ips():
|
44
|
+
ips.add(str(ip))
|
61
45
|
return list(ips)
|
62
46
|
|
63
47
|
@export(property=True)
|
@@ -0,0 +1,90 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
import plistlib
|
4
|
+
from functools import cache, lru_cache
|
5
|
+
from typing import Iterator
|
6
|
+
|
7
|
+
from dissect.target.helpers.record import MacInterfaceRecord
|
8
|
+
from dissect.target.plugins.general.network import NetworkPlugin
|
9
|
+
from dissect.target.target import Target
|
10
|
+
|
11
|
+
|
12
|
+
class MacNetworkPlugin(NetworkPlugin):
|
13
|
+
def __init__(self, target: Target):
|
14
|
+
super().__init__(target)
|
15
|
+
self._plistnetwork = cache(self._plistnetwork)
|
16
|
+
self._plistlease = lru_cache(32)(self._plistlease)
|
17
|
+
|
18
|
+
def _plistlease(self, devname: str) -> dict:
|
19
|
+
for lease in self.target.fs.glob_ext(f"/private/var/db/dhcpclient/leases/{devname}*"):
|
20
|
+
return plistlib.load(lease.open())
|
21
|
+
return {}
|
22
|
+
|
23
|
+
def _plistnetwork(self) -> dict:
|
24
|
+
if (preferences := self.target.fs.path("/Library/Preferences/SystemConfiguration/preferences.plist")).exists():
|
25
|
+
return plistlib.load(preferences.open())
|
26
|
+
|
27
|
+
def _interfaces(self) -> Iterator[MacInterfaceRecord]:
|
28
|
+
plistnetwork = self._plistnetwork()
|
29
|
+
current_set = plistnetwork.get("CurrentSet")
|
30
|
+
sets = plistnetwork.get("Sets", {})
|
31
|
+
for name, _set in sets.items():
|
32
|
+
if f"/Sets/{name}" == current_set:
|
33
|
+
item = _set
|
34
|
+
for key in ["Network", "Global", "IPv4", "ServiceOrder"]:
|
35
|
+
item = item.get(key, {})
|
36
|
+
service_order = item
|
37
|
+
break
|
38
|
+
|
39
|
+
network = plistnetwork.get("NetworkServices", {})
|
40
|
+
vlans = plistnetwork.get("VirtualNetworkInterfaces", {}).get("VLAN", {})
|
41
|
+
|
42
|
+
vlan_lookup = {key: vlan.get("Tag") for key, vlan in vlans.items()}
|
43
|
+
|
44
|
+
for _id, interface in network.items():
|
45
|
+
dns = set()
|
46
|
+
gateways = set()
|
47
|
+
ips = set()
|
48
|
+
device = interface.get("Interface", {})
|
49
|
+
name = device.get("DeviceName")
|
50
|
+
_type = device.get("Type")
|
51
|
+
vlan = vlan_lookup.get(name)
|
52
|
+
dhcp = False
|
53
|
+
subnetmask = []
|
54
|
+
network = []
|
55
|
+
interface_service_order = service_order.index(_id) if _id in service_order else None
|
56
|
+
try:
|
57
|
+
for addr in interface.get("DNS", {}).get("ServerAddresses", {}):
|
58
|
+
dns.add(addr)
|
59
|
+
for addresses in [interface.get("IPv4", {}), interface.get("IPv6", {})]:
|
60
|
+
subnetmask += filter(lambda mask: mask != "", addresses.get("SubnetMasks", []))
|
61
|
+
if router := addresses.get("Router"):
|
62
|
+
gateways.add(router)
|
63
|
+
if addresses.get("ConfigMethod", "") == "DHCP":
|
64
|
+
ips.add(self._plistlease(name).get("IPAddress"))
|
65
|
+
dhcp = True
|
66
|
+
else:
|
67
|
+
for addr in addresses.get("Addresses", []):
|
68
|
+
ips.add(addr)
|
69
|
+
|
70
|
+
if subnetmask:
|
71
|
+
network = self.calculate_network(ips, subnetmask)
|
72
|
+
|
73
|
+
yield MacInterfaceRecord(
|
74
|
+
name=name,
|
75
|
+
type=_type,
|
76
|
+
enabled=not interface.get("__INACTIVE__", False),
|
77
|
+
dns=list(dns),
|
78
|
+
ip=list(ips),
|
79
|
+
gateway=list(gateways),
|
80
|
+
source="NetworkServices",
|
81
|
+
vlan=vlan,
|
82
|
+
network=network,
|
83
|
+
interface_service_order=interface_service_order,
|
84
|
+
dhcp=dhcp,
|
85
|
+
_target=self.target,
|
86
|
+
)
|
87
|
+
|
88
|
+
except Exception as e:
|
89
|
+
self.target.log.warning("Error reading configuration for network device %s: %s", name, e)
|
90
|
+
continue
|
@@ -0,0 +1,132 @@
|
|
1
|
+
from typing import Iterator
|
2
|
+
|
3
|
+
from dissect.target.exceptions import UnsupportedPluginError
|
4
|
+
from dissect.target.helpers import configutil
|
5
|
+
from dissect.target.helpers.descriptor_extensions import UserRecordDescriptorExtension
|
6
|
+
from dissect.target.helpers.fsutil import TargetPath
|
7
|
+
from dissect.target.helpers.record import create_extended_descriptor
|
8
|
+
from dissect.target.plugin import Plugin, alias, export
|
9
|
+
from dissect.target.plugins.general.users import UserDetails
|
10
|
+
from dissect.target.target import Target
|
11
|
+
|
12
|
+
TrashRecord = create_extended_descriptor([UserRecordDescriptorExtension])(
|
13
|
+
"linux/filesystem/recyclebin",
|
14
|
+
[
|
15
|
+
("datetime", "ts"),
|
16
|
+
("path", "path"),
|
17
|
+
("filesize", "filesize"),
|
18
|
+
("path", "deleted_path"),
|
19
|
+
("path", "source"),
|
20
|
+
],
|
21
|
+
)
|
22
|
+
|
23
|
+
|
24
|
+
class GnomeTrashPlugin(Plugin):
|
25
|
+
"""Linux GNOME Trash plugin."""
|
26
|
+
|
27
|
+
PATHS = [
|
28
|
+
# Default $XDG_DATA_HOME/Trash
|
29
|
+
".local/share/Trash",
|
30
|
+
]
|
31
|
+
|
32
|
+
def __init__(self, target: Target):
|
33
|
+
super().__init__(target)
|
34
|
+
self.trashes = list(self._garbage_collector())
|
35
|
+
|
36
|
+
def _garbage_collector(self) -> Iterator[tuple[UserDetails, TargetPath]]:
|
37
|
+
"""it aint much, but its honest work"""
|
38
|
+
for user_details in self.target.user_details.all_with_home():
|
39
|
+
for trash_path in self.PATHS:
|
40
|
+
if (path := user_details.home_path.joinpath(trash_path)).exists():
|
41
|
+
yield user_details, path
|
42
|
+
|
43
|
+
def check_compatible(self) -> None:
|
44
|
+
if not self.trashes:
|
45
|
+
raise UnsupportedPluginError("No Trash folder(s) found")
|
46
|
+
|
47
|
+
@export(record=TrashRecord)
|
48
|
+
@alias(name="recyclebin")
|
49
|
+
def trash(self) -> Iterator[TrashRecord]:
|
50
|
+
"""Yield deleted files from GNOME Trash folders.
|
51
|
+
|
52
|
+
Recovers deleted files and artifacts from ``$HOME/.local/share/Trash``.
|
53
|
+
Probably also works with other desktop interfaces as long as they follow the Trash specification from FreeDesktop.
|
54
|
+
|
55
|
+
Currently does not parse media trash locations such as ``/media/$Label/.Trash-1000/*``.
|
56
|
+
|
57
|
+
Resources:
|
58
|
+
- https://specifications.freedesktop.org/trash-spec/latest/
|
59
|
+
- https://github.com/GNOME/glib/blob/main/gio/glocalfile.c
|
60
|
+
- https://specifications.freedesktop.org/basedir-spec/latest/
|
61
|
+
|
62
|
+
Yields ``TrashRecord``s with the following fields:
|
63
|
+
|
64
|
+
.. code-block:: text
|
65
|
+
|
66
|
+
ts (datetime): timestamp when the file was deleted or for expunged files when it could not be permanently deleted
|
67
|
+
path (path): path where the file was located before it was deleted
|
68
|
+
filesize (filesize): size in bytes of the deleted file
|
69
|
+
deleted_path (path): path to the current location of the deleted file
|
70
|
+
source (path): path to the .trashinfo file
|
71
|
+
""" # noqa: E501
|
72
|
+
|
73
|
+
for user_details, trash in self.trashes:
|
74
|
+
for trash_info_file in trash.glob("info/*.trashinfo"):
|
75
|
+
trash_info = configutil.parse(trash_info_file, hint="ini").get("Trash Info", {})
|
76
|
+
original_path = self.target.fs.path(trash_info.get("Path", ""))
|
77
|
+
|
78
|
+
# We use the basename of the .trashinfo file and not the Path variable inside the
|
79
|
+
# ini file. This way we can keep duplicate basenames of trashed files separated correctly.
|
80
|
+
deleted_path = trash / "files" / trash_info_file.name.replace(".trashinfo", "")
|
81
|
+
|
82
|
+
if deleted_path.exists():
|
83
|
+
deleted_files = [deleted_path]
|
84
|
+
|
85
|
+
if deleted_path.is_dir():
|
86
|
+
for child in deleted_path.rglob("*"):
|
87
|
+
deleted_files.append(child)
|
88
|
+
|
89
|
+
for file in deleted_files:
|
90
|
+
# NOTE: We currently do not 'fix' the original_path of files inside deleted directories.
|
91
|
+
# This would require guessing where the parent folder starts, which is impossible without
|
92
|
+
# making assumptions.
|
93
|
+
yield TrashRecord(
|
94
|
+
ts=trash_info.get("DeletionDate", 0),
|
95
|
+
path=original_path,
|
96
|
+
filesize=file.lstat().st_size if file.is_file() else None,
|
97
|
+
deleted_path=file,
|
98
|
+
source=trash_info_file,
|
99
|
+
_user=user_details.user,
|
100
|
+
_target=self.target,
|
101
|
+
)
|
102
|
+
|
103
|
+
# We cannot determine if the deleted entry is a directory since the path does
|
104
|
+
# not exist at $TRASH/files, so we work with what we have instead.
|
105
|
+
else:
|
106
|
+
self.target.log.warning(f"Expected trashed file(s) at {deleted_path}")
|
107
|
+
yield TrashRecord(
|
108
|
+
ts=trash_info.get("DeletionDate", 0),
|
109
|
+
path=original_path,
|
110
|
+
filesize=0,
|
111
|
+
deleted_path=deleted_path,
|
112
|
+
source=trash_info_file,
|
113
|
+
_user=user_details.user,
|
114
|
+
_target=self.target,
|
115
|
+
)
|
116
|
+
|
117
|
+
# We also iterate expunged folders, they can contain files that could not be
|
118
|
+
# deleted when the user pressed the "empty trash" button in the file manager.
|
119
|
+
# Resources:
|
120
|
+
# - https://gitlab.gnome.org/GNOME/glib/-/issues/1665
|
121
|
+
# - https://bugs.launchpad.net/ubuntu/+source/nautilus/+bug/422012
|
122
|
+
for item in (trash / "expunged").rglob("*/*"):
|
123
|
+
stat = item.lstat()
|
124
|
+
yield TrashRecord(
|
125
|
+
ts=stat.st_mtime, # NOTE: This is the timestamp at which the file failed to delete
|
126
|
+
path=None, # We do not know the original file path
|
127
|
+
filesize=stat.st_size if item.is_file() else None,
|
128
|
+
deleted_path=item,
|
129
|
+
source=trash / "expunged",
|
130
|
+
_user=user_details.user,
|
131
|
+
_target=self.target,
|
132
|
+
)
|
dissect/target/tools/utils.py
CHANGED
@@ -90,6 +90,10 @@ def generate_argparse_for_unbound_method(
|
|
90
90
|
raise ValueError(f"Value `{method}` is not an unbound plugin method")
|
91
91
|
|
92
92
|
desc = method.__doc__ or docs.get_func_description(method, with_docstrings=True)
|
93
|
+
|
94
|
+
if "\n" in desc:
|
95
|
+
desc = inspect.cleandoc(desc)
|
96
|
+
|
93
97
|
help_formatter = argparse.RawDescriptionHelpFormatter
|
94
98
|
parser = argparse.ArgumentParser(description=desc, formatter_class=help_formatter, conflict_handler="resolve")
|
95
99
|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: dissect.target
|
3
|
-
Version: 3.20.
|
3
|
+
Version: 3.20.dev11
|
4
4
|
Summary: This module ties all other Dissect modules together, it provides a programming API and command line tools which allow easy access to various data sources inside disk images or file collections (a.k.a. targets)
|
5
5
|
Author-email: Dissect Team <dissect@fox-it.com>
|
6
6
|
License: Affero General Public License v3
|
@@ -61,7 +61,7 @@ dissect/target/helpers/mui.py,sha256=i-7XoHbu4WO2fYapK9yGAMW04rFlgRispknc1KQIS5Q
|
|
61
61
|
dissect/target/helpers/network_managers.py,sha256=ByBSe2K3c8hgQC6dokcf-hHdmPcD8PmrOj0xs1C3yhs,25743
|
62
62
|
dissect/target/helpers/polypath.py,sha256=h8p7m_OCNiQljGwoZh5Aflr9H2ot6CZr6WKq1OSw58o,2175
|
63
63
|
dissect/target/helpers/protobuf.py,sha256=b4DsnqrRLrefcDjx7rQno-_LBcwtJXxuKf5RdOegzfE,1537
|
64
|
-
dissect/target/helpers/record.py,sha256=
|
64
|
+
dissect/target/helpers/record.py,sha256=7Se6ZV8cvwEaGSjRd9bKhVnUAn4W4KR2eqP6AbQhTH4,5892
|
65
65
|
dissect/target/helpers/record_modifier.py,sha256=O_Jj7zOi891HIyAYjxxe6LFPYETHdMa5lNjo4NA_T_w,3969
|
66
66
|
dissect/target/helpers/regutil.py,sha256=kX-sSZbW8Qkg29Dn_9zYbaQrwLumrr4Y8zJ1EhHXIAM,27337
|
67
67
|
dissect/target/helpers/shell_application_ids.py,sha256=hYxrP-YtHK7ZM0ectJFHfoMB8QUXLbYNKmKXMWLZRlA,38132
|
@@ -198,6 +198,7 @@ dissect/target/plugins/os/unix/history.py,sha256=rvRlcHw3wEtgdyfjX-RBLQUQAd0uHzf
|
|
198
198
|
dissect/target/plugins/os/unix/locale.py,sha256=XOcKBwfK3YJ266eBFKNc1xaZgY8QEQGJOS8PJRJt4ME,4292
|
199
199
|
dissect/target/plugins/os/unix/packagemanager.py,sha256=Wm2AAJOD_B3FAcZNXgWtSm_YwbvrHBYOP8bPmOXNjG4,2427
|
200
200
|
dissect/target/plugins/os/unix/shadow.py,sha256=W6W6rMru7IVnuBc6sl5wsRWTOrJdS1s7_2_q7QRf7Is,4148
|
201
|
+
dissect/target/plugins/os/unix/trash.py,sha256=wzq9nprRKjFs9SHaENz8PMbpQMfFoLH1KW4EPWMLJdA,6099
|
201
202
|
dissect/target/plugins/os/unix/bsd/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
202
203
|
dissect/target/plugins/os/unix/bsd/_os.py,sha256=e5rttTOFOmd7e2HqP9ZZFMEiPLBr-8rfH0XH1IIeroQ,1372
|
203
204
|
dissect/target/plugins/os/unix/bsd/citrix/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -210,7 +211,8 @@ dissect/target/plugins/os/unix/bsd/ios/_os.py,sha256=VlJXGxkQZ4RbGbSC-FlbR2YWOJp
|
|
210
211
|
dissect/target/plugins/os/unix/bsd/openbsd/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
211
212
|
dissect/target/plugins/os/unix/bsd/openbsd/_os.py,sha256=9npz-osM-wHmjOACUqof5N5HJeps7J8KuyenUS5MZDs,923
|
212
213
|
dissect/target/plugins/os/unix/bsd/osx/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
213
|
-
dissect/target/plugins/os/unix/bsd/osx/_os.py,sha256=
|
214
|
+
dissect/target/plugins/os/unix/bsd/osx/_os.py,sha256=PKh8VM_O0FmmYy7UMPth6uhusCJzekla20sUPK6esRg,3416
|
215
|
+
dissect/target/plugins/os/unix/bsd/osx/network.py,sha256=j2yq2QTAmAuZBu3j0vHnHHxkUyeB4b-6WdUSWCE_QsE,3691
|
214
216
|
dissect/target/plugins/os/unix/bsd/osx/user.py,sha256=qopB0s3n7e6Q7NjWzn8Z-dKtDtU7e6In4Vm7hIvvedo,2322
|
215
217
|
dissect/target/plugins/os/unix/esxi/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
216
218
|
dissect/target/plugins/os/unix/esxi/_os.py,sha256=s6pAgUyfHh3QcY6sgvk5uVMmLvqK1tIHWR7MSbrFn8w,17789
|
@@ -349,7 +351,7 @@ dissect/target/tools/mount.py,sha256=8GRYnu4xEmFBHxuIZAYhOMyyTGX8fat1Ou07DNiUnW4
|
|
349
351
|
dissect/target/tools/query.py,sha256=e-yAN9zdQjuOiTuoOQoo17mVEQGGcOgaA9YkF4GYpkM,15394
|
350
352
|
dissect/target/tools/reg.py,sha256=FDsiBBDxjWVUBTRj8xn82vZe-J_d9piM-TKS3PHZCcM,3193
|
351
353
|
dissect/target/tools/shell.py,sha256=dmshIriwdd_UwrdUcTfWkcYD8Z0mjzbDqwyZG-snDdM,50482
|
352
|
-
dissect/target/tools/utils.py,sha256=
|
354
|
+
dissect/target/tools/utils.py,sha256=JJZDSso1CEK2sv4Z3HJNgqxH6G9S5lbmV-C3h-XmcMo,12035
|
353
355
|
dissect/target/tools/yara.py,sha256=70k-2VMulf1EdkX03nCACzejaOEcsFHOyX-4E40MdQU,2044
|
354
356
|
dissect/target/tools/dump/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
355
357
|
dissect/target/tools/dump/run.py,sha256=aD84peRS4zHqC78fH7Vd4ni3m1ZmVP70LyMwBRvoDGY,9463
|
@@ -363,10 +365,10 @@ dissect/target/volumes/luks.py,sha256=OmCMsw6rCUXG1_plnLVLTpsvE1n_6WtoRUGQbpmu1z
|
|
363
365
|
dissect/target/volumes/lvm.py,sha256=wwQVR9I3G9YzmY6UxFsH2Y4MXGBcKL9aayWGCDTiWMU,2269
|
364
366
|
dissect/target/volumes/md.py,sha256=7ShPtusuLGaIv27SvEETtgsuoQyAa4iAAeOR1NEaajI,1689
|
365
367
|
dissect/target/volumes/vmfs.py,sha256=-LoUbn9WNwTtLi_4K34uV_-wDw2W5hgaqxZNj4UmqAQ,1730
|
366
|
-
dissect.target-3.20.
|
367
|
-
dissect.target-3.20.
|
368
|
-
dissect.target-3.20.
|
369
|
-
dissect.target-3.20.
|
370
|
-
dissect.target-3.20.
|
371
|
-
dissect.target-3.20.
|
372
|
-
dissect.target-3.20.
|
368
|
+
dissect.target-3.20.dev11.dist-info/COPYRIGHT,sha256=m-9ih2RVhMiXHI2bf_oNSSgHgkeIvaYRVfKTwFbnJPA,301
|
369
|
+
dissect.target-3.20.dev11.dist-info/LICENSE,sha256=DZak_2itbUtvHzD3E7GNUYSRK6jdOJ-GqncQ2weavLA,34523
|
370
|
+
dissect.target-3.20.dev11.dist-info/METADATA,sha256=llgQpPEeplVoo7RGC0YISl3fl6aUEtvIq5O8bLhrhjY,12897
|
371
|
+
dissect.target-3.20.dev11.dist-info/WHEEL,sha256=GV9aMThwP_4oNCtvEC2ec3qUYutgWeAzklro_0m4WJQ,91
|
372
|
+
dissect.target-3.20.dev11.dist-info/entry_points.txt,sha256=BWuxAb_6AvUAQpIQOQU0IMTlaF6TDht2AIZK8bHd-zE,492
|
373
|
+
dissect.target-3.20.dev11.dist-info/top_level.txt,sha256=Mn-CQzEYsAbkxrUI0TnplHuXnGVKzxpDw_po_sXpvv4,8
|
374
|
+
dissect.target-3.20.dev11.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|