dissect.target 3.13.dev26__py3-none-any.whl → 3.14__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
Files changed (138) hide show
  1. dissect/target/container.py +9 -1
  2. dissect/target/containers/asdf.py +2 -0
  3. dissect/target/containers/ewf.py +2 -0
  4. dissect/target/containers/hdd.py +2 -0
  5. dissect/target/containers/hds.py +2 -0
  6. dissect/target/containers/qcow2.py +2 -0
  7. dissect/target/containers/raw.py +2 -0
  8. dissect/target/containers/split.py +2 -0
  9. dissect/target/containers/vdi.py +2 -0
  10. dissect/target/containers/vhd.py +2 -0
  11. dissect/target/containers/vhdx.py +2 -0
  12. dissect/target/containers/vmdk.py +2 -0
  13. dissect/target/filesystem.py +108 -15
  14. dissect/target/filesystems/ad1.py +1 -1
  15. dissect/target/filesystems/btrfs.py +180 -0
  16. dissect/target/filesystems/cb.py +4 -4
  17. dissect/target/filesystems/config.py +161 -31
  18. dissect/target/filesystems/dir.py +1 -1
  19. dissect/target/filesystems/exfat.py +1 -1
  20. dissect/target/filesystems/extfs.py +5 -1
  21. dissect/target/filesystems/fat.py +1 -1
  22. dissect/target/filesystems/ffs.py +1 -1
  23. dissect/target/filesystems/itunes.py +1 -1
  24. dissect/target/filesystems/ntfs.py +1 -1
  25. dissect/target/filesystems/smb.py +1 -1
  26. dissect/target/filesystems/squashfs.py +1 -1
  27. dissect/target/filesystems/tar.py +1 -1
  28. dissect/target/filesystems/vmfs.py +1 -1
  29. dissect/target/filesystems/xfs.py +1 -1
  30. dissect/target/filesystems/zip.py +1 -1
  31. dissect/target/helpers/cache.py +2 -2
  32. dissect/target/helpers/configutil.py +283 -83
  33. dissect/target/helpers/fsutil.py +9 -6
  34. dissect/target/helpers/hashutil.py +20 -19
  35. dissect/target/helpers/utils.py +14 -3
  36. dissect/target/loaders/ad1.py +1 -1
  37. dissect/target/loaders/asdf.py +1 -1
  38. dissect/target/loaders/log.py +2 -2
  39. dissect/target/loaders/smb.py +23 -13
  40. dissect/target/loaders/targetd.py +12 -2
  41. dissect/target/loaders/vma.py +1 -1
  42. dissect/target/loaders/xva.py +1 -1
  43. dissect/target/plugin.py +14 -2
  44. dissect/target/plugins/apps/av/sophos.py +1 -2
  45. dissect/target/plugins/apps/av/symantec.py +3 -4
  46. dissect/target/plugins/apps/av/trendmicro.py +2 -3
  47. dissect/target/plugins/{browsers → apps/browser}/chrome.py +6 -3
  48. dissect/target/plugins/{browsers → apps/browser}/chromium.py +18 -13
  49. dissect/target/plugins/{browsers → apps/browser}/edge.py +6 -3
  50. dissect/target/plugins/{browsers → apps/browser}/firefox.py +3 -7
  51. dissect/target/plugins/{browsers → apps/browser}/iexplore.py +14 -4
  52. dissect/target/plugins/apps/remoteaccess/teamviewer.py +55 -27
  53. dissect/target/plugins/apps/ssh/opensshd.py +31 -30
  54. dissect/target/plugins/apps/{webservers → webserver}/apache.py +1 -1
  55. dissect/target/plugins/apps/{webservers → webserver}/caddy.py +1 -1
  56. dissect/target/plugins/apps/{webservers → webserver}/iis.py +1 -1
  57. dissect/target/plugins/apps/{webservers → webserver}/nginx.py +1 -1
  58. dissect/target/plugins/child/hyperv.py +1 -2
  59. dissect/target/plugins/child/vmware_workstation.py +1 -3
  60. dissect/target/plugins/filesystem/acquire_handles.py +2 -0
  61. dissect/target/plugins/filesystem/acquire_hash.py +1 -7
  62. dissect/target/plugins/filesystem/icat.py +5 -5
  63. dissect/target/plugins/filesystem/ntfs/mft.py +2 -2
  64. dissect/target/plugins/filesystem/ntfs/mft_timeline.py +2 -2
  65. dissect/target/plugins/filesystem/ntfs/usnjrnl.py +2 -3
  66. dissect/target/plugins/filesystem/resolver.py +1 -1
  67. dissect/target/plugins/filesystem/unix/capability.py +77 -66
  68. dissect/target/plugins/filesystem/walkfs.py +25 -19
  69. dissect/target/plugins/filesystem/yara.py +20 -19
  70. dissect/target/plugins/general/config.py +28 -11
  71. dissect/target/plugins/os/unix/_os.py +28 -21
  72. dissect/target/plugins/os/unix/bsd/osx/user.py +1 -3
  73. dissect/target/plugins/os/unix/cronjobs.py +4 -16
  74. dissect/target/plugins/os/unix/{linux/esxi → esxi}/_os.py +5 -6
  75. dissect/target/plugins/os/unix/generic.py +5 -1
  76. dissect/target/plugins/os/unix/history.py +2 -1
  77. dissect/target/plugins/os/unix/linux/_os.py +12 -5
  78. dissect/target/plugins/os/unix/linux/services.py +112 -0
  79. dissect/target/plugins/os/unix/linux/suse/zypper.py +4 -4
  80. dissect/target/plugins/os/unix/locale.py +3 -1
  81. dissect/target/plugins/os/unix/log/journal.py +7 -6
  82. dissect/target/plugins/os/unix/packagemanager.py +3 -3
  83. dissect/target/plugins/os/unix/shadow.py +1 -1
  84. dissect/target/plugins/os/windows/_os.py +2 -1
  85. dissect/target/plugins/os/windows/amcache.py +9 -10
  86. dissect/target/plugins/os/windows/catroot.py +2 -2
  87. dissect/target/plugins/os/windows/cim.py +5 -4
  88. dissect/target/plugins/os/windows/datetime.py +4 -1
  89. dissect/target/plugins/os/windows/defender.py +3 -3
  90. dissect/target/plugins/os/windows/generic.py +10 -11
  91. dissect/target/plugins/os/windows/lnk.py +6 -6
  92. dissect/target/plugins/os/windows/log/amcache.py +3 -5
  93. dissect/target/plugins/os/windows/log/pfro.py +1 -3
  94. dissect/target/plugins/os/windows/prefetch.py +5 -6
  95. dissect/target/plugins/os/windows/recyclebin.py +3 -4
  96. dissect/target/plugins/os/windows/regf/7zip.py +2 -4
  97. dissect/target/plugins/os/windows/regf/bam.py +1 -2
  98. dissect/target/plugins/os/windows/regf/cit.py +4 -5
  99. dissect/target/plugins/os/windows/regf/mru.py +6 -2
  100. dissect/target/plugins/os/windows/regf/muicache.py +1 -3
  101. dissect/target/plugins/os/windows/regf/recentfilecache.py +1 -2
  102. dissect/target/plugins/os/windows/regf/shimcache.py +1 -2
  103. dissect/target/plugins/os/windows/regf/trusteddocs.py +1 -1
  104. dissect/target/plugins/os/windows/regf/userassist.py +1 -2
  105. dissect/target/plugins/os/windows/services.py +2 -4
  106. dissect/target/plugins/os/windows/sru.py +4 -4
  107. dissect/target/plugins/os/windows/startupinfo.py +5 -6
  108. dissect/target/plugins/os/windows/syscache.py +2 -3
  109. dissect/target/target.py +65 -32
  110. dissect/target/tools/info.py +2 -1
  111. dissect/target/tools/mount.py +2 -12
  112. dissect/target/tools/shell.py +3 -2
  113. dissect/target/volume.py +10 -9
  114. dissect/target/volumes/bde.py +1 -1
  115. dissect/target/volumes/ddf.py +2 -0
  116. dissect/target/volumes/disk.py +2 -0
  117. dissect/target/volumes/luks.py +1 -1
  118. dissect/target/volumes/lvm.py +2 -0
  119. dissect/target/volumes/md.py +2 -0
  120. dissect/target/volumes/vmfs.py +2 -0
  121. {dissect.target-3.13.dev26.dist-info → dissect.target-3.14.dist-info}/METADATA +2 -1
  122. {dissect.target-3.13.dev26.dist-info → dissect.target-3.14.dist-info}/RECORD +137 -136
  123. {dissect.target-3.13.dev26.dist-info → dissect.target-3.14.dist-info}/WHEEL +1 -1
  124. dissect/target/plugins/os/unix/services.py +0 -151
  125. /dissect/target/plugins/apps/{containers → browser}/__init__.py +0 -0
  126. /dissect/target/plugins/{browsers → apps/browser}/browser.py +0 -0
  127. /dissect/target/plugins/apps/{vpns → container}/__init__.py +0 -0
  128. /dissect/target/plugins/apps/{containers → container}/docker.py +0 -0
  129. /dissect/target/plugins/apps/{webservers → vpn}/__init__.py +0 -0
  130. /dissect/target/plugins/apps/{vpns → vpn}/openvpn.py +0 -0
  131. /dissect/target/plugins/apps/{vpns → vpn}/wireguard.py +0 -0
  132. /dissect/target/plugins/{browsers → apps/webserver}/__init__.py +0 -0
  133. /dissect/target/plugins/apps/{webservers/webservers.py → webserver/webserver.py} +0 -0
  134. /dissect/target/plugins/os/unix/{linux/esxi → esxi}/__init__.py +0 -0
  135. {dissect.target-3.13.dev26.dist-info → dissect.target-3.14.dist-info}/COPYRIGHT +0 -0
  136. {dissect.target-3.13.dev26.dist-info → dissect.target-3.14.dist-info}/LICENSE +0 -0
  137. {dissect.target-3.13.dev26.dist-info → dissect.target-3.14.dist-info}/entry_points.txt +0 -0
  138. {dissect.target-3.13.dev26.dist-info → dissect.target-3.14.dist-info}/top_level.txt +0 -0
