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.
Files changed (53) hide show
  1. dissect/target/filesystem.py +1 -1
  2. dissect/target/filesystems/btrfs.py +2 -2
  3. dissect/target/helpers/cache.py +2 -2
  4. dissect/target/helpers/fsutil.py +9 -6
  5. dissect/target/helpers/hashutil.py +1 -5
  6. dissect/target/loaders/log.py +2 -2
  7. dissect/target/loaders/smb.py +23 -13
  8. dissect/target/plugins/apps/av/sophos.py +1 -2
  9. dissect/target/plugins/apps/av/trendmicro.py +2 -3
  10. dissect/target/plugins/apps/browser/chromium.py +4 -11
  11. dissect/target/plugins/apps/browser/firefox.py +2 -6
  12. dissect/target/plugins/child/hyperv.py +1 -2
  13. dissect/target/plugins/child/vmware_workstation.py +1 -3
  14. dissect/target/plugins/filesystem/acquire_handles.py +2 -0
  15. dissect/target/plugins/filesystem/acquire_hash.py +1 -7
  16. dissect/target/plugins/filesystem/ntfs/usnjrnl.py +1 -2
  17. dissect/target/plugins/filesystem/resolver.py +1 -1
  18. dissect/target/plugins/filesystem/unix/capability.py +77 -66
  19. dissect/target/plugins/filesystem/walkfs.py +23 -19
  20. dissect/target/plugins/filesystem/yara.py +20 -19
  21. dissect/target/plugins/os/unix/_os.py +1 -3
  22. dissect/target/plugins/os/unix/bsd/osx/user.py +1 -3
  23. dissect/target/plugins/os/unix/esxi/_os.py +1 -2
  24. dissect/target/plugins/os/unix/log/journal.py +7 -6
  25. dissect/target/plugins/os/windows/_os.py +2 -1
  26. dissect/target/plugins/os/windows/amcache.py +9 -10
  27. dissect/target/plugins/os/windows/catroot.py +2 -2
  28. dissect/target/plugins/os/windows/generic.py +10 -11
  29. dissect/target/plugins/os/windows/lnk.py +5 -6
  30. dissect/target/plugins/os/windows/log/amcache.py +3 -5
  31. dissect/target/plugins/os/windows/log/pfro.py +1 -3
  32. dissect/target/plugins/os/windows/prefetch.py +5 -6
  33. dissect/target/plugins/os/windows/recyclebin.py +3 -4
  34. dissect/target/plugins/os/windows/regf/7zip.py +2 -4
  35. dissect/target/plugins/os/windows/regf/bam.py +1 -2
  36. dissect/target/plugins/os/windows/regf/cit.py +4 -5
  37. dissect/target/plugins/os/windows/regf/muicache.py +1 -3
  38. dissect/target/plugins/os/windows/regf/recentfilecache.py +1 -2
  39. dissect/target/plugins/os/windows/regf/shimcache.py +1 -2
  40. dissect/target/plugins/os/windows/regf/trusteddocs.py +1 -1
  41. dissect/target/plugins/os/windows/regf/userassist.py +1 -2
  42. dissect/target/plugins/os/windows/services.py +2 -4
  43. dissect/target/plugins/os/windows/sru.py +4 -4
  44. dissect/target/plugins/os/windows/startupinfo.py +5 -6
  45. dissect/target/plugins/os/windows/syscache.py +1 -2
  46. dissect/target/target.py +2 -1
  47. {dissect.target-3.14.dev20.dist-info → dissect.target-3.14.dev23.dist-info}/METADATA +1 -1
  48. {dissect.target-3.14.dev20.dist-info → dissect.target-3.14.dev23.dist-info}/RECORD +53 -53
  49. {dissect.target-3.14.dev20.dist-info → dissect.target-3.14.dev23.dist-info}/COPYRIGHT +0 -0
  50. {dissect.target-3.14.dev20.dist-info → dissect.target-3.14.dev23.dist-info}/LICENSE +0 -0
  51. {dissect.target-3.14.dev20.dist-info → dissect.target-3.14.dev23.dist-info}/WHEEL +0 -0
  52. {dissect.target-3.14.dev20.dist-info → dissect.target-3.14.dev23.dist-info}/entry_points.txt +0 -0
  53. {dissect.target-3.14.dev20.dist-info → dissect.target-3.14.dev23.dist-info}/top_level.txt +0 -0
@@ -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.__fstype__)
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
- __fstype__ = "btrfs"
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
- __fstype__ = "btrfs"
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)
@@ -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=True):
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=True):
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)
@@ -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
- # CPython >= 3.10
729
- if "b" not in mode and hasattr(io, "text_encoding"):
730
- # Vermin linting needs to be skipped for this line as this is
731
- # guarded by an explicit check for availability.
732
- # novermin
733
- encoding = io.text_encoding(encoding)
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 = path_type(resolved_path)
92
+ resolved_path = target.fs.path(resolved_path)
97
93
  record_kwargs = dict()
