dissect.target 3.16.dev20__py3-none-any.whl → 3.16.dev22__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
@@ -120,9 +120,9 @@ class ConfigurationFilesystem(VirtualFilesystem):
120
120
  try:
121
121
  config_parser = parse(entry, *args, **kwargs)
122
122
  entry = ConfigurationEntry(self, entry.path, entry, config_parser)
123
- except ConfigurationParsingError:
123
+ except ConfigurationParsingError as e:
124
124
  # If a parsing error gets created, it should return the `entry`
125
- log.debug("Error when parsing %s", entry.path)
125
+ log.debug("Error when parsing %s with message '%s'", entry.path, e)
126
126
 
127
127
  return entry
128
128
 
@@ -220,13 +220,15 @@ class ConfigurationEntry(FilesystemEntry):
220
220
  output_buffer = io.StringIO()
221
221
 
222
222
  if isinstance(values, list):
223
- output_buffer.write(textwrap.indent(text="\n".join(values), prefix=prefix))
223
+ # Explicitly convert the list values to strings
224
+ _text = "\n".join(str(val) for val in values)
225
+ output_buffer.write(textwrap.indent(text=_text, prefix=prefix))
224
226
  elif hasattr(values, "keys"):
225
227
  for key, value in values.items():
226
228
  output_buffer.write(textwrap.indent(key, prefix=prefix) + "\n")
227
229
  output_buffer.write(self._write_value_mapping(value, indentation_nr + 4))
228
230
  else:
229
- output_buffer.write(textwrap.indent(values, prefix=prefix) + "\n")
231
+ output_buffer.write(textwrap.indent(str(values), prefix=prefix) + "\n")
230
232
 
231
233
  return output_buffer.getvalue()
232
234
 
@@ -1,6 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import io
4
+ import json
4
5
  import re
5
6
  from collections import deque
6
7
  from configparser import ConfigParser, MissingSectionHeaderError
@@ -26,6 +27,13 @@ from dissect.target.exceptions import ConfigurationParsingError, FileNotFoundErr
26
27
  from dissect.target.filesystem import FilesystemEntry
27
28
  from dissect.target.helpers.fsutil import TargetPath
28
29
 
30
+ try:
31
+ import yaml
32
+
33
+ PY_YAML = True
34
+ except (AttributeError, ImportError):
35
+ PY_YAML = False
36
+
29
37
 
30
38
  def _update_dictionary(current: dict[str, Any], key: str, value: Any) -> None:
31
39
  if prev_value := current.get(key):
@@ -151,7 +159,7 @@ class ConfigurationParser:
151
159
  try:
152
160
  self.parse_file(fh)
153
161
  except Exception as e:
154
- raise ConfigurationParsingError from e
162
+ raise ConfigurationParsingError(*e.args) from e
155
163
 
156
164
  if self.collapse_all or self.collapse:
157
165
  self.parsed_data = self._collapse_dict(self.parsed_data)
@@ -329,6 +337,77 @@ class Xml(ConfigurationParser):
329
337
  self.parsed_data = tree
330
338
 
331
339
 
