dissect.target 3.20.dev9__py3-none-any.whl → 3.20.dev11__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
@@ -180,7 +180,8 @@ MacInterfaceRecord = TargetRecordDescriptor(
180
180
  [
181
181
  *COMMON_INTERFACE_ELEMENTS,
182
182
  ("varint", "vlan"),
183
- ("string", "proxy"),
183
+ ("net.ipnetwork[]", "network"),
184
184
  ("varint", "interface_service_order"),
185
+ ("boolean", "dhcp"),
185
186
  ],
186
187
  )
@@ -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
- # Static configured IP-addresses
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
+ )
@@ -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.dev9
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=vbsZBUQ5sxsWGaGUJk2yHV5miXvK9HfZgVarM5Cmqto,5852
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=KvP7YJ7apVwoIop7MR-8q5QbVGoB6MdR42l6ssEe6es,4081
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=cGk_DxEqVL0ofxlIC15GD4w3PV5RSE_IaKvVkAxEhR8,11974
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.dev9.dist-info/COPYRIGHT,sha256=m-9ih2RVhMiXHI2bf_oNSSgHgkeIvaYRVfKTwFbnJPA,301
367
- dissect.target-3.20.dev9.dist-info/LICENSE,sha256=DZak_2itbUtvHzD3E7GNUYSRK6jdOJ-GqncQ2weavLA,34523
368
- dissect.target-3.20.dev9.dist-info/METADATA,sha256=GtbUWaQU48N28P1Ob2MQ2AvdNbGJzwoFOBM5ml9Jk88,12896
369
- dissect.target-3.20.dev9.dist-info/WHEEL,sha256=GV9aMThwP_4oNCtvEC2ec3qUYutgWeAzklro_0m4WJQ,91
370
- dissect.target-3.20.dev9.dist-info/entry_points.txt,sha256=BWuxAb_6AvUAQpIQOQU0IMTlaF6TDht2AIZK8bHd-zE,492
371
- dissect.target-3.20.dev9.dist-info/top_level.txt,sha256=Mn-CQzEYsAbkxrUI0TnplHuXnGVKzxpDw_po_sXpvv4,8
372
- dissect.target-3.20.dev9.dist-info/RECORD,,
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,,