dissect.target 3.16.dev20__py3-none-any.whl → 3.16.dev22__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/filesystems/config.py +6 -4
- dissect/target/helpers/configutil.py +83 -2
- dissect/target/plugins/apps/browser/brave.py +68 -0
- dissect/target/plugins/apps/browser/chromium.py +10 -4
- {dissect.target-3.16.dev20.dist-info → dissect.target-3.16.dev22.dist-info}/METADATA +1 -1
- {dissect.target-3.16.dev20.dist-info → dissect.target-3.16.dev22.dist-info}/RECORD +11 -10
- {dissect.target-3.16.dev20.dist-info → dissect.target-3.16.dev22.dist-info}/COPYRIGHT +0 -0
- {dissect.target-3.16.dev20.dist-info → dissect.target-3.16.dev22.dist-info}/LICENSE +0 -0
- {dissect.target-3.16.dev20.dist-info → dissect.target-3.16.dev22.dist-info}/WHEEL +0 -0
- {dissect.target-3.16.dev20.dist-info → dissect.target-3.16.dev22.dist-info}/entry_points.txt +0 -0
- {dissect.target-3.16.dev20.dist-info → dissect.target-3.16.dev22.dist-info}/top_level.txt +0 -0
@@ -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
|
-
|
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(
|
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
|
-
|
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.
|
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=
|
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=
|
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=
|
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.
|
327
|
-
dissect.target-3.16.
|
328
|
-
dissect.target-3.16.
|
329
|
-
dissect.target-3.16.
|
330
|
-
dissect.target-3.16.
|
331
|
-
dissect.target-3.16.
|
332
|
-
dissect.target-3.16.
|
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,,
|
File without changes
|
File without changes
|
File without changes
|
{dissect.target-3.16.dev20.dist-info → dissect.target-3.16.dev22.dist-info}/entry_points.txt
RENAMED
File without changes
|
File without changes
|