340
+ class ListUnwrapper:
341
+ """Provides utility functions to unwrap dictionary objects out of lists."""
342
+
343
+ @staticmethod
344
+ def unwrap(data: Union[dict, list]) -> Union[dict, list]:
345
+ """Transforms a list with dictionaries to a dictionary.
346
+
347
+ The order of the list is preserved. If no dictionary is found,
348
+ the list remains untouched:
349
+
350
+ ["value1", "value2"] -> ["value1", "value2"]
351
+
352
+ {"data": "value"} -> {"data": "value"}
353
+
354
+ [{"data": "value"}] -> {
355
+ "list_item0": {
356
+ "data": "value"
357
+ }
358
+ }
359
+ """
360
+ orig = ListUnwrapper._unwrap_dict_list(data)
361
+ return ListUnwrapper._unwrap_dict(orig)
362
+
363
+ @staticmethod
364
+ def _unwrap_dict(data: Union[dict, list]) -> Union[dict, list]:
365
+ """Looks for dictionaries and unwraps its values."""
366
+
367
+ if not isinstance(data, dict):
368
+ return data
369
+
370
+ root = dict()
371
+ for key, value in data.items():
372
+ _value = ListUnwrapper._unwrap_dict_list(value)
373
+ if isinstance(_value, dict):
374
+ _value = ListUnwrapper._unwrap_dict(_value)
375
+ root[key] = _value
376
+
377
+ return root
378
+
379
+ @staticmethod
380
+ def _unwrap_dict_list(data: Union[dict, list]) -> Union[dict, list]:
381
+ """Unwraps a list containing dictionaries."""
382
+ if not isinstance(data, list) or not any(isinstance(obj, dict) for obj in data):
383
+ return data
384
+
385
+ return_value = {}
386
+ for idx, elem in enumerate(data):
387
+ return_value[f"list_item{idx}"] = elem
388
+
389
+ return return_value
390
+
391
+
392
+ class Json(ConfigurationParser):
393
+ """Parses a JSON file."""
394
+
395
+ def parse_file(self, fh: TextIO):
396
+ parsed_data = json.load(fh)
397
+ self.parsed_data = ListUnwrapper.unwrap(parsed_data)
398
+
399
+
400
+ class Yaml(ConfigurationParser):
401
+ """Parses a Yaml file."""
402
+
403
+ def parse_file(self, fh: TextIO) -> None:
404
+ if PY_YAML:
405
+ parsed_data = yaml.load(fh, yaml.BaseLoader)
406
+ self.parsed_data = ListUnwrapper.unwrap(parsed_data)
407
+ else:
408
+ raise ConfigurationParsingError("Failed to parse file, please install PyYAML.")
409
+
410
+
332
411
  class ScopeManager:
333
412
  """A (context)manager for dictionary scoping.
334
413
 
@@ -609,7 +688,9 @@ MATCH_MAP: dict[str, ParserConfig] = {
609
688
  CONFIG_MAP: dict[tuple[str, ...], ParserConfig] = {
610
689
  "ini": ParserConfig(Ini),
611
690
  "xml": ParserConfig(Xml),
612
- "json": ParserConfig(Txt),
691
+ "json": ParserConfig(Json),
692
+ "yml": ParserConfig(Yaml),
693
+ "yaml": ParserConfig(Yaml),
613
694
  "cnf": ParserConfig(Default),
614
695
  "conf": ParserConfig(Default, separator=(r"\s",)),
615
696
  "sample": ParserConfig(Txt),
@@ -0,0 +1,68 @@
1
+ from typing import Iterator
2
+
3
+ from dissect.target.helpers.descriptor_extensions import UserRecordDescriptorExtension
4
+ from dissect.target.helpers.record import create_extended_descriptor
5
+ from dissect.target.plugin import export
6
+ from dissect.target.plugins.apps.browser.browser import (
7
+ GENERIC_COOKIE_FIELDS,
8
+ GENERIC_DOWNLOAD_RECORD_FIELDS,
9
+ GENERIC_EXTENSION_RECORD_FIELDS,
10
+ GENERIC_HISTORY_RECORD_FIELDS,
11
+ BrowserPlugin,
12
+ )
13
+ from dissect.target.plugins.apps.browser.chromium import (
14
+ CHROMIUM_DOWNLOAD_RECORD_FIELDS,
15
+ ChromiumMixin,
16
+ )
17
+
18
+
19
+ class BravePlugin(ChromiumMixin, BrowserPlugin):
20
+ """Brave browser plugin."""
21
+
22
+ __namespace__ = "brave"
23
+
24
+ DIRS = [
25
+ # Windows
26
+ "AppData/Local/BraveSoftware/Brave-Browser/User Data/Default",
27
+ "AppData/Roaming/BraveSoftware/Brave-Browser/User Data/Default",
28
+ # Linux
29
+ ".config/BraveSoftware/Default",
30
+ # Macos
31
+ "Library/Application Support/BraveSoftware/Default",
32
+ ]
33
+
34
+ BrowserHistoryRecord = create_extended_descriptor([UserRecordDescriptorExtension])(
35
+ "browser/brave/history", GENERIC_HISTORY_RECORD_FIELDS
36
+ )
37
+
38
+ BrowserCookieRecord = create_extended_descriptor([UserRecordDescriptorExtension])(
39
+ "browser/brave/cookie", GENERIC_COOKIE_FIELDS
40
+ )
41
+
42
+ BrowserDownloadRecord = create_extended_descriptor([UserRecordDescriptorExtension])(
43
+ "browser/brave/download", GENERIC_DOWNLOAD_RECORD_FIELDS + CHROMIUM_DOWNLOAD_RECORD_FIELDS
44
+ )
45
+
46
+ BrowserExtensionRecord = create_extended_descriptor([UserRecordDescriptorExtension])(
47
+ "browser/brave/extension", GENERIC_EXTENSION_RECORD_FIELDS
48
+ )
49
+
50
+ @export(record=BrowserHistoryRecord)
51
+ def history(self) -> Iterator[BrowserHistoryRecord]:
52
+ """Return browser history records for Brave."""
53
+ yield from super().history("brave")
54
+
55
+ @export(record=BrowserCookieRecord)
56
+ def cookies(self) -> Iterator[BrowserCookieRecord]:
57
+ """Return browser cookie records for Brave."""
58
+ yield from super().cookies("brave")
59
+
60
+ @export(record=BrowserDownloadRecord)
61
+ def downloads(self) -> Iterator[BrowserDownloadRecord]:
62
+ """Return browser download records for Brave."""
63
+ yield from super().downloads("brave")
64
+
65
+ @export(record=BrowserExtensionRecord)
66
+ def extensions(self) -> Iterator[BrowserExtensionRecord]:
67
+ """Return browser extension records for Brave."""
68
+ yield from super().extensions("brave")
@@ -1,3 +1,4 @@
1
+ import itertools
1
2
  import json
2
3
  from collections import defaultdict
3
4
  from typing import Iterator, Optional
@@ -9,7 +10,7 @@ from dissect.util.ts import webkittimestamp
9
10
 
10
11
  from dissect.target.exceptions import FileNotFoundError, UnsupportedPluginError
11
12
  from dissect.target.helpers.descriptor_extensions import UserRecordDescriptorExtension
12
- from dissect.target.helpers.fsutil import TargetPath
13
+ from dissect.target.helpers.fsutil import TargetPath, join
13
14
  from dissect.target.helpers.record import create_extended_descriptor
14
15
  from dissect.target.plugin import export
15
16
  from dissect.target.plugins.apps.browser.browser import (
@@ -69,11 +70,12 @@ class ChromiumMixin:
69
70
  users_dirs.append((user_details.user, cur_dir))
70
71
  return users_dirs
71
72
 
72
- def _iter_db(self, filename: str) -> Iterator[SQLite3]:
73
+ def _iter_db(self, filename: str, subdirs: Optional[list[str]] = None) -> Iterator[SQLite3]:
73
74
  """Generate a connection to a sqlite database file.
74
75
 
75
76
  Args:
76
77
  filename: The filename as string of the database where the data is stored.
78
+ subdirs: Subdirectories to also try for every configured directory.
77
79
 
78
80
  Yields:
79
81
  opened db_file (SQLite3)
@@ -83,7 +85,11 @@ class ChromiumMixin:
83
85
  SQLError: If the history file could not be opened.
84
86
  """
85
87
 
86
- for user, cur_dir in self._build_userdirs(self.DIRS):
88
+ dirs = self.DIRS
89
+ if subdirs:
90
+ dirs.extend([join(dir, subdir) for dir, subdir in itertools.product(self.DIRS, subdirs)])
91
+
92
+ for user, cur_dir in self._build_userdirs(dirs):
87
93
  db_file = cur_dir.joinpath(filename)
88
94
  try:
89
95
  yield user, db_file, sqlite3.SQLite3(db_file.open())
@@ -198,7 +204,7 @@ class ChromiumMixin:
198
204
  is_http_only (bool): Cookie http only flag.
199
205
  same_site (bool): Cookie same site flag.
200
206
  """
201
- for user, db_file, db in self._iter_db("Cookies"):
207
+ for user, db_file, db in self._iter_db("Cookies", subdirs=["Network"]):
202
208
  try:
