dissect.target 3.19.dev18__py3-none-any.whl → 3.19.dev19__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/loader.py +1 -0
- dissect/target/loaders/ab.py +285 -0
- dissect/target/plugins/os/unix/linux/android/_os.py +15 -24
- {dissect.target-3.19.dev18.dist-info → dissect.target-3.19.dev19.dist-info}/METADATA +1 -1
- {dissect.target-3.19.dev18.dist-info → dissect.target-3.19.dev19.dist-info}/RECORD +10 -9
- {dissect.target-3.19.dev18.dist-info → dissect.target-3.19.dev19.dist-info}/COPYRIGHT +0 -0
- {dissect.target-3.19.dev18.dist-info → dissect.target-3.19.dev19.dist-info}/LICENSE +0 -0
- {dissect.target-3.19.dev18.dist-info → dissect.target-3.19.dev19.dist-info}/WHEEL +0 -0
- {dissect.target-3.19.dev18.dist-info → dissect.target-3.19.dev19.dist-info}/entry_points.txt +0 -0
- {dissect.target-3.19.dev18.dist-info → dissect.target-3.19.dev19.dist-info}/top_level.txt +0 -0
dissect/target/loader.py
CHANGED
@@ -195,6 +195,7 @@ register("vma", "VmaLoader")
|
|
195
195
|
register("kape", "KapeLoader")
|
196
196
|
register("tanium", "TaniumLoader")
|
197
197
|
register("itunes", "ITunesLoader")
|
198
|
+
register("ab", "AndroidBackupLoader")
|
198
199
|
register("target", "TargetLoader")
|
199
200
|
register("log", "LogLoader")
|
200
201
|
# Disabling ResLoader because of DIS-536
|
@@ -0,0 +1,285 @@
|
|
1
|
+
import hashlib
|
2
|
+
import io
|
3
|
+
import posixpath
|
4
|
+
import shutil
|
5
|
+
import struct
|
6
|
+
from pathlib import Path
|
7
|
+
from typing import BinaryIO
|
8
|
+
|
9
|
+
try:
|
10
|
+
from Crypto.Cipher import AES
|
11
|
+
|
12
|
+
HAS_PYCRYPTODOME = True
|
13
|
+
except ImportError:
|
14
|
+
HAS_PYCRYPTODOME = False
|
15
|
+
|
16
|
+
from dissect.util.stream import AlignedStream, RelativeStream, ZlibStream
|
17
|
+
|
18
|
+
from dissect.target.exceptions import LoaderError
|
19
|
+
from dissect.target.filesystem import VirtualFilesystem
|
20
|
+
from dissect.target.filesystems.tar import TarFilesystem
|
21
|
+
from dissect.target.helpers import keychain
|
22
|
+
from dissect.target.loader import Loader
|
23
|
+
from dissect.target.plugins.os.unix.linux.android._os import AndroidPlugin
|
24
|
+
from dissect.target.target import Target
|
25
|
+
|
26
|
+
DIRECTORY_MAPPING = {
|
27
|
+
"a": "/data/app/{id}",
|
28
|
+
"f": "/data/data/{id}/files",
|
29
|
+
"db": "/data/data/{id}/databases",
|
30
|
+
"ef": "/storage/emulated/0/Android/data/{id}",
|
31
|
+
"sp": "/data/data/{id}/shared_preferences",
|
32
|
+
"r": "/data/data/{id}",
|
33
|
+
"obb": "/storage/emulated/0/Android/obb/{id}",
|
34
|
+
}
|
35
|
+
|
36
|
+
|
37
|
+
class AndroidBackupLoader(Loader):
|
38
|
+
"""Load Android backup files.
|
39
|
+
|
40
|
+
References:
|
41
|
+
- http://fileformats.archiveteam.org/wiki/Android_ADB_Backup
|
42
|
+
"""
|
43
|
+
|
44
|
+
def __init__(self, path: Path, **kwargs):
|
45
|
+
super().__init__(path)
|
46
|
+
self.ab = AndroidBackup(path.open("rb"))
|
47
|
+
|
48
|
+
if self.ab.encrypted:
|
49
|
+
for key in keychain.get_keys_for_provider("ab") + keychain.get_keys_without_provider():
|
50
|
+
if key.key_type == keychain.KeyType.PASSPHRASE:
|
51
|
+
try:
|
52
|
+
self.ab.unlock(key.value)
|
53
|
+
break
|
54
|
+
except ValueError:
|
55
|
+
continue
|
56
|
+
else:
|
57
|
+
raise LoaderError(f"Missing password for encrypted Android Backup: {self.path}")
|
58
|
+
|
59
|
+
@staticmethod
|
60
|
+
def detect(path: Path) -> bool:
|
61
|
+
if path.suffix.lower() == ".ab":
|
62
|
+
return True
|
63
|
+
|
64
|
+
if path.is_file():
|
65
|
+
# The file extension can be chosen freely so let's also test for the file magic
|
66
|
+
with path.open("rb") as fh:
|
67
|
+
if fh.read(15) == b"ANDROID BACKUP\n":
|
68
|
+
return True
|
69
|
+
|
70
|
+
return False
|
71
|
+
|
72
|
+
def map(self, target: Target) -> None:
|
73
|
+
if self.ab.compressed or self.ab.encrypted:
|
74
|
+
if self.ab.compressed and not self.ab.encrypted:
|
75
|
+
word = "compressed"
|
76
|
+
elif self.ab.encrypted and not self.ab.compressed:
|
77
|
+
word = "encrypted"
|
78
|
+
else:
|
79
|
+
word = "compressed and encrypted"
|
80
|
+
|
81
|
+
target.log.warning(
|
82
|
+
f"Backup file is {word}, consider unwrapping with "
|
83
|
+
"`python -m dissect.target.loaders.ab <path/to/backup.ab>`"
|
84
|
+
)
|
85
|
+
|
86
|
+
vfs = VirtualFilesystem(case_sensitive=False)
|
87
|
+
|
88
|
+
fs = TarFilesystem(self.ab.open())
|
89
|
+
for app in fs.path("/apps").iterdir():
|
90
|
+
for subdir in app.iterdir():
|
91
|
+
if subdir.name not in DIRECTORY_MAPPING:
|
92
|
+
continue
|
93
|
+
|
94
|
+
path = DIRECTORY_MAPPING[subdir.name].format(id=app.name)
|
95
|
+
|
96
|
+
# TODO: Remove once we move towards "directory entries"
|
97
|
+
entry = subdir.get()
|
98
|
+
entry.name = posixpath.basename(path)
|
99
|
+
|
100
|
+
vfs.map_file_entry(path, entry)
|
101
|
+
|
102
|
+
target.filesystems.add(vfs)
|
103
|
+
target._os_plugin = AndroidPlugin.create(target, vfs)
|
104
|
+
|
105
|
+
|
106
|
+
class AndroidBackup:
|
107
|
+
def __init__(self, fh: BinaryIO):
|
108
|
+
self.fh = fh
|
109
|
+
|
110
|
+
size = fh.seek(0, io.SEEK_END)
|
111
|
+
fh.seek(0)
|
112
|
+
|
113
|
+
# Don't readline() straight away as we may be reading something other than a backup file
|
114
|
+
magic = fh.read(15)
|
115
|
+
if magic != b"ANDROID BACKUP\n":
|
116
|
+
raise ValueError("Not a valid Android Backup file")
|
117
|
+
|
118
|
+
self.version = int(fh.read(2)[:1])
|
119
|
+
self.compressed = bool(int(fh.read(2)[:1]))
|
120
|
+
|
121
|
+
self.encrypted = False
|
122
|
+
self.unlocked = True
|
123
|
+
self.encryption = fh.readline().strip().decode()
|
124
|
+
|
125
|
+
if self.encryption != "none":
|
126
|
+
self.encrypted = True
|
127
|
+
self.unlocked = False
|
128
|
+
self._user_salt = bytes.fromhex(fh.readline().strip().decode())
|
129
|
+
self._ck_salt = bytes.fromhex(fh.readline().strip().decode())
|
130
|
+
self._rounds = int(fh.readline().strip())
|
131
|
+
self._user_iv = bytes.fromhex(fh.readline().strip().decode())
|
132
|
+
self._master_key = bytes.fromhex(fh.readline().strip().decode())
|
133
|
+
|
134
|
+
self._mk = None
|
135
|
+
self._iv = None
|
136
|
+
|
137
|
+
self._data_offset = fh.tell()
|
138
|
+
self.size = size - self._data_offset
|
139
|
+
|
140
|
+
def unlock(self, password: str) -> None:
|
141
|
+
if not self.encrypted:
|
142
|
+
raise ValueError("Android Backup is not encrypted")
|
143
|
+
|
144
|
+
self._mk, self._iv = self._decrypt_mk(password)
|
145
|
+
self.unlocked = True
|
146
|
+
|
147
|
+
def _decrypt_mk(self, password: str) -> tuple[bytes, bytes]:
|
148
|
+
user_key = hashlib.pbkdf2_hmac("sha1", password.encode(), self._user_salt, self._rounds, 32)
|
149
|
+
|
150
|
+
blob = AES.new(user_key, AES.MODE_CBC, iv=self._user_iv).decrypt(self._master_key)
|
151
|
+
blob = blob[: -blob[-1]]
|
152
|
+
|
153
|
+
offset = 0
|
154
|
+
iv_len = blob[offset]
|
155
|
+
offset += 1
|
156
|
+
iv = blob[offset : offset + iv_len]
|
157
|
+
|
158
|
+
offset += iv_len
|
159
|
+
mk_len = blob[offset]
|
160
|
+
offset += 1
|
161
|
+
mk = blob[offset : offset + mk_len]
|
162
|
+
|
163
|
+
offset += mk_len
|
164
|
+
checksum_len = blob[offset]
|
165
|
+
offset += 1
|
166
|
+
checksum = blob[offset : offset + checksum_len]
|
167
|
+
|
168
|
+
ck_mk = _encode_bytes(mk) if self.version >= 2 else mk
|
169
|
+
our_checksum = hashlib.pbkdf2_hmac("sha1", ck_mk, self._ck_salt, self._rounds, 32)
|
170
|
+
if our_checksum != checksum:
|
171
|
+
# Try reverse encoding for good measure
|
172
|
+
ck_mk = mk if self.version >= 2 else _encode_bytes(mk)
|
173
|
+
our_checksum = hashlib.pbkdf2_hmac("sha1", ck_mk, self._ck_salt, self._rounds, 32)
|
174
|
+
|
175
|
+
if our_checksum != checksum:
|
176
|
+
raise ValueError("Invalid password: master key checksum does not match")
|
177
|
+
|
178
|
+
return mk, iv
|
179
|
+
|
180
|
+
def open(self) -> BinaryIO:
|
181
|
+
fh = RelativeStream(self.fh, self._data_offset)
|
182
|
+
|
183
|
+
if self.encrypted:
|
184
|
+
if not self.unlocked:
|
185
|
+
raise ValueError("Missing password for encrypted Android Backup")
|
186
|
+
fh = CipherStream(fh, self._mk, self._iv, self.size)
|
187
|
+
|
188
|
+
if self.compressed:
|
189
|
+
fh = ZlibStream(fh)
|
190
|
+
|
191
|
+
return fh
|
192
|
+
|
193
|
+
|
194
|
+
class CipherStream(AlignedStream):
|
195
|
+
"""Transparently AES-CBC decrypted stream."""
|
196
|
+
|
197
|
+
def __init__(self, fh: BinaryIO, key: bytes, iv: bytes, size: int):
|
198
|
+
self._fh = fh
|
199
|
+
|
200
|
+
self._key = key
|
201
|
+
self._iv = iv
|
202
|
+
self._cipher = None
|
203
|
+
self._cipher_offset = 0
|
204
|
+
self._reset_cipher()
|
205
|
+
|
206
|
+
super().__init__(size)
|
207
|
+
|
208
|
+
def _reset_cipher(self) -> None:
|
209
|
+
self._cipher = AES.new(self._key, AES.MODE_CBC, iv=self._iv)
|
210
|
+
self._cipher_offset = 0
|
211
|
+
|
212
|
+
def _seek_cipher(self, offset: int) -> None:
|
213
|
+
"""CBC is dependent on previous blocks so to seek the cipher, decrypt and discard to the wanted offset."""
|
214
|
+
if offset < self._cipher_offset:
|
215
|
+
self._reset_cipher()
|
216
|
+
self._fh.seek(0)
|
217
|
+
|
218
|
+
while self._cipher_offset < offset:
|
219
|
+
read_size = min(offset - self._cipher_offset, self.align)
|
220
|
+
self._cipher.decrypt(self._fh.read(read_size))
|
221
|
+
self._cipher_offset += read_size
|
222
|
+
|
223
|
+
def _read(self, offset: int, length: int) -> bytes:
|
224
|
+
self._seek_cipher(offset)
|
225
|
+
|
226
|
+
data = self._cipher.decrypt(self._fh.read(length))
|
227
|
+
if offset + length >= self.size:
|
228
|
+
# Remove padding
|
229
|
+
data = data[: -data[-1]]
|
230
|
+
|
231
|
+
self._cipher_offset += len(data)
|
232
|
+
|
233
|
+
return data
|
234
|
+
|
235
|
+
|
236
|
+
def _encode_bytes(buf: bytes) -> bytes:
|
237
|
+
# Emulate byte[] -> char[] -> utf8 byte[] casting
|
238
|
+
return struct.pack(">32h", *struct.unpack(">32b", buf)).decode("utf-16-be").encode("utf-8")
|
239
|
+
|
240
|
+
|
241
|
+
def main() -> None:
|
242
|
+
import argparse
|
243
|
+
|
244
|
+
parser = argparse.ArgumentParser(description="Android Backup file unwrapper")
|
245
|
+
parser.add_argument("path", type=Path, help="source path")
|
246
|
+
parser.add_argument("-p", "--password", help="encryption password")
|
247
|
+
parser.add_argument("-t", "--tar", action="store_true", help="write a tar file instead of a plain Android Backup")
|
248
|
+
parser.add_argument("-o", "--output", type=Path, help="output path")
|
249
|
+
args = parser.parse_args()
|
250
|
+
|
251
|
+
if not args.path.is_file():
|
252
|
+
parser.exit("source path does not exist or is not a file")
|
253
|
+
|
254
|
+
ext = ".tar" if args.tar else ".plain.ab"
|
255
|
+
if args.output is None:
|
256
|
+
output = args.path.with_suffix(ext)
|
257
|
+
elif args.output.is_dir():
|
258
|
+
output = args.output.joinpath(args.path.name).with_suffix(ext)
|
259
|
+
else:
|
260
|
+
output = args.output
|
261
|
+
|
262
|
+
if output.exists():
|
263
|
+
parser.exit(f"output path already exists: {output}")
|
264
|
+
|
265
|
+
print(f"unwrapping {args.path} -> {output}")
|
266
|
+
with args.path.open("rb") as fh:
|
267
|
+
ab = AndroidBackup(fh)
|
268
|
+
|
269
|
+
if ab.encrypted:
|
270
|
+
if not args.password:
|
271
|
+
parser.exit("missing password for encrypted Android Backup")
|
272
|
+
ab.unlock(args.password)
|
273
|
+
|
274
|
+
with ab.open() as fhab, output.open("wb") as fhout:
|
275
|
+
if not args.tar:
|
276
|
+
fhout.write(b"ANDROID BACKUP\n") # header
|
277
|
+
fhout.write(b"5\n") # version
|
278
|
+
fhout.write(b"0\n") # compressed
|
279
|
+
fhout.write(b"none\n") # encryption
|
280
|
+
|
281
|
+
shutil.copyfileobj(fhab, fhout, 1024 * 1024 * 64)
|
282
|
+
|
283
|
+
|
284
|
+
if __name__ == "__main__":
|
285
|
+
main()
|
@@ -1,33 +1,23 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
|
-
from typing import Iterator, Optional
|
3
|
+
from typing import Iterator, Optional
|
4
4
|
|
5
5
|
from dissect.target.filesystem import Filesystem
|
6
|
-
from dissect.target.helpers
|
6
|
+
from dissect.target.helpers import configutil
|
7
|
+
from dissect.target.helpers.record import EmptyRecord
|
7
8
|
from dissect.target.plugin import OperatingSystem, export
|
8
9
|
from dissect.target.plugins.os.unix.linux._os import LinuxPlugin
|
9
10
|
from dissect.target.target import Target
|
10
11
|
|
11
12
|
|
12
|
-
class BuildProp:
|
13
|
-
def __init__(self, fh: TextIO):
|
14
|
-
self.props = {}
|
15
|
-
|
16
|
-
for line in fh:
|
17
|
-
line = line.strip()
|
18
|
-
|
19
|
-
if not line or line.startswith("#"):
|
20
|
-
continue
|
21
|
-
|
22
|
-
k, v = line.split("=")
|
23
|
-
self.props[k] = v
|
24
|
-
|
25
|
-
|
26
13
|
class AndroidPlugin(LinuxPlugin):
|
27
14
|
def __init__(self, target: Target):
|
28
15
|
super().__init__(target)
|
29
16
|
self.target = target
|
30
|
-
|
17
|
+
|
18
|
+
self.props = {}
|
19
|
+
if (build_prop := self.target.fs.path("/build.prop")).exists():
|
20
|
+
self.props = configutil.parse(build_prop, separator=("=",), comment_prefixes=("#",)).parsed_data
|
31
21
|
|
32
22
|
@classmethod
|
33
23
|
def detect(cls, target: Target) -> Optional[Filesystem]:
|
@@ -42,8 +32,8 @@ class AndroidPlugin(LinuxPlugin):
|
|
42
32
|
return cls(target)
|
43
33
|
|
44
34
|
@export(property=True)
|
45
|
-
def hostname(self) -> str:
|
46
|
-
return self.props.
|
35
|
+
def hostname(self) -> Optional[str]:
|
36
|
+
return self.props.get("ro.build.host")
|
47
37
|
|
48
38
|
@export(property=True)
|
49
39
|
def ips(self) -> list[str]:
|
@@ -53,11 +43,11 @@ class AndroidPlugin(LinuxPlugin):
|
|
53
43
|
def version(self) -> str:
|
54
44
|
full_version = "Android"
|
55
45
|
|
56
|
-
release_version = self.props.
|
57
|
-
if release_version := self.props.
|
46
|
+
release_version = self.props.get("ro.build.version.release")
|
47
|
+
if release_version := self.props.get("ro.build.version.release"):
|
58
48
|
full_version += f" {release_version}"
|
59
49
|
|
60
|
-
if security_patch_version := self.props.
|
50
|
+
if security_patch_version := self.props.get("ro.build.version.security_patch"):
|
61
51
|
full_version += f" ({security_patch_version})"
|
62
52
|
|
63
53
|
return full_version
|
@@ -66,5 +56,6 @@ class AndroidPlugin(LinuxPlugin):
|
|
66
56
|
def os(self) -> str:
|
67
57
|
return OperatingSystem.ANDROID.value
|
68
58
|
|
69
|
-
|
70
|
-
|
59
|
+
@export(record=EmptyRecord)
|
60
|
+
def users(self) -> Iterator[EmptyRecord]:
|
61
|
+
yield from ()
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: dissect.target
|
3
|
-
Version: 3.19.
|
3
|
+
Version: 3.19.dev19
|
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
|
@@ -2,7 +2,7 @@ dissect/target/__init__.py,sha256=Oc7ounTgq2hE4nR6YcNabetc7SQA40ldSa35VEdZcQU,63
|
|
2
2
|
dissect/target/container.py,sha256=0YcwcGmfJjhPXUB6DEcjWEoSuAtTDxMDpoTviMrLsxM,9353
|
3
3
|
dissect/target/exceptions.py,sha256=ULi7NXlqju_d8KENEL3aimmfKTFfbNssfeWhAnOB654,2972
|
4
4
|
dissect/target/filesystem.py,sha256=G1gbOUpnQZyovubYGEUKgaDV0eHH5vE83-0gTc5PZAM,59793
|
5
|
-
dissect/target/loader.py,sha256=
|
5
|
+
dissect/target/loader.py,sha256=I8WNzDA0SMy42F7zfyBcSKj_VKNv64213WUvtGZ77qE,7374
|
6
6
|
dissect/target/plugin.py,sha256=HAN8maaDt-Rlqt8Rr1IW7gXQpzNQZjCVz-i4aSPphSw,48677
|
7
7
|
dissect/target/report.py,sha256=06uiP4MbNI8cWMVrC1SasNS-Yg6ptjVjckwj8Yhe0Js,7958
|
8
8
|
dissect/target/target.py,sha256=8vg0VdEQuy5Ih5ewlm0b64o3HcJq_Nley4Ygyp2fLI4,32362
|
@@ -75,6 +75,7 @@ dissect/target/helpers/compat/path_39.py,sha256=FIyZ3sb-XQhJnm02jVdOc6ncjCWa9OVx
|
|
75
75
|
dissect/target/helpers/compat/path_common.py,sha256=X9mAPoP6E5e_1idiZz7-FPRsOwcAjQ5FP70k30s_yMA,7739
|
76
76
|
dissect/target/helpers/data/windowsZones.xml,sha256=4OijeR7oxI0ZwPTSwCkmtcofOsUCjSnbZ4dQxVOM_4o,50005
|
77
77
|
dissect/target/loaders/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
78
|
+
dissect/target/loaders/ab.py,sha256=iwj1LHe_-VaBmj6d-nKrvim1UvrJ-nzp2LlgCFlOuUk,9484
|
78
79
|
dissect/target/loaders/ad1.py,sha256=1_VmPZckDzXVvNF-HNtoUZqabnhCKBLUD3vVaitHQ00,571
|
79
80
|
dissect/target/loaders/asdf.py,sha256=dvPPDBrnz2JPXpCbqsu-NgQWIdVGMOit2KAdhIO1iiQ,972
|
80
81
|
dissect/target/loaders/cb.py,sha256=EGhdytBKBdofTd89juavDZZbmupEZmMBadeUXvVIK20,6612
|
@@ -222,7 +223,7 @@ dissect/target/plugins/os/unix/linux/processes.py,sha256=rvDJWAp16WAJZ91A8_GJJIj
|
|
222
223
|
dissect/target/plugins/os/unix/linux/services.py,sha256=-d2y073mOXUM3XCzRgDVCRFR9eTLoVuN8FsZVewHzRg,4075
|
223
224
|
dissect/target/plugins/os/unix/linux/sockets.py,sha256=CXstlQt0tLcVSpvi0xOXJu580O6BGUBW3lJQt20aMUw,9920
|
224
225
|
dissect/target/plugins/os/unix/linux/android/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
225
|
-
dissect/target/plugins/os/unix/linux/android/_os.py,sha256
|
226
|
+
dissect/target/plugins/os/unix/linux/android/_os.py,sha256=-VWLkL3mFAr-ZxwH1qdSPNLvOgbYN92d4HyM2LSty5w,1947
|
226
227
|
dissect/target/plugins/os/unix/linux/debian/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
227
228
|
dissect/target/plugins/os/unix/linux/debian/_os.py,sha256=GI19ZqcyfZ1mUYg2NCx93HkZje9MWMU8FYNKQv-G6go,498
|
228
229
|
dissect/target/plugins/os/unix/linux/debian/apt.py,sha256=dkTfLrS-MS8wfrXILFLHDoLqBkM_w16KTRQ7ysiZkZY,4316
|
@@ -344,10 +345,10 @@ dissect/target/volumes/luks.py,sha256=OmCMsw6rCUXG1_plnLVLTpsvE1n_6WtoRUGQbpmu1z
|
|
344
345
|
dissect/target/volumes/lvm.py,sha256=wwQVR9I3G9YzmY6UxFsH2Y4MXGBcKL9aayWGCDTiWMU,2269
|
345
346
|
dissect/target/volumes/md.py,sha256=j1K1iKmspl0C_OJFc7-Q1BMWN2OCC5EVANIgVlJ_fIE,1673
|
346
347
|
dissect/target/volumes/vmfs.py,sha256=-LoUbn9WNwTtLi_4K34uV_-wDw2W5hgaqxZNj4UmqAQ,1730
|
347
|
-
dissect.target-3.19.
|
348
|
-
dissect.target-3.19.
|
349
|
-
dissect.target-3.19.
|
350
|
-
dissect.target-3.19.
|
351
|
-
dissect.target-3.19.
|
352
|
-
dissect.target-3.19.
|
353
|
-
dissect.target-3.19.
|
348
|
+
dissect.target-3.19.dev19.dist-info/COPYRIGHT,sha256=m-9ih2RVhMiXHI2bf_oNSSgHgkeIvaYRVfKTwFbnJPA,301
|
349
|
+
dissect.target-3.19.dev19.dist-info/LICENSE,sha256=DZak_2itbUtvHzD3E7GNUYSRK6jdOJ-GqncQ2weavLA,34523
|
350
|
+
dissect.target-3.19.dev19.dist-info/METADATA,sha256=1_oiRc_YPQzlraRQsEAAfx7MTXUAjB505xvN06L7us0,12719
|
351
|
+
dissect.target-3.19.dev19.dist-info/WHEEL,sha256=R0nc6qTxuoLk7ShA2_Y-UWkN8ZdfDBG2B6Eqpz2WXbs,91
|
352
|
+
dissect.target-3.19.dev19.dist-info/entry_points.txt,sha256=tvFPa-Ap-gakjaPwRc6Fl6mxHzxEZ_arAVU-IUYeo_s,447
|
353
|
+
dissect.target-3.19.dev19.dist-info/top_level.txt,sha256=Mn-CQzEYsAbkxrUI0TnplHuXnGVKzxpDw_po_sXpvv4,8
|
354
|
+
dissect.target-3.19.dev19.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
{dissect.target-3.19.dev18.dist-info → dissect.target-3.19.dev19.dist-info}/entry_points.txt
RENAMED
File without changes
|
File without changes
|