@@ -12,7 +12,7 @@ from dissect.target.exceptions import FileNotFoundError as DissectFileNotFoundEr
12
12
  from dissect.target.exceptions import PluginError, UnsupportedPluginError
13
13
  from dissect.target.helpers import fsutil
14
14
  from dissect.target.helpers.record import TargetRecordDescriptor
15
- from dissect.target.plugins.apps.webservers.webservers import WebserverAccessLogRecord
15
+ from dissect.target.plugins.apps.webserver.webserver import WebserverAccessLogRecord
16
16
 
17
17
  LOG_RECORD_NAME = "filesystem/windows/iis/logs"
18
18
 
@@ -6,7 +6,7 @@ from typing import Iterator
6
6
  from dissect.target import plugin
7
7
  from dissect.target.exceptions import FileNotFoundError, UnsupportedPluginError
8
8
  from dissect.target.helpers.fsutil import open_decompress
9
- from dissect.target.plugins.apps.webservers.webservers import WebserverAccessLogRecord
9
+ from dissect.target.plugins.apps.webserver.webserver import WebserverAccessLogRecord
10
10
  from dissect.target.target import Target
11
11
 
12
12
  LOG_REGEX = re.compile(
@@ -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,
@@ -16,7 +16,7 @@ class ICatPlugin(Plugin):
16
16
 
17
17
  def check_compatible(self) -> None:
18
18
  filesystems = self.target.filesystems
19
- if not any(fs.__fstype__ in self.FS_SUPPORTED for fs in filesystems):
19
+ if not any(fs.__type__ in self.FS_SUPPORTED for fs in filesystems):
20
20
  raise UnsupportedPluginError("No supported filesystems found")
21
21
 
22
22
  @arg("--segment", "--inode", "-i", dest="inum", required=True, type=int, help="MFT segment or inode number")
@@ -72,14 +72,14 @@ class ICatPlugin(Plugin):
72
72
  )