203
209
  for cookie in db.table("cookies").rows():
204
210
  yield self.BrowserCookieRecord(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: dissect.target
3
- Version: 3.16.dev20
3
+ Version: 3.16.dev22
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
@@ -24,7 +24,7 @@ dissect/target/filesystems/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJ
24
24
  dissect/target/filesystems/ad1.py,sha256=nEPzaaRsb6bL4ItFo0uLdmdLvrmK9BjqHeD3FOp3WQI,2413
25
25
  dissect/target/filesystems/btrfs.py,sha256=5MBi193ZvclkEQcxDr_sDHfj_FYU_hyYNRL4YqpDu4M,6243
26
26
  dissect/target/filesystems/cb.py,sha256=6LcoJiwsYu1Han31IUzVpZVDTifhTLTx_gLfNpB_p6k,5329
27
- dissect/target/filesystems/config.py,sha256=n1MR7a3tGXszpusLSDxZdTYo8IRLtDC_Xd95zPDTzzA,11295
27
+ dissect/target/filesystems/config.py,sha256=C2JnzBzMqbAjchGFDwURItCeUY7uxkhw1Gen-6cGkAc,11432
28
28
  dissect/target/filesystems/dir.py,sha256=7GRvojL151_Vk9e3vqgZbWE3I8IL9bU6LUKc_xjk6D4,4050
29
29
  dissect/target/filesystems/exfat.py,sha256=PRkZPUVN5NlgB1VetFtywdNgF6Yj5OBtF5a25t-fFvw,5917
30
30
  dissect/target/filesystems/extfs.py,sha256=9Cke-H0CL-SPd3-xvdAgfc3YA5hYso0sq6hm0C9vGII,4640
@@ -42,7 +42,7 @@ dissect/target/filesystems/zip.py,sha256=WT1bQhzX_1MXXVZTKrJniae4xqRqMZ8FsfbvhgG
42
42
  dissect/target/helpers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
43
43
  dissect/target/helpers/cache.py,sha256=_0w_iPD1OM066Ueyadb70erQW05jNnpJe-bDDN1UyXc,8444
44
44
  dissect/target/helpers/config.py,sha256=6917CZ6eDHaK_tOoiVEIndyhRXO6r6eCBIleq6f47PQ,2346
45
- dissect/target/helpers/configutil.py,sha256=u8pG_6dznwnQwj7JpSH54NcYwLAhFkgdyixoBVTDWM0,22587
45
+ dissect/target/helpers/configutil.py,sha256=t_UNvcWuMMT5C1tut_PgTwCnVUodf6RjhfXP4FSkmdo,25068
46
46
  dissect/target/helpers/cyber.py,sha256=Ki5oSU0GgQxjgC_yWoeieGP7GOY5blQCzNX7vy7Pgas,16782
47
47
  dissect/target/helpers/descriptor_extensions.py,sha256=uT8GwznfDAiIgMM7JKKOY0PXKMv2c0GCqJTCkWFgops,2605
48
48
  dissect/target/helpers/docs.py,sha256=J5U65Y3yOTqxDEZRCdrEmO63XQCeDzOJea1PwPM6Cyc,5146
@@ -112,9 +112,10 @@ dissect/target/plugins/apps/av/sophos.py,sha256=gSfTvjBZMuT0hsL-p4oYxuYmakbqApoO
112
112
  dissect/target/plugins/apps/av/symantec.py,sha256=RFLyNW6FyuoGcirJ4xHbQM8oGjua9W4zXmC7YDF-H20,14109
113
113
  dissect/target/plugins/apps/av/trendmicro.py,sha256=jloy_N4hHAqF1sVIEeD5Q7LRYal3_os14Umk-hGaAR4,4613
114
114
  dissect/target/plugins/apps/browser/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
115
+ dissect/target/plugins/apps/browser/brave.py,sha256=Fid4P5sUuRQsn7YKwHJodj_Jnfp1H7fAvNs_rL53QCI,2462
115
116
  dissect/target/plugins/apps/browser/browser.py,sha256=_QP1u57-wOSiLvpTUotWDpqBdRn-WEWpBDzCMqZTYO0,2682
116
117
  dissect/target/plugins/apps/browser/chrome.py,sha256=XMDq3v-fA0W16gm5jXryP73PEtF7bRw5Pfqy5JQd-U8,2635
117
- dissect/target/plugins/apps/browser/chromium.py,sha256=Y1sS0EqF5F5abpLXNog2HwI5QV5d3qnBvZMnE0MPdyU,17774
118
+ dissect/target/plugins/apps/browser/chromium.py,sha256=QswqB1sSc6i1wpRbZnTvvq-UeEz0bN7pefc_gf5w4Wc,18078
118
119
  dissect/target/plugins/apps/browser/edge.py,sha256=cjMbAGtlTVyJLuha3D0uNbai0mJnkXmp6d0gBfceWB4,2473
119
120
  dissect/target/plugins/apps/browser/firefox.py,sha256=6dUTNfclNTsqB_GA-4q38tyHPuiw8lgNEmmtfIWbMUY,11373
120
121
  dissect/target/plugins/apps/browser/iexplore.py,sha256=LUXXCjMBBFcFN2ceBpks8qM1PyOvrBPn1guA4WM4oSU,8706
@@ -323,10 +324,10 @@ dissect/target/volumes/luks.py,sha256=OmCMsw6rCUXG1_plnLVLTpsvE1n_6WtoRUGQbpmu1z
323
324
  dissect/target/volumes/lvm.py,sha256=wwQVR9I3G9YzmY6UxFsH2Y4MXGBcKL9aayWGCDTiWMU,2269
324
325
  dissect/target/volumes/md.py,sha256=j1K1iKmspl0C_OJFc7-Q1BMWN2OCC5EVANIgVlJ_fIE,1673
325
326
  dissect/target/volumes/vmfs.py,sha256=-LoUbn9WNwTtLi_4K34uV_-wDw2W5hgaqxZNj4UmqAQ,1730
326
- dissect.target-3.16.dev20.dist-info/COPYRIGHT,sha256=m-9ih2RVhMiXHI2bf_oNSSgHgkeIvaYRVfKTwFbnJPA,301
327
- dissect.target-3.16.dev20.dist-info/LICENSE,sha256=DZak_2itbUtvHzD3E7GNUYSRK6jdOJ-GqncQ2weavLA,34523
328
- dissect.target-3.16.dev20.dist-info/METADATA,sha256=nTJx6hfkv0OTGu8iDxjh3WG5KdZKEQExyc47vUkPjAM,11113
329
- dissect.target-3.16.dev20.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
330
- dissect.target-3.16.dev20.dist-info/entry_points.txt,sha256=tvFPa-Ap-gakjaPwRc6Fl6mxHzxEZ_arAVU-IUYeo_s,447
331
- dissect.target-3.16.dev20.dist-info/top_level.txt,sha256=Mn-CQzEYsAbkxrUI0TnplHuXnGVKzxpDw_po_sXpvv4,8
332
- dissect.target-3.16.dev20.dist-info/RECORD,,
327
+ dissect.target-3.16.dev22.dist-info/COPYRIGHT,sha256=m-9ih2RVhMiXHI2bf_oNSSgHgkeIvaYRVfKTwFbnJPA,301
328
+ dissect.target-3.16.dev22.dist-info/LICENSE,sha256=DZak_2itbUtvHzD3E7GNUYSRK6jdOJ-GqncQ2weavLA,34523
329
+ dissect.target-3.16.dev22.dist-info/METADATA,sha256=eeXkQBdpMCeA7btOTstW1H1q8GW0NRRd_5KgqB7YaeE,11113
330
+ dissect.target-3.16.dev22.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
331
+ dissect.target-3.16.dev22.dist-info/entry_points.txt,sha256=tvFPa-Ap-gakjaPwRc6Fl6mxHzxEZ_arAVU-IUYeo_s,447
332
+ dissect.target-3.16.dev22.dist-info/top_level.txt,sha256=Mn-CQzEYsAbkxrUI0TnplHuXnGVKzxpDw_po_sXpvv4,8
333
+ dissect.target-3.16.dev22.dist-info/RECORD,,