dissect.target 3.19.dev40__py3-none-any.whl → 3.19.dev42__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/filesystems/extfs.py +4 -0
- dissect/target/helpers/shell_application_ids.py +732 -0
- dissect/target/helpers/utils.py +11 -0
- dissect/target/loaders/tar.py +8 -4
- dissect/target/loaders/velociraptor.py +6 -6
- dissect/target/plugin.py +50 -0
- dissect/target/plugins/os/unix/history.py +3 -7
- dissect/target/plugins/os/windows/catroot.py +1 -11
- dissect/target/plugins/os/windows/jumplist.py +292 -0
- dissect/target/plugins/os/windows/lnk.py +84 -89
- dissect/target/target.py +1 -1
- dissect/target/tools/fs.py +25 -65
- dissect/target/tools/fsutils.py +243 -0
- dissect/target/tools/info.py +5 -1
- dissect/target/tools/shell.py +473 -347
- dissect/target/tools/utils.py +9 -0
- {dissect.target-3.19.dev40.dist-info → dissect.target-3.19.dev42.dist-info}/METADATA +10 -6
- {dissect.target-3.19.dev40.dist-info → dissect.target-3.19.dev42.dist-info}/RECORD +23 -20
- {dissect.target-3.19.dev40.dist-info → dissect.target-3.19.dev42.dist-info}/COPYRIGHT +0 -0
- {dissect.target-3.19.dev40.dist-info → dissect.target-3.19.dev42.dist-info}/LICENSE +0 -0
- {dissect.target-3.19.dev40.dist-info → dissect.target-3.19.dev42.dist-info}/WHEEL +0 -0
- {dissect.target-3.19.dev40.dist-info → dissect.target-3.19.dev42.dist-info}/entry_points.txt +0 -0
- {dissect.target-3.19.dev40.dist-info → dissect.target-3.19.dev42.dist-info}/top_level.txt +0 -0
@@ -34,6 +34,89 @@ LnkRecord = TargetRecordDescriptor(
|
|
34
34
|
)
|
35
35
|
|
36
36
|
|
37
|
+
def parse_lnk_file(target: Target, lnk_file: Lnk, lnk_path: TargetPath) -> Iterator[LnkRecord]:
|
38
|
+
# we need to get the active codepage from the system to properly decode some values
|
39
|
+
codepage = target.codepage or "ascii"
|
40
|
+
|
41
|
+
lnk_net_name = lnk_device_name = None
|
42
|
+
|
43
|
+
if lnk_file.link_header:
|
44
|
+
lnk_name = lnk_file.stringdata.name_string.string if lnk_file.flag("has_name") else None
|
45
|
+
|
46
|
+
lnk_mtime = ts.from_unix(lnk_path.stat().st_mtime)
|
47
|
+
lnk_atime = ts.from_unix(lnk_path.stat().st_atime)
|
48
|
+
lnk_ctime = ts.from_unix(lnk_path.stat().st_ctime)
|
49
|
+
|
50
|
+
lnk_relativepath = (
|
51
|
+
target.fs.path(lnk_file.stringdata.relative_path.string) if lnk_file.flag("has_relative_path") else None
|
52
|
+
)
|
53
|
+
lnk_workdir = (
|
54
|
+
target.fs.path(lnk_file.stringdata.working_dir.string) if lnk_file.flag("has_working_dir") else None
|
55
|
+
)
|
56
|
+
lnk_iconlocation = (
|
57
|
+
target.fs.path(lnk_file.stringdata.icon_location.string) if lnk_file.flag("has_icon_location") else None
|
58
|
+
)
|
59
|
+
lnk_arguments = lnk_file.stringdata.command_line_arguments.string if lnk_file.flag("has_arguments") else None
|
60
|
+
local_base_path = (
|
61
|
+
lnk_file.linkinfo.local_base_path.decode(codepage)
|
62
|
+
if lnk_file.flag("has_link_info") and lnk_file.linkinfo.flag("volumeid_and_local_basepath")
|
63
|
+
else None
|
64
|
+
)
|
65
|
+
common_path_suffix = (
|
66
|
+
lnk_file.linkinfo.common_path_suffix.decode(codepage) if lnk_file.flag("has_link_info") else None
|
67
|
+
)
|
68
|
+
|
69
|
+
if local_base_path and common_path_suffix:
|
70
|
+
lnk_full_path = target.fs.path(local_base_path + common_path_suffix)
|
71
|
+
elif local_base_path and not common_path_suffix:
|
72
|
+
lnk_full_path = target.fs.path(local_base_path)
|
73
|
+
else:
|
74
|
+
lnk_full_path = None
|
75
|
+
|
76
|
+
if lnk_file.flag("has_link_info"):
|
77
|
+
if lnk_file.linkinfo.flag("common_network_relative_link_and_pathsuffix"):
|
78
|
+
lnk_net_name = (
|
79
|
+
lnk_file.linkinfo.common_network_relative_link.net_name.decode()
|
80
|
+
if lnk_file.linkinfo.common_network_relative_link.net_name
|
81
|
+
else None
|
82
|
+
)
|
83
|
+
lnk_device_name = (
|
84
|
+
lnk_file.linkinfo.common_network_relative_link.device_name.decode()
|
85
|
+
if lnk_file.linkinfo.common_network_relative_link.device_name
|
86
|
+
else None
|
87
|
+
)
|
88
|
+
try:
|
89
|
+
machine_id = lnk_file.extradata.TRACKER_PROPS.machine_id.decode(codepage).strip("\x00")
|
90
|
+
except AttributeError:
|
91
|
+
machine_id = None
|
92
|
+
|
93
|
+
target_mtime = ts.wintimestamp(lnk_file.link_header.write_time)
|
94
|
+
target_atime = ts.wintimestamp(lnk_file.link_header.access_time)
|
95
|
+
target_ctime = ts.wintimestamp(lnk_file.link_header.creation_time)
|
96
|
+
|
97
|
+
return LnkRecord(
|
98
|
+
lnk_path=lnk_path,
|
99
|
+
lnk_name=lnk_name,
|
100
|
+
lnk_mtime=lnk_mtime,
|
101
|
+
lnk_atime=lnk_atime,
|
102
|
+
lnk_ctime=lnk_ctime,
|
103
|
+
lnk_relativepath=lnk_relativepath,
|
104
|
+
lnk_workdir=lnk_workdir,
|
105
|
+
lnk_iconlocation=lnk_iconlocation,
|
106
|
+
lnk_arguments=lnk_arguments,
|
107
|
+
local_base_path=local_base_path,
|
108
|
+
common_path_suffix=common_path_suffix,
|
109
|
+
lnk_full_path=lnk_full_path,
|
110
|
+
lnk_net_name=lnk_net_name,
|
111
|
+
lnk_device_name=lnk_device_name,
|
112
|
+
machine_id=machine_id,
|
113
|
+
target_mtime=target_mtime,
|
114
|
+
target_atime=target_atime,
|
115
|
+
target_ctime=target_ctime,
|
116
|
+
_target=target,
|
117
|
+
)
|
118
|
+
|
119
|
+
|
37
120
|
class LnkPlugin(Plugin):
|
38
121
|
def __init__(self, target: Target) -> None:
|
39
122
|
super().__init__(target)
|
@@ -74,97 +157,9 @@ class LnkPlugin(Plugin):
|
|
74
157
|
target_ctime (datetime): Creation time of the target (linked) file.
|
75
158
|
"""
|
76
159
|
|
77
|
-
# we need to get the active codepage from the system to properly decode some values
|
78
|
-
codepage = self.target.codepage or "ascii"
|
79
|
-
|
80
160
|
for entry in self.lnk_entries(path):
|
81
161
|
lnk_file = Lnk(entry.open())
|
82
|
-
|
83
|
-
|
84
|
-
if lnk_file.link_header:
|
85
|
-
lnk_path = entry
|
86
|
-
lnk_name = lnk_file.stringdata.name_string.string if lnk_file.flag("has_name") else None
|
87
|
-
|
88
|
-
lnk_mtime = ts.from_unix(entry.stat().st_mtime)
|
89
|
-
lnk_atime = ts.from_unix(entry.stat().st_atime)
|
90
|
-
lnk_ctime = ts.from_unix(entry.stat().st_ctime)
|
91
|
-
|
92
|
-
lnk_relativepath = (
|
93
|
-
self.target.fs.path(lnk_file.stringdata.relative_path.string)
|
94
|
-
if lnk_file.flag("has_relative_path")
|
95
|
-
else None
|
96
|
-
)
|
97
|
-
lnk_workdir = (
|
98
|
-
self.target.fs.path(lnk_file.stringdata.working_dir.string)
|
99
|
-
if lnk_file.flag("has_working_dir")
|
100
|
-
else None
|
101
|
-
)
|
102
|
-
lnk_iconlocation = (
|
103
|
-
self.target.fs.path(lnk_file.stringdata.icon_location.string)
|
104
|
-
if lnk_file.flag("has_icon_location")
|
105
|
-
else None
|
106
|
-
)
|
107
|
-
lnk_arguments = (
|
108
|
-
lnk_file.stringdata.command_line_arguments.string if lnk_file.flag("has_arguments") else None
|
109
|
-
)
|
110
|
-
local_base_path = (
|
111
|
-
lnk_file.linkinfo.local_base_path.decode(codepage)
|
112
|
-
if lnk_file.flag("has_link_info") and lnk_file.linkinfo.flag("volumeid_and_local_basepath")
|
113
|
-
else None
|
114
|
-
)
|
115
|
-
common_path_suffix = (
|
116
|
-
lnk_file.linkinfo.common_path_suffix.decode(codepage) if lnk_file.flag("has_link_info") else None
|
117
|
-
)
|
118
|
-
|
119
|
-
if local_base_path and common_path_suffix:
|
120
|
-
lnk_full_path = self.target.fs.path(local_base_path + common_path_suffix)
|
121
|
-
elif local_base_path and not common_path_suffix:
|
122
|
-
lnk_full_path = self.target.fs.path(local_base_path)
|
123
|
-
else:
|
124
|
-
lnk_full_path = None
|
125
|
-
|
126
|
-
if lnk_file.flag("has_link_info"):
|
127
|
-
if lnk_file.linkinfo.flag("common_network_relative_link_and_pathsuffix"):
|
128
|
-
lnk_net_name = (
|
129
|
-
lnk_file.linkinfo.common_network_relative_link.net_name.decode()
|
130
|
-
if lnk_file.linkinfo.common_network_relative_link.net_name
|
131
|
-
else None
|
132
|
-
)
|
133
|
-
lnk_device_name = (
|
134
|
-
lnk_file.linkinfo.common_network_relative_link.device_name.decode()
|
135
|
-
if lnk_file.linkinfo.common_network_relative_link.device_name
|
136
|
-
else None
|
137
|
-
)
|
138
|
-
try:
|
139
|
-
machine_id = lnk_file.extradata.TRACKER_PROPS.machine_id.decode(codepage).strip("\x00")
|
140
|
-
except AttributeError:
|
141
|
-
machine_id = None
|
142
|
-
|
143
|
-
target_mtime = ts.wintimestamp(lnk_file.link_header.write_time)
|
144
|
-
target_atime = ts.wintimestamp(lnk_file.link_header.access_time)
|
145
|
-
target_ctime = ts.wintimestamp(lnk_file.link_header.creation_time)
|
146
|
-
|
147
|
-
yield LnkRecord(
|
148
|
-
lnk_path=lnk_path,
|
149
|
-
lnk_name=lnk_name,
|
150
|
-
lnk_mtime=lnk_mtime,
|
151
|
-
lnk_atime=lnk_atime,
|
152
|
-
lnk_ctime=lnk_ctime,
|
153
|
-
lnk_relativepath=lnk_relativepath,
|
154
|
-
lnk_workdir=lnk_workdir,
|
155
|
-
lnk_iconlocation=lnk_iconlocation,
|
156
|
-
lnk_arguments=lnk_arguments,
|
157
|
-
local_base_path=local_base_path,
|
158
|
-
common_path_suffix=common_path_suffix,
|
159
|
-
lnk_full_path=lnk_full_path,
|
160
|
-
lnk_net_name=lnk_net_name,
|
161
|
-
lnk_device_name=lnk_device_name,
|
162
|
-
machine_id=machine_id,
|
163
|
-
target_mtime=target_mtime,
|
164
|
-
target_atime=target_atime,
|
165
|
-
target_ctime=target_ctime,
|
166
|
-
_target=self.target,
|
167
|
-
)
|
162
|
+
yield parse_lnk_file(self.target, lnk_file, entry)
|
168
163
|
|
169
164
|
def lnk_entries(self, path: Optional[str] = None) -> Iterator[TargetPath]:
|
170
165
|
if path:
|
dissect/target/target.py
CHANGED
@@ -87,7 +87,7 @@ 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
92
|
self.log.warning("Error loading config file: %s", self.path)
|
93
93
|
self.log.debug("", exc_info=e)
|
dissect/target/tools/fs.py
CHANGED
@@ -2,9 +2,7 @@
|
|
2
2
|
# -*- coding: utf-8 -*-
|
3
3
|
|
4
4
|
import argparse
|
5
|
-
import datetime
|
6
5
|
import logging
|
7
|
-
import operator
|
8
6
|
import os
|
9
7
|
import pathlib
|
10
8
|
import shutil
|
@@ -13,7 +11,7 @@ import sys
|
|
13
11
|
from dissect.target import Target
|
14
12
|
from dissect.target.exceptions import TargetError
|
15
13
|
from dissect.target.helpers.fsutil import TargetPath
|
16
|
-
from dissect.target.tools.
|
14
|
+
from dissect.target.tools.fsutils import print_ls, print_stat
|
17
15
|
from dissect.target.tools.utils import (
|
18
16
|
catch_sigpipe,
|
19
17
|
configure_generic_arguments,
|
@@ -25,11 +23,6 @@ logging.lastResort = None
|
|
25
23
|
logging.raiseExceptions = False
|
26
24
|
|
27
25
|
|
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:])
|
31
|
-
|
32
|
-
|
33
26
|
def ls(t: Target, path: TargetPath, args: argparse.Namespace) -> None:
|
34
27
|
if args.use_ctime and args.use_atime:
|
35
28
|
log.error("Can't specify -c and -u at the same time")
|
@@ -37,63 +30,20 @@ def ls(t: Target, path: TargetPath, args: argparse.Namespace) -> None:
|
|
37
30
|
if not path or not path.exists():
|
38
31
|
return
|
39
32
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
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}")
|
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
|
+
)
|
97
47
|
|
98
48
|
|
99
49
|
def cat(t: Target, path: TargetPath, args: argparse.Namespace) -> None:
|
@@ -120,6 +70,12 @@ def cp(t: Target, path: TargetPath, args: argparse.Namespace) -> None:
|
|
120
70
|
print("[!] Failed, unsuported file type: %s" % path)
|
121
71
|
|
122
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
|
+
|
123
79
|
def _extract_path(path: TargetPath, output_path: str) -> None:
|
124
80
|
print("%s -> %s" % (path, output_path))
|
125
81
|
|
@@ -172,6 +128,10 @@ def main() -> None:
|
|
172
128
|
parser_cat = subparsers.add_parser("cat", help="dump file contents", parents=[baseparser])
|
173
129
|
parser_cat.set_defaults(handler=cat)
|
174
130
|
|
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
|
+
|
175
135
|
parser_find = subparsers.add_parser("walk", help="perform a walk", parents=[baseparser])
|
176
136
|
parser_find.set_defaults(handler=walk)
|
177
137
|
|
@@ -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
|
|
@@ -138,10 +139,13 @@ def print_target_info(target: Target) -> None:
|
|
138
139
|
if isinstance(value, list):
|
139
140
|
value = ", ".join(value)
|
140
141
|
|
142
|
+
if isinstance(value, datetime):
|
143
|
+
value = value.isoformat(timespec="microseconds")
|
144
|
+
|
141
145
|
if name == "hostname":
|
142
146
|
print()
|
143
147
|
|
144
|
-
print(f"{name.capitalize().replace('_', ' ')}
|
148
|
+
print(f"{name.capitalize().replace('_', ' '):14s} : {value}")
|
145
149
|
|
146
150
|
|
147
151
|
def get_disks_info(target: Target) -> list[dict[str, Union[str, int]]]:
|