dissect.target 3.20.dev10__py3-none-any.whl → 3.20.dev12__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/plugins/os/unix/log/messages.py +6 -11
- dissect/target/plugins/os/unix/trash.py +132 -0
- dissect/target/tools/utils.py +4 -0
- {dissect.target-3.20.dev10.dist-info → dissect.target-3.20.dev12.dist-info}/METADATA +1 -1
- {dissect.target-3.20.dev10.dist-info → dissect.target-3.20.dev12.dist-info}/RECORD +10 -9
- {dissect.target-3.20.dev10.dist-info → dissect.target-3.20.dev12.dist-info}/COPYRIGHT +0 -0
- {dissect.target-3.20.dev10.dist-info → dissect.target-3.20.dev12.dist-info}/LICENSE +0 -0
- {dissect.target-3.20.dev10.dist-info → dissect.target-3.20.dev12.dist-info}/WHEEL +0 -0
- {dissect.target-3.20.dev10.dist-info → dissect.target-3.20.dev12.dist-info}/entry_points.txt +0 -0
- {dissect.target-3.20.dev10.dist-info → dissect.target-3.20.dev12.dist-info}/top_level.txt +0 -0
@@ -6,7 +6,7 @@ from dissect.target import Target
|
|
6
6
|
from dissect.target.exceptions import UnsupportedPluginError
|
7
7
|
from dissect.target.helpers.record import TargetRecordDescriptor
|
8
8
|
from dissect.target.helpers.utils import year_rollover_helper
|
9
|
-
from dissect.target.plugin import Plugin, export
|
9
|
+
from dissect.target.plugin import Plugin, alias, export
|
10
10
|
|
11
11
|
MessagesRecord = TargetRecordDescriptor(
|
12
12
|
"linux/log/messages",
|
@@ -24,7 +24,9 @@ RE_TS = re.compile(r"(\w+\s{1,2}\d+\s\d{2}:\d{2}:\d{2})")
|
|
24
24
|
RE_DAEMON = re.compile(r"^[^:]+:\d+:\d+[^\[\]:]+\s([^\[:]+)[\[|:]{1}")
|
25
25
|
RE_PID = re.compile(r"\w\[(\d+)\]")
|
26
26
|
RE_MSG = re.compile(r"[^:]+:\d+:\d+[^:]+:\s(.*)$")
|
27
|
-
RE_CLOUD_INIT_LINE = re.compile(
|
27
|
+
RE_CLOUD_INIT_LINE = re.compile(
|
28
|
+
r"^(?P<ts>\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2},\d{3}) - (?P<daemon>.*)\[(?P<log_level>\w+)\]\: (?P<message>.*)$"
|
29
|
+
)
|
28
30
|
|
29
31
|
|
30
32
|
class MessagesPlugin(Plugin):
|
@@ -43,19 +45,12 @@ class MessagesPlugin(Plugin):
|
|
43
45
|
if not self.log_files:
|
44
46
|
raise UnsupportedPluginError("No log files found")
|
45
47
|
|
46
|
-
@
|
47
|
-
def syslog(self) -> Iterator[MessagesRecord]:
|
48
|
-
"""Return contents of /var/log/messages*, /var/log/syslog* and cloud-init logs.
|
49
|
-
|
50
|
-
See ``messages`` for more information.
|
51
|
-
"""
|
52
|
-
return self.messages()
|
53
|
-
|
48
|
+
@alias("syslog")
|
54
49
|
@export(record=MessagesRecord)
|
55
50
|
def messages(self) -> Iterator[MessagesRecord]:
|
56
51
|
"""Return contents of /var/log/messages*, /var/log/syslog* and cloud-init logs.
|
57
52
|
|
58
|
-
|
53
|
+
Due to year rollover detection, the contents of the files are returned in reverse.
|
59
54
|
|
60
55
|
The messages log file holds information about a variety of events such as the system error messages, system
|
61
56
|
startups and shutdowns, change in the network configuration, etc. Aims to store valuable, non-debug and
|
@@ -0,0 +1,132 @@
|
|
1
|
+
from typing import Iterator
|
2
|
+
|
3
|
+
from dissect.target.exceptions import UnsupportedPluginError
|
4
|
+
from dissect.target.helpers import configutil
|
5
|
+
from dissect.target.helpers.descriptor_extensions import UserRecordDescriptorExtension
|
6
|
+
from dissect.target.helpers.fsutil import TargetPath
|
7
|
+
from dissect.target.helpers.record import create_extended_descriptor
|
8
|
+
from dissect.target.plugin import Plugin, alias, export
|
9
|
+
from dissect.target.plugins.general.users import UserDetails
|
10
|
+
from dissect.target.target import Target
|
11
|
+
|
12
|
+
TrashRecord = create_extended_descriptor([UserRecordDescriptorExtension])(
|
13
|
+
"linux/filesystem/recyclebin",
|
14
|
+
[
|
15
|
+
("datetime", "ts"),
|
16
|
+
("path", "path"),
|
17
|
+
("filesize", "filesize"),
|
18
|
+
("path", "deleted_path"),
|
19
|
+
("path", "source"),
|
20
|
+
],
|
21
|
+
)
|
22
|
+
|
23
|
+
|
24
|
+
class GnomeTrashPlugin(Plugin):
|
25
|
+
"""Linux GNOME Trash plugin."""
|
26
|
+
|
27
|
+
PATHS = [
|
28
|
+
# Default $XDG_DATA_HOME/Trash
|
29
|
+
".local/share/Trash",
|
30
|
+
]
|
31
|
+
|
32
|
+
def __init__(self, target: Target):
|
33
|
+
super().__init__(target)
|
34
|
+
self.trashes = list(self._garbage_collector())
|
35
|
+
|
36
|
+
def _garbage_collector(self) -> Iterator[tuple[UserDetails, TargetPath]]:
|
37
|
+
"""it aint much, but its honest work"""
|
38
|
+
for user_details in self.target.user_details.all_with_home():
|
39
|
+
for trash_path in self.PATHS:
|
40
|
+
if (path := user_details.home_path.joinpath(trash_path)).exists():
|
41
|
+
yield user_details, path
|
42
|
+
|
43
|
+
def check_compatible(self) -> None:
|
44
|
+
if not self.trashes:
|
45
|
+
raise UnsupportedPluginError("No Trash folder(s) found")
|
46
|
+
|
47
|
+
@export(record=TrashRecord)
|
48
|
+
@alias(name="recyclebin")
|
49
|
+
def trash(self) -> Iterator[TrashRecord]:
|
50
|
+
"""Yield deleted files from GNOME Trash folders.
|
51
|
+
|
52
|
+
Recovers deleted files and artifacts from ``$HOME/.local/share/Trash``.
|
53
|
+
Probably also works with other desktop interfaces as long as they follow the Trash specification from FreeDesktop.
|
54
|
+
|
55
|
+
Currently does not parse media trash locations such as ``/media/$Label/.Trash-1000/*``.
|
56
|
+
|
57
|
+
Resources:
|
58
|
+
- https://specifications.freedesktop.org/trash-spec/latest/
|
59
|
+
- https://github.com/GNOME/glib/blob/main/gio/glocalfile.c
|
60
|
+
- https://specifications.freedesktop.org/basedir-spec/latest/
|
61
|
+
|
62
|
+
Yields ``TrashRecord``s with the following fields:
|
63
|
+
|
64
|
+
.. code-block:: text
|
65
|
+
|
66
|
+
ts (datetime): timestamp when the file was deleted or for expunged files when it could not be permanently deleted
|
67
|
+
path (path): path where the file was located before it was deleted
|
68
|
+
filesize (filesize): size in bytes of the deleted file
|
69
|
+
deleted_path (path): path to the current location of the deleted file
|
70
|
+
source (path): path to the .trashinfo file
|
71
|
+
""" # noqa: E501
|
72
|
+
|
73
|
+
for user_details, trash in self.trashes:
|
74
|
+
for trash_info_file in trash.glob("info/*.trashinfo"):
|
75
|
+
trash_info = configutil.parse(trash_info_file, hint="ini").get("Trash Info", {})
|
76
|
+
original_path = self.target.fs.path(trash_info.get("Path", ""))
|
77
|
+
|
78
|
+
# We use the basename of the .trashinfo file and not the Path variable inside the
|
79
|
+
# ini file. This way we can keep duplicate basenames of trashed files separated correctly.
|
80
|
+
deleted_path = trash / "files" / trash_info_file.name.replace(".trashinfo", "")
|
81
|
+
|
82
|
+
if deleted_path.exists():
|
83
|
+
deleted_files = [deleted_path]
|
84
|
+
|
85
|
+
if deleted_path.is_dir():
|
86
|
+
for child in deleted_path.rglob("*"):
|
87
|
+
deleted_files.append(child)
|
88
|
+
|
89
|
+
for file in deleted_files:
|
90
|
+
# NOTE: We currently do not 'fix' the original_path of files inside deleted directories.
|
91
|
+
# This would require guessing where the parent folder starts, which is impossible without
|
92
|
+
# making assumptions.
|
93
|
+
yield TrashRecord(
|
94
|
+
ts=trash_info.get("DeletionDate", 0),
|
95
|
+
path=original_path,
|
96
|
+
filesize=file.lstat().st_size if file.is_file() else None,
|
97
|
+
deleted_path=file,
|
98
|
+
source=trash_info_file,
|
99
|
+
_user=user_details.user,
|
100
|
+
_target=self.target,
|
101
|
+
)
|
102
|
+
|
103
|
+
# We cannot determine if the deleted entry is a directory since the path does
|
104
|
+
# not exist at $TRASH/files, so we work with what we have instead.
|
105
|
+
else:
|
106
|
+
self.target.log.warning(f"Expected trashed file(s) at {deleted_path}")
|
107
|
+
yield TrashRecord(
|
108
|
+
ts=trash_info.get("DeletionDate", 0),
|
109
|
+
path=original_path,
|
110
|
+
filesize=0,
|
111
|
+
deleted_path=deleted_path,
|
112
|
+
source=trash_info_file,
|
113
|
+
_user=user_details.user,
|
114
|
+
_target=self.target,
|
115
|
+
)
|
116
|
+
|
117
|
+
# We also iterate expunged folders, they can contain files that could not be
|
118
|
+
# deleted when the user pressed the "empty trash" button in the file manager.
|
119
|
+
# Resources:
|
120
|
+
# - https://gitlab.gnome.org/GNOME/glib/-/issues/1665
|
121
|
+
# - https://bugs.launchpad.net/ubuntu/+source/nautilus/+bug/422012
|
122
|
+
for item in (trash / "expunged").rglob("*/*"):
|
123
|
+
stat = item.lstat()
|
124
|
+
yield TrashRecord(
|
125
|
+
ts=stat.st_mtime, # NOTE: This is the timestamp at which the file failed to delete
|
126
|
+
path=None, # We do not know the original file path
|
127
|
+
filesize=stat.st_size if item.is_file() else None,
|
128
|
+
deleted_path=item,
|
129
|
+
source=trash / "expunged",
|
130
|
+
_user=user_details.user,
|
131
|
+
_target=self.target,
|
132
|
+
)
|
dissect/target/tools/utils.py
CHANGED
@@ -90,6 +90,10 @@ def generate_argparse_for_unbound_method(
|
|
90
90
|
raise ValueError(f"Value `{method}` is not an unbound plugin method")
|
91
91
|
|
92
92
|
desc = method.__doc__ or docs.get_func_description(method, with_docstrings=True)
|
93
|
+
|
94
|
+
if "\n" in desc:
|
95
|
+
desc = inspect.cleandoc(desc)
|
96
|
+
|
93
97
|
help_formatter = argparse.RawDescriptionHelpFormatter
|
94
98
|
parser = argparse.ArgumentParser(description=desc, formatter_class=help_formatter, conflict_handler="resolve")
|
95
99
|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: dissect.target
|
3
|
-
Version: 3.20.
|
3
|
+
Version: 3.20.dev12
|
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
|
@@ -198,6 +198,7 @@ dissect/target/plugins/os/unix/history.py,sha256=rvRlcHw3wEtgdyfjX-RBLQUQAd0uHzf
|
|
198
198
|
dissect/target/plugins/os/unix/locale.py,sha256=XOcKBwfK3YJ266eBFKNc1xaZgY8QEQGJOS8PJRJt4ME,4292
|
199
199
|
dissect/target/plugins/os/unix/packagemanager.py,sha256=Wm2AAJOD_B3FAcZNXgWtSm_YwbvrHBYOP8bPmOXNjG4,2427
|
200
200
|
dissect/target/plugins/os/unix/shadow.py,sha256=W6W6rMru7IVnuBc6sl5wsRWTOrJdS1s7_2_q7QRf7Is,4148
|
201
|
+
dissect/target/plugins/os/unix/trash.py,sha256=wzq9nprRKjFs9SHaENz8PMbpQMfFoLH1KW4EPWMLJdA,6099
|
201
202
|
dissect/target/plugins/os/unix/bsd/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
202
203
|
dissect/target/plugins/os/unix/bsd/_os.py,sha256=e5rttTOFOmd7e2HqP9ZZFMEiPLBr-8rfH0XH1IIeroQ,1372
|
203
204
|
dissect/target/plugins/os/unix/bsd/citrix/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -258,7 +259,7 @@ dissect/target/plugins/os/unix/log/audit.py,sha256=OjorWTmCFvCI5RJq6m6WNW0Lhb-po
|
|
258
259
|
dissect/target/plugins/os/unix/log/auth.py,sha256=l7gCuRdvv9gL0U1N0yrR9hVsMnr4t_k4t-n-f6PrOxg,2388
|
259
260
|
dissect/target/plugins/os/unix/log/journal.py,sha256=auVRfrW4NRU7HguoDLTz4l_IwNdPZLPAqD7jhrOTzH8,17404
|
260
261
|
dissect/target/plugins/os/unix/log/lastlog.py,sha256=Wq89wRSFZSBsoKVCxjDofnC4yw9XJ4iOF0XJe9EucCo,2448
|
261
|
-
dissect/target/plugins/os/unix/log/messages.py,sha256=
|
262
|
+
dissect/target/plugins/os/unix/log/messages.py,sha256=O10Uw3PGTanfGpphUWYqOwOIR7XiiM-clfboVCoiP0U,4501
|
262
263
|
dissect/target/plugins/os/unix/log/utmp.py,sha256=1nPHIaBUHt_9z6PDrvyqg4huKLihUaWLrMmgMsbaeIo,7755
|
263
264
|
dissect/target/plugins/os/windows/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
264
265
|
dissect/target/plugins/os/windows/_os.py,sha256=-x5TD5BvFw-7zEfqT6WG7n04YSeyr7wVLO07y6xkBP8,12476
|
@@ -350,7 +351,7 @@ dissect/target/tools/mount.py,sha256=8GRYnu4xEmFBHxuIZAYhOMyyTGX8fat1Ou07DNiUnW4
|
|
350
351
|
dissect/target/tools/query.py,sha256=e-yAN9zdQjuOiTuoOQoo17mVEQGGcOgaA9YkF4GYpkM,15394
|
351
352
|
dissect/target/tools/reg.py,sha256=FDsiBBDxjWVUBTRj8xn82vZe-J_d9piM-TKS3PHZCcM,3193
|
352
353
|
dissect/target/tools/shell.py,sha256=dmshIriwdd_UwrdUcTfWkcYD8Z0mjzbDqwyZG-snDdM,50482
|
353
|
-
dissect/target/tools/utils.py,sha256=
|
354
|
+
dissect/target/tools/utils.py,sha256=JJZDSso1CEK2sv4Z3HJNgqxH6G9S5lbmV-C3h-XmcMo,12035
|
354
355
|
dissect/target/tools/yara.py,sha256=70k-2VMulf1EdkX03nCACzejaOEcsFHOyX-4E40MdQU,2044
|
355
356
|
dissect/target/tools/dump/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
356
357
|
dissect/target/tools/dump/run.py,sha256=aD84peRS4zHqC78fH7Vd4ni3m1ZmVP70LyMwBRvoDGY,9463
|
@@ -364,10 +365,10 @@ dissect/target/volumes/luks.py,sha256=OmCMsw6rCUXG1_plnLVLTpsvE1n_6WtoRUGQbpmu1z
|
|
364
365
|
dissect/target/volumes/lvm.py,sha256=wwQVR9I3G9YzmY6UxFsH2Y4MXGBcKL9aayWGCDTiWMU,2269
|
365
366
|
dissect/target/volumes/md.py,sha256=7ShPtusuLGaIv27SvEETtgsuoQyAa4iAAeOR1NEaajI,1689
|
366
367
|
dissect/target/volumes/vmfs.py,sha256=-LoUbn9WNwTtLi_4K34uV_-wDw2W5hgaqxZNj4UmqAQ,1730
|
367
|
-
dissect.target-3.20.
|
368
|
-
dissect.target-3.20.
|
369
|
-
dissect.target-3.20.
|
370
|
-
dissect.target-3.20.
|
371
|
-
dissect.target-3.20.
|
372
|
-
dissect.target-3.20.
|
373
|
-
dissect.target-3.20.
|
368
|
+
dissect.target-3.20.dev12.dist-info/COPYRIGHT,sha256=m-9ih2RVhMiXHI2bf_oNSSgHgkeIvaYRVfKTwFbnJPA,301
|
369
|
+
dissect.target-3.20.dev12.dist-info/LICENSE,sha256=DZak_2itbUtvHzD3E7GNUYSRK6jdOJ-GqncQ2weavLA,34523
|
370
|
+
dissect.target-3.20.dev12.dist-info/METADATA,sha256=PD0L9p9grMOhAAKSl17OUW03GOBhlRdnI6qX6nyKWT0,12897
|
371
|
+
dissect.target-3.20.dev12.dist-info/WHEEL,sha256=GV9aMThwP_4oNCtvEC2ec3qUYutgWeAzklro_0m4WJQ,91
|
372
|
+
dissect.target-3.20.dev12.dist-info/entry_points.txt,sha256=BWuxAb_6AvUAQpIQOQU0IMTlaF6TDht2AIZK8bHd-zE,492
|
373
|
+
dissect.target-3.20.dev12.dist-info/top_level.txt,sha256=Mn-CQzEYsAbkxrUI0TnplHuXnGVKzxpDw_po_sXpvv4,8
|
374
|
+
dissect.target-3.20.dev12.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
{dissect.target-3.20.dev10.dist-info → dissect.target-3.20.dev12.dist-info}/entry_points.txt
RENAMED
File without changes
|
File without changes
|