dissect.target 3.19.dev17__py3-none-any.whl → 3.19.dev19__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
 
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, TextIO
3
+ from typing import Iterator, Optional
4
4
 
5
5
  from dissect.target.filesystem import Filesystem
6
- from dissect.target.helpers.record import UnixUserRecord
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
- self.props = BuildProp(self.target.fs.path("/build.prop").open("rt"))
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.props["ro.build.host"]
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.props.get("ro.build.version.release")
57
- if release_version := self.props.props.get("ro.build.version.release"):
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.props.get("ro.build.version.security_patch"):
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
- def users(self) -> Iterator[UnixUserRecord]:
70
- raise NotImplementedError()
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.dev17
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=hjKInZAEcv43RiqxZJ0yBI4Y2YZ2-nrsKWu_BKrgba4,7336
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
@@ -46,7 +46,7 @@ dissect/target/filesystems/zip.py,sha256=WT1bQhzX_1MXXVZTKrJniae4xqRqMZ8FsfbvhgG
46
46
  dissect/target/helpers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
47
47
  dissect/target/helpers/cache.py,sha256=TXlJBdFRz6V9zKs903am4Yawr0maYw5kZY0RqklDQJM,8568
48
48
  dissect/target/helpers/config.py,sha256=6917CZ6eDHaK_tOoiVEIndyhRXO6r6eCBIleq6f47PQ,2346
49
- dissect/target/helpers/configutil.py,sha256=wUVnfygXKargH1cAojGbTfgLffXYhbMZOrDP0BUwzdI,25638
49
+ dissect/target/helpers/configutil.py,sha256=SLJFt_k5ezm7CIr_mcZqVrV-2YrcX5D48KgyC9oyWGw,27645
50
50
  dissect/target/helpers/cyber.py,sha256=WnJlk-HqAETmDAgLq92JPxyDLxvzSoFV_WrO-odVKBI,16805
51
51
  dissect/target/helpers/descriptor_extensions.py,sha256=uT8GwznfDAiIgMM7JKKOY0PXKMv2c0GCqJTCkWFgops,2605
52
52
  dissect/target/helpers/docs.py,sha256=J5U65Y3yOTqxDEZRCdrEmO63XQCeDzOJea1PwPM6Cyc,5146
@@ -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=trmESlpHdwVu7wV18RevEhh_TsVyfKPFCd5Usb5-fSU,2056
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.dev17.dist-info/COPYRIGHT,sha256=m-9ih2RVhMiXHI2bf_oNSSgHgkeIvaYRVfKTwFbnJPA,301
348
- dissect.target-3.19.dev17.dist-info/LICENSE,sha256=DZak_2itbUtvHzD3E7GNUYSRK6jdOJ-GqncQ2weavLA,34523
349
- dissect.target-3.19.dev17.dist-info/METADATA,sha256=X8_Xig8_3plt8hHkWQbqILH5AFS3OjCmN1je1MyG1p8,12719
350
- dissect.target-3.19.dev17.dist-info/WHEEL,sha256=Wyh-_nZ0DJYolHNn1_hMa4lM7uDedD_RGVwbmTjyItk,91
351
- dissect.target-3.19.dev17.dist-info/entry_points.txt,sha256=tvFPa-Ap-gakjaPwRc6Fl6mxHzxEZ_arAVU-IUYeo_s,447
352
- dissect.target-3.19.dev17.dist-info/top_level.txt,sha256=Mn-CQzEYsAbkxrUI0TnplHuXnGVKzxpDw_po_sXpvv4,8
353
- dissect.target-3.19.dev17.dist-info/RECORD,,
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,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (71.1.0)
2
+ Generator: setuptools (72.1.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5