dissect.target 3.19.dev35__py3-none-any.whl → 3.19.dev37__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/loaders/libvirt.py +40 -0
- dissect/target/plugins/child/qemu.py +21 -0
- dissect/target/plugins/filesystem/ntfs/mft.py +74 -52
- {dissect.target-3.19.dev35.dist-info → dissect.target-3.19.dev37.dist-info}/METADATA +1 -1
- {dissect.target-3.19.dev35.dist-info → dissect.target-3.19.dev37.dist-info}/RECORD +10 -8
- {dissect.target-3.19.dev35.dist-info → dissect.target-3.19.dev37.dist-info}/COPYRIGHT +0 -0
- {dissect.target-3.19.dev35.dist-info → dissect.target-3.19.dev37.dist-info}/LICENSE +0 -0
- {dissect.target-3.19.dev35.dist-info → dissect.target-3.19.dev37.dist-info}/WHEEL +0 -0
- {dissect.target-3.19.dev35.dist-info → dissect.target-3.19.dev37.dist-info}/entry_points.txt +0 -0
- {dissect.target-3.19.dev35.dist-info → dissect.target-3.19.dev37.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,40 @@
|
|
1
|
+
from pathlib import Path
|
2
|
+
|
3
|
+
from defusedxml import ElementTree
|
4
|
+
|
5
|
+
from dissect.target import container
|
6
|
+
from dissect.target.helpers import fsutil
|
7
|
+
from dissect.target.loader import Loader
|
8
|
+
from dissect.target.target import Target
|
9
|
+
|
10
|
+
|
11
|
+
class LibvirtLoader(Loader):
|
12
|
+
"""Load libvirt xml configuration files."""
|
13
|
+
|
14
|
+
def __init__(self, path: Path, **kwargs):
|
15
|
+
path = path.resolve()
|
16
|
+
self.base_dir = path.parent
|
17
|
+
super().__init__(path)
|
18
|
+
|
19
|
+
@staticmethod
|
20
|
+
def detect(path: Path) -> bool:
|
21
|
+
if path.suffix.lower() != ".xml":
|
22
|
+
return False
|
23
|
+
|
24
|
+
with path.open("rb") as fh:
|
25
|
+
lines = fh.read(512).split(b"\n")
|
26
|
+
# From what I've seen, these are are always at the start of the file
|
27
|
+
# If its generated using virt-install
|
28
|
+
needles = [b"<domain", b"<name>", b"<uuid>"]
|
29
|
+
return all(any(needle in line for line in lines) for needle in needles)
|
30
|
+
|
31
|
+
def map(self, target: Target) -> None:
|
32
|
+
xml_data = ElementTree.fromstring(self.path.read_text())
|
33
|
+
for disk in xml_data.findall("devices/disk/source"):
|
34
|
+
if not (file := disk.get("file")):
|
35
|
+
continue
|
36
|
+
|
37
|
+
for part in [fsutil.basename(file), file]:
|
38
|
+
if (path := self.base_dir.joinpath(part)).exists():
|
39
|
+
target.disks.add(container.open(path))
|
40
|
+
break
|
@@ -0,0 +1,21 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
from typing import Iterator
|
4
|
+
|
5
|
+
from dissect.target.exceptions import UnsupportedPluginError
|
6
|
+
from dissect.target.helpers.record import ChildTargetRecord
|
7
|
+
from dissect.target.plugin import ChildTargetPlugin
|
8
|
+
|
9
|
+
|
10
|
+
class QemuChildTargetPlugin(ChildTargetPlugin):
|
11
|
+
"""Child target plugin that yields all QEMU domains from a KVM libvirt deamon."""
|
12
|
+
|
13
|
+
__type__ = "qemu"
|
14
|
+
|
15
|
+
def check_compatible(self) -> None:
|
16
|
+
if not self.target.fs.path("/etc/libvirt/qemu").exists():
|
17
|
+
raise UnsupportedPluginError("No libvirt QEMU installation found")
|
18
|
+
|
19
|
+
def list_children(self) -> Iterator[ChildTargetRecord]:
|
20
|
+
for domain in self.target.fs.path("/etc/libvirt/qemu").glob("*.xml"):
|
21
|
+
yield ChildTargetRecord(type=self.__type__, path=domain)
|
@@ -1,3 +1,5 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
1
3
|
import logging
|
2
4
|
from typing import Callable, Iterator
|
3
5
|
|
@@ -8,6 +10,7 @@ from flow.record import Record
|
|
8
10
|
from flow.record.fieldtypes import windows_path
|
9
11
|
|
10
12
|
from dissect.target.exceptions import UnsupportedPluginError
|
13
|
+
from dissect.target.filesystems.ntfs import NtfsFilesystem
|
11
14
|
from dissect.target.helpers.record import TargetRecordDescriptor
|
12
15
|
from dissect.target.plugin import Plugin, arg, export
|
13
16
|
from dissect.target.plugins.filesystem.ntfs.utils import (
|
@@ -119,9 +122,12 @@ COMPACT_RECORD_TYPES = {
|
|
119
122
|
|
120
123
|
|
121
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
|
+
|
122
129
|
def check_compatible(self) -> None:
|
123
|
-
|
124
|
-
if not len(ntfs_filesystems):
|
130
|
+
if not len(self.ntfs_filesystems):
|
125
131
|
raise UnsupportedPluginError("No NTFS filesystems found")
|
126
132
|
|
127
133
|
@export(
|
@@ -133,12 +139,17 @@ class MftPlugin(Plugin):
|
|
133
139
|
]
|
134
140
|
)
|
135
141
|
@arg("--compact", action="store_true", help="compacts the MFT entry timestamps into a single record")
|
142
|
+
@arg("--fs", type=int, default=None, help="optional filesystem index, zero indexed")
|
143
|
+
@arg("--start", type=int, default=0, help="the first MFT segment number")
|
144
|
+
@arg("--end", type=int, default=-1, help="the last MFT segment number")
|
136
145
|
@arg(
|
137
146
|
"--macb",
|
138
147
|
action="store_true",
|
139
148
|
help="compacts the MFT entry timestamps into aggregated records with MACB bitfield",
|
140
149
|
)
|
141
|
-
def mft(
|
150
|
+
def mft(
|
151
|
+
self, compact: bool = False, fs: int | None = None, start: int = 0, end: int = -1, macb: bool = False
|
152
|
+
) -> Iterator[Record]:
|
142
153
|
"""Return the MFT records of all NTFS filesystems.
|
143
154
|
|
144
155
|
The Master File Table (MFT) contains primarily metadata about every file and folder on a NFTS filesystem.
|
@@ -167,55 +178,66 @@ class MftPlugin(Plugin):
|
|
167
178
|
elif macb:
|
168
179
|
aggr = macb_aggr
|
169
180
|
|
170
|
-
|
171
|
-
if fs.__type__ != "ntfs":
|
172
|
-
continue
|
173
|
-
|
174
|
-
# If this filesystem is a "fake" NTFS filesystem, used to enhance a
|
175
|
-
# VirtualFilesystem, The driveletter (more accurate mount point)
|
176
|
-
# returned will be that of the VirtualFilesystem. This makes sure
|
177
|
-
# the paths returned in the records are actually reachable.
|
178
|
-
drive_letter = get_drive_letter(self.target, fs)
|
179
|
-
volume_uuid = get_volume_identifier(fs)
|
180
|
-
|
181
|
+
if fs is not None:
|
181
182
|
try:
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
183
|
+
filesystem = self.ntfs_filesystems[fs]
|
184
|
+
except KeyError:
|
185
|
+
self.target.log.error("NTFS filesystem with index number %s does not exist", fs)
|
186
|
+
return
|
187
|
+
|
188
|
+
yield from self.segments(filesystem, record_formatter, aggr, start, end)
|
189
|
+
else:
|
190
|
+
for filesystem in self.ntfs_filesystems.values():
|
191
|
+
yield from self.segments(filesystem, record_formatter, aggr, start, end)
|
192
|
+
|
193
|
+
def segments(
|
194
|
+
self, fs: NtfsFilesystem, record_formatter: Callable, aggr: Callable, start: int, end: int
|
195
|
+
) -> Iterator[Record]:
|
196
|
+
# If this filesystem is a "fake" NTFS filesystem, used to enhance a
|
197
|
+
# VirtualFilesystem, The driveletter (more accurate mount point)
|
198
|
+
# returned will be that of the VirtualFilesystem. This makes sure
|
199
|
+
# the paths returned in the records are actually reachable.
|
200
|
+
drive_letter = get_drive_letter(self.target, fs)
|
201
|
+
volume_uuid = get_volume_identifier(fs)
|
202
|
+
|
203
|
+
try:
|
204
|
+
for record in fs.ntfs.mft.segments(start, end):
|
205
|
+
try:
|
206
|
+
inuse = bool(record.header.Flags & FILE_RECORD_SEGMENT_IN_USE)
|
207
|
+
owner, _ = get_owner_and_group(record, fs)
|
208
|
+
resident = None
|
209
|
+
size = None
|
210
|
+
|
211
|
+
if not record.is_dir():
|
212
|
+
for data_attribute in record.attributes.DATA:
|
213
|
+
if data_attribute.name == "":
|
214
|
+
resident = data_attribute.resident
|
215
|
+
break
|
216
|
+
|
217
|
+
size = get_record_size(record)
|
218
|
+
|
219
|
+
for path in record.full_paths():
|
220
|
+
path = f"{drive_letter}{path}"
|
221
|
+
yield from aggr(
|
222
|
+
self.mft_records(
|
223
|
+
drive_letter=drive_letter,
|
224
|
+
record=record,
|
225
|
+
segment=record.segment,
|
226
|
+
path=path,
|
227
|
+
owner=owner,
|
228
|
+
size=size,
|
229
|
+
resident=resident,
|
230
|
+
inuse=inuse,
|
231
|
+
volume_uuid=volume_uuid,
|
232
|
+
record_formatter=record_formatter,
|
212
233
|
)
|
213
|
-
|
214
|
-
|
215
|
-
|
234
|
+
)
|
235
|
+
except Exception as e:
|
236
|
+
self.target.log.warning("An error occured parsing MFT segment %d: %s", record.segment, str(e))
|
237
|
+
self.target.log.debug("", exc_info=e)
|
216
238
|
|
217
|
-
|
218
|
-
|
239
|
+
except Exception:
|
240
|
+
log.exception("An error occured constructing FilesystemRecords")
|
219
241
|
|
220
242
|
def mft_records(
|
221
243
|
self,
|
@@ -229,7 +251,7 @@ class MftPlugin(Plugin):
|
|
229
251
|
inuse: bool,
|
230
252
|
volume_uuid: str,
|
231
253
|
record_formatter: Callable,
|
232
|
-
):
|
254
|
+
) -> Iterator[Record]:
|
233
255
|
for attr in record.attributes.STANDARD_INFORMATION:
|
234
256
|
yield from record_formatter(
|
235
257
|
attr=attr,
|
@@ -286,7 +308,7 @@ class MftPlugin(Plugin):
|
|
286
308
|
)
|
287
309
|
|
288
310
|
|
289
|
-
def compacted_formatter(attr: Attribute, record_type: InformationType, **kwargs):
|
311
|
+
def compacted_formatter(attr: Attribute, record_type: InformationType, **kwargs) -> Iterator[Record]:
|
290
312
|
record_desc = COMPACT_RECORD_TYPES.get(record_type)
|
291
313
|
yield record_desc(
|
292
314
|
creation_time=attr.creation_time,
|
@@ -297,7 +319,7 @@ def compacted_formatter(attr: Attribute, record_type: InformationType, **kwargs)
|
|
297
319
|
)
|
298
320
|
|
299
321
|
|
300
|
-
def formatter(attr: Attribute, record_type: InformationType, **kwargs):
|
322
|
+
def formatter(attr: Attribute, record_type: InformationType, **kwargs) -> Iterator[Record]:
|
301
323
|
record_desc = RECORD_TYPES.get(record_type)
|
302
324
|
for type, timestamp in [
|
303
325
|
("B", attr.creation_time),
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: dissect.target
|
3
|
-
Version: 3.19.
|
3
|
+
Version: 3.19.dev37
|
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
|
@@ -84,6 +84,7 @@ dissect/target/loaders/dir.py,sha256=F-PgvBw82XmL0rdKyBxznUkDc5Oct6-_Y9xM4fhvA6I
|
|
84
84
|
dissect/target/loaders/hyperv.py,sha256=_IOUJEO0BXaCBZ6sjIX0DZTkG9UNW5Vs9VcNHYv073w,5928
|
85
85
|
dissect/target/loaders/itunes.py,sha256=rKOhlDRypQBGkuSZudMDS1Mlb9XV6BD5FRvM7tGq9jU,13128
|
86
86
|
dissect/target/loaders/kape.py,sha256=t5TfrGLqPeIpUUpXzIl6aHsqXMEGDqJ5YwDCs07DiBA,1237
|
87
|
+
dissect/target/loaders/libvirt.py,sha256=_3EFIytMGbiLMISHx4QXVrDebsRO6J6sMkE3TH68qsg,1374
|
87
88
|
dissect/target/loaders/local.py,sha256=Ul-LCd_fY7SyWOVR6nH-NqbkuNpxoZVmffwrkvQElU8,16453
|
88
89
|
dissect/target/loaders/log.py,sha256=cCkDIRS4aPlX3U-n_jUKaI2FPSV3BDpfqKceaU7rBbo,1507
|
89
90
|
dissect/target/loaders/mqtt.py,sha256=pn2VtFh0jeYXMod4CuZOKGhe2ScQixJ1Xhx6MHe0rzk,16540
|
@@ -155,6 +156,7 @@ dissect/target/plugins/child/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NM
|
|
155
156
|
dissect/target/plugins/child/docker.py,sha256=frBZ8UUzbtkT9VrK1fwUzXDAdkHESdPCb-QI_OP9Jj4,872
|
156
157
|
dissect/target/plugins/child/esxi.py,sha256=GfgQzxntcHcyxAE2QjMJ-TrFhklweSXLbYh0uuv-klg,693
|
157
158
|
dissect/target/plugins/child/hyperv.py,sha256=R2qVeu4p_9V53jO-65znN0LwX9v3FVA-9jbbtOQcEz8,2236
|
159
|
+
dissect/target/plugins/child/qemu.py,sha256=vNzQwzFO964jYaI67MlX8vpWyHxpegjIU5F29zHKOGI,791
|
158
160
|
dissect/target/plugins/child/virtuozzo.py,sha256=Mx4ZxEl21g7IYkzraw4FBZup5EfrkFDv4WuTE3hxguw,1206
|
159
161
|
dissect/target/plugins/child/vmware_workstation.py,sha256=8wkA_tSufvBUyp4XQHzRzFETf5ROlyyO_MVS3TExyfw,1570
|
160
162
|
dissect/target/plugins/child/wsl.py,sha256=IssQgYET1T-XR5ZX2lGlNFJ_u_3QECpMF_7kXu09HTE,2469
|
@@ -166,7 +168,7 @@ dissect/target/plugins/filesystem/resolver.py,sha256=HfyASUFV4F9uD-yFXilFpPTORAs
|
|
166
168
|
dissect/target/plugins/filesystem/walkfs.py,sha256=e8HEZcV5Wiua26FGWL3xgiQ_PIhcNvGI5KCdsAx2Nmo,2298
|
167
169
|
dissect/target/plugins/filesystem/yara.py,sha256=zh4hU3L_egddLqDeaHDVuCWYhTlNzPYPVak36Q6IMxI,6621
|
168
170
|
dissect/target/plugins/filesystem/ntfs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
169
|
-
dissect/target/plugins/filesystem/ntfs/mft.py,sha256=
|
171
|
+
dissect/target/plugins/filesystem/ntfs/mft.py,sha256=WzvKSlH4egvDsuonRQ3AjYS59t9jjFX_-GOsAPLUeSk,12418
|
170
172
|
dissect/target/plugins/filesystem/ntfs/mft_timeline.py,sha256=vvNFAZbr7s3X2OTYf4ES_L6-XsouTXcTymfxnHfZ1Rw,6791
|
171
173
|
dissect/target/plugins/filesystem/ntfs/usnjrnl.py,sha256=uiT1ipmcAo__6VIUi8R_vvIu22vdnjMACKwLSAbzYjs,3704
|
172
174
|
dissect/target/plugins/filesystem/ntfs/utils.py,sha256=xG7Lgw9NX4tDDrZVRm0vycFVJTOM7j-HrjqzDh0f4uA,3136
|
@@ -346,10 +348,10 @@ dissect/target/volumes/luks.py,sha256=OmCMsw6rCUXG1_plnLVLTpsvE1n_6WtoRUGQbpmu1z
|
|
346
348
|
dissect/target/volumes/lvm.py,sha256=wwQVR9I3G9YzmY6UxFsH2Y4MXGBcKL9aayWGCDTiWMU,2269
|
347
349
|
dissect/target/volumes/md.py,sha256=7ShPtusuLGaIv27SvEETtgsuoQyAa4iAAeOR1NEaajI,1689
|
348
350
|
dissect/target/volumes/vmfs.py,sha256=-LoUbn9WNwTtLi_4K34uV_-wDw2W5hgaqxZNj4UmqAQ,1730
|
349
|
-
dissect.target-3.19.
|
350
|
-
dissect.target-3.19.
|
351
|
-
dissect.target-3.19.
|
352
|
-
dissect.target-3.19.
|
353
|
-
dissect.target-3.19.
|
354
|
-
dissect.target-3.19.
|
355
|
-
dissect.target-3.19.
|
351
|
+
dissect.target-3.19.dev37.dist-info/COPYRIGHT,sha256=m-9ih2RVhMiXHI2bf_oNSSgHgkeIvaYRVfKTwFbnJPA,301
|
352
|
+
dissect.target-3.19.dev37.dist-info/LICENSE,sha256=DZak_2itbUtvHzD3E7GNUYSRK6jdOJ-GqncQ2weavLA,34523
|
353
|
+
dissect.target-3.19.dev37.dist-info/METADATA,sha256=mXarkSk3Roesdmk1FGrFsYXYFROLuwmF6QxdMO3YOVQ,12719
|
354
|
+
dissect.target-3.19.dev37.dist-info/WHEEL,sha256=R0nc6qTxuoLk7ShA2_Y-UWkN8ZdfDBG2B6Eqpz2WXbs,91
|
355
|
+
dissect.target-3.19.dev37.dist-info/entry_points.txt,sha256=BWuxAb_6AvUAQpIQOQU0IMTlaF6TDht2AIZK8bHd-zE,492
|
356
|
+
dissect.target-3.19.dev37.dist-info/top_level.txt,sha256=Mn-CQzEYsAbkxrUI0TnplHuXnGVKzxpDw_po_sXpvv4,8
|
357
|
+
dissect.target-3.19.dev37.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
{dissect.target-3.19.dev35.dist-info → dissect.target-3.19.dev37.dist-info}/entry_points.txt
RENAMED
File without changes
|
File without changes
|