dissect.target 3.14.dev20__py3-none-any.whl → 3.14.dev23__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/filesystem.py +1 -1
- dissect/target/filesystems/btrfs.py +2 -2
- dissect/target/helpers/cache.py +2 -2
- dissect/target/helpers/fsutil.py +9 -6
- dissect/target/helpers/hashutil.py +1 -5
- dissect/target/loaders/log.py +2 -2
- dissect/target/loaders/smb.py +23 -13
- dissect/target/plugins/apps/av/sophos.py +1 -2
- dissect/target/plugins/apps/av/trendmicro.py +2 -3
- dissect/target/plugins/apps/browser/chromium.py +4 -11
- dissect/target/plugins/apps/browser/firefox.py +2 -6
- dissect/target/plugins/child/hyperv.py +1 -2
- dissect/target/plugins/child/vmware_workstation.py +1 -3
- dissect/target/plugins/filesystem/acquire_handles.py +2 -0
- dissect/target/plugins/filesystem/acquire_hash.py +1 -7
- dissect/target/plugins/filesystem/ntfs/usnjrnl.py +1 -2
- dissect/target/plugins/filesystem/resolver.py +1 -1
- dissect/target/plugins/filesystem/unix/capability.py +77 -66
- dissect/target/plugins/filesystem/walkfs.py +23 -19
- dissect/target/plugins/filesystem/yara.py +20 -19
- dissect/target/plugins/os/unix/_os.py +1 -3
- dissect/target/plugins/os/unix/bsd/osx/user.py +1 -3
- dissect/target/plugins/os/unix/esxi/_os.py +1 -2
- dissect/target/plugins/os/unix/log/journal.py +7 -6
- dissect/target/plugins/os/windows/_os.py +2 -1
- dissect/target/plugins/os/windows/amcache.py +9 -10
- dissect/target/plugins/os/windows/catroot.py +2 -2
- dissect/target/plugins/os/windows/generic.py +10 -11
- dissect/target/plugins/os/windows/lnk.py +5 -6
- dissect/target/plugins/os/windows/log/amcache.py +3 -5
- dissect/target/plugins/os/windows/log/pfro.py +1 -3
- dissect/target/plugins/os/windows/prefetch.py +5 -6
- dissect/target/plugins/os/windows/recyclebin.py +3 -4
- dissect/target/plugins/os/windows/regf/7zip.py +2 -4
- dissect/target/plugins/os/windows/regf/bam.py +1 -2
- dissect/target/plugins/os/windows/regf/cit.py +4 -5
- dissect/target/plugins/os/windows/regf/muicache.py +1 -3
- dissect/target/plugins/os/windows/regf/recentfilecache.py +1 -2
- dissect/target/plugins/os/windows/regf/shimcache.py +1 -2
- dissect/target/plugins/os/windows/regf/trusteddocs.py +1 -1
- dissect/target/plugins/os/windows/regf/userassist.py +1 -2
- dissect/target/plugins/os/windows/services.py +2 -4
- dissect/target/plugins/os/windows/sru.py +4 -4
- dissect/target/plugins/os/windows/startupinfo.py +5 -6
- dissect/target/plugins/os/windows/syscache.py +1 -2
- dissect/target/target.py +2 -1
- {dissect.target-3.14.dev20.dist-info → dissect.target-3.14.dev23.dist-info}/METADATA +1 -1
- {dissect.target-3.14.dev20.dist-info → dissect.target-3.14.dev23.dist-info}/RECORD +53 -53
- {dissect.target-3.14.dev20.dist-info → dissect.target-3.14.dev23.dist-info}/COPYRIGHT +0 -0
- {dissect.target-3.14.dev20.dist-info → dissect.target-3.14.dev23.dist-info}/LICENSE +0 -0
- {dissect.target-3.14.dev20.dist-info → dissect.target-3.14.dev23.dist-info}/WHEEL +0 -0
- {dissect.target-3.14.dev20.dist-info → dissect.target-3.14.dev23.dist-info}/entry_points.txt +0 -0
- {dissect.target-3.14.dev20.dist-info → dissect.target-3.14.dev23.dist-info}/top_level.txt +0 -0
dissect/target/filesystem.py
CHANGED
@@ -147,7 +147,7 @@ class Filesystem:
|
|
147
147
|
except NotImplementedError:
|
148
148
|
raise
|
149
149
|
except Exception as e:
|
150
|
-
log.warning("Failed to detect ID on %s filesystem", cls.
|
150
|
+
log.warning("Failed to detect ID on %s filesystem", cls.__type__)
|
151
151
|
log.debug("", exc_info=e)
|
152
152
|
finally:
|
153
153
|
fh.seek(offset)
|
@@ -17,7 +17,7 @@ from dissect.target.helpers import fsutil
|
|
17
17
|
|
18
18
|
|
19
19
|
class BtrfsFilesystem(Filesystem):
|
20
|
-
|
20
|
+
__type__ = "btrfs"
|
21
21
|
__multi_volume__ = True
|
22
22
|
|
23
23
|
def __init__(self, fh: Union[BinaryIO, list[BinaryIO]], *args, **kwargs):
|
@@ -55,7 +55,7 @@ class BtrfsFilesystem(Filesystem):
|
|
55
55
|
|
56
56
|
|
57
57
|
class BtrfsSubvolumeFilesystem(Filesystem):
|
58
|
-
|
58
|
+
__type__ = "btrfs"
|
59
59
|
|
60
60
|
def __init__(self, fs: BtrfsFilesystem, subvol: Optional[str] = None, subvolid: Optional[int] = None):
|
61
61
|
super().__init__(fs.volume, alt_separator=fs.alt_separator, case_sensitive=fs.case_sensitive)
|
dissect/target/helpers/cache.py
CHANGED
@@ -144,7 +144,7 @@ class Cache:
|
|
144
144
|
|
145
145
|
if read_file_cache:
|
146
146
|
target.log.debug("Reading from cache file: %s", cache_file)
|
147
|
-
if os.access(cache_file, os.R_OK, effective_ids=
|
147
|
+
if os.access(cache_file, os.R_OK, effective_ids=bool(os.supports_effective_ids)):
|
148
148
|
if os.stat(cache_file).st_size != 0:
|
149
149
|
try:
|
150
150
|
return self.open_reader(cache_file, output)
|
@@ -173,7 +173,7 @@ class Cache:
|
|
173
173
|
err,
|
174
174
|
)
|
175
175
|
|
176
|
-
if os.access(temp_dir, os.W_OK | os.R_OK | os.X_OK, effective_ids=
|
176
|
+
if os.access(temp_dir, os.W_OK | os.R_OK | os.X_OK, effective_ids=bool(os.supports_effective_ids)):
|
177
177
|
if os.path.exists(temp_path):
|
178
178
|
try:
|
179
179
|
os.remove(temp_path)
|
dissect/target/helpers/fsutil.py
CHANGED
@@ -725,12 +725,15 @@ class TargetPath(Path, PureDissectPath):
|
|
725
725
|
|
726
726
|
def open(self, mode='rb', buffering=0, encoding=None,
|
727
727
|
errors=None, newline=None):
|
728
|
-
|
729
|
-
if "b" not in mode
|
730
|
-
|
731
|
-
#
|
732
|
-
|
733
|
-
|
728
|
+
|
729
|
+
if "b" not in mode:
|
730
|
+
encoding = encoding or "UTF-8"
|
731
|
+
# CPython >= 3.10
|
732
|
+
if hasattr(io, "text_encoding"):
|
733
|
+
# Vermin linting needs to be skipped for this line as this is
|
734
|
+
# guarded by an explicit check for availability.
|
735
|
+
# novermin
|
736
|
+
encoding = io.text_encoding(encoding)
|
734
737
|
return self._accessor.open(self, mode, buffering, encoding, errors,
|
735
738
|
newline)
|
736
739
|
|
@@ -72,10 +72,6 @@ def hash_uri_records(target: Target, record: Record) -> Record:
|
|
72
72
|
|
73
73
|
def hash_path_records(target: Target, record: Record) -> Record:
|
74
74
|
"""Hash files from path fields inside the record."""
|
75
|
-
if target.os == "windows":
|
76
|
-
path_type = fieldtypes.windows_path
|
77
|
-
else:
|
78
|
-
path_type = fieldtypes.posix_path
|
79
75
|
|
80
76
|
hash_records = []
|
81
77
|
|
@@ -93,7 +89,7 @@ def hash_path_records(target: Target, record: Record) -> Record:
|
|
93
89
|
except (FileNotFoundError, IsADirectoryError):
|
94
90
|
pass
|
95
91
|
else:
|
96
|
-
resolved_path =
|
92
|
+
resolved_path = target.fs.path(resolved_path)
|
97
93
|
record_kwargs = dict()
|
98
94
|
record_def = list()
|
99
95
|
|
dissect/target/loaders/log.py
CHANGED
@@ -26,12 +26,12 @@ class LogLoader(Loader):
|
|
26
26
|
|
27
27
|
def map(self, target: Target) -> None:
|
28
28
|
self.target = target
|
29
|
-
vfs = VirtualFilesystem(case_sensitive=False)
|
29
|
+
vfs = VirtualFilesystem(case_sensitive=False, alt_separator=target.fs.alt_separator)
|
30
30
|
for entry in self.path.parent.glob(self.path.name):
|
31
31
|
ext = self.options.get("hint", entry.suffix.lower()).strip(".")
|
32
32
|
if (mapping := self.LOGS_DIRS.get(ext, None)) is None:
|
33
33
|
continue
|
34
|
-
mapping = str(
|
34
|
+
mapping = str(vfs.path(mapping).joinpath(entry.name))
|
35
35
|
vfs.map_file(mapping, str(entry))
|
36
36
|
target.filesystems.add(vfs)
|
37
37
|
target.fs = vfs
|
dissect/target/loaders/smb.py
CHANGED
@@ -11,6 +11,7 @@ from urllib.parse import ParseResult, parse_qsl
|
|
11
11
|
from dissect.regf import regf
|
12
12
|
from dissect.util import ts
|
13
13
|
from impacket.dcerpc.v5 import rpcrt, rrp, scmr, transport
|
14
|
+
from impacket.dcerpc.v5.rpcrt import DCERPCException
|
14
15
|
from impacket.smbconnection import SessionError, SMBConnection
|
15
16
|
|
16
17
|
from dissect.target import Target
|
@@ -189,18 +190,24 @@ class SmbRegistry(RegistryPlugin):
|
|
189
190
|
return False
|
190
191
|
|
191
192
|
def _init_registry(self) -> None:
|
192
|
-
|
193
|
-
|
193
|
+
try:
|
194
|
+
self._svcctl = _connect_rpc(self.conn, "ncacn_np:445[\\pipe\\svcctl]", scmr.MSRPC_UUID_SCMR)
|
195
|
+
self._check_service_status()
|
194
196
|
|
195
|
-
|
197
|
+
self._winreg = _connect_rpc(self.conn, "ncacn_np:445[\\pipe\\winreg]", rrp.MSRPC_UUID_RRP)
|
196
198
|
|
197
|
-
|
198
|
-
|
199
|
+
hklm_hive = SmbRegistryHive(
|
200
|
+
self._winreg, "HKEY_LOCAL_MACHINE", rrp.hOpenLocalMachine(self._winreg)["phKey"]
|
201
|
+
)
|
202
|
+
hku_hive = SmbRegistryHive(self._winreg, "HKEY_USERS", rrp.hOpenUsers(self._winreg)["phKey"])
|
199
203
|
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
+
self._add_hive("HKLM", hklm_hive, TargetPath(self.target.fs, "HKLM"))
|
205
|
+
self._add_hive("HKU", hku_hive, TargetPath(self.target.fs, "HKU"))
|
206
|
+
self._map_hive("HKEY_LOCAL_MACHINE", hklm_hive)
|
207
|
+
self._map_hive("HKEY_USERS", hku_hive)
|
208
|
+
except SessionError:
|
209
|
+
self.target.log.info("Failed to open remote registry, registry will not be available")
|
210
|
+
return # no registry access, probably no access rights
|
204
211
|
|
205
212
|
def _init_users(self) -> None:
|
206
213
|
pass
|
@@ -212,15 +219,18 @@ class SmbRegistry(RegistryPlugin):
|
|
212
219
|
if hasattr(self, "_was_disabled") and self._was_disabled:
|
213
220
|
scmr.hRChangeServiceConfigW(self._svcctl, self._svc_handle, dwStartType=0x4)
|
214
221
|
|
215
|
-
if
|
222
|
+
if getattr(self, "_svcctl", None):
|
216
223
|
self._svcctl.disconnect()
|
217
224
|
|
218
|
-
if
|
225
|
+
if getattr(self, "_winreg", None):
|
219
226
|
self._winreg.disconnect()
|
220
227
|
|
221
228
|
def _check_service_status(self) -> None:
|
222
|
-
|
223
|
-
|
229
|
+
try:
|
230
|
+
manager_handle = scmr.hROpenSCManagerW(self._svcctl)["lpScHandle"]
|
231
|
+
self._svc_handle = scmr.hROpenServiceW(self._svcctl, manager_handle, "RemoteRegistry")["lpServiceHandle"]
|
232
|
+
except DCERPCException:
|
233
|
+
return
|
224
234
|
|
225
235
|
current_state = scmr.hRQueryServiceStatus(self._svcctl, self._svc_handle)["lpServiceStatus"]["dwCurrentState"]
|
226
236
|
if current_state == scmr.SERVICE_STOPPED:
|
@@ -3,7 +3,6 @@ from typing import Iterator
|
|
3
3
|
|
4
4
|
from dissect.sql import sqlite3
|
5
5
|
from dissect.util.ts import wintimestamp
|
6
|
-
from flow.record.fieldtypes import path
|
7
6
|
|
8
7
|
from dissect.target import Target
|
9
8
|
from dissect.target.exceptions import UnsupportedPluginError
|
@@ -105,7 +104,7 @@ class SophosPlugin(Plugin):
|
|
105
104
|
yield SophosLogRecord(
|
106
105
|
ts=ts,
|
107
106
|
description=details.get("threat_name", details),
|
108
|
-
path=path
|
107
|
+
path=self.target.fs.path(path_to_infected_file),
|
109
108
|
_target=self.target,
|
110
109
|
)
|
111
110
|
except Exception as error:
|
@@ -2,7 +2,6 @@ from typing import Iterator
|
|
2
2
|
|
3
3
|
from dissect import cstruct
|
4
4
|
from dissect.util.ts import from_unix
|
5
|
-
from flow.record.fieldtypes import path
|
6
5
|
|
7
6
|
from dissect.target import Target
|
8
7
|
from dissect.target.exceptions import UnsupportedPluginError
|
@@ -86,7 +85,7 @@ class TrendMicroPlugin(Plugin):
|
|
86
85
|
yield TrendMicroWFLogRecord(
|
87
86
|
ts=from_unix(int(cells[9])),
|
88
87
|
threat=cells[2],
|
89
|
-
path=path
|
88
|
+
path=self.target.fs.path(cells[6] + cells[7]),
|
90
89
|
lineno=lineno,
|
91
90
|
)
|
92
91
|
|
@@ -115,7 +114,7 @@ class TrendMicroPlugin(Plugin):
|
|
115
114
|
remote_ip=entry.remote_ip.strip(b"\x00").decode(self.codepage),
|
116
115
|
port=entry.port,
|
117
116
|
direction=("out" if entry.direction == b"\x01" else "in"),
|
118
|
-
path=path
|
117
|
+
path=self.target.fs.path(entry.path.strip(b"\x00").decode(self.codepage)),
|
119
118
|
description=entry.description.strip("\x00"),
|
120
119
|
)
|
121
120
|
except EOFError:
|
@@ -6,7 +6,6 @@ from dissect.sql import sqlite3
|
|
6
6
|
from dissect.sql.exceptions import Error as SQLError
|
7
7
|
from dissect.sql.sqlite3 import SQLite3
|
8
8
|
from dissect.util.ts import webkittimestamp
|
9
|
-
from flow.record.fieldtypes import path
|
10
9
|
|
11
10
|
from dissect.target.exceptions import FileNotFoundError, UnsupportedPluginError
|
12
11
|
from dissect.target.helpers.descriptor_extensions import UserRecordDescriptorExtension
|
@@ -142,11 +141,8 @@ class ChromiumMixin:
|
|
142
141
|
chain.sort(key=lambda row: row.chain_index)
|
143
142
|
|
144
143
|
for row in db.table("downloads").rows():
|
145
|
-
download_path
|
146
|
-
|
147
|
-
download_path = path.from_windows(download_path)
|
148
|
-
elif download_path:
|
149
|
-
download_path = path.from_posix(download_path)
|
144
|
+
if download_path := row.target_path:
|
145
|
+
download_path = self.target.fs.path(download_path)
|
150
146
|
|
151
147
|
url = None
|
152
148
|
download_chain = download_chains.get(row.id)
|
@@ -216,11 +212,8 @@ class ChromiumMixin:
|
|
216
212
|
if ts_update:
|
217
213
|
ts_update = webkittimestamp(ts_update)
|
218
214
|
|
219
|
-
ext_path
|
220
|
-
|
221
|
-
ext_path = path.from_windows(ext_path)
|
222
|
-
elif ext_path:
|
223
|
-
ext_path = path.from_posix(ext_path)
|
215
|
+
if ext_path := extension_data.get("path"):
|
216
|
+
ext_path = self.target.fs.path(ext_path)
|
224
217
|
|
225
218
|
manifest = extension_data.get("manifest")
|
226
219
|
if manifest:
|
@@ -5,7 +5,6 @@ from dissect.sql import sqlite3
|
|
5
5
|
from dissect.sql.exceptions import Error as SQLError
|
6
6
|
from dissect.sql.sqlite3 import SQLite3
|
7
7
|
from dissect.util.ts import from_unix_ms, from_unix_us
|
8
|
-
from flow.record.fieldtypes import path
|
9
8
|
|
10
9
|
from dissect.target.exceptions import FileNotFoundError, UnsupportedPluginError
|
11
10
|
from dissect.target.helpers.descriptor_extensions import UserRecordDescriptorExtension
|
@@ -197,12 +196,9 @@ class FirefoxPlugin(BrowserPlugin):
|
|
197
196
|
state = content.get("state")
|
198
197
|
|
199
198
|
dest_file_info = annotation.get("downloads/destinationFileURI", {})
|
200
|
-
download_path = dest_file_info.get("content")
|
201
199
|
|
202
|
-
if download_path
|
203
|
-
download_path = path
|
204
|
-
elif download_path:
|
205
|
-
download_path = path.from_posix(download_path)
|
200
|
+
if download_path := dest_file_info.get("content"):
|
201
|
+
download_path = self.target.fs.path(download_path)
|
206
202
|
|
207
203
|
place = places.get(place_id)
|
208
204
|
url = place.get("url")
|
@@ -3,7 +3,6 @@ from __future__ import annotations
|
|
3
3
|
from typing import TYPE_CHECKING, Iterator
|
4
4
|
|
5
5
|
from dissect.hypervisor import hyperv
|
6
|
-
from flow.record.fieldtypes import path
|
7
6
|
|
8
7
|
from dissect.target.exceptions import UnsupportedPluginError
|
9
8
|
from dissect.target.helpers.record import ChildTargetRecord
|
@@ -47,7 +46,7 @@ class HyperVChildTargetPlugin(ChildTargetPlugin):
|
|
47
46
|
for vm_path in virtual_machines.values():
|
48
47
|
yield ChildTargetRecord(
|
49
48
|
type=self.__type__,
|
50
|
-
path=path
|
49
|
+
path=self.target.fs.path(vm_path),
|
51
50
|
_target=self.target,
|
52
51
|
)
|
53
52
|
|
@@ -1,5 +1,3 @@
|
|
1
|
-
from flow.record.fieldtypes import path
|
2
|
-
|
3
1
|
from dissect.target.exceptions import UnsupportedPluginError
|
4
2
|
from dissect.target.helpers.record import ChildTargetRecord
|
5
3
|
from dissect.target.plugin import ChildTargetPlugin
|
@@ -42,6 +40,6 @@ class WorkstationChildTargetPlugin(ChildTargetPlugin):
|
|
42
40
|
|
43
41
|
yield ChildTargetRecord(
|
44
42
|
type=self.__type__,
|
45
|
-
path=path
|
43
|
+
path=self.target.fs.path(value.strip('"')),
|
46
44
|
_target=self.target,
|
47
45
|
)
|
@@ -43,4 +43,6 @@ class OpenHandlesPlugin(Plugin):
|
|
43
43
|
"""
|
44
44
|
with self.open_handles_file.open() as fh:
|
45
45
|
for row in csv.DictReader(gzip.open(fh, "rt")):
|
46
|
+
if name := row.get("name"):
|
47
|
+
row.update({"name": self.target.fs.path(name)})
|
46
48
|
yield AcquireOpenHandlesRecord(_target=self.target, **row)
|
@@ -1,8 +1,6 @@
|
|
1
1
|
import csv
|
2
2
|
import gzip
|
3
3
|
|
4
|
-
from flow.record.fieldtypes import posix_path, windows_path
|
5
|
-
|
6
4
|
from dissect.target.exceptions import UnsupportedPluginError
|
7
5
|
from dissect.target.helpers.record import TargetRecordDescriptor
|
8
6
|
from dissect.target.plugin import Plugin, export
|
@@ -35,15 +33,11 @@ class AcquireHashPlugin(Plugin):
|
|
35
33
|
An Acquire file container contains a file hashes csv when the hashes module was used. The content of this csv
|
36
34
|
file is returned.
|
37
35
|
"""
|
38
|
-
if self.target.os == "windows":
|
39
|
-
path_type = windows_path
|
40
|
-
else:
|
41
|
-
path_type = posix_path
|
42
36
|
|
43
37
|
with self.hash_file.open() as fh:
|
44
38
|
for row in csv.DictReader(gzip.open(fh, "rt")):
|
45
39
|
yield AcquireHashRecord(
|
46
|
-
path=
|
40
|
+
path=self.target.fs.path((row["path"])),
|
47
41
|
filesize=row["file-size"],
|
48
42
|
digest=(row["md5"] or None, row["sha1"] or None, row["sha256"] or None),
|
49
43
|
_target=self.target,
|
@@ -1,7 +1,6 @@
|
|
1
1
|
from typing import Iterator
|
2
2
|
|
3
3
|
from dissect.ntfs.c_ntfs import segment_reference
|
4
|
-
from flow.record.fieldtypes import path as rpath
|
5
4
|
|
6
5
|
from dissect.target.helpers.record import TargetRecordDescriptor
|
7
6
|
from dissect.target.plugin import Plugin, export
|
@@ -64,7 +63,7 @@ class UsnjrnlPlugin(Plugin):
|
|
64
63
|
yield UsnjrnlRecord(
|
65
64
|
ts=ts,
|
66
65
|
segment=f"{segment}#{record.FileReferenceNumber.SequenceNumber}",
|
67
|
-
path=
|
66
|
+
path=self.target.fs.path(path),
|
68
67
|
usn=record.Usn,
|
69
68
|
reason=str(record.Reason).replace("USN_REASON.", ""),
|
70
69
|
attr=str(record.FileAttributes).replace("FILE_ATTRIBUTE.", ""),
|
@@ -98,7 +98,7 @@ class ResolverPlugin(Plugin):
|
|
98
98
|
return lookup_ext
|
99
99
|
|
100
100
|
for search_path in search_paths:
|
101
|
-
lookup_path =
|
101
|
+
lookup_path = fsutil.join(search_path, lookup_ext, alt_separator=self.target.fs.alt_separator)
|
102
102
|
if self.target.fs.exists(lookup_path):
|
103
103
|
return lookup_path
|
104
104
|
|
@@ -2,9 +2,10 @@ import struct
|
|
2
2
|
from enum import IntEnum
|
3
3
|
from io import BytesIO
|
4
4
|
|
5
|
-
from dissect.target.exceptions import UnsupportedPluginError
|
5
|
+
from dissect.target.exceptions import FileNotFoundError, UnsupportedPluginError
|
6
6
|
from dissect.target.helpers.record import TargetRecordDescriptor
|
7
7
|
from dissect.target.plugin import Plugin, export
|
8
|
+
from dissect.target.plugins.filesystem.walkfs import generate_record
|
8
9
|
|
9
10
|
CapabilityRecord = TargetRecordDescriptor(
|
10
11
|
"filesystem/unix/capability",
|
@@ -87,72 +88,82 @@ class CapabilityPlugin(Plugin):
|
|
87
88
|
@export(record=CapabilityRecord)
|
88
89
|
def capability_binaries(self):
|
89
90
|
"""Find all files that have capabilities set."""
|
90
|
-
for
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
for attr in attrs:
|
98
|
-
if attr.name != "security.capability":
|
91
|
+
for path_entries, _, files in self.target.fs.walk_ext("/"):
|
92
|
+
entries = [path_entries[-1]] + files
|
93
|
+
for entry in entries:
|
94
|
+
path = self.target.fs.path(entry.path)
|
95
|
+
try:
|
96
|
+
record = generate_record(self.target, path)
|
97
|
+
except FileNotFoundError:
|
99
98
|
continue
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
# The struct is small enough we can just use struct
|
105
|
-
magic_etc = struct.unpack("<I", buf.read(4))[0]
|
106
|
-
cap_revision = magic_etc & VFS_CAP_REVISION_MASK
|
107
|
-
|
108
|
-
permitted_caps = []
|
109
|
-
inheritable_caps = []
|
110
|
-
rootid = None
|
111
|
-
|
112
|
-
if cap_revision == VFS_CAP_REVISION_1:
|
113
|
-
num_caps = VFS_CAP_U32_1
|
114
|
-
data_len = (1 + 2 * VFS_CAP_U32_1) * 4
|
115
|
-
elif cap_revision == VFS_CAP_REVISION_2:
|
116
|
-
num_caps = VFS_CAP_U32_2
|
117
|
-
data_len = (1 + 2 * VFS_CAP_U32_2) * 4
|
118
|
-
elif cap_revision == VFS_CAP_REVISION_3:
|
119
|
-
num_caps = VFS_CAP_U32_3
|
120
|
-
data_len = (2 + 2 * VFS_CAP_U32_2) * 4
|
121
|
-
else:
|
122
|
-
self.target.log.error("Unexpected capability revision: %s", entry)
|
99
|
+
try:
|
100
|
+
attrs = path.get().lattr()
|
101
|
+
except TypeError:
|
102
|
+
# Virtual(File|Directory|Symlink) instances don't have a functional lattr()
|
123
103
|
continue
|
124
|
-
|
125
|
-
|
126
|
-
self.target.log.error("Unexpected capability length: %s", entry)
|
104
|
+
except Exception:
|
105
|
+
self.target.log.exception("Failed to get attrs for entry %s", entry)
|
127
106
|
continue
|
128
107
|
|
129
|
-
for
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
108
|
+
for attr in attrs:
|
109
|
+
if attr.name != "security.capability":
|
110
|
+
continue
|
111
|
+
|
112
|
+
buf = BytesIO(attr.value)
|
113
|
+
|
114
|
+
# Reference: https://github.com/torvalds/linux/blob/master/include/uapi/linux/capability.h
|
115
|
+
# The struct is small enough we can just use struct
|
116
|
+
magic_etc = struct.unpack("<I", buf.read(4))[0]
|
117
|
+
cap_revision = magic_etc & VFS_CAP_REVISION_MASK
|
118
|
+
|
119
|
+
permitted_caps = []
|
120
|
+
inheritable_caps = []
|
121
|
+
rootid = None
|
122
|
+
|
123
|
+
if cap_revision == VFS_CAP_REVISION_1:
|
124
|
+
num_caps = VFS_CAP_U32_1
|
125
|
+
data_len = (1 + 2 * VFS_CAP_U32_1) * 4
|
126
|
+
elif cap_revision == VFS_CAP_REVISION_2:
|
127
|
+
num_caps = VFS_CAP_U32_2
|
128
|
+
data_len = (1 + 2 * VFS_CAP_U32_2) * 4
|
129
|
+
elif cap_revision == VFS_CAP_REVISION_3:
|
130
|
+
num_caps = VFS_CAP_U32_3
|
131
|
+
data_len = (2 + 2 * VFS_CAP_U32_2) * 4
|
132
|
+
else:
|
133
|
+
self.target.log.error("Unexpected capability revision: %s", entry)
|
134
|
+
continue
|
135
|
+
|
136
|
+
if data_len != len(attr.value):
|
137
|
+
self.target.log.error("Unexpected capability length: %s", entry)
|
138
|
+
continue
|
139
|
+
|
140
|
+
for _ in range(num_caps):
|
141
|
+
permitted_val, inheritable_val = struct.unpack("<2I", buf.read(8))
|
142
|
+
permitted_caps.append(permitted_val)
|
143
|
+
inheritable_caps.append(inheritable_val)
|
144
|
+
|
145
|
+
if cap_revision == VFS_CAP_REVISION_3:
|
146
|
+
rootid = struct.unpack("<I", buf.read(4))[0]
|
147
|
+
|
148
|
+
permitted = []
|
149
|
+
inheritable = []
|
150
|
+
|
151
|
+
for capability in Capabilities:
|
152
|
+
for caps, results in [(permitted_caps, permitted), (inheritable_caps, inheritable)]:
|
153
|
+
# CAP_TO_INDEX
|
154
|
+
cap_index = capability.value >> 5
|
155
|
+
if cap_index >= len(caps):
|
156
|
+
# We loop over all capabilities, but might only have a version 1 caps list
|
157
|
+
continue
|
158
|
+
|
159
|
+
if caps[cap_index] & (1 << (capability.value & 31)) != 0:
|
160
|
+
results.append(capability.name)
|
161
|
+
|
162
|
+
yield CapabilityRecord(
|
163
|
+
record=record,
|
164
|
+
permitted=permitted,
|
165
|
+
inheritable=inheritable,
|
166
|
+
effective=magic_etc & VFS_CAP_FLAGS_EFFECTIVE != 0,
|
167
|
+
rootid=rootid,
|
168
|
+
_target=self.target,
|
169
|
+
)
|
@@ -1,8 +1,13 @@
|
|
1
|
+
from typing import Iterable
|
2
|
+
|
1
3
|
from dissect.util.ts import from_unix
|
2
4
|
|
3
5
|
from dissect.target.exceptions import FileNotFoundError, UnsupportedPluginError
|
6
|
+
from dissect.target.filesystem import RootFilesystemEntry
|
7
|
+
from dissect.target.helpers.fsutil import TargetPath
|
4
8
|
from dissect.target.helpers.record import TargetRecordDescriptor
|
5
|
-
from dissect.target.plugin import Plugin, export
|
9
|
+
from dissect.target.plugin import Plugin, export
|
10
|
+
from dissect.target.target import Target
|
6
11
|
|
7
12
|
FilesystemRecord = TargetRecordDescriptor(
|
8
13
|
"filesystem/entry",
|
@@ -17,8 +22,7 @@ FilesystemRecord = TargetRecordDescriptor(
|
|
17
22
|
("uint32", "mode"),
|
18
23
|
("uint32", "uid"),
|
19
24
|
("uint32", "gid"),
|
20
|
-
("string", "
|
21
|
-
("uint32", "fsidx"),
|
25
|
+
("string[]", "fstypes"),
|
22
26
|
],
|
23
27
|
)
|
24
28
|
|
@@ -29,36 +33,36 @@ class WalkFSPlugin(Plugin):
|
|
29
33
|
raise UnsupportedPluginError("No filesystems found")
|
30
34
|
|
31
35
|
@export(record=FilesystemRecord)
|
32
|
-
def walkfs(self):
|
36
|
+
def walkfs(self) -> Iterable[FilesystemRecord]:
|
33
37
|
"""Walk a target's filesystem and return all filesystem entries."""
|
34
|
-
for _,
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
def walkfs_ext(self, root="/", pattern="*"):
|
39
|
-
for idx, fs in enumerate(self.target.filesystems):
|
40
|
-
for entry in fs.path(root).rglob(pattern):
|
38
|
+
for path_entries, _, files in self.target.fs.walk_ext("/"):
|
39
|
+
entries = [path_entries[-1]] + files
|
40
|
+
for entry in entries:
|
41
|
+
path = self.target.fs.path(entry.path)
|
41
42
|
try:
|
42
|
-
|
43
|
+
record = generate_record(self.target, path)
|
43
44
|
except FileNotFoundError:
|
44
45
|
continue
|
45
|
-
|
46
|
-
self.target.log.exception("Failed to generate record from entry %s", entry)
|
46
|
+
yield record
|
47
47
|
|
48
48
|
|
49
|
-
def generate_record(target
|
50
|
-
stat =
|
49
|
+
def generate_record(target: Target, path: TargetPath) -> FilesystemRecord:
|
50
|
+
stat = path.lstat()
|
51
|
+
entry = path.get()
|
52
|
+
if isinstance(entry, RootFilesystemEntry):
|
53
|
+
fs_types = [sub_entry.fs.__type__ for sub_entry in entry.entries]
|
54
|
+
else:
|
55
|
+
fs_types = [entry.fs.__type__]
|
51
56
|
return FilesystemRecord(
|
52
57
|
atime=from_unix(stat.st_atime),
|
53
58
|
mtime=from_unix(stat.st_mtime),
|
54
59
|
ctime=from_unix(stat.st_ctime),
|
55
60
|
ino=stat.st_ino,
|
56
|
-
path=
|
61
|
+
path=path,
|
57
62
|
size=stat.st_size,
|
58
63
|
mode=stat.st_mode,
|
59
64
|
uid=stat.st_uid,
|
60
65
|
gid=stat.st_gid,
|
61
|
-
|
62
|
-
fsidx=idx,
|
66
|
+
fstypes=fs_types,
|
63
67
|
_target=target,
|
64
68
|
)
|