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
dissect/target/target.py
CHANGED
@@ -87,9 +87,10 @@ class Target:
|
|
87
87
|
self._applied = False
|
88
88
|
|
89
89
|
try:
|
90
|
-
self._config = config.load(self.path)
|
90
|
+
self._config = config.load([self.path, Path.cwd(), Path.home()])
|
91
91
|
except Exception as e:
|
92
|
-
self.log.
|
92
|
+
self.log.warning("Error loading config file: %s", self.path)
|
93
|
+
self.log.debug("", exc_info=e)
|
93
94
|
self._config = config.load(None) # This loads an empty config.
|
94
95
|
|
95
96
|
# Fill the disks and/or volumes and/or filesystems and apply() will
|
@@ -344,7 +345,7 @@ class Target:
|
|
344
345
|
child_plugin.check_compatible()
|
345
346
|
self._child_plugins[child_plugin.__type__] = child_plugin
|
346
347
|
except PluginError as e:
|
347
|
-
self.log.
|
348
|
+
self.log.debug("Child plugin reported itself as incompatible: %s (%s)", plugin_desc["class"], e)
|
348
349
|
except Exception:
|
349
350
|
self.log.exception(
|
350
351
|
"An exception occurred while checking for child plugin compatibility: %s", plugin_desc["class"]
|
dissect/target/tools/fs.py
CHANGED
@@ -3,7 +3,6 @@
|
|
3
3
|
|
4
4
|
import argparse
|
5
5
|
import logging
|
6
|
-
import operator
|
7
6
|
import os
|
8
7
|
import pathlib
|
9
8
|
import shutil
|
@@ -12,6 +11,7 @@ import sys
|
|
12
11
|
from dissect.target import Target
|
13
12
|
from dissect.target.exceptions import TargetError
|
14
13
|
from dissect.target.helpers.fsutil import TargetPath
|
14
|
+
from dissect.target.tools.fsutils import print_ls, print_stat
|
15
15
|
from dissect.target.tools.utils import (
|
16
16
|
catch_sigpipe,
|
17
17
|
configure_generic_arguments,
|
@@ -23,24 +23,42 @@ logging.lastResort = None
|
|
23
23
|
logging.raiseExceptions = False
|
24
24
|
|
25
25
|
|
26
|
-
def ls(t, path, args):
|
27
|
-
|
28
|
-
|
26
|
+
def ls(t: Target, path: TargetPath, args: argparse.Namespace) -> None:
|
27
|
+
if args.use_ctime and args.use_atime:
|
28
|
+
log.error("Can't specify -c and -u at the same time")
|
29
|
+
return
|
30
|
+
if not path or not path.exists():
|
31
|
+
return
|
32
|
+
|
33
|
+
# Only output with colors if stdout is a tty
|
34
|
+
use_colors = sys.stdout.buffer.isatty()
|
35
|
+
|
36
|
+
print_ls(
|
37
|
+
path,
|
38
|
+
0,
|
39
|
+
sys.stdout,
|
40
|
+
args.l,
|
41
|
+
args.human_readable,
|
42
|
+
args.recursive,
|
43
|
+
args.use_ctime,
|
44
|
+
args.use_atime,
|
45
|
+
use_colors,
|
46
|
+
)
|
29
47
|
|
30
48
|
|
31
|
-
def cat(t, path, args):
|
49
|
+
def cat(t: Target, path: TargetPath, args: argparse.Namespace) -> None:
|
32
50
|
stdout = sys.stdout
|
33
51
|
if hasattr(stdout, "buffer"):
|
34
52
|
stdout = stdout.buffer
|
35
53
|
shutil.copyfileobj(path.open(), stdout)
|
36
54
|
|
37
55
|
|
38
|
-
def walk(t, path, args):
|
56
|
+
def walk(t: Target, path: TargetPath, args: argparse.Namespace) -> None:
|
39
57
|
for e in path.rglob("*"):
|
40
58
|
print(str(e))
|
41
59
|
|
42
60
|
|
43
|
-
def cp(t, path, args):
|
61
|
+
def cp(t: Target, path: TargetPath, args: argparse.Namespace) -> None:
|
44
62
|
output = os.path.abspath(os.path.expanduser(args.output))
|
45
63
|
if path.is_file():
|
46
64
|
_extract_path(path, os.path.join(output, path.name))
|
@@ -52,6 +70,12 @@ def cp(t, path, args):
|
|
52
70
|
print("[!] Failed, unsuported file type: %s" % path)
|
53
71
|
|
54
72
|
|
73
|
+
def stat(t: Target, path: TargetPath, args: argparse.Namespace) -> None:
|
74
|
+
if not path or not path.exists():
|
75
|
+
return
|
76
|
+
print_stat(path, sys.stdout, args.dereference)
|
77
|
+
|
78
|
+
|
55
79
|
def _extract_path(path: TargetPath, output_path: str) -> None:
|
56
80
|
print("%s -> %s" % (path, output_path))
|
57
81
|
|
@@ -75,7 +99,7 @@ def _extract_path(path: TargetPath, output_path: str) -> None:
|
|
75
99
|
|
76
100
|
|
77
101
|
@catch_sigpipe
|
78
|
-
def main():
|
102
|
+
def main() -> None:
|
79
103
|
help_formatter = argparse.ArgumentDefaultsHelpFormatter
|
80
104
|
parser = argparse.ArgumentParser(
|
81
105
|
description="dissect.target",
|
@@ -85,24 +109,38 @@ def main():
|
|
85
109
|
parser.add_argument("target", type=pathlib.Path, help="Target to load", metavar="TARGET")
|
86
110
|
|
87
111
|
baseparser = argparse.ArgumentParser(add_help=False)
|
88
|
-
baseparser.add_argument("path", type=str, help="
|
112
|
+
baseparser.add_argument("path", type=str, help="path to perform an action on", metavar="PATH")
|
89
113
|
|
90
|
-
subparsers = parser.add_subparsers(dest="subcommand", help="
|
91
|
-
parser_ls = subparsers.add_parser(
|
114
|
+
subparsers = parser.add_subparsers(dest="subcommand", help="subcommands for performing various actions")
|
115
|
+
parser_ls = subparsers.add_parser(
|
116
|
+
"ls", help="Show a directory listing", parents=[baseparser], conflict_handler="resolve"
|
117
|
+
)
|
118
|
+
parser_ls.add_argument("-l", action="store_true")
|
119
|
+
parser_ls.add_argument("-a", "--all", action="store_true") # ignored but included for proper argument parsing
|
120
|
+
parser_ls.add_argument("-h", "--human-readable", action="store_true")
|
121
|
+
parser_ls.add_argument("-R", "--recursive", action="store_true", help="recursively list subdirectories encountered")
|
122
|
+
parser_ls.add_argument(
|
123
|
+
"-c", action="store_true", dest="use_ctime", help="show time when file status was last changed"
|
124
|
+
)
|
125
|
+
parser_ls.add_argument("-u", action="store_true", dest="use_atime", help="show time of last access")
|
92
126
|
parser_ls.set_defaults(handler=ls)
|
93
127
|
|
94
|
-
parser_cat = subparsers.add_parser("cat", help="
|
128
|
+
parser_cat = subparsers.add_parser("cat", help="dump file contents", parents=[baseparser])
|
95
129
|
parser_cat.set_defaults(handler=cat)
|
96
130
|
|
97
|
-
|
131
|
+
parser_stat = subparsers.add_parser("stat", help="display file status", parents=[baseparser])
|
132
|
+
parser_stat.add_argument("-L", "--dereference", action="store_true")
|
133
|
+
parser_stat.set_defaults(handler=stat)
|
134
|
+
|
135
|
+
parser_find = subparsers.add_parser("walk", help="perform a walk", parents=[baseparser])
|
98
136
|
parser_find.set_defaults(handler=walk)
|
99
137
|
|
100
138
|
parser_cp = subparsers.add_parser(
|
101
139
|
"cp",
|
102
|
-
help="
|
140
|
+
help="copy multiple files to a directory specified by --output",
|
103
141
|
parents=[baseparser],
|
104
142
|
)
|
105
|
-
parser_cp.add_argument("-o", "--output", type=str, default=".", help="
|
143
|
+
parser_cp.add_argument("-o", "--output", type=str, default=".", help="output directory")
|
106
144
|
parser_cp.set_defaults(handler=cp)
|
107
145
|
|
108
146
|
configure_generic_arguments(parser)
|
@@ -0,0 +1,243 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
import os
|
4
|
+
import stat
|
5
|
+
from datetime import datetime, timezone
|
6
|
+
from typing import TextIO
|
7
|
+
|
8
|
+
from dissect.target.exceptions import FileNotFoundError
|
9
|
+
from dissect.target.filesystem import FilesystemEntry, LayerFilesystemEntry
|
10
|
+
from dissect.target.helpers import fsutil
|
11
|
+
from dissect.target.helpers.fsutil import TargetPath
|
12
|
+
|
13
|
+
# ['mode', 'addr', 'dev', 'nlink', 'uid', 'gid', 'size', 'atime', 'mtime', 'ctime']
|
14
|
+
STAT_TEMPLATE = """ File: {path} {symlink}
|
15
|
+
Size: {size} Blocks: {blocks} IO Block: {blksize} {filetype}
|
16
|
+
Device: {device} Inode: {inode} Links: {nlink}
|
17
|
+
Access: ({modeord}/{modestr}) Uid: ( {uid} ) Gid: ( {gid} )
|
18
|
+
Access: {atime}
|
19
|
+
Modify: {mtime}
|
20
|
+
Change: {ctime}
|
21
|
+
Birth: {btime}"""
|
22
|
+
|
23
|
+
FALLBACK_LS_COLORS = "rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:mi=00:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32" # noqa: E501
|
24
|
+
|
25
|
+
|
26
|
+
def prepare_ls_colors() -> dict[str, str]:
|
27
|
+
"""Parse the LS_COLORS environment variable so we can use it later."""
|
28
|
+
d = {}
|
29
|
+
ls_colors = os.environ.get("LS_COLORS", FALLBACK_LS_COLORS)
|
30
|
+
for line in ls_colors.split(":"):
|
31
|
+
if not line:
|
32
|
+
continue
|
33
|
+
|
34
|
+
ft, _, value = line.partition("=")
|
35
|
+
if ft.startswith("*"):
|
36
|
+
ft = ft[1:]
|
37
|
+
|
38
|
+
d[ft] = f"\x1b[{value}m{{}}\x1b[0m"
|
39
|
+
|
40
|
+
return d
|
41
|
+
|
42
|
+
|
43
|
+
LS_COLORS = prepare_ls_colors()
|
44
|
+
|
45
|
+
|
46
|
+
def fmt_ls_colors(ft: str, name: str) -> str:
|
47
|
+
"""Helper method to colorize strings according to LS_COLORS."""
|
48
|
+
try:
|
49
|
+
return LS_COLORS[ft].format(name)
|
50
|
+
except KeyError:
|
51
|
+
pass
|
52
|
+
|
53
|
+
try:
|
54
|
+
return LS_COLORS[fsutil.splitext(name)[1]].format(name)
|
55
|
+
except KeyError:
|
56
|
+
pass
|
57
|
+
|
58
|
+
return name
|
59
|
+
|
60
|
+
|
61
|
+
def human_size(bytes: int, units: list[str] = ["", "K", "M", "G", "T", "P", "E"]) -> str:
|
62
|
+
"""Helper function to return the human readable string representation of bytes."""
|
63
|
+
return str(bytes) + units[0] if bytes < 1024 else human_size(bytes >> 10, units[1:])
|
64
|
+
|
65
|
+
|
66
|
+
def stat_modestr(st: fsutil.stat_result) -> str:
|
67
|
+
"""Helper method for generating a mode string from a numerical mode value."""
|
68
|
+
return stat.filemode(st.st_mode)
|
69
|
+
|
70
|
+
|
71
|
+
def print_extensive_file_stat_listing(
|
72
|
+
stdout: TextIO,
|
73
|
+
name: str,
|
74
|
+
entry: FilesystemEntry | None = None,
|
75
|
+
timestamp: datetime | None = None,
|
76
|
+
human_readable: bool = False,
|
77
|
+
) -> None:
|
78
|
+
"""Print the file status as a single line."""
|
79
|
+
if entry is not None:
|
80
|
+
try:
|
81
|
+
entry_stat = entry.lstat()
|
82
|
+
if timestamp is None:
|
83
|
+
timestamp = entry_stat.st_mtime
|
84
|
+
symlink = f" -> {entry.readlink()}" if entry.is_symlink() else ""
|
85
|
+
utc_time = datetime.fromtimestamp(timestamp, tz=timezone.utc).isoformat(timespec="microseconds")
|
86
|
+
size = f"{human_size(entry_stat.st_size):5s}" if human_readable else f"{entry_stat.st_size:10d}"
|
87
|
+
|
88
|
+
print(
|
89
|
+
(
|
90
|
+
f"{stat_modestr(entry_stat)} {entry_stat.st_uid:4d} {entry_stat.st_gid:4d} {size} "
|
91
|
+
f"{utc_time} {name}{symlink}"
|
92
|
+
),
|
93
|
+
file=stdout,
|
94
|
+
)
|
95
|
+
return
|
96
|
+
except FileNotFoundError:
|
97
|
+
pass
|
98
|
+
|
99
|
+
hr_spaces = f"{'':5s}" if human_readable else " "
|
100
|
+
regular_spaces = f"{'':10s}" if not human_readable else " "
|
101
|
+
|
102
|
+
print(f"?????????? ? ?{regular_spaces}?{hr_spaces}????-??-??T??:??:??.??????+??:?? {name}", file=stdout)
|
103
|
+
|
104
|
+
|
105
|
+
def ls_scandir(path: fsutil.TargetPath, color: bool = False) -> list[tuple[fsutil.TargetPath, str]]:
|
106
|
+
"""List a directory for the given path."""
|
107
|
+
result = []
|
108
|
+
if not path.exists() or not path.is_dir():
|
109
|
+
return []
|
110
|
+
|
111
|
+
for file_ in path.iterdir():
|
112
|
+
file_type = None
|
113
|
+
if color:
|
114
|
+
if file_.is_symlink():
|
115
|
+
file_type = "ln"
|
116
|
+
elif file_.is_dir():
|
117
|
+
file_type = "di"
|
118
|
+
elif file_.is_file():
|
119
|
+
file_type = "fi"
|
120
|
+
|
121
|
+
result.append((file_, fmt_ls_colors(file_type, file_.name) if color else file_.name))
|
122
|
+
|
123
|
+
# If we happen to scan an NTFS filesystem see if any of the
|
124
|
+
# entries has an alternative data stream and also list them.
|
125
|
+
entry = file_.get()
|
126
|
+
if isinstance(entry, LayerFilesystemEntry):
|
127
|
+
if entry.entries.fs.__type__ == "ntfs":
|
128
|
+
attrs = entry.lattr()
|
129
|
+
for data_stream in attrs.DATA:
|
130
|
+
if data_stream.name != "":
|
131
|
+
name = f"{file_.name}:{data_stream.name}"
|
132
|
+
result.append((file_, fmt_ls_colors(file_type, name) if color else name))
|
133
|
+
|
134
|
+
result.sort(key=lambda e: e[0].name)
|
135
|
+
|
136
|
+
return result
|
137
|
+
|
138
|
+
|
139
|
+
def print_ls(
|
140
|
+
path: fsutil.TargetPath,
|
141
|
+
depth: int,
|
142
|
+
stdout: TextIO,
|
143
|
+
long_listing: bool = False,
|
144
|
+
human_readable: bool = False,
|
145
|
+
recursive: bool = False,
|
146
|
+
use_ctime: bool = False,
|
147
|
+
use_atime: bool = False,
|
148
|
+
color: bool = True,
|
149
|
+
) -> None:
|
150
|
+
"""Print ls output"""
|
151
|
+
subdirs = []
|
152
|
+
|
153
|
+
if path.is_dir():
|
154
|
+
contents = ls_scandir(path, color)
|
155
|
+
elif path.is_file():
|
156
|
+
contents = [(path, path.name)]
|
157
|
+
|
158
|
+
if depth > 0:
|
159
|
+
print(f"\n{str(path)}:", file=stdout)
|
160
|
+
|
161
|
+
if not long_listing:
|
162
|
+
for target_path, name in contents:
|
163
|
+
print(name, file=stdout)
|
164
|
+
if target_path.is_dir():
|
165
|
+
subdirs.append(target_path)
|
166
|
+
else:
|
167
|
+
if len(contents) > 1:
|
168
|
+
print(f"total {len(contents)}", file=stdout)
|
169
|
+
for target_path, name in contents:
|
170
|
+
try:
|
171
|
+
entry = target_path.get()
|
172
|
+
entry_stat = entry.lstat()
|
173
|
+
show_time = entry_stat.st_mtime
|
174
|
+
if use_ctime:
|
175
|
+
show_time = entry_stat.st_ctime
|
176
|
+
elif use_atime:
|
177
|
+
show_time = entry_stat.st_atime
|
178
|
+
except FileNotFoundError:
|
179
|
+
entry = None
|
180
|
+
show_time = None
|
181
|
+
print_extensive_file_stat_listing(stdout, name, entry, show_time, human_readable)
|
182
|
+
if target_path.is_dir():
|
183
|
+
subdirs.append(target_path)
|
184
|
+
|
185
|
+
if recursive and subdirs:
|
186
|
+
for subdir in subdirs:
|
187
|
+
print_ls(subdir, depth + 1, stdout, long_listing, human_readable, recursive, use_ctime, use_atime, color)
|
188
|
+
|
189
|
+
|
190
|
+
def print_stat(path: fsutil.TargetPath, stdout: TextIO, dereference: bool = False) -> None:
|
191
|
+
"""Print file status."""
|
192
|
+
symlink = f"-> {path.readlink()}" if path.is_symlink() else ""
|
193
|
+
s = path.stat() if dereference else path.lstat()
|
194
|
+
|
195
|
+
def filetype(path: TargetPath) -> str:
|
196
|
+
if path.is_dir():
|
197
|
+
return "directory"
|
198
|
+
elif path.is_symlink():
|
199
|
+
return "symbolic link"
|
200
|
+
elif path.is_file():
|
201
|
+
return "regular file"
|
202
|
+
|
203
|
+
res = STAT_TEMPLATE.format(
|
204
|
+
path=path,
|
205
|
+
symlink=symlink,
|
206
|
+
size=s.st_size,
|
207
|
+
filetype=filetype(path),
|
208
|
+
device="?",
|
209
|
+
inode=s.st_ino,
|
210
|
+
blocks=s.st_blocks or "?",
|
211
|
+
blksize=s.st_blksize or "?",
|
212
|
+
nlink=s.st_nlink,
|
213
|
+
modeord=oct(stat.S_IMODE(s.st_mode)),
|
214
|
+
modestr=stat_modestr(s),
|
215
|
+
uid=s.st_uid,
|
216
|
+
gid=s.st_gid,
|
217
|
+
atime=datetime.fromtimestamp(s.st_atime, tz=timezone.utc).isoformat(timespec="microseconds"),
|
218
|
+
mtime=datetime.fromtimestamp(s.st_mtime, tz=timezone.utc).isoformat(timespec="microseconds"),
|
219
|
+
ctime=datetime.fromtimestamp(s.st_ctime, tz=timezone.utc).isoformat(timespec="microseconds"),
|
220
|
+
btime=datetime.fromtimestamp(s.st_birthtime, tz=timezone.utc).isoformat(timespec="microseconds")
|
221
|
+
if hasattr(s, "st_birthtime") and s.st_birthtime
|
222
|
+
else "?",
|
223
|
+
)
|
224
|
+
print(res, file=stdout)
|
225
|
+
|
226
|
+
try:
|
227
|
+
if (xattr := path.get().attr()) and isinstance(xattr, list) and hasattr(xattr[0], "name"):
|
228
|
+
print(" Attr:")
|
229
|
+
print_xattr(path.name, xattr, stdout)
|
230
|
+
except Exception:
|
231
|
+
pass
|
232
|
+
|
233
|
+
|
234
|
+
def print_xattr(basename: str, xattr: list, stdout: TextIO) -> None:
|
235
|
+
"""Mimics getfattr -d {file} behaviour."""
|
236
|
+
if not hasattr(xattr[0], "name"):
|
237
|
+
return
|
238
|
+
|
239
|
+
XATTR_TEMPLATE = "# file: {basename}\n{attrs}"
|
240
|
+
res = XATTR_TEMPLATE.format(
|
241
|
+
basename=basename, attrs="\n".join([f'{attr.name}="{attr.value.decode()}"' for attr in xattr])
|
242
|
+
)
|
243
|
+
print(res, file=stdout)
|
dissect/target/tools/info.py
CHANGED
@@ -4,6 +4,7 @@
|
|
4
4
|
import argparse
|
5
5
|
import json
|
6
6
|
import logging
|
7
|
+
from datetime import datetime
|
7
8
|
from pathlib import Path
|
8
9
|
from typing import Union
|
9
10
|
|
@@ -12,6 +13,7 @@ from dissect.target.exceptions import TargetError
|
|
12
13
|
from dissect.target.helpers.record import TargetRecordDescriptor
|
13
14
|
from dissect.target.tools.query import record_output
|
14
15
|
from dissect.target.tools.utils import (
|
16
|
+
args_to_uri,
|
15
17
|
catch_sigpipe,
|
16
18
|
configure_generic_arguments,
|
17
19
|
process_generic_arguments,
|
@@ -50,14 +52,14 @@ def main():
|
|
50
52
|
)
|
51
53
|
parser.add_argument("targets", metavar="TARGETS", nargs="*", help="Targets to display info from")
|
52
54
|
parser.add_argument("--from-file", nargs="?", type=Path, help="file containing targets to load")
|
53
|
-
parser.add_argument("-d", "--delimiter", default=" ", action="store", metavar="','")
|
54
55
|
parser.add_argument("-s", "--strings", action="store_true", help="print output as string")
|
55
56
|
parser.add_argument("-r", "--record", action="store_true", help="print output as record")
|
56
57
|
parser.add_argument("-j", "--json", action="store_true", help="output records as pretty json")
|
57
58
|
parser.add_argument("-J", "--jsonlines", action="store_true", help="output records as one-line json")
|
59
|
+
parser.add_argument("-L", "--loader", action="store", default=None, help="select a specific loader (i.e. vmx, raw)")
|
58
60
|
configure_generic_arguments(parser)
|
59
61
|
|
60
|
-
args = parser.
|
62
|
+
args, rest = parser.parse_known_args()
|
61
63
|
|
62
64
|
process_generic_arguments(args)
|
63
65
|
|
@@ -73,8 +75,10 @@ def main():
|
|
73
75
|
targets = targets[:-1]
|
74
76
|
args.targets = targets
|
75
77
|
|
78
|
+
targets = args_to_uri(args.targets, args.loader, rest) if args.loader else args.targets
|
79
|
+
|
76
80
|
try:
|
77
|
-
for i, target in enumerate(Target.open_all(
|
81
|
+
for i, target in enumerate(Target.open_all(targets)):
|
78
82
|
try:
|
79
83
|
if args.jsonlines:
|
80
84
|
print(json.dumps(get_target_info(target), default=str))
|
@@ -135,10 +139,13 @@ def print_target_info(target: Target) -> None:
|
|
135
139
|
if isinstance(value, list):
|
136
140
|
value = ", ".join(value)
|
137
141
|
|
142
|
+
if isinstance(value, datetime):
|
143
|
+
value = value.isoformat(timespec="microseconds")
|
144
|
+
|
138
145
|
if name == "hostname":
|
139
146
|
print()
|
140
147
|
|
141
|
-
print(f"{name.capitalize().replace('_', ' ')}
|
148
|
+
print(f"{name.capitalize().replace('_', ' '):14s} : {value}")
|
142
149
|
|
143
150
|
|
144
151
|
def get_disks_info(target: Target) -> list[dict[str, Union[str, int]]]:
|
dissect/target/tools/query.py
CHANGED
@@ -5,7 +5,7 @@ import argparse
|
|
5
5
|
import logging
|
6
6
|
import pathlib
|
7
7
|
import sys
|
8
|
-
from datetime import datetime
|
8
|
+
from datetime import datetime, timezone
|
9
9
|
from typing import Callable
|
10
10
|
|
11
11
|
from flow.record import RecordPrinter, RecordStreamWriter, RecordWriter
|
@@ -390,7 +390,7 @@ def main():
|
|
390
390
|
log.debug("", exc_info=e)
|
391
391
|
parser.exit(1)
|
392
392
|
|
393
|
-
timestamp = datetime.
|
393
|
+
timestamp = datetime.now(tz=timezone.utc)
|
394
394
|
|
395
395
|
execution_report.set_plugin_stats(PLUGINS)
|
396
396
|
log.debug("%s", execution_report.get_formatted_report())
|