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.
Files changed (95) hide show
  1. dissect/target/filesystem.py +44 -25
  2. dissect/target/filesystems/config.py +32 -21
  3. dissect/target/filesystems/extfs.py +4 -0
  4. dissect/target/filesystems/itunes.py +1 -1
  5. dissect/target/filesystems/tar.py +1 -1
  6. dissect/target/filesystems/zip.py +81 -46
  7. dissect/target/helpers/config.py +22 -7
  8. dissect/target/helpers/configutil.py +69 -5
  9. dissect/target/helpers/cyber.py +4 -2
  10. dissect/target/helpers/fsutil.py +32 -4
  11. dissect/target/helpers/loaderutil.py +26 -7
  12. dissect/target/helpers/network_managers.py +22 -7
  13. dissect/target/helpers/record.py +37 -0
  14. dissect/target/helpers/record_modifier.py +23 -4
  15. dissect/target/helpers/shell_application_ids.py +732 -0
  16. dissect/target/helpers/utils.py +11 -0
  17. dissect/target/loader.py +1 -0
  18. dissect/target/loaders/ab.py +285 -0
  19. dissect/target/loaders/libvirt.py +40 -0
  20. dissect/target/loaders/mqtt.py +14 -1
  21. dissect/target/loaders/tar.py +8 -4
  22. dissect/target/loaders/utm.py +3 -0
  23. dissect/target/loaders/velociraptor.py +6 -6
  24. dissect/target/plugin.py +60 -3
  25. dissect/target/plugins/apps/browser/chrome.py +1 -0
  26. dissect/target/plugins/apps/browser/chromium.py +7 -5
  27. dissect/target/plugins/apps/browser/edge.py +1 -0
  28. dissect/target/plugins/apps/browser/firefox.py +82 -36
  29. dissect/target/plugins/apps/remoteaccess/anydesk.py +70 -50
  30. dissect/target/plugins/apps/remoteaccess/remoteaccess.py +8 -8
  31. dissect/target/plugins/apps/remoteaccess/teamviewer.py +46 -31
  32. dissect/target/plugins/apps/ssh/openssh.py +1 -1
  33. dissect/target/plugins/apps/ssh/ssh.py +177 -0
  34. dissect/target/plugins/apps/texteditor/__init__.py +0 -0
  35. dissect/target/plugins/apps/texteditor/texteditor.py +13 -0
  36. dissect/target/plugins/apps/texteditor/windowsnotepad.py +340 -0
  37. dissect/target/plugins/child/qemu.py +21 -0
  38. dissect/target/plugins/filesystem/ntfs/mft.py +132 -45
  39. dissect/target/plugins/filesystem/unix/capability.py +102 -87
  40. dissect/target/plugins/filesystem/walkfs.py +32 -21
  41. dissect/target/plugins/filesystem/yara.py +144 -23
  42. dissect/target/plugins/general/network.py +82 -0
  43. dissect/target/plugins/general/users.py +14 -10
  44. dissect/target/plugins/os/unix/_os.py +19 -5
  45. dissect/target/plugins/os/unix/bsd/freebsd/_os.py +3 -5
  46. dissect/target/plugins/os/unix/esxi/_os.py +29 -23
  47. dissect/target/plugins/os/unix/etc/etc.py +5 -8
  48. dissect/target/plugins/os/unix/history.py +3 -7
  49. dissect/target/plugins/os/unix/linux/_os.py +15 -14
  50. dissect/target/plugins/os/unix/linux/android/_os.py +15 -24
  51. dissect/target/plugins/os/unix/linux/redhat/_os.py +1 -1
  52. dissect/target/plugins/os/unix/locale.py +17 -6
  53. dissect/target/plugins/os/unix/shadow.py +47 -31
  54. dissect/target/plugins/os/windows/_os.py +4 -4
  55. dissect/target/plugins/os/windows/adpolicy.py +4 -1
  56. dissect/target/plugins/os/windows/catroot.py +1 -11
  57. dissect/target/plugins/os/windows/credential/__init__.py +0 -0
  58. dissect/target/plugins/os/windows/credential/lsa.py +174 -0
  59. dissect/target/plugins/os/windows/{sam.py → credential/sam.py} +5 -2
  60. dissect/target/plugins/os/windows/defender.py +6 -3
  61. dissect/target/plugins/os/windows/dpapi/blob.py +3 -0
  62. dissect/target/plugins/os/windows/dpapi/crypto.py +61 -23
  63. dissect/target/plugins/os/windows/dpapi/dpapi.py +127 -133
  64. dissect/target/plugins/os/windows/dpapi/keyprovider/__init__.py +0 -0
  65. dissect/target/plugins/os/windows/dpapi/keyprovider/credhist.py +21 -0
  66. dissect/target/plugins/os/windows/dpapi/keyprovider/empty.py +17 -0
  67. dissect/target/plugins/os/windows/dpapi/keyprovider/keychain.py +20 -0
  68. dissect/target/plugins/os/windows/dpapi/keyprovider/keyprovider.py +8 -0
  69. dissect/target/plugins/os/windows/dpapi/keyprovider/lsa.py +38 -0
  70. dissect/target/plugins/os/windows/dpapi/master_key.py +3 -0
  71. dissect/target/plugins/os/windows/jumplist.py +292 -0
  72. dissect/target/plugins/os/windows/lnk.py +96 -93
  73. dissect/target/plugins/os/windows/regf/shellbags.py +8 -5
  74. dissect/target/plugins/os/windows/regf/shimcache.py +2 -2
  75. dissect/target/plugins/os/windows/regf/usb.py +179 -114
  76. dissect/target/plugins/os/windows/task_helpers/tasks_xml.py +1 -1
  77. dissect/target/plugins/os/windows/wua_history.py +1073 -0
  78. dissect/target/target.py +4 -3
  79. dissect/target/tools/fs.py +53 -15
  80. dissect/target/tools/fsutils.py +243 -0
  81. dissect/target/tools/info.py +11 -4
  82. dissect/target/tools/query.py +2 -2
  83. dissect/target/tools/shell.py +505 -333
  84. dissect/target/tools/utils.py +23 -2
  85. dissect/target/tools/yara.py +65 -0
  86. dissect/target/volumes/md.py +2 -2
  87. {dissect.target-3.18.dev15.dist-info → dissect.target-3.19.dist-info}/METADATA +11 -7
  88. {dissect.target-3.18.dev15.dist-info → dissect.target-3.19.dist-info}/RECORD +94 -75
  89. {dissect.target-3.18.dev15.dist-info → dissect.target-3.19.dist-info}/WHEEL +1 -1
  90. {dissect.target-3.18.dev15.dist-info → dissect.target-3.19.dist-info}/entry_points.txt +1 -0
  91. dissect/target/helpers/ssh.py +0 -177
  92. /dissect/target/plugins/os/windows/{credhist.py → credential/credhist.py} +0 -0
  93. {dissect.target-3.18.dev15.dist-info → dissect.target-3.19.dist-info}/COPYRIGHT +0 -0
  94. {dissect.target-3.18.dev15.dist-info → dissect.target-3.19.dist-info}/LICENSE +0 -0
  95. {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.debug("Error loading config file", exc_info=e)
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.info("Child plugin reported itself as incompatible: %s (%s)", plugin_desc["class"], e)
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"]
@@ -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
- for e in sorted(path.iterdir(), key=operator.attrgetter("name")):
28
- print(e.name)
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="Path to perform an action on", metavar="PATH")
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="Subcommands for performing various actions")
91
- parser_ls = subparsers.add_parser("ls", help="Show a directory listing", parents=[baseparser])
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="Dump file contents", parents=[baseparser])
128
+ parser_cat = subparsers.add_parser("cat", help="dump file contents", parents=[baseparser])
95
129
  parser_cat.set_defaults(handler=cat)
96
130
 
97
- parser_find = subparsers.add_parser("walk", help="Perform a walk", parents=[baseparser])
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="Copy multiple files to a directory specified by --output",
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="Output directory")
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)
@@ -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.parse_args()
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(args.targets)):
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('_', ' ')}" + (14 - len(name)) * " " + f" : {value}")
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]]]:
@@ -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.utcnow()
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())