dissect.target 3.15.dev13__py3-none-any.whl → 3.15.dev14__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.
@@ -5,59 +5,15 @@ from typing import Iterator
5
5
 
6
6
  from dissect.target import Target
7
7
  from dissect.target.exceptions import UnsupportedPluginError
8
- from dissect.target.helpers.descriptor_extensions import UserRecordDescriptorExtension
9
8
  from dissect.target.helpers.fsutil import TargetPath
10
- from dissect.target.helpers.record import create_extended_descriptor
11
9
  from dissect.target.helpers.ssh import SSHPrivateKey
12
- from dissect.target.plugin import Plugin, export
13
-
14
- OpenSSHUserRecordDescriptor = create_extended_descriptor([UserRecordDescriptorExtension])
15
-
16
- COMMON_ELLEMENTS = [
17
- ("string", "key_type"),
18
- ("string", "comment"),
19
- ("path", "path"),
20
- ]
21
-
22
- AuthorizedKeysRecord = OpenSSHUserRecordDescriptor(
23
- "application/openssh/authorized_keys",
24
- [
25
- *COMMON_ELLEMENTS,
26
- ("string", "public_key"),
27
- ("string", "options"),
28
- ],
29
- )
30
-
31
-
32
- KnownHostRecord = OpenSSHUserRecordDescriptor(
33
- "application/openssh/known_host",
34
- [
35
- *COMMON_ELLEMENTS,
36
- ("string", "hostname_pattern"),
37
- ("string", "public_key"),
38
- ("string", "marker"),
39
- ],
40
- )
41
-
42
-
43
- PrivateKeyRecord = OpenSSHUserRecordDescriptor(
44
- "application/openssh/private_key",
45
- [
46
- *COMMON_ELLEMENTS,
47
- ("datetime", "mtime_ts"),
48
- ("string", "key_format"),
49
- ("string", "public_key"),
50
- ("boolean", "encrypted"),
51
- ],
52
- )
53
-
54
- PublicKeyRecord = OpenSSHUserRecordDescriptor(
55
- "application/openssh/public_key",
56
- [
57
- *COMMON_ELLEMENTS,
58
- ("datetime", "mtime_ts"),
59
- ("string", "public_key"),
60
- ],
10
+ from dissect.target.plugin import export
11
+ from dissect.target.plugins.apps.ssh.ssh import (
12
+ AuthorizedKeysRecord,
13
+ KnownHostRecord,
14
+ PrivateKeyRecord,
15
+ PublicKeyRecord,
16
+ SSHPlugin,
61
17
  )
62
18
 
63
19
 
@@ -72,8 +28,8 @@ def find_sshd_directory(target: Target) -> TargetPath:
72
28
  return target.fs.path("/etc/ssh/")
73
29
 
74
30
 
75
- class OpenSSHPlugin(Plugin):
76
- __namespace__ = "ssh"
31
+ class OpenSSHPlugin(SSHPlugin):
32
+ __namespace__ = "openssh"
77
33
 
78
34
  SSHD_DIRECTORIES = ["/sysvol/ProgramData/ssh", "/etc/ssh"]
79
35
 
@@ -136,7 +92,8 @@ class OpenSSHPlugin(Plugin):
136
92
 
137
93
  for hostname in hostnames:
138
94
  yield KnownHostRecord(
139
- hostname_pattern=hostname,
95
+ host=hostname,
96
+ port=None,
140
97
  key_type=keytype,
141
98
  public_key=public_key,
142
99
  comment=comment,
@@ -3,8 +3,9 @@ from typing import TYPE_CHECKING, Any, Callable, Iterator, Optional, Union
3
3
  from dissect.target import Target
4
4
  from dissect.target.exceptions import UnsupportedPluginError
5
5
  from dissect.target.helpers.record import DynamicDescriptor, TargetRecordDescriptor
6
- from dissect.target.plugin import Plugin, export
6
+ from dissect.target.plugin import export
7
7
  from dissect.target.plugins.apps.ssh.openssh import find_sshd_directory
8
+ from dissect.target.plugins.apps.ssh.ssh import SSHPlugin
8
9
 
9
10
  if TYPE_CHECKING:
10
11
  from dissect.target.plugins.general.config import ConfigurationTreePlugin
@@ -72,8 +73,8 @@ SSHD_MULTIPLE_DEFINITIONS_ALLOWED_FIELDS = (
72
73
  )
73
74
 
74
75
 
75
- class SSHServerPlugin(Plugin):
76
- __namespace__ = "sshd"
76
+ class SSHServerPlugin(SSHPlugin):
77
+ __namespace__ = "opensshd"
77
78
 
78
79
  def __init__(self, target: Target):
79
80
  super().__init__(target)
@@ -0,0 +1,236 @@
1
+ import logging
2
+ from datetime import datetime
3
+ from pathlib import Path
4
+ from typing import Iterator, Optional, Union
5
+
6
+ from Crypto.PublicKey import ECC, RSA
7
+ from flow.record.fieldtypes import posix_path, windows_path
8
+
9
+ from dissect.target.exceptions import RegistryKeyNotFoundError, UnsupportedPluginError
10
+ from dissect.target.helpers.descriptor_extensions import UserRecordDescriptorExtension
11
+ from dissect.target.helpers.fsutil import TargetPath, open_decompress
12
+ from dissect.target.helpers.record import create_extended_descriptor
13
+ from dissect.target.helpers.regutil import RegistryKey
14
+ from dissect.target.plugin import export
15
+ from dissect.target.plugins.apps.ssh.ssh import KnownHostRecord, SSHPlugin
16
+ from dissect.target.plugins.general.users import UserDetails
17
+
18
+ log = logging.getLogger(__name__)
19
+
20
+ PuTTYUserRecordDescriptor = create_extended_descriptor([UserRecordDescriptorExtension])
21
+ PuTTYSessionRecord = PuTTYUserRecordDescriptor(
22
+ "application/putty/saved_session",
23
+ [
24
+ ("datetime", "ts"),
25
+ ("string", "session_name"),
26
+ ("string", "protocol"),
27
+ ("string", "host"),
28
+ ("string", "user"),
29
+ ("varint", "port"),
30
+ ("string", "remote_command"),
31
+ ("string", "port_forward"),
32
+ ("string", "manual_ssh_host_keys"),
33
+ ("path", "path"),
34
+ ],
35
+ )
36
+
37
+
38
+ class PuTTYPlugin(SSHPlugin):
39
+ """Extract artifacts from the PuTTY client.
40
+
41
+ NOTE:
42
+ - Does not parse ``$HOME/.putty/randomseed`` (GNU/Linux)
43
+ and ``HKCU\\Software\\SimonTatham\\PuTTY\\RandSeedFile`` (Windows)
44
+
45
+ Resources:
46
+ - http://www.chiark.greenend.org.uk/~sgtatham/putty/0.78/puttydoc.txt
47
+ - http://www.chiark.greenend.org.uk/~sgtatham/putty/faq.html#faq-settings
48
+ """
49
+
50
+ __namespace__ = "putty"
51
+
52
+ def __init__(self, target):
53
+ super().__init__(target)
54
+
55
+ self.regf_installs, self.path_installs = self._detect_putty()
56
+
57
+ def _detect_putty(
58
+ self,
59
+ ) -> tuple[list[set[RegistryKey, Optional[UserDetails]]], list[set[TargetPath, Optional[UserDetails]]]]:
60
+ regf_installs, path_installs = [], []
61
+
62
+ if self.target.has_function("registry"):
63
+ for key in self.target.registry.keys("HKCU\\Software\\SimonTatham\\PuTTY"):
64
+ user_details = self.target.registry.get_user_details(key)
65
+ regf_installs.append((key, user_details))
66
+
67
+ for user_details in self.target.user_details.all_with_home():
68
+ if (putty_path := user_details.home_path.joinpath(".putty")).exists():
69
+ path_installs.append((putty_path, user_details))
70
+
71
+ return regf_installs, path_installs
72
+
73
+ def check_compatible(self) -> None:
74
+ if not any(self.regf_installs + self.path_installs):
75
+ raise UnsupportedPluginError("No PuTTY installations found")
76
+
77
+ @export(record=KnownHostRecord)
78
+ def known_hosts(self) -> Iterator[KnownHostRecord]:
79
+ """Parse PuTTY saved SshHostKeys."""
80
+
81
+ for putty_key, user_details in self.regf_installs:
82
+ yield from self._regf_known_hosts(putty_key, user_details)
83
+
84
+ for putty_path, user_details in self.path_installs:
85
+ yield from self._path_known_hosts(putty_path, user_details)
86
+
87
+ def _regf_known_hosts(self, putty_key: RegistryKey, user_details: UserDetails) -> Iterator[KnownHostRecord]:
88
+ """Parse PuTTY traces in Windows registry."""
89
+
90
+ try:
91
+ ssh_host_keys = putty_key.subkey("SshHostKeys")
92
+ except RegistryKeyNotFoundError:
93
+ return
94
+
95
+ for entry in ssh_host_keys.values():
96
+ key_type, host = entry.name.split("@")
97
+ port, host = host.split(":")
98
+
99
+ yield KnownHostRecord(
100
+ mtime_ts=ssh_host_keys.ts,
101
+ host=host,
102
+ port=port,
103
+ key_type=key_type,
104
+ public_key=construct_public_key(key_type, entry.value),
105
+ comment="",
106
+ marker=None,
107
+ path=windows_path(ssh_host_keys.path),
108
+ _target=self.target,
109
+ _user=user_details.user if user_details else None,
110
+ )
111
+
112
+ def _path_known_hosts(self, putty_path: TargetPath, user_details: UserDetails) -> Iterator[KnownHostRecord]:
113
+ """Parse PuTTY traces in ``.putty`` folders"""
114
+ ssh_host_keys_path = putty_path.joinpath("sshhostkeys")
115
+
116
+ if ssh_host_keys_path.exists():
117
+ ts = ssh_host_keys_path.stat().st_mtime
118
+
119
+ for line in open_decompress(ssh_host_keys_path, "rt"):
120
+ parts = line.split()
121
+ key_type, host = parts[0].split("@")
122
+ port, host = host.split(":")
123
+
124
+ yield KnownHostRecord(
125
+ mtime_ts=ts,
126
+ host=host,
127
+ port=port,
128
+ key_type=key_type,
129
+ public_key=construct_public_key(key_type, parts[1]),
130
+ comment="",
131
+ marker=None,
132
+ path=posix_path(ssh_host_keys_path),
133
+ _target=self.target,
134
+ _user=user_details.user if user_details else None,
135
+ )
136
+
137
+ @export(record=PuTTYSessionRecord)
138
+ def sessions(self) -> Iterator[PuTTYSessionRecord]:
139
+ """Parse PuTTY saved session configuration files."""
140
+
141
+ for putty_key, user_details in self.regf_installs:
142
+ yield from self._regf_sessions(putty_key, user_details)
143
+
144
+ for putty_path, user_details in self.path_installs:
145
+ yield from self._path_sessions(putty_path, user_details)
146
+
147
+ def _regf_sessions(self, putty_key: RegistryKey, user_details: UserDetails) -> Iterator[PuTTYSessionRecord]:
148
+ try:
149
+ sessions = putty_key.subkey("Sessions")
150
+ except RegistryKeyNotFoundError:
151
+ return
152
+
153
+ for session in sessions.subkeys():
154
+ cfg = {s.name: s.value for s in session.values()}
155
+ yield from self._build_session_record(
156
+ session.ts, session.name, windows_path(session.path), cfg, user_details
157
+ )
158
+
159
+ def _path_sessions(self, putty_path: TargetPath, user_details: UserDetails) -> Iterator[PuTTYSessionRecord]:
160
+ sessions_dir = putty_path.joinpath("sessions")
161
+ if sessions_dir.exists():
162
+ for session in sessions_dir.glob("*"):
163
+ if session.is_file():
164
+ cfg = dict(map(str.strip, line.split("=", maxsplit=1)) for line in session.open("rt").readlines())
165
+ yield from self._build_session_record(
166
+ session.stat().st_mtime, session.name, session, cfg, user_details
167
+ )
168
+
169
+ def _build_session_record(
170
+ self, ts: float, name: Union[float, datetime], source: Path, cfg: dict, user_details: UserDetails
171
+ ) -> PuTTYSessionRecord:
172
+ host, user = parse_host_user(cfg.get("HostName"), cfg.get("UserName"))
173
+
174
+ yield PuTTYSessionRecord(
175
+ ts=ts,
176
+ session_name=name,
177
+ protocol=cfg.get("Protocol"),
178
+ host=host,
179
+ user=user,
180
+ port=cfg.get("PortNumber"),
181
+ remote_command=cfg.get("RemoteCommand"),
182
+ port_forward=cfg.get("PortForwardings"),
183
+ manual_ssh_host_keys=cfg.get("SSHManualHostKeys"),
184
+ path=source,
185
+ _target=self.target,
186
+ _user=user_details.user if user_details else None,
187
+ )
188
+
189
+
190
+ def parse_host_user(host: str, user: str) -> tuple[str, str]:
191
+ """Parse host and user from PuTTY hostname component."""
192
+ if "@" in host:
193
+ parsed_user, parsed_host = host.split("@")
194
+ user = user or parsed_user
195
+ host = parsed_host
196
+
197
+ return host, user
198
+
199
+
200
+ def construct_public_key(key_type: str, iv: str) -> str:
201
+ """Returns OpenSSH format public key calculated from PuTTY SshHostKeys format.
202
+
203
+ PuTTY stores raw public key components instead of OpenSSH-formatted public keys
204
+ or fingerprints. With RSA public keys the exponent and modulus are stored.
205
+ With ECC keys the x and y prime coordinates are stored together with the curve type.
206
+
207
+ Currently supports ``ssh-ed25519``, ``ecdsa-sha2-nistp256`` and ``rsa2`` key types.
208
+
209
+ NOTE:
210
+ - Sha256 fingerprints of the reconstructed public keys are currently not generated.
211
+ - More key types could be supported in the future.
212
+
213
+ Resources:
214
+ - https://github.com/github/putty/blob/master/contrib/kh2reg.py
215
+ - https://pycryptodome.readthedocs.io/en/latest/src/public_key/rsa.html
216
+ - https://pycryptodome.readthedocs.io/en/latest/src/public_key/ecc.html
217
+ - https://github.com/mkorthof/reg2kh
218
+ """
219
+
220
+ if key_type == "ssh-ed25519":
221
+ x, y = iv.split(",")
222
+ key = ECC.construct(curve="ed25519", point_x=int(x, 16), point_y=int(y, 16))
223
+ return key.public_key().export_key(format="OpenSSH").split()[-1]
224
+
225
+ if key_type == "ecdsa-sha2-nistp256":
226
+ _, x, y = iv.split(",")
227
+ key = ECC.construct(curve="NIST P-256", point_x=int(x, 16), point_y=int(y, 16))
228
+ return key.public_key().export_key(format="OpenSSH").split()[-1]
229
+
230
+ if key_type == "rsa2":
231
+ exponent, modulus = iv.split(",")
232
+ key = RSA.construct((int(modulus, 16), int(exponent, 16)))
233
+ return key.public_key().export_key(format="OpenSSH").decode("utf-8").split()[-1]
234
+
235
+ log.warning("Could not reconstruct public key: type %s not implemented.", key_type)
236
+ return iv
@@ -0,0 +1,58 @@
1
+ from dissect.target.helpers.descriptor_extensions import UserRecordDescriptorExtension
2
+ from dissect.target.helpers.record import create_extended_descriptor
3
+ from dissect.target.plugin import NamespacePlugin
4
+
5
+ OpenSSHUserRecordDescriptor = create_extended_descriptor([UserRecordDescriptorExtension])
6
+
7
+ COMMON_ELLEMENTS = [
8
+ ("string", "key_type"),
9
+ ("string", "comment"),
10
+ ("path", "path"),
11
+ ]
12
+
13
+ AuthorizedKeysRecord = OpenSSHUserRecordDescriptor(
14
+ "application/openssh/authorized_keys",
15
+ [
16
+ *COMMON_ELLEMENTS,
17
+ ("string", "public_key"),
18
+ ("string", "options"),
19
+ ],
20
+ )
21
+
22
+
23
+ KnownHostRecord = OpenSSHUserRecordDescriptor(
24
+ "application/openssh/known_host",
25
+ [
26
+ ("datetime", "mtime_ts"),
27
+ *COMMON_ELLEMENTS,
28
+ ("string", "host"),
29
+ ("varint", "port"),
30
+ ("string", "public_key"),
31
+ ("string", "marker"),
32
+ ],
33
+ )
34
+
35
+
36
+ PrivateKeyRecord = OpenSSHUserRecordDescriptor(
37
+ "application/openssh/private_key",
38
+ [
39
+ ("datetime", "mtime_ts"),
40
+ *COMMON_ELLEMENTS,
41
+ ("string", "key_format"),
42
+ ("string", "public_key"),
43
+ ("boolean", "encrypted"),
44
+ ],
45
+ )
46
+
47
+ PublicKeyRecord = OpenSSHUserRecordDescriptor(
48
+ "application/openssh/public_key",
49
+ [
50
+ ("datetime", "mtime_ts"),
51
+ *COMMON_ELLEMENTS,
52
+ ("string", "public_key"),
53
+ ],
54
+ )
55
+
56
+
57
+ class SSHPlugin(NamespacePlugin):
58
+ __namespace__ = "ssh"
@@ -329,7 +329,7 @@ class RegistryPlugin(Plugin):
329
329
  @internal
330
330
  def get_user_details(self, key: RegistryKey) -> UserDetails:
331
331
  """Return user details for the user who owns a registry hive that contains the provided key"""
332
- if not key.hive or not key.hive.filepath:
332
+ if not key.hive or not getattr(key.hive, "filepath", None):
333
333
  return
334
334
 
335
335
  return self._hives_to_users.get(key.hive)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: dissect.target
3
- Version: 3.15.dev13
3
+ Version: 3.15.dev14
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
@@ -117,8 +117,10 @@ dissect/target/plugins/apps/remoteaccess/teamviewer.py,sha256=SiEH36HM2NvdPuCjfL
117
117
  dissect/target/plugins/apps/shell/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
118
118
  dissect/target/plugins/apps/shell/powershell.py,sha256=biPSMRWxPI6kRqP0-75yMtrw0Ti2Bzfl_xI3xbmmF48,2641
119
119
  dissect/target/plugins/apps/ssh/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
120
- dissect/target/plugins/apps/ssh/openssh.py,sha256=ebHX6_uoOHZMgHYa_MiaYjvhitrceYNjl_R71JBY5Ow,8154
121
- dissect/target/plugins/apps/ssh/opensshd.py,sha256=FWhEkaKVMAADoOO7yI2H3uHK4jmQoC4v_eOaj_4jaeY,5453
120
+ dissect/target/plugins/apps/ssh/openssh.py,sha256=jDNP8aq9JHivosexPlxWRUgeJo1MHclb336dzO1zRJc,7086
121
+ dissect/target/plugins/apps/ssh/opensshd.py,sha256=DaXKdgGF3GYHHA4buEvphcm6FF4C8YFjgD96Dv6rRnM,5510
122
+ dissect/target/plugins/apps/ssh/putty.py,sha256=N8ssjutUVN50JNA5fEIVISbP5sJ7bGTFidRbX3uNG5Y,9404
123
+ dissect/target/plugins/apps/ssh/ssh.py,sha256=uCaoWlT2bgKLUHA1aL6XymJDWJ8JmLsN8PB1C66eidY,1409
122
124
  dissect/target/plugins/apps/vpn/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
123
125
  dissect/target/plugins/apps/vpn/openvpn.py,sha256=NZeFSFgGAifevGIQBusdbBRFOPxu0584Th8rKE-XSus,6875
124
126
  dissect/target/plugins/apps/vpn/wireguard.py,sha256=45WvCqQQGrG3DVDH5ghcsGpM_BomF4RcTLzcIvnyuNs,6554
@@ -237,7 +239,7 @@ dissect/target/plugins/os/windows/locale.py,sha256=yXVdclpUqss9h8Nq7N4kg3OHwWGDf
237
239
  dissect/target/plugins/os/windows/notifications.py,sha256=64xHHueHwtJCc8RTAF70oa0RxvqfCu_DBPWRSZBnYZc,17386
238
240
  dissect/target/plugins/os/windows/prefetch.py,sha256=5hRxdIP9sIV5Q9TAScMjLbl_mImZ37abvdE_pAd6rh4,10398
239
241
  dissect/target/plugins/os/windows/recyclebin.py,sha256=4GSj0Q3YvONufnqANbnG0ffiMQyToCiL5s35Wmu4JOQ,4898
240
- dissect/target/plugins/os/windows/registry.py,sha256=HqO8Yw2TDKhhkR7fOochzinJN_nQ4qKKx7fJ34qh7tw,12770
242
+ dissect/target/plugins/os/windows/registry.py,sha256=IBRqltJ_4fZpVuwMVCAH_nS8JUaNVjsC1jh9AZSNHL4,12788
241
243
  dissect/target/plugins/os/windows/sam.py,sha256=Es_8ROQ6R6-akuTtegCdsJHXzZJNhzgoFuS8y9xNN8E,15267
242
244
  dissect/target/plugins/os/windows/services.py,sha256=_6YkuoZD8LUxk72R3n1p1bOBab3A1wszdB1NuPavIGM,6037
243
245
  dissect/target/plugins/os/windows/sru.py,sha256=sOM7CyMkW8XIXzI75GL69WoqUrSK2X99TFIfdQR2D64,17767
@@ -306,10 +308,10 @@ dissect/target/volumes/luks.py,sha256=v_mHW05KM5iG8JDe47i2V4Q9O0r4rnAMA9m_qc9cYw
306
308
  dissect/target/volumes/lvm.py,sha256=wwQVR9I3G9YzmY6UxFsH2Y4MXGBcKL9aayWGCDTiWMU,2269
307
309
  dissect/target/volumes/md.py,sha256=j1K1iKmspl0C_OJFc7-Q1BMWN2OCC5EVANIgVlJ_fIE,1673
308
310
  dissect/target/volumes/vmfs.py,sha256=-LoUbn9WNwTtLi_4K34uV_-wDw2W5hgaqxZNj4UmqAQ,1730
309
- dissect.target-3.15.dev13.dist-info/COPYRIGHT,sha256=m-9ih2RVhMiXHI2bf_oNSSgHgkeIvaYRVfKTwFbnJPA,301
310
- dissect.target-3.15.dev13.dist-info/LICENSE,sha256=DZak_2itbUtvHzD3E7GNUYSRK6jdOJ-GqncQ2weavLA,34523
311
- dissect.target-3.15.dev13.dist-info/METADATA,sha256=Z8KblBmaZNzFZ5Ud5_48fnD3IyiOh2jmHhvi5ZjlH8w,11107
312
- dissect.target-3.15.dev13.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
313
- dissect.target-3.15.dev13.dist-info/entry_points.txt,sha256=tvFPa-Ap-gakjaPwRc6Fl6mxHzxEZ_arAVU-IUYeo_s,447
314
- dissect.target-3.15.dev13.dist-info/top_level.txt,sha256=Mn-CQzEYsAbkxrUI0TnplHuXnGVKzxpDw_po_sXpvv4,8
315
- dissect.target-3.15.dev13.dist-info/RECORD,,
311
+ dissect.target-3.15.dev14.dist-info/COPYRIGHT,sha256=m-9ih2RVhMiXHI2bf_oNSSgHgkeIvaYRVfKTwFbnJPA,301
312
+ dissect.target-3.15.dev14.dist-info/LICENSE,sha256=DZak_2itbUtvHzD3E7GNUYSRK6jdOJ-GqncQ2weavLA,34523
313
+ dissect.target-3.15.dev14.dist-info/METADATA,sha256=a7gsrN05qksoHcTNVGfHVUklhofWCTLyKBiNfwWLru0,11107
314
+ dissect.target-3.15.dev14.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
315
+ dissect.target-3.15.dev14.dist-info/entry_points.txt,sha256=tvFPa-Ap-gakjaPwRc6Fl6mxHzxEZ_arAVU-IUYeo_s,447
316
+ dissect.target-3.15.dev14.dist-info/top_level.txt,sha256=Mn-CQzEYsAbkxrUI0TnplHuXnGVKzxpDw_po_sXpvv4,8
317
+ dissect.target-3.15.dev14.dist-info/RECORD,,