73
73
  return
74
74
 
75
- if filesystem.__fstype__ == "ntfs" or open_as == "ntfs":
75
+ if filesystem.__type__ == "ntfs" or open_as == "ntfs":
76
76
  fh = filesystem.ntfs.mft(inum).open(ads)
77
- elif filesystem.__fstype__ == "ext":
77
+ elif filesystem.__type__ == "ext":
78
78
  fh = filesystem.extfs.get_inode(inum).open()
79
- elif filesystem.__fstype__ == "xfs":
79
+ elif filesystem.__type__ == "xfs":
80
80
  fh = filesystem.xfs.get_inode(inum).open()
81
81
  else:
82
- self.target.log.exception('Unsupported FS type "%s"', filesystem.__fstype__)
82
+ self.target.log.exception('Unsupported FS type "%s"', filesystem.__type__)
83
83
  return
84
84
 
85
85
  shutil.copyfileobj(fh, sys.stdout.buffer)
@@ -105,7 +105,7 @@ COMPACT_RECORD_TYPES = {
105
105
 
106
106
  class MftPlugin(Plugin):
107
107
  def check_compatible(self) -> None:
108
- ntfs_filesystems = [fs for fs in self.target.filesystems if fs.__fstype__ == "ntfs"]
108
+ ntfs_filesystems = [fs for fs in self.target.filesystems if fs.__type__ == "ntfs"]
109
109
  if not len(ntfs_filesystems):
110
110
  raise UnsupportedPluginError("No NTFS filesystems found")
111
111
 
@@ -133,7 +133,7 @@ class MftPlugin(Plugin):
133
133
  record_formatter = formatter
134
134
 
135
135
  for fs in self.target.filesystems:
136
- if fs.__fstype__ != "ntfs":
136
+ if fs.__type__ != "ntfs":
137
137
  continue
138
138
 
139
139
  drive_letter = get_drive_letter(self.target, fs)
@@ -94,7 +94,7 @@ def format_info(
94
94
 
95
95
  class MftTimelinePlugin(Plugin):
96
96
  def check_compatible(self) -> None:
97
- ntfs_filesystems = [fs for fs in self.target.filesystems if fs.__fstype__ == "ntfs"]
97
+ ntfs_filesystems = [fs for fs in self.target.filesystems if fs.__type__ == "ntfs"]
98
98
  if not len(ntfs_filesystems):
99
99
  raise UnsupportedPluginError("No MFT timelines found")
100
100
 
@@ -109,7 +109,7 @@ class MftTimelinePlugin(Plugin):
109
109
  - https://docs.microsoft.com/en-us/windows/win32/fileio/master-file-table
110
110
  """
111
111
  for fs in self.target.filesystems:
112
- if fs.__fstype__ != "ntfs":
112
+ if fs.__type__ != "ntfs":
113
113
  continue
114
114
 
115
115
  drive_letter = get_drive_letter(self.target, fs)
@@ -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
@@ -41,7 +40,7 @@ class UsnjrnlPlugin(Plugin):
41
40
  """
42
41
  target = self.target
43
42
  for fs in self.target.filesystems:
44
- if fs.__fstype__ != "ntfs":
43
+ if fs.__type__ != "ntfs":
45
44
  continue
46
45
 
47
46
  usnjrnl = fs.ntfs.usnjrnl
@@ -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,38 @@ 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
+ btime = from_unix(stat.st_birthtime) if stat.st_birthtime else None
52
+ entry = path.get()
53
+ if isinstance(entry, RootFilesystemEntry):
54
+ fs_types = [sub_entry.fs.__type__ for sub_entry in entry.entries]
55
+ else:
56
+ fs_types = [entry.fs.__type__]
51
57
  return FilesystemRecord(
52
58
  atime=from_unix(stat.st_atime),
53
59
  mtime=from_unix(stat.st_mtime),
54
60
  ctime=from_unix(stat.st_ctime),
61
+ btime=btime,
55
62
  ino=stat.st_ino,
56
- path=entry,
63
+ path=path,
57
64
  size=stat.st_size,
58
65
  mode=stat.st_mode,
59
66
  uid=stat.st_uid,
60
67
  gid=stat.st_gid,
61
- fstype=entry.get().fs.__fstype__,
62
- fsidx=idx,
68
+ fstypes=fs_types,
63
69
  _target=target,
64
70
  )
@@ -5,7 +5,7 @@ try:
5
5
  except ImportError:
6
6
  raise ImportError("Please install 'yara-python' to use 'target-query -f yara'.")
7
7
 
8
- from dissect.target.exceptions import FileNotFoundError, UnsupportedPluginError
8
+ from dissect.target.exceptions import FileNotFoundError
9
9
  from dissect.target.helpers.record import TargetRecordDescriptor
10
10
  from dissect.target.plugin import Plugin, arg, export
11
11
 
@@ -26,8 +26,7 @@ class YaraPlugin(Plugin):
26
26
  DEFAULT_MAX_SIZE = 10 * 1024 * 1024
27
27
 
28
28
  def check_compatible(self) -> None:
29
- if not self.target.has_function("walkfs"):
30
- raise UnsupportedPluginError("No walkfs plugin found")
29
+ pass
31
30
 
32
31
  @arg("--rule-files", "-r", type=Path, nargs="+", required=True, help="path to YARA rule file")
33
32
  @arg("--scan-path", default="/", help="path to recursively scan")
@@ -43,20 +42,22 @@ class YaraPlugin(Plugin):
43
42
  rule_data = "\n".join([rule_file.read_text() for rule_file in rule_files])
44
43
 
45
44
  rules = yara.compile(source=rule_data)
46
- for entry, _ in self.target.walkfs_ext(scan_path):
47
- try:
48
- if not entry.is_file() or entry.stat().st_size > max_size:
45
+ for _, _, files in self.target.fs.walk_ext(scan_path):
46
+ for file_entry in files:
47
+ path = self.target.fs.path(file_entry.path)
48
+ try:
49
+ if path.stat().st_size > max_size:
50
+ continue
51
+
52
+ for match in rules.match(data=path.read_bytes()):
53
+ yield YaraMatchRecord(
54
+ path=path,
55
+ digest=path.get().hash(),
56
+ rule=match.rule,
57
+ tags=match.tags,
58
+ _target=self.target,
59
+ )
60
+ except FileNotFoundError:
49
61
  continue
50
-
51
- for match in rules.match(data=entry.read_bytes()):
52
- yield YaraMatchRecord(
53
- path=entry,
54
- digest=entry.get().hash(),
55
- rule=match.rule,
56
- tags=match.tags,
57
- _target=self.target,
58
- )
59
- except FileNotFoundError:
60
- continue
61
- except Exception:
62
- self.target.log.exception("Error scanning file: %s", entry)
62
+ except Exception:
63
+ self.target.log.exception("Error scanning file: %s", path)
@@ -1,7 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from functools import lru_cache
4
- from typing import Optional, Union
4
+ from typing import Iterable, Optional, Union
5
5
 
6
6
  from dissect.target import Target
7
7
  from dissect.target.exceptions import UnsupportedPluginError
@@ -25,7 +25,7 @@ class ConfigurationTreePlugin(Plugin):
25
25
  if target_dir_path.is_dir():
26
26
  self.config_fs = ConfigurationFilesystem(target, dir_path)
27
27
 
28
- self.get = lru_cache(128)(self.get)
28
+ self._get = lru_cache(128)(self._get)
29
29
 
30
30
  def check_compatible(self) -> None:
31
31
  # This should be able to be retrieved, regardless of OS
@@ -37,29 +37,46 @@ class ConfigurationTreePlugin(Plugin):
37
37
  self,
38
38
  path: Optional[Union[TargetPath, str]] = None,
39
39
  hint: Optional[str] = None,
40
- collapse: Optional[Union[bool, set]] = None,
40
+ collapse: Optional[Union[bool, Iterable[str]]] = None,
41
41
  collapse_inverse: Optional[bool] = None,
42
- seperator: Optional[tuple[str]] = None,
42
+ separator: Optional[tuple[str]] = None,
43
43
  comment_prefixes: Optional[tuple[str]] = None,
44
44
  as_dict: bool = False,
45
45
  ) -> Union[ConfigurationFilesystem, ConfigurationEntry, dict]:
46
- """Create a configuration entry from a file, or a ConfigurationFilesystem from a directory.
46
+ """Create a configuration entry from a file, or a :class:`.ConfigurationFilesystem` from a directory.
47
47
 
48
48
  If a directory is specified in ``path``, the other arguments should be provided in the ``get`` call if needed.
49
49
 
50
50
  Args:
51
- path: The path to either a directory or file
52
- hint: What kind of parser it should use
53
- collapse: Wether it should collapse all or only certain keys.
54
- seperator: What seperator should be used for the parser.
51
+ path: The path to either a directory or file.
52
+ hint: What kind of parser it should use.
53
+ collapse: Whether it should collapse everything or just a certain set of keys.
54
+ collapse_inverse: Invert the collapse function to collapse everything but the keys inside ``collapse``.
55
+ separator: The separator that should be used for parsing.
55
56
  comment_prefixes: What is specified as a comment.
56
- as_dict: Returns the dictionary instead of an entry.
57
+ as_dict: Return a dictionary instead of an entry.
57
58
  """
58
- return self.get(path, as_dict, hint, collapse, collapse_inverse, seperator, comment_prefixes)
59
+ return self.get(
60
+ path=path,
61
+ as_dict=as_dict,
62
+ hint=hint,
63
+ collapse=collapse,
64
+ collapse_inverse=collapse_inverse,
65
+ separator=separator,
66
+ comment_prefixes=comment_prefixes,
67
+ )
59
68
 
60
69
  @internal
61
70
  def get(
62
71
  self, path: Optional[Union[TargetPath, str]] = None, as_dict: bool = False, *args, **kwargs
72
+ ) -> Union[ConfigurationFilesystem, ConfigurationEntry, dict]:
73
+ if collapse := kwargs.pop("collapse", None):
74
+ kwargs.update({"collapse": frozenset(collapse)})
75
+
76
+ return self._get(path, as_dict, *args, **kwargs)
77
+
78
+ def _get(
79
+ self, path: Optional[Union[TargetPath, str]] = None, as_dict: bool = False, *args, **kwargs
63
80
  ) -> Union[ConfigurationFilesystem, ConfigurationEntry, dict]:
64
81
  if not path:
65
82
  return self.config_fs