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.
- dissect/target/container.py +9 -1
- dissect/target/containers/asdf.py +2 -0
- dissect/target/containers/ewf.py +2 -0
- dissect/target/containers/hdd.py +2 -0
- dissect/target/containers/hds.py +2 -0
- dissect/target/containers/qcow2.py +2 -0
- dissect/target/containers/raw.py +2 -0
- dissect/target/containers/split.py +2 -0
- dissect/target/containers/vdi.py +2 -0
- dissect/target/containers/vhd.py +2 -0
- dissect/target/containers/vhdx.py +2 -0
- dissect/target/containers/vmdk.py +2 -0
- dissect/target/filesystem.py +108 -15
- dissect/target/filesystems/ad1.py +1 -1
- dissect/target/filesystems/btrfs.py +180 -0
- dissect/target/filesystems/cb.py +4 -4
- dissect/target/filesystems/config.py +161 -31
- dissect/target/filesystems/dir.py +1 -1
- dissect/target/filesystems/exfat.py +1 -1
- dissect/target/filesystems/extfs.py +5 -1
- dissect/target/filesystems/fat.py +1 -1
- dissect/target/filesystems/ffs.py +1 -1
- dissect/target/filesystems/itunes.py +1 -1
- dissect/target/filesystems/ntfs.py +1 -1
- dissect/target/filesystems/smb.py +1 -1
- dissect/target/filesystems/squashfs.py +1 -1
- dissect/target/filesystems/tar.py +1 -1
- dissect/target/filesystems/vmfs.py +1 -1
- dissect/target/filesystems/xfs.py +1 -1
- dissect/target/filesystems/zip.py +1 -1
- dissect/target/helpers/cache.py +2 -2
- dissect/target/helpers/configutil.py +283 -83
- dissect/target/helpers/fsutil.py +9 -6
- dissect/target/helpers/hashutil.py +20 -19
- dissect/target/helpers/utils.py +14 -3
- dissect/target/loaders/ad1.py +1 -1
- dissect/target/loaders/asdf.py +1 -1
- dissect/target/loaders/log.py +2 -2
- dissect/target/loaders/smb.py +23 -13
- dissect/target/loaders/targetd.py +12 -2
- dissect/target/loaders/vma.py +1 -1
- dissect/target/loaders/xva.py +1 -1
- dissect/target/plugin.py +14 -2
- dissect/target/plugins/apps/av/sophos.py +1 -2
- dissect/target/plugins/apps/av/symantec.py +3 -4
- dissect/target/plugins/apps/av/trendmicro.py +2 -3
- dissect/target/plugins/{browsers → apps/browser}/chrome.py +6 -3
- dissect/target/plugins/{browsers → apps/browser}/chromium.py +18 -13
- dissect/target/plugins/{browsers → apps/browser}/edge.py +6 -3
- dissect/target/plugins/{browsers → apps/browser}/firefox.py +3 -7
- dissect/target/plugins/{browsers → apps/browser}/iexplore.py +14 -4
- dissect/target/plugins/apps/remoteaccess/teamviewer.py +55 -27
- dissect/target/plugins/apps/ssh/opensshd.py +31 -30
- dissect/target/plugins/apps/{webservers → webserver}/apache.py +1 -1
- dissect/target/plugins/apps/{webservers → webserver}/caddy.py +1 -1
- dissect/target/plugins/apps/{webservers → webserver}/iis.py +1 -1
- dissect/target/plugins/apps/{webservers → webserver}/nginx.py +1 -1
- 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/icat.py +5 -5
- dissect/target/plugins/filesystem/ntfs/mft.py +2 -2
- dissect/target/plugins/filesystem/ntfs/mft_timeline.py +2 -2
- dissect/target/plugins/filesystem/ntfs/usnjrnl.py +2 -3
- dissect/target/plugins/filesystem/resolver.py +1 -1
- dissect/target/plugins/filesystem/unix/capability.py +77 -66
- dissect/target/plugins/filesystem/walkfs.py +25 -19
- dissect/target/plugins/filesystem/yara.py +20 -19
- dissect/target/plugins/general/config.py +28 -11
- dissect/target/plugins/os/unix/_os.py +28 -21
- dissect/target/plugins/os/unix/bsd/osx/user.py +1 -3
- dissect/target/plugins/os/unix/cronjobs.py +4 -16
- dissect/target/plugins/os/unix/{linux/esxi → esxi}/_os.py +5 -6
- dissect/target/plugins/os/unix/generic.py +5 -1
- dissect/target/plugins/os/unix/history.py +2 -1
- dissect/target/plugins/os/unix/linux/_os.py +12 -5
- dissect/target/plugins/os/unix/linux/services.py +112 -0
- dissect/target/plugins/os/unix/linux/suse/zypper.py +4 -4
- dissect/target/plugins/os/unix/locale.py +3 -1
- dissect/target/plugins/os/unix/log/journal.py +7 -6
- dissect/target/plugins/os/unix/packagemanager.py +3 -3
- dissect/target/plugins/os/unix/shadow.py +1 -1
- 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/cim.py +5 -4
- dissect/target/plugins/os/windows/datetime.py +4 -1
- dissect/target/plugins/os/windows/defender.py +3 -3
- dissect/target/plugins/os/windows/generic.py +10 -11
- dissect/target/plugins/os/windows/lnk.py +6 -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/mru.py +6 -2
- 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 +2 -3
- dissect/target/target.py +65 -32
- dissect/target/tools/info.py +2 -1
- dissect/target/tools/mount.py +2 -12
- dissect/target/tools/shell.py +3 -2
- dissect/target/volume.py +10 -9
- dissect/target/volumes/bde.py +1 -1
- dissect/target/volumes/ddf.py +2 -0
- dissect/target/volumes/disk.py +2 -0
- dissect/target/volumes/luks.py +1 -1
- dissect/target/volumes/lvm.py +2 -0
- dissect/target/volumes/md.py +2 -0
- dissect/target/volumes/vmfs.py +2 -0
- {dissect.target-3.13.dev26.dist-info → dissect.target-3.14.dist-info}/METADATA +2 -1
- {dissect.target-3.13.dev26.dist-info → dissect.target-3.14.dist-info}/RECORD +137 -136
- {dissect.target-3.13.dev26.dist-info → dissect.target-3.14.dist-info}/WHEEL +1 -1
- dissect/target/plugins/os/unix/services.py +0 -151
- /dissect/target/plugins/apps/{containers → browser}/__init__.py +0 -0
- /dissect/target/plugins/{browsers → apps/browser}/browser.py +0 -0
- /dissect/target/plugins/apps/{vpns → container}/__init__.py +0 -0
- /dissect/target/plugins/apps/{containers → container}/docker.py +0 -0
- /dissect/target/plugins/apps/{webservers → vpn}/__init__.py +0 -0
- /dissect/target/plugins/apps/{vpns → vpn}/openvpn.py +0 -0
- /dissect/target/plugins/apps/{vpns → vpn}/wireguard.py +0 -0
- /dissect/target/plugins/{browsers → apps/webserver}/__init__.py +0 -0
- /dissect/target/plugins/apps/{webservers/webservers.py → webserver/webserver.py} +0 -0
- /dissect/target/plugins/os/unix/{linux/esxi → esxi}/__init__.py +0 -0
- {dissect.target-3.13.dev26.dist-info → dissect.target-3.14.dist-info}/COPYRIGHT +0 -0
- {dissect.target-3.13.dev26.dist-info → dissect.target-3.14.dist-info}/LICENSE +0 -0
- {dissect.target-3.13.dev26.dist-info → dissect.target-3.14.dist-info}/entry_points.txt +0 -0
- {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.
|
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.
|
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
|
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,
|
@@ -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.
|
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.
|
75
|
+
if filesystem.__type__ == "ntfs" or open_as == "ntfs":
|
76
76
|
fh = filesystem.ntfs.mft(inum).open(ads)
|
77
|
-
elif filesystem.
|
77
|
+
elif filesystem.__type__ == "ext":
|
78
78
|
fh = filesystem.extfs.get_inode(inum).open()
|
79
|
-
elif filesystem.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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=
|
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,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 _,
|
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
|
+
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=
|
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
|
-
|
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
|
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
|
-
|
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
|
47
|
-
|
48
|
-
|
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
|
-
|
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.
|
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,
|
40
|
+
collapse: Optional[Union[bool, Iterable[str]]] = None,
|
41
41
|
collapse_inverse: Optional[bool] = None,
|
42
|
-
|
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:
|
54
|
-
|
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:
|
57
|
+
as_dict: Return a dictionary instead of an entry.
|
57
58
|
"""
|
58
|
-
return self.get(
|
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
|