dissect.target 3.19.dev24__py3-none-any.whl → 3.19.dev26__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- 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
|