dissect.target 3.19.dev28__py3-none-any.whl → 3.19.dev30__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.
@@ -3,6 +3,7 @@ import json
3
3
  import logging
4
4
  from base64 import b64decode
5
5
  from hashlib import pbkdf2_hmac, sha1
6
+ from itertools import chain
6
7
  from typing import Iterator, Optional
7
8
 
8
9
  from dissect.sql import sqlite3
@@ -14,7 +15,7 @@ from dissect.target.exceptions import FileNotFoundError, UnsupportedPluginError
14
15
  from dissect.target.helpers.descriptor_extensions import UserRecordDescriptorExtension
15
16
  from dissect.target.helpers.fsutil import TargetPath
16
17
  from dissect.target.helpers.record import create_extended_descriptor
17
- from dissect.target.plugin import export
18
+ from dissect.target.plugin import OperatingSystem, export
18
19
  from dissect.target.plugins.apps.browser.browser import (
19
20
  GENERIC_COOKIE_FIELDS,
20
21
  GENERIC_DOWNLOAD_RECORD_FIELDS,
@@ -24,7 +25,7 @@ from dissect.target.plugins.apps.browser.browser import (
24
25
  BrowserPlugin,
25
26
  try_idna,
26
27
  )
27
- from dissect.target.plugins.general.users import UserDetails
28
+ from dissect.target.plugins.general.users import UserRecord
28
29
 
29
30
  try:
30
31
  from asn1crypto import algos, core
@@ -44,7 +45,10 @@ try:
44
45
  except ImportError:
45
46
  HAS_CRYPTO = False
46
47
 
47
- FIREFOX_EXTENSION_RECORD_FIELDS = [("uri", "source_uri"), ("string[]", "optional_permissions")]
48
+ FIREFOX_EXTENSION_RECORD_FIELDS = [
49
+ ("uri", "source_uri"),
50
+ ("string[]", "optional_permissions"),
51
+ ]
48
52
 
49
53
  log = logging.getLogger(__name__)
50
54
 
@@ -54,7 +58,7 @@ class FirefoxPlugin(BrowserPlugin):
54
58
 
55
59
  __namespace__ = "firefox"
56
60
 
57
- DIRS = [
61
+ USER_DIRS = [
58
62
  # Windows
59
63
  "AppData/Roaming/Mozilla/Firefox/Profiles",
60
64
  "AppData/local/Mozilla/Firefox/Profiles",
@@ -66,6 +70,10 @@ class FirefoxPlugin(BrowserPlugin):
66
70
  "Library/Application Support/Firefox",
67
71
  ]
68
72
 
73
+ SYSTEM_DIRS = [
74
+ "/data/data/org.mozilla.vrbrowser/files/mozilla",
75
+ ]
76
+
69
77
  BrowserHistoryRecord = create_extended_descriptor([UserRecordDescriptorExtension])(
70
78
  "browser/firefox/history", GENERIC_HISTORY_RECORD_FIELDS
71
79
  )
@@ -79,7 +87,8 @@ class FirefoxPlugin(BrowserPlugin):
79
87
  )
80
88
 
81
89
  BrowserExtensionRecord = create_extended_descriptor([UserRecordDescriptorExtension])(
82
- "browser/firefox/extension", GENERIC_EXTENSION_RECORD_FIELDS + FIREFOX_EXTENSION_RECORD_FIELDS
90
+ "browser/firefox/extension",
91
+ GENERIC_EXTENSION_RECORD_FIELDS + FIREFOX_EXTENSION_RECORD_FIELDS,
83
92
  )
84
93
 
85
94
  BrowserPasswordRecord = create_extended_descriptor([UserRecordDescriptorExtension])(
@@ -88,27 +97,32 @@ class FirefoxPlugin(BrowserPlugin):
88
97
 
89
98
  def __init__(self, target):
90
99
  super().__init__(target)
91
- self.users_dirs: list[tuple[UserDetails, TargetPath]] = []
100
+ self.dirs: list[tuple[UserRecord, TargetPath]] = []
101
+
92
102
  for user_details in self.target.user_details.all_with_home():
93
- for directory in self.DIRS:
103
+ for directory in self.USER_DIRS:
94
104
  cur_dir = user_details.home_path.joinpath(directory)
95
105
  if not cur_dir.exists():
96
106
  continue
97
- self.users_dirs.append((user_details, cur_dir))
107
+ self.dirs.append((user_details.user, cur_dir))
108
+
109
+ for directory in self.SYSTEM_DIRS:
110
+ if (cur_dir := target.fs.path(directory)).exists():
111
+ self.dirs.append((None, cur_dir))
98
112
 
99
113
  def check_compatible(self) -> None:
100
- if not len(self.users_dirs):
114
+ if not len(self.dirs):
101
115
  raise UnsupportedPluginError("No Firefox directories found")
102
116
 
103
- def _iter_profiles(self) -> Iterator[tuple[UserDetails, TargetPath, TargetPath]]:
117
+ def _iter_profiles(self) -> Iterator[tuple[UserRecord, TargetPath, TargetPath]]:
104
118
  """Yield user directories."""
105
- for user, cur_dir in self.users_dirs:
119
+ for user, cur_dir in self.dirs:
106
120
  for profile_dir in cur_dir.iterdir():
107
121
  if not profile_dir.is_dir():
108
122
  continue
109
123
  yield user, cur_dir, profile_dir
110
124
 
111
- def _iter_db(self, filename: str) -> Iterator[tuple[UserDetails, SQLite3]]:
125
+ def _iter_db(self, filename: str) -> Iterator[tuple[UserRecord, SQLite3]]:
112
126
  """Yield opened history database files of all users.
113
127
 
114
128
  Args:
@@ -117,12 +131,24 @@ class FirefoxPlugin(BrowserPlugin):
117
131
  Yields:
118
132
  Opened SQLite3 databases.
119
133
  """
120
- for user, cur_dir, profile_dir in self._iter_profiles():
121
- db_file = profile_dir.joinpath(filename)
134
+ iter_system = ((None, system_dir, None) for user, system_dir in self.dirs if user is None)
135
+
136
+ for user, cur_dir, profile_dir in chain(iter_system, self._iter_profiles()):
137
+ if user is None and profile_dir is None:
138
+ db_file = cur_dir.parent.joinpath(filename)
139
+ # On some Android variants, some files may exist in the base directory (places.sqlite) but others
140
+ # in a nested profile directory (cookies.sqlite)
141
+ # /data/data/org.mozilla.vrbrowser/files/places.sqlite
142
+ # /data/data/org.mozilla.vrbrowser/files/mozilla/xxxxxx.default/cookies.sqlite
143
+ if not db_file.exists():
144
+ continue
145
+ else:
146
+ db_file = profile_dir.joinpath(filename)
147
+
122
148
  try:
123
149
  yield user, db_file, sqlite3.SQLite3(db_file.open())
124
150
  except FileNotFoundError:
125
- self.target.log.warning("Could not find %s file: %s", filename, db_file)
151
+ self.target.log.info("Could not find %s file: %s", filename, db_file)
126
152
  except SQLError as e:
127
153
  self.target.log.warning("Could not open %s file: %s", filename, db_file)
128
154
  self.target.log.debug("", exc_info=e)
@@ -151,6 +177,11 @@ class FirefoxPlugin(BrowserPlugin):
151
177
  from_url (uri): URL of the "from" visit.
152
178
  source: (path): The source file of the history record.
153
179
  """
180
+ if self.target.os == OperatingSystem.ANDROID:
181
+ from_timestamp = from_unix_ms
182
+ else:
183
+ from_timestamp = from_unix_us
184
+
154
185
  for user, db_file, db in self._iter_db("places.sqlite"):
155
186
  try:
156
187
  places = {row.id: row for row in db.table("moz_places").rows()}
@@ -167,7 +198,7 @@ class FirefoxPlugin(BrowserPlugin):
167
198
  from_visit, from_place = None, None
168
199
 
169
200
  yield self.BrowserHistoryRecord(
170
- ts=from_unix_us(row.visit_date),
201
+ ts=from_timestamp(row.visit_date),
171
202
  browser="firefox",
172
203
  id=row.id,
173
204
  url=try_idna(place.url),
@@ -183,7 +214,7 @@ class FirefoxPlugin(BrowserPlugin):
183
214
  from_url=try_idna(from_place.url) if from_place else None,
184
215
  source=db_file,
185
216
  _target=self.target,
186
- _user=user.user,
217
+ _user=user,
187
218
  )
188
219
  except SQLError as e:
189
220
  self.target.log.warning("Error processing history file: %s", db_file, exc_info=e)
@@ -229,7 +260,7 @@ class FirefoxPlugin(BrowserPlugin):
229
260
  same_site=bool(cookie.sameSite),
230
261
  source=db_file,
231
262
  _target=self.target,
232
- _user=user.user,
263
+ _user=user,
233
264
  )
234
265
  except SQLError as e:
235
266
  self.target.log.warning("Error processing cookie file: %s", db_file, exc_info=e)
@@ -255,7 +286,10 @@ class FirefoxPlugin(BrowserPlugin):
255
286
  for user, db_file, db in self._iter_db("places.sqlite"):
256
287
  try:
257
288
  places = {row.id: row for row in db.table("moz_places").rows()}
258
- attributes = {row.id: row.name for row in db.table("moz_anno_attributes").rows()}
289
+ if not (moz_anno_attributes := db.table("moz_anno_attributes")):
290
+ continue
291
+
292
+ attributes = {row.id: row.name for row in moz_anno_attributes.rows()}
259
293
  annotations = {}
260
294
 
261
295
  for row in db.table("moz_annos"):
@@ -316,7 +350,7 @@ class FirefoxPlugin(BrowserPlugin):
316
350
  state=state,
317
351
  source=db_file,
318
352
  _target=self.target,
319
- _user=user.user,
353
+ _user=user,
320
354
  )
321
355
  except SQLError as e:
322
356
  self.target.log.warning("Error processing history file: %s", db_file, exc_info=e)
@@ -351,7 +385,9 @@ class FirefoxPlugin(BrowserPlugin):
351
385
 
352
386
  if not extension_file.exists():
353
387
  self.target.log.warning(
354
- "No 'extensions.json' addon file found for user %s in directory %s", user.user.name, profile_dir
388
+ "No 'extensions.json' addon file found for user %s in directory %s",
389
+ user.name,
390
+ profile_dir,
355
391
  )
356
392
  continue
357
393
 
@@ -360,8 +396,8 @@ class FirefoxPlugin(BrowserPlugin):
360
396
 
361
397
  for extension in extensions.get("addons", []):
362
398
  yield self.BrowserExtensionRecord(
363
- ts_install=extension.get("installDate", 0) // 1000,
364
- ts_update=extension.get("updateDate", 0) // 1000,
399
+ ts_install=from_unix_ms(extension.get("installDate", 0)),
400
+ ts_update=from_unix_ms(extension.get("updateDate", 0)),
365
401
  browser="firefox",
366
402
  id=extension.get("id"),
367
403
  name=(extension.get("defaultLocale", {}) or {}).get("name"),
@@ -377,12 +413,14 @@ class FirefoxPlugin(BrowserPlugin):
377
413
  optional_permissions=(extension.get("optionalPermissions", {}) or {}).get("permissions"),
378
414
  source=extension_file,
379
415
  _target=self.target,
380
- _user=user.user,
416
+ _user=user,
381
417
  )
382
418
 
383
419
  except FileNotFoundError:
384
420
  self.target.log.info(
385
- "No 'extensions.json' addon file found for user %s in directory %s", user.user.name, profile_dir
421
+ "No 'extensions.json' addon file found for user %s in directory %s",
422
+ user.name,
423
+ profile_dir,
386
424
  )
387
425
  except json.JSONDecodeError:
388
426
  self.target.log.warning(
@@ -410,7 +448,9 @@ class FirefoxPlugin(BrowserPlugin):
410
448
 
411
449
  if not login_file.exists():
412
450
  self.target.log.warning(
413
- "No 'logins.json' password file found for user %s in directory %s", user, profile_dir
451
+ "No 'logins.json' password file found for user %s in directory %s",
452
+ user.name,
453
+ profile_dir,
414
454
  )
415
455
  continue
416
456
 
@@ -445,9 +485,9 @@ class FirefoxPlugin(BrowserPlugin):
445
485
  break
446
486
 
447
487
  yield self.BrowserPasswordRecord(
448
- ts_created=login.get("timeCreated", 0) // 1000,
449
- ts_last_used=login.get("timeLastUsed", 0) // 1000,
450
- ts_last_changed=login.get("timePasswordChanged", 0) // 1000,
488
+ ts_created=from_unix_ms(login.get("timeCreated", 0)),
489
+ ts_last_used=from_unix_ms(login.get("timeLastUsed", 0)),
490
+ ts_last_changed=from_unix_ms(login.get("timePasswordChanged", 0)),
451
491
  browser="firefox",
452
492
  id=login.get("id"),
453
493
  url=login.get("hostname"),
@@ -457,14 +497,19 @@ class FirefoxPlugin(BrowserPlugin):
457
497
  decrypted_password=decrypted_password,
458
498
  source=login_file,
459
499
  _target=self.target,
460
- _user=user.user,
500
+ _user=user,
461
501
  )
462
502
 
463
503
  except FileNotFoundError:
464
- self.target.log.info("No password file found for user %s in directory %s", user, profile_dir)
504
+ self.target.log.info(
505
+ "No password file found for user %s in directory %s",
506
+ user.name,
507
+ profile_dir,
508
+ )
465
509
  except json.JSONDecodeError:
466
510
  self.target.log.warning(
467
- "logins.json file in directory %s is malformed, consider inspecting the file manually", profile_dir
511
+ "logins.json file in directory %s is malformed, consider inspecting the file manually",
512
+ profile_dir,
468
513
  )
469
514
 
470
515
 
@@ -1,5 +1,7 @@
1
+ from __future__ import annotations
2
+
1
3
  from functools import lru_cache
2
- from typing import Generator, NamedTuple, Optional, Union
4
+ from typing import Iterator, NamedTuple, Union
3
5
 
4
6
  from dissect.target import Target
5
7
  from dissect.target.exceptions import UnsupportedPluginError
@@ -7,10 +9,12 @@ from dissect.target.helpers.fsutil import TargetPath
7
9
  from dissect.target.helpers.record import UnixUserRecord, WindowsUserRecord
8
10
  from dissect.target.plugin import InternalPlugin
9
11
 
12
+ UserRecord = Union[UnixUserRecord, WindowsUserRecord]
13
+
10
14
 
11
15
  class UserDetails(NamedTuple):
12
- user: Union[UnixUserRecord, WindowsUserRecord]
13
- home_path: Optional[TargetPath]
16
+ user: UserRecord
17
+ home_path: TargetPath | None
14
18
 
15
19
 
16
20
  class UsersPlugin(InternalPlugin):
@@ -28,11 +32,11 @@ class UsersPlugin(InternalPlugin):
28
32
 
29
33
  def find(
30
34
  self,
31
- sid: Optional[str] = None,
32
- uid: Optional[str] = None,
33
- username: Optional[str] = None,
35
+ sid: str | None = None,
36
+ uid: str | None = None,
37
+ username: str | None = None,
34
38
  force_case_sensitive: bool = False,
35
- ) -> Optional[UserDetails]:
39
+ ) -> UserDetails | None:
36
40
  """Find User record matching provided sid, uid or username and return UserDetails object"""
37
41
  if all(map(lambda x: x is None, [sid, uid, username])):
38
42
  raise ValueError("Either sid or uid or username is expected")
@@ -52,7 +56,7 @@ class UsersPlugin(InternalPlugin):
52
56
  ):
53
57
  return self.get(user)
54
58
 
55
- def get(self, user: Union[UnixUserRecord, WindowsUserRecord]) -> UserDetails:
59
+ def get(self, user: UserRecord) -> UserDetails:
56
60
  """Return additional details about the user"""
57
61
  # Resolving the user home can not use the user's environment variables,
58
62
  # as those depend on the user's home to be known first. So we resolve
@@ -60,12 +64,12 @@ class UsersPlugin(InternalPlugin):
60
64
  home_path = self.target.fs.path(self.target.resolve(str(user.home))) if user.home else None
61
65
  return UserDetails(user=user, home_path=home_path)
62
66
 
63
- def all(self) -> Generator[UserDetails, None, None]:
67
+ def all(self) -> Iterator[UserDetails]:
64
68
  """Return UserDetails objects for all users found"""
65
69
  for user in self.target.users():
66
70
  yield self.get(user)
67
71
 
68
- def all_with_home(self) -> Generator[UserDetails, None, None]:
72
+ def all_with_home(self) -> Iterator[UserDetails]:
69
73
  """Return UserDetails objects for users that have existing directory set as home directory"""
70
74
  for user in self.target.users():
71
75
  if user.home:
@@ -8,7 +8,7 @@ import subprocess
8
8
  from configparser import ConfigParser
9
9
  from configparser import Error as ConfigParserError
10
10
  from io import BytesIO
11
- from typing import Any, BinaryIO, Iterator, Optional, TextIO
11
+ from typing import Any, BinaryIO, Iterator, TextIO
12
12
 
13
13
  from defusedxml import ElementTree
14
14
  from dissect.hypervisor.util import vmtar
@@ -73,7 +73,7 @@ class ESXiPlugin(UnixPlugin):
73
73
  if configstore.exists():
74
74
  self._configstore = parse_config_store(configstore.open())
75
75
 
76
- def _cfg(self, path: str) -> Optional[str]:
76
+ def _cfg(self, path: str) -> str | None:
77
77
  if not self._config:
78
78
  self.target.log.warning("No ESXi config!")
79
79
  return None
@@ -87,7 +87,7 @@ class ESXiPlugin(UnixPlugin):
87
87
  return obj.get(value_name) if obj else None
88
88
 
89
89
  @classmethod
90
- def detect(cls, target: Target) -> Optional[Filesystem]:
90
+ def detect(cls, target: Target) -> Filesystem | None:
91
91
  bootbanks = [
92
92
  fs for fs in target.filesystems if fs.path("boot.cfg").exists() and list(fs.path("/").glob("*.v00"))
93
93
  ]
@@ -128,7 +128,7 @@ class ESXiPlugin(UnixPlugin):
128
128
  return "localhost"
129
129
 
130
130
  @export(property=True)
131
- def domain(self) -> Optional[str]:
131
+ def domain(self) -> str | None:
132
132
  if hostname := self._cfg("/adv/Misc/HostName"):
133
133
  return hostname.partition(".")[2]
134
134
 
@@ -146,7 +146,7 @@ class ESXiPlugin(UnixPlugin):
146
146
  return list(result)
147
147
 
148
148
  @export(property=True)
149
- def version(self) -> Optional[str]:
149
+ def version(self) -> str | None:
150
150
  boot_cfg = self.target.fs.path("/bootbank/boot.cfg")
151
151
  if not boot_cfg.exists():
152
152
  return None
@@ -187,11 +187,11 @@ class ESXiPlugin(UnixPlugin):
187
187
  return self._configstore
188
188
 
189
189
  @export(property=True)
190
- def os(self):
190
+ def os(self) -> str:
191
191
  return OperatingSystem.ESXI.value
192
192
 
193
193
 
194
- def _mount_modules(target: Target, sysvol: Filesystem, cfg: dict[str, str]):
194
+ def _mount_modules(target: Target, sysvol: Filesystem, cfg: dict[str, str]) -> None:
195
195
  modules = [m.strip() for m in cfg["modules"].split("---")]
196
196
 
197
197
  for module in modules:
@@ -218,20 +218,22 @@ def _mount_modules(target: Target, sysvol: Filesystem, cfg: dict[str, str]):
218
218
  target.fs.append_layer().mount("/", tfs)
219
219
 
220
220
 
221
- def _mount_local(target: Target, local_layer: VirtualFilesystem):
221
+ def _mount_local(target: Target, local_layer: VirtualFilesystem) -> None:
222
222
  local_tgz = target.fs.path("local.tgz")
223
+ local_tgz_ve = target.fs.path("local.tgz.ve")
223
224
  local_fs = None
224
225
 
225
226
  if local_tgz.exists():
226
227
  local_fs = tar.TarFilesystem(local_tgz.open())
227
- else:
228
- local_tgz_ve = target.fs.path("local.tgz.ve")
228
+ elif local_tgz_ve.exists():
229
229
  # In the case "encryption.info" does not exist, but ".#encryption.info" does
230
230
  encryption_info = next(target.fs.path("/").glob("*encryption.info"), None)
231
231
  if not local_tgz_ve.exists() or not encryption_info.exists():
232
232
  raise ValueError("Unable to find valid configuration archive")
233
233
 
234
234
  local_fs = _create_local_fs(target, local_tgz_ve, encryption_info)
235
+ else:
236
+ target.log.warning("No local.tgz or local.tgz.ve found, skipping local state")
235
237
 
236
238
  if local_fs:
237
239
  local_layer.mount("/", local_fs)
@@ -245,7 +247,7 @@ def _decrypt_envelope(local_tgz_ve: TargetPath, encryption_info: TargetPath) ->
245
247
  return local_tgz
246
248
 
247
249
 
248
- def _decrypt_crypto_util(local_tgz_ve: TargetPath) -> Optional[BytesIO]:
250
+ def _decrypt_crypto_util(local_tgz_ve: TargetPath) -> BytesIO | None:
249
251
  """Decrypt ``local.tgz.ve`` using ESXi ``crypto-util``.
250
252
 
251
253
  We write to stdout, but this results in ``crypto-util`` exiting with a non-zero return code
@@ -264,9 +266,7 @@ def _decrypt_crypto_util(local_tgz_ve: TargetPath) -> Optional[BytesIO]:
264
266
  return BytesIO(result.stdout)
265
267
 
266
268
 
267
- def _create_local_fs(
268
- target: Target, local_tgz_ve: TargetPath, encryption_info: TargetPath
269
- ) -> Optional[tar.TarFilesystem]:
269
+ def _create_local_fs(target: Target, local_tgz_ve: TargetPath, encryption_info: TargetPath) -> tar.TarFilesystem | None:
270
270
  local_tgz = None
271
271
 
272
272
  if HAS_ENVELOPE:
@@ -292,7 +292,7 @@ def _create_local_fs(
292
292
  return tar.TarFilesystem(local_tgz)
293
293
 
294
294
 
295
- def _mount_filesystems(target: Target, sysvol: Filesystem, cfg: dict[str, str]):
295
+ def _mount_filesystems(target: Target, sysvol: Filesystem, cfg: dict[str, str]) -> None:
296
296
  version = cfg["build"]
297
297
 
298
298
  osdata_fs = None
@@ -371,7 +371,7 @@ def _mount_filesystems(target: Target, sysvol: Filesystem, cfg: dict[str, str]):
371
371
  target.fs.symlink(f"/vmfs/volumes/LOCKER-{locker_fs.vmfs.uuid}", "/locker")
372
372
 
373
373
 
374
- def _link_log_dir(target: Target, cfg: dict[str, str], plugin_obj: ESXiPlugin):
374
+ def _link_log_dir(target: Target, cfg: dict[str, str], plugin_obj: ESXiPlugin) -> None:
375
375
  version = cfg["build"]
376
376
 
377
377
  # Don't really know how ESXi does this, but let's just take a shortcut for now
@@ -441,7 +441,7 @@ def parse_esx_conf(fh: TextIO) -> dict[str, Any]:
441
441
  return config
442
442
 
443
443
 
444
- def _traverse(path: str, obj: dict[str, Any], create: bool = False):
444
+ def _traverse(path: str, obj: dict[str, Any], create: bool = False) -> dict[str, Any] | None:
445
445
  parts = path.strip("/").split("/")
446
446
  path_parts = parts[:-1]
447
447
  for part in path_parts:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: dissect.target
3
- Version: 3.19.dev28
3
+ Version: 3.19.dev30
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
@@ -124,7 +124,7 @@ dissect/target/plugins/apps/browser/browser.py,sha256=rBIwcgdl73gm-8APwx2jEUAYXR
124
124
  dissect/target/plugins/apps/browser/chrome.py,sha256=DMONTYE95sI_jcmyQOapHwWQWwrezfYMllVCCPwhEP0,3117
125
125
  dissect/target/plugins/apps/browser/chromium.py,sha256=V08Hq8GHMPd7snynh5RKQl4YHlhtwmlMsodLZeOnf_4,28103
126
126
  dissect/target/plugins/apps/browser/edge.py,sha256=tuuIbm4s8nNstA6nIOEfU0LG0jt20a8gf3rve2SXtdM,2953
127
- dissect/target/plugins/apps/browser/firefox.py,sha256=PmSTVSqimigA89hRcYMB4vNYXe3IwnTm_fLf4NhRbUo,31026
127
+ dissect/target/plugins/apps/browser/firefox.py,sha256=mZBBagFfIdiz9kUyK4Hi989I4g3CWrClBbmpaGMRKxg,32472
128
128
  dissect/target/plugins/apps/browser/iexplore.py,sha256=g_xw0toaiyjevxO8g9XPCOqc-CXZp39FVquRhPFGdTE,8801
129
129
  dissect/target/plugins/apps/container/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
130
130
  dissect/target/plugins/apps/container/docker.py,sha256=KxQRbKGgxkf3YFBMa7fjeJ7qo8qjFys7zEmfQhDTnLw,15305
@@ -181,7 +181,7 @@ dissect/target/plugins/general/loaders.py,sha256=6iUxhlSAgo7qSE8_XFxgiihK8sdMiP-
181
181
  dissect/target/plugins/general/osinfo.py,sha256=RdK5mw3-H9H3sGXz8yP8U_p3wUG1Ww7_HBKZpFdsbTE,1358
182
182
  dissect/target/plugins/general/plugins.py,sha256=4URjS6DN1Ey6Cqlbyx6NfFGgQZpWDrqxl8KLcZFODGE,4479
183
183
  dissect/target/plugins/general/scrape.py,sha256=Fz7BNXflvuxlnVulyyDhLpyU8D_hJdH6vWVtER9vjTg,6651
184
- dissect/target/plugins/general/users.py,sha256=cQXPQ2XbkPjckCPHYTUW4JEhYN0_CT8JI8hJPZn3qSs,3030
184
+ dissect/target/plugins/general/users.py,sha256=yy9gvRXfN9BT71v4Xqo5hpwfgN9he9Otu8TBPZ_Tegs,3009
185
185
  dissect/target/plugins/os/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
186
186
  dissect/target/plugins/os/unix/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
187
187
  dissect/target/plugins/os/unix/_os.py,sha256=u7m97qASdm_l90k4i7dzMtu2kcF6fVPKutx4_ISQTSg,14606
@@ -208,7 +208,7 @@ dissect/target/plugins/os/unix/bsd/osx/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JC
208
208
  dissect/target/plugins/os/unix/bsd/osx/_os.py,sha256=KvP7YJ7apVwoIop7MR-8q5QbVGoB6MdR42l6ssEe6es,4081
209
209
  dissect/target/plugins/os/unix/bsd/osx/user.py,sha256=qopB0s3n7e6Q7NjWzn8Z-dKtDtU7e6In4Vm7hIvvedo,2322
210
210
  dissect/target/plugins/os/unix/esxi/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
211
- dissect/target/plugins/os/unix/esxi/_os.py,sha256=JOJ6j57vFCojgBNkju-7MG2nQqwl4Qc-agXTwjhZPgY,17644
211
+ dissect/target/plugins/os/unix/esxi/_os.py,sha256=s6pAgUyfHh3QcY6sgvk5uVMmLvqK1tIHWR7MSbrFn8w,17789
212
212
  dissect/target/plugins/os/unix/etc/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
213
213
  dissect/target/plugins/os/unix/etc/etc.py,sha256=WNCtO7NWOKRDBiV-XjXqgPuGRDE_Zyw6XWz3kTm__QE,2493
214
214
  dissect/target/plugins/os/unix/linux/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -346,10 +346,10 @@ dissect/target/volumes/luks.py,sha256=OmCMsw6rCUXG1_plnLVLTpsvE1n_6WtoRUGQbpmu1z
346
346
  dissect/target/volumes/lvm.py,sha256=wwQVR9I3G9YzmY6UxFsH2Y4MXGBcKL9aayWGCDTiWMU,2269
347
347
  dissect/target/volumes/md.py,sha256=7ShPtusuLGaIv27SvEETtgsuoQyAa4iAAeOR1NEaajI,1689
348
348
  dissect/target/volumes/vmfs.py,sha256=-LoUbn9WNwTtLi_4K34uV_-wDw2W5hgaqxZNj4UmqAQ,1730
349
- dissect.target-3.19.dev28.dist-info/COPYRIGHT,sha256=m-9ih2RVhMiXHI2bf_oNSSgHgkeIvaYRVfKTwFbnJPA,301
350
- dissect.target-3.19.dev28.dist-info/LICENSE,sha256=DZak_2itbUtvHzD3E7GNUYSRK6jdOJ-GqncQ2weavLA,34523
351
- dissect.target-3.19.dev28.dist-info/METADATA,sha256=s0RI4tiEkq-koJ8Y_fUAJU1dmMcu1X73uF8fwSN_F7o,12719
352
- dissect.target-3.19.dev28.dist-info/WHEEL,sha256=R0nc6qTxuoLk7ShA2_Y-UWkN8ZdfDBG2B6Eqpz2WXbs,91
353
- dissect.target-3.19.dev28.dist-info/entry_points.txt,sha256=BWuxAb_6AvUAQpIQOQU0IMTlaF6TDht2AIZK8bHd-zE,492
354
- dissect.target-3.19.dev28.dist-info/top_level.txt,sha256=Mn-CQzEYsAbkxrUI0TnplHuXnGVKzxpDw_po_sXpvv4,8
355
- dissect.target-3.19.dev28.dist-info/RECORD,,
349
+ dissect.target-3.19.dev30.dist-info/COPYRIGHT,sha256=m-9ih2RVhMiXHI2bf_oNSSgHgkeIvaYRVfKTwFbnJPA,301
350
+ dissect.target-3.19.dev30.dist-info/LICENSE,sha256=DZak_2itbUtvHzD3E7GNUYSRK6jdOJ-GqncQ2weavLA,34523
351
+ dissect.target-3.19.dev30.dist-info/METADATA,sha256=fH2AsY4Du8s64GYdA4y4mJGfiEihIMfqkbOgE1ALNqc,12719
352
+ dissect.target-3.19.dev30.dist-info/WHEEL,sha256=R0nc6qTxuoLk7ShA2_Y-UWkN8ZdfDBG2B6Eqpz2WXbs,91
353
+ dissect.target-3.19.dev30.dist-info/entry_points.txt,sha256=BWuxAb_6AvUAQpIQOQU0IMTlaF6TDht2AIZK8bHd-zE,492
354
+ dissect.target-3.19.dev30.dist-info/top_level.txt,sha256=Mn-CQzEYsAbkxrUI0TnplHuXnGVKzxpDw_po_sXpvv4,8
355
+ dissect.target-3.19.dev30.dist-info/RECORD,,