dissect.target 3.19.dev24__py3-none-any.whl → 3.19.dev26__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/filesystem/ntfs/mft.py +78 -17
- dissect/target/tools/fs.py +92 -14
- {dissect.target-3.19.dev24.dist-info → dissect.target-3.19.dev26.dist-info}/METADATA +1 -1
- {dissect.target-3.19.dev24.dist-info → dissect.target-3.19.dev26.dist-info}/RECORD +9 -9
- {dissect.target-3.19.dev24.dist-info → dissect.target-3.19.dev26.dist-info}/COPYRIGHT +0 -0
- {dissect.target-3.19.dev24.dist-info → dissect.target-3.19.dev26.dist-info}/LICENSE +0 -0
- {dissect.target-3.19.dev24.dist-info → dissect.target-3.19.dev26.dist-info}/WHEEL +0 -0
- {dissect.target-3.19.dev24.dist-info → dissect.target-3.19.dev26.dist-info}/entry_points.txt +0 -0
- {dissect.target-3.19.dev24.dist-info → dissect.target-3.19.dev26.dist-info}/top_level.txt +0 -0
@@ -1,9 +1,10 @@
|
|
1
1
|
import logging
|
2
|
-
from typing import Callable
|
2
|
+
from typing import Callable, Iterator
|
3
3
|
|
4
4
|
from dissect.ntfs.attr import Attribute
|
5
5
|
from dissect.ntfs.c_ntfs import FILE_RECORD_SEGMENT_IN_USE
|
6
6
|
from dissect.ntfs.mft import MftRecord
|
7
|
+
from flow.record import Record
|
7
8
|
from flow.record.fieldtypes import windows_path
|
8
9
|
|
9
10
|
from dissect.target.exceptions import UnsupportedPluginError
|
@@ -53,7 +54,6 @@ FilesystemStdRecord = TargetRecordDescriptor(
|
|
53
54
|
],
|
54
55
|
)
|
55
56
|
|
56
|
-
|
57
57
|
FilesystemFilenameCompactRecord = TargetRecordDescriptor(
|
58
58
|
"filesystem/ntfs/mft/filename/compact",
|
59
59
|
[
|
@@ -90,6 +90,21 @@ FilesystemFilenameRecord = TargetRecordDescriptor(
|
|
90
90
|
],
|
91
91
|
)
|
92
92
|
|
93
|
+
FilesystemMACBRecord = TargetRecordDescriptor(
|
94
|
+
"filesystem/ntfs/mft/macb",
|
95
|
+
[
|
96
|
+
("datetime", "ts"),
|
97
|
+
("string", "macb"),
|
98
|
+
("uint32", "filename_index"),
|
99
|
+
("uint32", "segment"),
|
100
|
+
("path", "path"),
|
101
|
+
("string", "owner"),
|
102
|
+
("filesize", "filesize"),
|
103
|
+
("boolean", "resident"),
|
104
|
+
("boolean", "inuse"),
|
105
|
+
("string", "volume_uuid"),
|
106
|
+
],
|
107
|
+
)
|
93
108
|
|
94
109
|
RECORD_TYPES = {
|
95
110
|
InformationType.STANDARD_INFORMATION: FilesystemStdRecord,
|
@@ -118,7 +133,12 @@ class MftPlugin(Plugin):
|
|
118
133
|
]
|
119
134
|
)
|
120
135
|
@arg("--compact", action="store_true", help="compacts the MFT entry timestamps into a single record")
|
121
|
-
|
136
|
+
@arg(
|
137
|
+
"--macb",
|
138
|
+
action="store_true",
|
139
|
+
help="compacts the MFT entry timestamps into aggregated records with MACB bitfield",
|
140
|
+
)
|
141
|
+
def mft(self, compact: bool = False, macb: bool = False):
|
122
142
|
"""Return the MFT records of all NTFS filesystems.
|
123
143
|
|
124
144
|
The Master File Table (MFT) contains primarily metadata about every file and folder on a NFTS filesystem.
|
@@ -133,10 +153,19 @@ class MftPlugin(Plugin):
|
|
133
153
|
- https://docs.microsoft.com/en-us/windows/win32/fileio/master-file-table
|
134
154
|
"""
|
135
155
|
|
136
|
-
|
156
|
+
record_formatter = formatter
|
157
|
+
|
158
|
+
def noaggr(records: list[Record]) -> Iterator[Record]:
|
159
|
+
yield from records
|
160
|
+
|
161
|
+
aggr = noaggr
|
162
|
+
|
163
|
+
if compact and macb:
|
164
|
+
raise ValueError("--macb and --compact are mutually exclusive")
|
165
|
+
elif compact:
|
137
166
|
record_formatter = compacted_formatter
|
138
|
-
|
139
|
-
|
167
|
+
elif macb:
|
168
|
+
aggr = macb_aggr
|
140
169
|
|
141
170
|
for fs in self.target.filesystems:
|
142
171
|
if fs.__type__ != "ntfs":
|
@@ -167,17 +196,19 @@ class MftPlugin(Plugin):
|
|
167
196
|
|
168
197
|
for path in record.full_paths():
|
169
198
|
path = f"{drive_letter}{path}"
|
170
|
-
yield from
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
199
|
+
yield from aggr(
|
200
|
+
self.mft_records(
|
201
|
+
drive_letter=drive_letter,
|
202
|
+
record=record,
|
203
|
+
segment=record.segment,
|
204
|
+
path=path,
|
205
|
+
owner=owner,
|
206
|
+
size=size,
|
207
|
+
resident=resident,
|
208
|
+
inuse=inuse,
|
209
|
+
volume_uuid=volume_uuid,
|
210
|
+
record_formatter=record_formatter,
|
211
|
+
)
|
181
212
|
)
|
182
213
|
except Exception as e:
|
183
214
|
self.target.log.warning("An error occured parsing MFT segment %d: %s", record.segment, str(e))
|
@@ -275,3 +306,33 @@ def formatter(attr: Attribute, record_type: InformationType, **kwargs):
|
|
275
306
|
("A", attr.last_access_time),
|
276
307
|
]:
|
277
308
|
yield record_desc(ts=timestamp, ts_type=type, **kwargs)
|
309
|
+
|
310
|
+
|
311
|
+
def macb_aggr(records: list[Record]) -> Iterator[Record]:
|
312
|
+
def macb_set(bitfield, index, letter):
|
313
|
+
return bitfield[:index] + letter + bitfield[index + 1 :]
|
314
|
+
|
315
|
+
macbs = []
|
316
|
+
for record in records:
|
317
|
+
found = False
|
318
|
+
|
319
|
+
offset_std = int(record._desc.name == "filesystem/ntfs/mft/std") * 5
|
320
|
+
offset_ads = (int(record.ads) * 10) if offset_std == 0 else 0
|
321
|
+
|
322
|
+
field = "MACB".find(record.ts_type) + offset_std + offset_ads
|
323
|
+
for macb in macbs:
|
324
|
+
if macb.ts == record.ts:
|
325
|
+
macb.macb = macb_set(macb.macb, field, record.ts_type)
|
326
|
+
found = True
|
327
|
+
break
|
328
|
+
|
329
|
+
if found:
|
330
|
+
continue
|
331
|
+
|
332
|
+
macb = FilesystemMACBRecord.init_from_record(record)
|
333
|
+
macb.macb = "..../..../...."
|
334
|
+
macb.macb = macb_set(macb.macb, field, record.ts_type)
|
335
|
+
|
336
|
+
macbs.append(macb)
|
337
|
+
|
338
|
+
yield from macbs
|
dissect/target/tools/fs.py
CHANGED
@@ -2,6 +2,7 @@
|
|
2
2
|
# -*- coding: utf-8 -*-
|
3
3
|
|
4
4
|
import argparse
|
5
|
+
import datetime
|
5
6
|
import logging
|
6
7
|
import operator
|
7
8
|
import os
|
@@ -12,6 +13,7 @@ import sys
|
|
12
13
|
from dissect.target import Target
|
13
14
|
from dissect.target.exceptions import TargetError
|
14
15
|
from dissect.target.helpers.fsutil import TargetPath
|
16
|
+
from dissect.target.tools.shell import stat_modestr
|
15
17
|
from dissect.target.tools.utils import (
|
16
18
|
catch_sigpipe,
|
17
19
|
configure_generic_arguments,
|
@@ -23,24 +25,90 @@ logging.lastResort = None
|
|
23
25
|
logging.raiseExceptions = False
|
24
26
|
|
25
27
|
|
26
|
-
def
|
27
|
-
|
28
|
-
|
28
|
+
def human_size(bytes: int, units: list[str] = ["", "K", "M", "G", "T", "P", "E"]) -> str:
|
29
|
+
"""Helper function to return the human readable string representation of bytes."""
|
30
|
+
return str(bytes) + units[0] if bytes < 1024 else human_size(bytes >> 10, units[1:])
|
29
31
|
|
30
32
|
|
31
|
-
def
|
33
|
+
def ls(t: Target, path: TargetPath, args: argparse.Namespace) -> None:
|
34
|
+
if args.use_ctime and args.use_atime:
|
35
|
+
log.error("Can't specify -c and -u at the same time")
|
36
|
+
return
|
37
|
+
if not path or not path.exists():
|
38
|
+
return
|
39
|
+
|
40
|
+
_print_ls(args, path, 0)
|
41
|
+
|
42
|
+
|
43
|
+
def _print_ls(args: argparse.Namespace, path: TargetPath, depth: int) -> None:
|
44
|
+
subdirs = []
|
45
|
+
|
46
|
+
if path.is_dir():
|
47
|
+
contents = sorted(path.iterdir(), key=operator.attrgetter("name"))
|
48
|
+
elif path.is_file():
|
49
|
+
contents = [path]
|
50
|
+
|
51
|
+
if depth > 0:
|
52
|
+
print(f"\n{str(path)}:")
|
53
|
+
|
54
|
+
if not args.l:
|
55
|
+
for entry in contents:
|
56
|
+
print(entry.name)
|
57
|
+
|
58
|
+
if entry.is_dir():
|
59
|
+
subdirs.append(entry)
|
60
|
+
else:
|
61
|
+
if len(contents) > 1:
|
62
|
+
print(f"total {len(contents)}")
|
63
|
+
|
64
|
+
for entry in contents:
|
65
|
+
_print_extensive_file_stat(args, entry, entry.name)
|
66
|
+
|
67
|
+
if entry.is_dir():
|
68
|
+
subdirs.append(entry)
|
69
|
+
|
70
|
+
if args.recursive and subdirs:
|
71
|
+
for subdir in subdirs:
|
72
|
+
_print_ls(args, subdir, depth + 1)
|
73
|
+
|
74
|
+
|
75
|
+
def _print_extensive_file_stat(args: argparse.Namespace, path: TargetPath, name: str) -> None:
|
76
|
+
try:
|
77
|
+
entry = path.get()
|
78
|
+
stat = entry.lstat()
|
79
|
+
symlink = f" -> {entry.readlink()}" if entry.is_symlink() else ""
|
80
|
+
show_time = stat.st_mtime
|
81
|
+
|
82
|
+
if args.use_ctime:
|
83
|
+
show_time = stat.st_ctime
|
84
|
+
elif args.use_atime:
|
85
|
+
show_time = stat.st_atime
|
86
|
+
|
87
|
+
utc_time = datetime.datetime.utcfromtimestamp(show_time).isoformat()
|
88
|
+
|
89
|
+
if args.human_readable:
|
90
|
+
size = human_size(stat.st_size)
|
91
|
+
else:
|
92
|
+
size = stat.st_size
|
93
|
+
|
94
|
+
print(f"{stat_modestr(stat)} {stat.st_uid:4d} {stat.st_gid:4d} {size:>6s} {utc_time} {name}{symlink}")
|
95
|
+
except FileNotFoundError:
|
96
|
+
print(f"?????????? ? ? ? ????-??-??T??:??:??.?????? {name}")
|
97
|
+
|
98
|
+
|
99
|
+
def cat(t: Target, path: TargetPath, args: argparse.Namespace) -> None:
|
32
100
|
stdout = sys.stdout
|
33
101
|
if hasattr(stdout, "buffer"):
|
34
102
|
stdout = stdout.buffer
|
35
103
|
shutil.copyfileobj(path.open(), stdout)
|
36
104
|
|
37
105
|
|
38
|
-
def walk(t, path, args):
|
106
|
+
def walk(t: Target, path: TargetPath, args: argparse.Namespace) -> None:
|
39
107
|
for e in path.rglob("*"):
|
40
108
|
print(str(e))
|
41
109
|
|
42
110
|
|
43
|
-
def cp(t, path, args):
|
111
|
+
def cp(t: Target, path: TargetPath, args: argparse.Namespace) -> None:
|
44
112
|
output = os.path.abspath(os.path.expanduser(args.output))
|
45
113
|
if path.is_file():
|
46
114
|
_extract_path(path, os.path.join(output, path.name))
|
@@ -75,7 +143,7 @@ def _extract_path(path: TargetPath, output_path: str) -> None:
|
|
75
143
|
|
76
144
|
|
77
145
|
@catch_sigpipe
|
78
|
-
def main():
|
146
|
+
def main() -> None:
|
79
147
|
help_formatter = argparse.ArgumentDefaultsHelpFormatter
|
80
148
|
parser = argparse.ArgumentParser(
|
81
149
|
description="dissect.target",
|
@@ -85,24 +153,34 @@ def main():
|
|
85
153
|
parser.add_argument("target", type=pathlib.Path, help="Target to load", metavar="TARGET")
|
86
154
|
|
87
155
|
baseparser = argparse.ArgumentParser(add_help=False)
|
88
|
-
baseparser.add_argument("path", type=str, help="
|
156
|
+
baseparser.add_argument("path", type=str, help="path to perform an action on", metavar="PATH")
|
89
157
|
|
90
|
-
subparsers = parser.add_subparsers(dest="subcommand", help="
|
91
|
-
parser_ls = subparsers.add_parser(
|
158
|
+
subparsers = parser.add_subparsers(dest="subcommand", help="subcommands for performing various actions")
|
159
|
+
parser_ls = subparsers.add_parser(
|
160
|
+
"ls", help="Show a directory listing", parents=[baseparser], conflict_handler="resolve"
|
161
|
+
)
|
162
|
+
parser_ls.add_argument("-l", action="store_true")
|
163
|
+
parser_ls.add_argument("-a", "--all", action="store_true") # ignored but included for proper argument parsing
|
164
|
+
parser_ls.add_argument("-h", "--human-readable", action="store_true")
|
165
|
+
parser_ls.add_argument("-R", "--recursive", action="store_true", help="recursively list subdirectories encountered")
|
166
|
+
parser_ls.add_argument(
|
167
|
+
"-c", action="store_true", dest="use_ctime", help="show time when file status was last changed"
|
168
|
+
)
|
169
|
+
parser_ls.add_argument("-u", action="store_true", dest="use_atime", help="show time of last access")
|
92
170
|
parser_ls.set_defaults(handler=ls)
|
93
171
|
|
94
|
-
parser_cat = subparsers.add_parser("cat", help="
|
172
|
+
parser_cat = subparsers.add_parser("cat", help="dump file contents", parents=[baseparser])
|
95
173
|
parser_cat.set_defaults(handler=cat)
|
96
174
|
|
97
|
-
parser_find = subparsers.add_parser("walk", help="
|
175
|
+
parser_find = subparsers.add_parser("walk", help="perform a walk", parents=[baseparser])
|
98
176
|
parser_find.set_defaults(handler=walk)
|
99
177
|
|
100
178
|
parser_cp = subparsers.add_parser(
|
101
179
|
"cp",
|
102
|
-
help="
|
180
|
+
help="copy multiple files to a directory specified by --output",
|
103
181
|
parents=[baseparser],
|
104
182
|
)
|
105
|
-
parser_cp.add_argument("-o", "--output", type=str, default=".", help="
|
183
|
+
parser_cp.add_argument("-o", "--output", type=str, default=".", help="output directory")
|
106
184
|
parser_cp.set_defaults(handler=cp)
|
107
185
|
|
108
186
|
configure_generic_arguments(parser)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: dissect.target
|
3
|
-
Version: 3.19.
|
3
|
+
Version: 3.19.dev26
|
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
|
@@ -166,7 +166,7 @@ dissect/target/plugins/filesystem/resolver.py,sha256=HfyASUFV4F9uD-yFXilFpPTORAs
|
|
166
166
|
dissect/target/plugins/filesystem/walkfs.py,sha256=e8HEZcV5Wiua26FGWL3xgiQ_PIhcNvGI5KCdsAx2Nmo,2298
|
167
167
|
dissect/target/plugins/filesystem/yara.py,sha256=JdWqbqDBhKrht3fTroqX7NpBU9khEQUWyMcDgLv2l2g,6686
|
168
168
|
dissect/target/plugins/filesystem/ntfs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
169
|
-
dissect/target/plugins/filesystem/ntfs/mft.py,sha256=
|
169
|
+
dissect/target/plugins/filesystem/ntfs/mft.py,sha256=2ibCLJA7yUrZshFSPKdjoNt3TpfwTtk-DaErghe91CM,11445
|
170
170
|
dissect/target/plugins/filesystem/ntfs/mft_timeline.py,sha256=vvNFAZbr7s3X2OTYf4ES_L6-XsouTXcTymfxnHfZ1Rw,6791
|
171
171
|
dissect/target/plugins/filesystem/ntfs/usnjrnl.py,sha256=uiT1ipmcAo__6VIUi8R_vvIu22vdnjMACKwLSAbzYjs,3704
|
172
172
|
dissect/target/plugins/filesystem/ntfs/utils.py,sha256=xG7Lgw9NX4tDDrZVRm0vycFVJTOM7j-HrjqzDh0f4uA,3136
|
@@ -325,7 +325,7 @@ dissect/target/plugins/os/windows/task_helpers/tasks_xml.py,sha256=oOsYse2-BrliV
|
|
325
325
|
dissect/target/tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
326
326
|
dissect/target/tools/build_pluginlist.py,sha256=5fomcuMwsVzcnYx5Htf5f9lSwsLeUUvomLUXNA4t7m4,849
|
327
327
|
dissect/target/tools/dd.py,sha256=rTM-lgXxrYBpVAtJqFqAatDz45bLoD8-mFt_59Q3Lio,1928
|
328
|
-
dissect/target/tools/fs.py,sha256=
|
328
|
+
dissect/target/tools/fs.py,sha256=bdFSckOO-dyvvBpxOgPIx_UKGEbWGbOHF7kl6rWyt7U,6654
|
329
329
|
dissect/target/tools/info.py,sha256=3smHr8I71yj3kCjsQ5nXkOHI9T_N8UwvkVa1CNOxB-s,5461
|
330
330
|
dissect/target/tools/logging.py,sha256=5ZnumtMWLyslxfrUGZ4ntRyf3obOOhmn8SBjKfdLcEg,4174
|
331
331
|
dissect/target/tools/mount.py,sha256=L_0tSmiBdW4aSaF0vXjB0bAkTC0kmT2N1hrbW6s5Jow,3254
|
@@ -346,10 +346,10 @@ dissect/target/volumes/luks.py,sha256=OmCMsw6rCUXG1_plnLVLTpsvE1n_6WtoRUGQbpmu1z
|
|
346
346
|
dissect/target/volumes/lvm.py,sha256=wwQVR9I3G9YzmY6UxFsH2Y4MXGBcKL9aayWGCDTiWMU,2269
|
347
347
|
dissect/target/volumes/md.py,sha256=j1K1iKmspl0C_OJFc7-Q1BMWN2OCC5EVANIgVlJ_fIE,1673
|
348
348
|
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.
|
349
|
+
dissect.target-3.19.dev26.dist-info/COPYRIGHT,sha256=m-9ih2RVhMiXHI2bf_oNSSgHgkeIvaYRVfKTwFbnJPA,301
|
350
|
+
dissect.target-3.19.dev26.dist-info/LICENSE,sha256=DZak_2itbUtvHzD3E7GNUYSRK6jdOJ-GqncQ2weavLA,34523
|
351
|
+
dissect.target-3.19.dev26.dist-info/METADATA,sha256=JqWUrdsr0XSeyFrdAPCqZYCIrmYfCWdPRFP13XZ4v1o,12719
|
352
|
+
dissect.target-3.19.dev26.dist-info/WHEEL,sha256=R0nc6qTxuoLk7ShA2_Y-UWkN8ZdfDBG2B6Eqpz2WXbs,91
|
353
|
+
dissect.target-3.19.dev26.dist-info/entry_points.txt,sha256=BWuxAb_6AvUAQpIQOQU0IMTlaF6TDht2AIZK8bHd-zE,492
|
354
|
+
dissect.target-3.19.dev26.dist-info/top_level.txt,sha256=Mn-CQzEYsAbkxrUI0TnplHuXnGVKzxpDw_po_sXpvv4,8
|
355
|
+
dissect.target-3.19.dev26.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
{dissect.target-3.19.dev24.dist-info → dissect.target-3.19.dev26.dist-info}/entry_points.txt
RENAMED
File without changes
|
File without changes
|