dissect.target 3.18.dev15__py3-none-any.whl → 3.19__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- dissect/target/filesystem.py +44 -25
- dissect/target/filesystems/config.py +32 -21
- dissect/target/filesystems/extfs.py +4 -0
- dissect/target/filesystems/itunes.py +1 -1
- dissect/target/filesystems/tar.py +1 -1
- dissect/target/filesystems/zip.py +81 -46
- dissect/target/helpers/config.py +22 -7
- dissect/target/helpers/configutil.py +69 -5
- dissect/target/helpers/cyber.py +4 -2
- dissect/target/helpers/fsutil.py +32 -4
- dissect/target/helpers/loaderutil.py +26 -7
- dissect/target/helpers/network_managers.py +22 -7
- dissect/target/helpers/record.py +37 -0
- dissect/target/helpers/record_modifier.py +23 -4
- dissect/target/helpers/shell_application_ids.py +732 -0
- dissect/target/helpers/utils.py +11 -0
- dissect/target/loader.py +1 -0
- dissect/target/loaders/ab.py +285 -0
- dissect/target/loaders/libvirt.py +40 -0
- dissect/target/loaders/mqtt.py +14 -1
- dissect/target/loaders/tar.py +8 -4
- dissect/target/loaders/utm.py +3 -0
- dissect/target/loaders/velociraptor.py +6 -6
- dissect/target/plugin.py +60 -3
- dissect/target/plugins/apps/browser/chrome.py +1 -0
- dissect/target/plugins/apps/browser/chromium.py +7 -5
- dissect/target/plugins/apps/browser/edge.py +1 -0
- dissect/target/plugins/apps/browser/firefox.py +82 -36
- dissect/target/plugins/apps/remoteaccess/anydesk.py +70 -50
- dissect/target/plugins/apps/remoteaccess/remoteaccess.py +8 -8
- dissect/target/plugins/apps/remoteaccess/teamviewer.py +46 -31
- dissect/target/plugins/apps/ssh/openssh.py +1 -1
- dissect/target/plugins/apps/ssh/ssh.py +177 -0
- dissect/target/plugins/apps/texteditor/__init__.py +0 -0
- dissect/target/plugins/apps/texteditor/texteditor.py +13 -0
- dissect/target/plugins/apps/texteditor/windowsnotepad.py +340 -0
- dissect/target/plugins/child/qemu.py +21 -0
- dissect/target/plugins/filesystem/ntfs/mft.py +132 -45
- dissect/target/plugins/filesystem/unix/capability.py +102 -87
- dissect/target/plugins/filesystem/walkfs.py +32 -21
- dissect/target/plugins/filesystem/yara.py +144 -23
- dissect/target/plugins/general/network.py +82 -0
- dissect/target/plugins/general/users.py +14 -10
- dissect/target/plugins/os/unix/_os.py +19 -5
- dissect/target/plugins/os/unix/bsd/freebsd/_os.py +3 -5
- dissect/target/plugins/os/unix/esxi/_os.py +29 -23
- dissect/target/plugins/os/unix/etc/etc.py +5 -8
- dissect/target/plugins/os/unix/history.py +3 -7
- dissect/target/plugins/os/unix/linux/_os.py +15 -14
- dissect/target/plugins/os/unix/linux/android/_os.py +15 -24
- dissect/target/plugins/os/unix/linux/redhat/_os.py +1 -1
- dissect/target/plugins/os/unix/locale.py +17 -6
- dissect/target/plugins/os/unix/shadow.py +47 -31
- dissect/target/plugins/os/windows/_os.py +4 -4
- dissect/target/plugins/os/windows/adpolicy.py +4 -1
- dissect/target/plugins/os/windows/catroot.py +1 -11
- dissect/target/plugins/os/windows/credential/__init__.py +0 -0
- dissect/target/plugins/os/windows/credential/lsa.py +174 -0
- dissect/target/plugins/os/windows/{sam.py → credential/sam.py} +5 -2
- dissect/target/plugins/os/windows/defender.py +6 -3
- dissect/target/plugins/os/windows/dpapi/blob.py +3 -0
- dissect/target/plugins/os/windows/dpapi/crypto.py +61 -23
- dissect/target/plugins/os/windows/dpapi/dpapi.py +127 -133
- dissect/target/plugins/os/windows/dpapi/keyprovider/__init__.py +0 -0
- dissect/target/plugins/os/windows/dpapi/keyprovider/credhist.py +21 -0
- dissect/target/plugins/os/windows/dpapi/keyprovider/empty.py +17 -0
- dissect/target/plugins/os/windows/dpapi/keyprovider/keychain.py +20 -0
- dissect/target/plugins/os/windows/dpapi/keyprovider/keyprovider.py +8 -0
- dissect/target/plugins/os/windows/dpapi/keyprovider/lsa.py +38 -0
- dissect/target/plugins/os/windows/dpapi/master_key.py +3 -0
- dissect/target/plugins/os/windows/jumplist.py +292 -0
- dissect/target/plugins/os/windows/lnk.py +96 -93
- dissect/target/plugins/os/windows/regf/shellbags.py +8 -5
- dissect/target/plugins/os/windows/regf/shimcache.py +2 -2
- dissect/target/plugins/os/windows/regf/usb.py +179 -114
- dissect/target/plugins/os/windows/task_helpers/tasks_xml.py +1 -1
- dissect/target/plugins/os/windows/wua_history.py +1073 -0
- dissect/target/target.py +4 -3
- dissect/target/tools/fs.py +53 -15
- dissect/target/tools/fsutils.py +243 -0
- dissect/target/tools/info.py +11 -4
- dissect/target/tools/query.py +2 -2
- dissect/target/tools/shell.py +505 -333
- dissect/target/tools/utils.py +23 -2
- dissect/target/tools/yara.py +65 -0
- dissect/target/volumes/md.py +2 -2
- {dissect.target-3.18.dev15.dist-info → dissect.target-3.19.dist-info}/METADATA +11 -7
- {dissect.target-3.18.dev15.dist-info → dissect.target-3.19.dist-info}/RECORD +94 -75
- {dissect.target-3.18.dev15.dist-info → dissect.target-3.19.dist-info}/WHEEL +1 -1
- {dissect.target-3.18.dev15.dist-info → dissect.target-3.19.dist-info}/entry_points.txt +1 -0
- dissect/target/helpers/ssh.py +0 -177
- /dissect/target/plugins/os/windows/{credhist.py → credential/credhist.py} +0 -0
- {dissect.target-3.18.dev15.dist-info → dissect.target-3.19.dist-info}/COPYRIGHT +0 -0
- {dissect.target-3.18.dev15.dist-info → dissect.target-3.19.dist-info}/LICENSE +0 -0
- {dissect.target-3.18.dev15.dist-info → dissect.target-3.19.dist-info}/top_level.txt +0 -0
@@ -1,12 +1,16 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
1
3
|
import logging
|
2
|
-
from typing import Callable
|
4
|
+
from typing import Callable, Iterator
|
3
5
|
|
4
6
|
from dissect.ntfs.attr import Attribute
|
5
7
|
from dissect.ntfs.c_ntfs import FILE_RECORD_SEGMENT_IN_USE
|
6
8
|
from dissect.ntfs.mft import MftRecord
|
9
|
+
from flow.record import Record
|
7
10
|
from flow.record.fieldtypes import windows_path
|
8
11
|
|
9
12
|
from dissect.target.exceptions import UnsupportedPluginError
|
13
|
+
from dissect.target.filesystems.ntfs import NtfsFilesystem
|
10
14
|
from dissect.target.helpers.record import TargetRecordDescriptor
|
11
15
|
from dissect.target.plugin import Plugin, arg, export
|
12
16
|
from dissect.target.plugins.filesystem.ntfs.utils import (
|
@@ -53,7 +57,6 @@ FilesystemStdRecord = TargetRecordDescriptor(
|
|
53
57
|
],
|
54
58
|
)
|
55
59
|
|
56
|
-
|
57
60
|
FilesystemFilenameCompactRecord = TargetRecordDescriptor(
|
58
61
|
"filesystem/ntfs/mft/filename/compact",
|
59
62
|
[
|
@@ -90,6 +93,21 @@ FilesystemFilenameRecord = TargetRecordDescriptor(
|
|
90
93
|
],
|
91
94
|
)
|
92
95
|
|
96
|
+
FilesystemMACBRecord = TargetRecordDescriptor(
|
97
|
+
"filesystem/ntfs/mft/macb",
|
98
|
+
[
|
99
|
+
("datetime", "ts"),
|
100
|
+
("string", "macb"),
|
101
|
+
("uint32", "filename_index"),
|
102
|
+
("uint32", "segment"),
|
103
|
+
("path", "path"),
|
104
|
+
("string", "owner"),
|
105
|
+
("filesize", "filesize"),
|
106
|
+
("boolean", "resident"),
|
107
|
+
("boolean", "inuse"),
|
108
|
+
("string", "volume_uuid"),
|
109
|
+
],
|
110
|
+
)
|
93
111
|
|
94
112
|
RECORD_TYPES = {
|
95
113
|
InformationType.STANDARD_INFORMATION: FilesystemStdRecord,
|
@@ -104,9 +122,12 @@ COMPACT_RECORD_TYPES = {
|
|
104
122
|
|
105
123
|
|
106
124
|
class MftPlugin(Plugin):
|
125
|
+
def __init__(self, target):
|
126
|
+
super().__init__(target)
|
127
|
+
self.ntfs_filesystems = {index: fs for index, fs in enumerate(self.target.filesystems) if fs.__type__ == "ntfs"}
|
128
|
+
|
107
129
|
def check_compatible(self) -> None:
|
108
|
-
|
109
|
-
if not len(ntfs_filesystems):
|
130
|
+
if not len(self.ntfs_filesystems):
|
110
131
|
raise UnsupportedPluginError("No NTFS filesystems found")
|
111
132
|
|
112
133
|
@export(
|
@@ -117,8 +138,24 @@ class MftPlugin(Plugin):
|
|
117
138
|
FilesystemFilenameCompactRecord,
|
118
139
|
]
|
119
140
|
)
|
120
|
-
@arg(
|
121
|
-
|
141
|
+
@arg(
|
142
|
+
"--compact",
|
143
|
+
group="fmt",
|
144
|
+
action="store_true",
|
145
|
+
help="compacts the MFT entry timestamps into a single record",
|
146
|
+
)
|
147
|
+
@arg("--fs", type=int, default=None, help="optional filesystem index, zero indexed")
|
148
|
+
@arg("--start", type=int, default=0, help="the first MFT segment number")
|
149
|
+
@arg("--end", type=int, default=-1, help="the last MFT segment number")
|
150
|
+
@arg(
|
151
|
+
"--macb",
|
152
|
+
group="fmt",
|
153
|
+
action="store_true",
|
154
|
+
help="compacts the MFT entry timestamps into aggregated records with MACB bitfield",
|
155
|
+
)
|
156
|
+
def mft(
|
157
|
+
self, compact: bool = False, fs: int | None = None, start: int = 0, end: int = -1, macb: bool = False
|
158
|
+
) -> Iterator[Record]:
|
122
159
|
"""Return the MFT records of all NTFS filesystems.
|
123
160
|
|
124
161
|
The Master File Table (MFT) contains primarily metadata about every file and folder on a NFTS filesystem.
|
@@ -133,41 +170,60 @@ class MftPlugin(Plugin):
|
|
133
170
|
- https://docs.microsoft.com/en-us/windows/win32/fileio/master-file-table
|
134
171
|
"""
|
135
172
|
|
136
|
-
|
137
|
-
record_formatter = compacted_formatter
|
138
|
-
else:
|
139
|
-
record_formatter = formatter
|
173
|
+
record_formatter = formatter
|
140
174
|
|
141
|
-
|
142
|
-
|
143
|
-
continue
|
175
|
+
def noaggr(records: list[Record]) -> Iterator[Record]:
|
176
|
+
yield from records
|
144
177
|
|
145
|
-
|
146
|
-
# VirtualFilesystem, The driveletter (more accurate mount point)
|
147
|
-
# returned will be that of the VirtualFilesystem. This makes sure
|
148
|
-
# the paths returned in the records are actually reachable.
|
149
|
-
drive_letter = get_drive_letter(self.target, fs)
|
150
|
-
volume_uuid = get_volume_identifier(fs)
|
178
|
+
aggr = noaggr
|
151
179
|
|
180
|
+
if compact:
|
181
|
+
record_formatter = compacted_formatter
|
182
|
+
elif macb:
|
183
|
+
aggr = macb_aggr
|
184
|
+
|
185
|
+
if fs is not None:
|
152
186
|
try:
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
187
|
+
filesystem = self.ntfs_filesystems[fs]
|
188
|
+
except KeyError:
|
189
|
+
self.target.log.error("NTFS filesystem with index number %s does not exist", fs)
|
190
|
+
return
|
191
|
+
|
192
|
+
yield from self.segments(filesystem, record_formatter, aggr, start, end)
|
193
|
+
else:
|
194
|
+
for filesystem in self.ntfs_filesystems.values():
|
195
|
+
yield from self.segments(filesystem, record_formatter, aggr, start, end)
|
196
|
+
|
197
|
+
def segments(
|
198
|
+
self, fs: NtfsFilesystem, record_formatter: Callable, aggr: Callable, start: int, end: int
|
199
|
+
) -> Iterator[Record]:
|
200
|
+
# If this filesystem is a "fake" NTFS filesystem, used to enhance a
|
201
|
+
# VirtualFilesystem, The driveletter (more accurate mount point)
|
202
|
+
# returned will be that of the VirtualFilesystem. This makes sure
|
203
|
+
# the paths returned in the records are actually reachable.
|
204
|
+
drive_letter = get_drive_letter(self.target, fs)
|
205
|
+
volume_uuid = get_volume_identifier(fs)
|
206
|
+
|
207
|
+
try:
|
208
|
+
for record in fs.ntfs.mft.segments(start, end):
|
209
|
+
try:
|
210
|
+
inuse = bool(record.header.Flags & FILE_RECORD_SEGMENT_IN_USE)
|
211
|
+
owner, _ = get_owner_and_group(record, fs)
|
212
|
+
resident = None
|
213
|
+
size = None
|
214
|
+
|
215
|
+
if not record.is_dir():
|
216
|
+
for data_attribute in record.attributes.DATA:
|
217
|
+
if data_attribute.name == "":
|
218
|
+
resident = data_attribute.resident
|
219
|
+
break
|
220
|
+
|
221
|
+
size = get_record_size(record)
|
222
|
+
|
223
|
+
for path in record.full_paths():
|
224
|
+
path = f"{drive_letter}{path}"
|
225
|
+
yield from aggr(
|
226
|
+
self.mft_records(
|
171
227
|
drive_letter=drive_letter,
|
172
228
|
record=record,
|
173
229
|
segment=record.segment,
|
@@ -179,12 +235,13 @@ class MftPlugin(Plugin):
|
|
179
235
|
volume_uuid=volume_uuid,
|
180
236
|
record_formatter=record_formatter,
|
181
237
|
)
|
182
|
-
|
183
|
-
|
184
|
-
|
238
|
+
)
|
239
|
+
except Exception as e:
|
240
|
+
self.target.log.warning("An error occured parsing MFT segment %d: %s", record.segment, str(e))
|
241
|
+
self.target.log.debug("", exc_info=e)
|
185
242
|
|
186
|
-
|
187
|
-
|
243
|
+
except Exception:
|
244
|
+
log.exception("An error occured constructing FilesystemRecords")
|
188
245
|
|
189
246
|
def mft_records(
|
190
247
|
self,
|
@@ -198,7 +255,7 @@ class MftPlugin(Plugin):
|
|
198
255
|
inuse: bool,
|
199
256
|
volume_uuid: str,
|
200
257
|
record_formatter: Callable,
|
201
|
-
):
|
258
|
+
) -> Iterator[Record]:
|
202
259
|
for attr in record.attributes.STANDARD_INFORMATION:
|
203
260
|
yield from record_formatter(
|
204
261
|
attr=attr,
|
@@ -255,7 +312,7 @@ class MftPlugin(Plugin):
|
|
255
312
|
)
|
256
313
|
|
257
314
|
|
258
|
-
def compacted_formatter(attr: Attribute, record_type: InformationType, **kwargs):
|
315
|
+
def compacted_formatter(attr: Attribute, record_type: InformationType, **kwargs) -> Iterator[Record]:
|
259
316
|
record_desc = COMPACT_RECORD_TYPES.get(record_type)
|
260
317
|
yield record_desc(
|
261
318
|
creation_time=attr.creation_time,
|
@@ -266,7 +323,7 @@ def compacted_formatter(attr: Attribute, record_type: InformationType, **kwargs)
|
|
266
323
|
)
|
267
324
|
|
268
325
|
|
269
|
-
def formatter(attr: Attribute, record_type: InformationType, **kwargs):
|
326
|
+
def formatter(attr: Attribute, record_type: InformationType, **kwargs) -> Iterator[Record]:
|
270
327
|
record_desc = RECORD_TYPES.get(record_type)
|
271
328
|
for type, timestamp in [
|
272
329
|
("B", attr.creation_time),
|
@@ -275,3 +332,33 @@ def formatter(attr: Attribute, record_type: InformationType, **kwargs):
|
|
275
332
|
("A", attr.last_access_time),
|
276
333
|
]:
|
277
334
|
yield record_desc(ts=timestamp, ts_type=type, **kwargs)
|
335
|
+
|
336
|
+
|
337
|
+
def macb_aggr(records: list[Record]) -> Iterator[Record]:
|
338
|
+
def macb_set(bitfield, index, letter):
|
339
|
+
return bitfield[:index] + letter + bitfield[index + 1 :]
|
340
|
+
|
341
|
+
macbs = []
|
342
|
+
for record in records:
|
343
|
+
found = False
|
344
|
+
|
345
|
+
offset_std = int(record._desc.name == "filesystem/ntfs/mft/std") * 5
|
346
|
+
offset_ads = (int(record.ads) * 10) if offset_std == 0 else 0
|
347
|
+
|
348
|
+
field = "MACB".find(record.ts_type) + offset_std + offset_ads
|
349
|
+
for macb in macbs:
|
350
|
+
if macb.ts == record.ts:
|
351
|
+
macb.macb = macb_set(macb.macb, field, record.ts_type)
|
352
|
+
found = True
|
353
|
+
break
|
354
|
+
|
355
|
+
if found:
|
356
|
+
continue
|
357
|
+
|
358
|
+
macb = FilesystemMACBRecord.init_from_record(record)
|
359
|
+
macb.macb = "..../..../...."
|
360
|
+
macb.macb = macb_set(macb.macb, field, record.ts_type)
|
361
|
+
|
362
|
+
macbs.append(macb)
|
363
|
+
|
364
|
+
yield from macbs
|
@@ -1,20 +1,20 @@
|
|
1
|
-
import struct
|
2
1
|
from enum import IntEnum
|
3
2
|
from io import BytesIO
|
3
|
+
from typing import Iterator
|
4
4
|
|
5
|
-
from dissect.target.exceptions import
|
5
|
+
from dissect.target.exceptions import UnsupportedPluginError
|
6
6
|
from dissect.target.helpers.record import TargetRecordDescriptor
|
7
7
|
from dissect.target.plugin import Plugin, export
|
8
|
-
from dissect.target.plugins.filesystem.walkfs import generate_record
|
9
8
|
|
10
9
|
CapabilityRecord = TargetRecordDescriptor(
|
11
10
|
"filesystem/unix/capability",
|
12
11
|
[
|
13
|
-
("
|
12
|
+
("datetime", "ts_mtime"),
|
13
|
+
("path", "path"),
|
14
14
|
("string[]", "permitted"),
|
15
15
|
("string[]", "inheritable"),
|
16
16
|
("boolean", "effective"),
|
17
|
-
("uint32", "
|
17
|
+
("uint32", "root_id"),
|
18
18
|
],
|
19
19
|
)
|
20
20
|
|
@@ -82,88 +82,103 @@ class CapabilityPlugin(Plugin):
|
|
82
82
|
"""Plugin to yield files with capabilites set."""
|
83
83
|
|
84
84
|
def check_compatible(self) -> None:
|
85
|
-
if not self.target.has_function("walkfs")
|
86
|
-
raise UnsupportedPluginError("
|
85
|
+
if not self.target.has_function("walkfs"):
|
86
|
+
raise UnsupportedPluginError("Need walkfs plugin")
|
87
|
+
|
88
|
+
if not any(fs.__type__ in ("extfs", "xfs") for fs in self.target.filesystems):
|
89
|
+
raise UnsupportedPluginError("Capability plugin only works on EXT and XFS filesystems")
|
87
90
|
|
88
91
|
@export(record=CapabilityRecord)
|
89
|
-
def capability_binaries(self):
|
90
|
-
"""Find all files that have capabilities set.
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
92
|
+
def capability_binaries(self) -> Iterator[CapabilityRecord]:
|
93
|
+
"""Find all files that have capabilities set on files.
|
94
|
+
|
95
|
+
Resources:
|
96
|
+
- https://github.com/torvalds/linux/blob/master/include/uapi/linux/capability.h
|
97
|
+
"""
|
98
|
+
|
99
|
+
for entry in self.target.fs.recurse("/"):
|
100
|
+
if not entry.is_file() or entry.is_symlink():
|
101
|
+
continue
|
102
|
+
|
103
|
+
try:
|
104
|
+
attrs = [attr for attr in entry.lattr() if attr.name == "security.capability"]
|
105
|
+
except Exception as e:
|
106
|
+
self.target.log.warning("Failed to get attrs for entry %s", entry)
|
107
|
+
self.target.log.debug("", exc_info=e)
|
108
|
+
continue
|
109
|
+
|
110
|
+
for attr in attrs:
|
99
111
|
try:
|
100
|
-
|
101
|
-
except
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
112
|
+
permitted, inheritable, effective, root_id = parse_attr(attr.value)
|
113
|
+
except ValueError as e:
|
114
|
+
self.target.log.warning("Could not parse attributes for entry %s: %s", entry, str(e.value))
|
115
|
+
self.target.log.debug("", exc_info=e)
|
116
|
+
|
117
|
+
yield CapabilityRecord(
|
118
|
+
ts_mtime=entry.lstat().st_mtime,
|
119
|
+
path=self.target.fs.path(entry.path),
|
120
|
+
permitted=permitted,
|
121
|
+
inheritable=inheritable,
|
122
|
+
effective=effective,
|
123
|
+
root_id=root_id,
|
124
|
+
_target=self.target,
|
125
|
+
)
|
126
|
+
|
127
|
+
|
128
|
+
def parse_attr(attr: bytes) -> tuple[list[str], list[str], bool, int]:
|
129
|
+
"""Efficiently parse a Linux xattr capability struct.
|
130
|
+
|
131
|
+
Returns:
|
132
|
+
A tuple of permitted capability names, inheritable capability names, effective flag and ``root_id``.
|
133
|
+
"""
|
134
|
+
buf = BytesIO(attr)
|
135
|
+
|
136
|
+
# The struct is small enough we can just use int.from_bytes
|
137
|
+
magic_etc = int.from_bytes(buf.read(4), "little")
|
138
|
+
effective = magic_etc & VFS_CAP_FLAGS_EFFECTIVE != 0
|
139
|
+
cap_revision = magic_etc & VFS_CAP_REVISION_MASK
|
140
|
+
|
141
|
+
permitted_caps = []
|
142
|
+
inheritable_caps = []
|
143
|
+
root_id = None
|
144
|
+
|
145
|
+
if cap_revision == VFS_CAP_REVISION_1:
|
146
|
+
num_caps = VFS_CAP_U32_1
|
147
|
+
data_len = (1 + 2 * VFS_CAP_U32_1) * 4
|
148
|
+
|
149
|
+
elif cap_revision == VFS_CAP_REVISION_2:
|
150
|
+
num_caps = VFS_CAP_U32_2
|
151
|
+
data_len = (1 + 2 * VFS_CAP_U32_2) * 4
|
152
|
+
|
153
|
+
elif cap_revision == VFS_CAP_REVISION_3:
|
154
|
+
num_caps = VFS_CAP_U32_3
|
155
|
+
data_len = (2 + 2 * VFS_CAP_U32_2) * 4
|
156
|
+
|
157
|
+
else:
|
158
|
+
raise ValueError("Unexpected capability revision '%s'" % cap_revision)
|
159
|
+
|
160
|
+
if data_len != (actual_len := len(attr)):
|
161
|
+
raise ValueError("Unexpected capability length (%s vs %s)", data_len, actual_len)
|
162
|
+
|
163
|
+
for _ in range(num_caps):
|
164
|
+
permitted_caps.append(int.from_bytes(buf.read(4), "little"))
|
165
|
+
inheritable_caps.append(int.from_bytes(buf.read(4), "little"))
|
166
|
+
|
167
|
+
if cap_revision == VFS_CAP_REVISION_3:
|
168
|
+
root_id = int.from_bytes(buf.read(4), "little")
|
169
|
+
|
170
|
+
permitted = []
|
171
|
+
inheritable = []
|
172
|
+
|
173
|
+
for capability in Capabilities:
|
174
|
+
for caps, results in [(permitted_caps, permitted), (inheritable_caps, inheritable)]:
|
175
|
+
# CAP_TO_INDEX
|
176
|
+
cap_index = capability.value >> 5
|
177
|
+
if cap_index >= len(caps):
|
178
|
+
# We loop over all capabilities, but might only have a version 1 caps list
|
179
|
+
continue
|
180
|
+
|
181
|
+
if caps[cap_index] & (1 << (capability.value & 31)) != 0:
|
182
|
+
results.append(capability.name)
|
183
|
+
|
184
|
+
return permitted, inheritable, effective, root_id
|
@@ -1,12 +1,11 @@
|
|
1
|
-
from typing import
|
1
|
+
from typing import Iterator
|
2
2
|
|
3
3
|
from dissect.util.ts import from_unix
|
4
4
|
|
5
5
|
from dissect.target.exceptions import FileNotFoundError, UnsupportedPluginError
|
6
|
-
from dissect.target.filesystem import LayerFilesystemEntry
|
7
|
-
from dissect.target.helpers.fsutil import TargetPath
|
6
|
+
from dissect.target.filesystem import FilesystemEntry, LayerFilesystemEntry
|
8
7
|
from dissect.target.helpers.record import TargetRecordDescriptor
|
9
|
-
from dissect.target.plugin import Plugin, export
|
8
|
+
from dissect.target.plugin import Plugin, arg, export
|
10
9
|
from dissect.target.target import Target
|
11
10
|
|
12
11
|
FilesystemRecord = TargetRecordDescriptor(
|
@@ -30,37 +29,49 @@ FilesystemRecord = TargetRecordDescriptor(
|
|
30
29
|
class WalkFSPlugin(Plugin):
|
31
30
|
def check_compatible(self) -> None:
|
32
31
|
if not len(self.target.filesystems):
|
33
|
-
raise UnsupportedPluginError("No filesystems
|
32
|
+
raise UnsupportedPluginError("No filesystems to walk")
|
34
33
|
|
35
34
|
@export(record=FilesystemRecord)
|
36
|
-
|
35
|
+
@arg("--walkfs-path", default="/", help="path to recursively walk")
|
36
|
+
def walkfs(self, walkfs_path: str = "/") -> Iterator[FilesystemRecord]:
|
37
37
|
"""Walk a target's filesystem and return all filesystem entries."""
|
38
|
-
for
|
39
|
-
|
40
|
-
|
41
|
-
path = self.target.fs.path(entry.path)
|
42
|
-
try:
|
43
|
-
record = generate_record(self.target, path)
|
44
|
-
except FileNotFoundError:
|
45
|
-
continue
|
46
|
-
yield record
|
38
|
+
for entry in self.target.fs.recurse(walkfs_path):
|
39
|
+
try:
|
40
|
+
yield generate_record(self.target, entry)
|
47
41
|
|
42
|
+
except FileNotFoundError as e:
|
43
|
+
self.target.log.warning("File not found: %s", entry)
|
44
|
+
self.target.log.debug("", exc_info=e)
|
45
|
+
except Exception as e:
|
46
|
+
self.target.log.warning("Exception generating record for: %s", entry)
|
47
|
+
self.target.log.debug("", exc_info=e)
|
48
|
+
continue
|
49
|
+
|
50
|
+
|
51
|
+
def generate_record(target: Target, entry: FilesystemEntry) -> FilesystemRecord:
|
52
|
+
"""Generate a :class:`FilesystemRecord` from the given :class:`FilesystemEntry`.
|
53
|
+
|
54
|
+
Args:
|
55
|
+
target: :class:`Target` instance
|
56
|
+
entry: :class:`FilesystemEntry` instance
|
57
|
+
|
58
|
+
Returns:
|
59
|
+
Generated :class:`FilesystemRecord` for the given :class:`FilesystemEntry`.
|
60
|
+
"""
|
61
|
+
stat = entry.lstat()
|
48
62
|
|
49
|
-
def generate_record(target: Target, path: TargetPath) -> FilesystemRecord:
|
50
|
-
stat = path.lstat()
|
51
|
-
btime = from_unix(stat.st_birthtime) if stat.st_birthtime else None
|
52
|
-
entry = path.get()
|
53
63
|
if isinstance(entry, LayerFilesystemEntry):
|
54
64
|
fs_types = [sub_entry.fs.__type__ for sub_entry in entry.entries]
|
55
65
|
else:
|
56
66
|
fs_types = [entry.fs.__type__]
|
67
|
+
|
57
68
|
return FilesystemRecord(
|
58
69
|
atime=from_unix(stat.st_atime),
|
59
70
|
mtime=from_unix(stat.st_mtime),
|
60
71
|
ctime=from_unix(stat.st_ctime),
|
61
|
-
btime=
|
72
|
+
btime=from_unix(stat.st_birthtime) if stat.st_birthtime else None,
|
62
73
|
ino=stat.st_ino,
|
63
|
-
path=path,
|
74
|
+
path=entry.path,
|
64
75
|
size=stat.st_size,
|
65
76
|
mode=stat.st_mode,
|
66
77
|
uid=stat.st_uid,
|