98
94
  record_def = list()
99
95
 
@@ -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(Path(mapping).joinpath(entry.name))
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
@@ -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
- self._svcctl = _connect_rpc(self.conn, "ncacn_np:445[\\pipe\\svcctl]", scmr.MSRPC_UUID_SCMR)
193
- self._check_service_status()
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
- self._winreg = _connect_rpc(self.conn, "ncacn_np:445[\\pipe\\winreg]", rrp.MSRPC_UUID_RRP)
197
+ self._winreg = _connect_rpc(self.conn, "ncacn_np:445[\\pipe\\winreg]", rrp.MSRPC_UUID_RRP)
196
198
 
197
- hklm_hive = SmbRegistryHive(self._winreg, "HKEY_LOCAL_MACHINE", rrp.hOpenLocalMachine(self._winreg)["phKey"])
198
- hku_hive = SmbRegistryHive(self._winreg, "HKEY_USERS", rrp.hOpenUsers(self._winreg)["phKey"])
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
- self._add_hive("HKLM", hklm_hive, TargetPath(self.target.fs, "HKLM"))
201
- self._add_hive("HKU", hku_hive, TargetPath(self.target.fs, "HKU"))
202
- self._map_hive("HKEY_LOCAL_MACHINE", hklm_hive)
203
- self._map_hive("HKEY_USERS", hku_hive)
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 hasattr(self, "_svcctl"):
222
+ if getattr(self, "_svcctl", None):
216
223
  self._svcctl.disconnect()
217
224
 
218
- if hasattr(self, "_winreg"):
225
+ if getattr(self, "_winreg", None):
219
226
  self._winreg.disconnect()
220
227
 
221
228
  def _check_service_status(self) -> None:
222
- manager_handle = scmr.hROpenSCManagerW(self._svcctl)["lpScHandle"]
223
- self._svc_handle = scmr.hROpenServiceW(self._svcctl, manager_handle, "RemoteRegistry")["lpServiceHandle"]
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.from_windows(path_to_infected_file),
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.from_windows(cells[6] + cells[7]),
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.from_windows(entry.path.strip(b"\x00").decode(self.codepage)),
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 = row.target_path
146
- if download_path and self.target.os == "windows":
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 = extension_data.get("path")
220
- if ext_path and self.target.os == "windows":
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 and self.target.os == "windows":
203
- download_path = path.from_windows(download_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.from_windows(vm_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.from_windows(value.strip('"')),
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=path_type(row["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=rpath.from_windows(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 = "/".join([search_path, lookup_ext])
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 entry, record in self.target.walkfs_ext():
91
- try:
92
- attrs = entry.get().lattr()
93
- except Exception:
94
- self.target.log.exception("Failed to get attrs for entry %s", entry)
95
- continue
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
- buf = BytesIO(attr.value)
102
-
103
- # Reference: https://github.com/torvalds/linux/blob/master/include/uapi/linux/capability.h
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
- if data_len != len(attr.value):
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 _ in range(num_caps):
130
- permitted_val, inheritable_val = struct.unpack("<2I", buf.read(8))
131
- permitted_caps.append(permitted_val)
132
- inheritable_caps.append(inheritable_val)
133
-
134
- if cap_revision == VFS_CAP_REVISION_3:
135
- rootid = struct.unpack("<I", buf.read(4))[0]
136
-
137
- permitted = []
138
- inheritable = []
139
-
140
- for capability in Capabilities:
141
- for caps, results in [(permitted_caps, permitted), (inheritable_caps, inheritable)]:
142
- # CAP_TO_INDEX
143
- cap_index = capability.value >> 5
144
- if cap_index >= len(caps):
145
- # We loop over all capabilities, but might only have a version 1 caps list
146
- continue
147
-
148
- if caps[cap_index] & (1 << (capability.value & 31)) != 0:
149
- results.append(capability.name)
150
-
151
- yield CapabilityRecord(
152
- record=record,
153
- permitted=permitted,
154
- inheritable=inheritable,
155
- effective=magic_etc & VFS_CAP_FLAGS_EFFECTIVE != 0,
156
- rootid=rootid,
157
- _target=self.target,
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, internal
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", "fstype"),
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 _, record in self.walkfs_ext():
35
- yield record
36
-
37
- @internal
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
- yield entry, generate_record(self.target, entry, idx)
43
+ record = generate_record(self.target, path)
43
44
  except FileNotFoundError:
44
45
  continue
45
- except Exception:
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, entry, idx):
50
- stat = entry.lstat()
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=entry,
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
- fstype=entry.get().fs.__type__,
62
- fsidx=idx,
66
+ fstypes=fs_types,
63
67
  _target=target,
64
68
  )