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
@@ -6,11 +6,10 @@ import uuid
|
|
6
6
|
from struct import unpack
|
7
7
|
from typing import Iterator, Optional, Union
|
8
8
|
|
9
|
-
from flow.record.fieldtypes import posix_path
|
10
|
-
|
11
9
|
from dissect.target.filesystem import Filesystem
|
12
10
|
from dissect.target.helpers.fsutil import TargetPath
|
13
11
|
from dissect.target.helpers.record import UnixUserRecord
|
12
|
+
from dissect.target.helpers.utils import parse_options_string
|
14
13
|
from dissect.target.plugin import OperatingSystem, OSPlugin, arg, export
|
15
14
|
from dissect.target.target import Target
|
16
15
|
|
@@ -61,7 +60,7 @@ class UnixPlugin(OSPlugin):
|
|
61
60
|
uid=pwent.get(2),
|
62
61
|
gid=pwent.get(3),
|
63
62
|
gecos=pwent.get(4),
|
64
|
-
home=
|
63
|
+
home=self.target.fs.path(pwent.get(5)),
|
65
64
|
shell=pwent.get(6),
|
66
65
|
source=passwd_file,
|
67
66
|
_target=self.target,
|
@@ -177,33 +176,41 @@ class UnixPlugin(OSPlugin):
|
|
177
176
|
def _add_mounts(self) -> None:
|
178
177
|
fstab = self.target.fs.path("/etc/fstab")
|
179
178
|
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
179
|
+
for dev_id, volume_name, mount_point, _, options in parse_fstab(fstab, self.target.log):
|
180
|
+
opts = parse_options_string(options)
|
181
|
+
subvol = opts.get("subvol", None)
|
182
|
+
subvolid = opts.get("subvolid", None)
|
183
|
+
for fs in self.target.filesystems:
|
184
184
|
fs_id = None
|
185
|
+
fs_subvol = None
|
186
|
+
fs_subvolid = None
|
187
|
+
fs_volume_name = fs.volume.name if fs.volume and not isinstance(fs.volume, list) else None
|
185
188
|
last_mount = None
|
186
189
|
|
187
190
|
if dev_id:
|
188
|
-
if
|
189
|
-
fs_id =
|
190
|
-
elif
|
191
|
-
fs_id =
|
192
|
-
last_mount =
|
193
|
-
elif
|
194
|
-
fs_id =
|
191
|
+
if fs.__type__ == "xfs":
|
192
|
+
fs_id = fs.xfs.uuid
|
193
|
+
elif fs.__type__ == "ext":
|
194
|
+
fs_id = fs.extfs.uuid
|
195
|
+
last_mount = fs.extfs.last_mount
|
196
|
+
elif fs.__type__ == "btrfs":
|
197
|
+
fs_id = fs.btrfs.uuid
|
198
|
+
fs_subvol = fs.subvolume.path
|
199
|
+
fs_subvolid = fs.subvolume.objectid
|
200
|
+
elif fs.__type__ == "fat":
|
201
|
+
fs_id = fs.fatfs.volume_id
|
195
202
|
# This normalizes fs_id to comply with libblkid generated UUIDs
|
196
203
|
# This is needed because FAT filesystems don't have a real UUID,
|
197
204
|
# but instead a volume_id which is not case-sensitive
|
198
205
|
fs_id = fs_id[:4].upper() + "-" + fs_id[4:].upper()
|
199
206
|
|
200
207
|
if (
|
201
|
-
(fs_id and (fs_id == dev_id))
|
202
|
-
or (
|
208
|
+
(fs_id and (fs_id == dev_id and (subvol == fs_subvol or subvolid == fs_subvolid)))
|
209
|
+
or (fs_volume_name and (fs_volume_name == volume_name))
|
203
210
|
or (last_mount and (last_mount == mount_point))
|
204
211
|
):
|
205
|
-
self.target.log.debug("Mounting %s at %s", volume, mount_point)
|
206
|
-
self.target.fs.mount(mount_point,
|
212
|
+
self.target.log.debug("Mounting %s (%s) at %s", fs, fs.volume, mount_point)
|
213
|
+
self.target.fs.mount(mount_point, fs)
|
207
214
|
|
208
215
|
def _parse_os_release(self, glob: Optional[str] = None) -> dict[str, str]:
|
209
216
|
"""Parse files containing Unix version information.
|
@@ -283,7 +290,7 @@ class UnixPlugin(OSPlugin):
|
|
283
290
|
def parse_fstab(
|
284
291
|
fstab: TargetPath,
|
285
292
|
log: logging.Logger = log,
|
286
|
-
) -> Iterator[tuple[Union[uuid.UUID, str], str, str, str]]:
|
293
|
+
) -> Iterator[tuple[Union[uuid.UUID, str], str, str, str, str]]:
|
287
294
|
"""Parse fstab file and return a generator that streams the details of entries,
|
288
295
|
with unsupported FS types and block devices filtered away.
|
289
296
|
"""
|
@@ -310,7 +317,7 @@ def parse_fstab(
|
|
310
317
|
if len(entry_parts) != 6:
|
311
318
|
continue
|
312
319
|
|
313
|
-
dev, mount_point, fs_type,
|
320
|
+
dev, mount_point, fs_type, options, _, _ = entry_parts
|
314
321
|
|
315
322
|
if fs_type in SKIP_FS_TYPES:
|
316
323
|
log.warning("Skipped FS type: %s, %s, %s", fs_type, dev, mount_point)
|
@@ -341,4 +348,4 @@ def parse_fstab(
|
|
341
348
|
except ValueError:
|
342
349
|
pass
|
343
350
|
|
344
|
-
yield dev_id, volume_name, fs_type,
|
351
|
+
yield dev_id, volume_name, mount_point, fs_type, options
|
@@ -1,8 +1,6 @@
|
|
1
1
|
import plistlib
|
2
2
|
from typing import Iterator
|
3
3
|
|
4
|
-
from flow.record.fieldtypes import posix_path
|
5
|
-
|
6
4
|
from dissect.target.exceptions import UnsupportedPluginError
|
7
5
|
from dissect.target.helpers.descriptor_extensions import UserRecordDescriptorExtension
|
8
6
|
from dissect.target.helpers.record import create_extended_descriptor
|
@@ -49,7 +47,7 @@ class UserPlugin(Plugin):
|
|
49
47
|
password_last_time=account_policy.get("passwordLastSetTime"),
|
50
48
|
failed_login_count=account_policy.get("failedLoginCount"),
|
51
49
|
failed_login_time=account_policy.get("failedLoginTimestamp"),
|
52
|
-
source=
|
50
|
+
source=self.target.fs.path(user_details.user.source),
|
53
51
|
_user=user_details.user,
|
54
52
|
_target=self.target,
|
55
53
|
)
|
@@ -4,7 +4,7 @@ from dissect.target.helpers.record import TargetRecordDescriptor
|
|
4
4
|
from dissect.target.plugin import Plugin, export
|
5
5
|
|
6
6
|
CronjobRecord = TargetRecordDescriptor(
|
7
|
-
"
|
7
|
+
"unix/cronjob",
|
8
8
|
[
|
9
9
|
("string", "minute"),
|
10
10
|
("string", "hour"),
|
@@ -12,13 +12,13 @@ CronjobRecord = TargetRecordDescriptor(
|
|
12
12
|
("string", "month"),
|
13
13
|
("string", "weekday"),
|
14
14
|
("string", "user"),
|
15
|
-
("
|
15
|
+
("string", "command"),
|
16
16
|
("path", "source"),
|
17
17
|
],
|
18
18
|
)
|
19
19
|
|
20
20
|
EnvironmentVariableRecord = TargetRecordDescriptor(
|
21
|
-
"
|
21
|
+
"unix/environmentvariable",
|
22
22
|
[
|
23
23
|
("string", "key"),
|
24
24
|
("string", "value"),
|
@@ -31,19 +31,6 @@ class CronjobPlugin(Plugin):
|
|
31
31
|
def check_compatible(self) -> None:
|
32
32
|
pass
|
33
33
|
|
34
|
-
def get_record(self, minute, hour, day, month, weekday, usr, cmd, path):
|
35
|
-
return CronjobRecord(
|
36
|
-
minute=minute,
|
37
|
-
hour=hour,
|
38
|
-
day=day,
|
39
|
-
month=month,
|
40
|
-
weekday=weekday,
|
41
|
-
user=usr,
|
42
|
-
command=cmd,
|
43
|
-
source=self.resolver.resolve(path),
|
44
|
-
_target=self.target,
|
45
|
-
)
|
46
|
-
|
47
34
|
def parse_crontab(self, file_path):
|
48
35
|
for line in file_path.open("rt"):
|
49
36
|
line = line.strip()
|
@@ -93,6 +80,7 @@ class CronjobPlugin(Plugin):
|
|
93
80
|
"/var/spool/cron",
|
94
81
|
"/var/spool/cron/crontabs",
|
95
82
|
"/etc/cron.d",
|
83
|
+
"/usr/local/etc/cron.d", # FreeBSD
|
96
84
|
]
|
97
85
|
for path in crontab_dirs:
|
98
86
|
fspath = self.target.fs.path(path)
|
@@ -12,7 +12,6 @@ from typing import Any, BinaryIO, Iterator, Optional, TextIO
|
|
12
12
|
from defusedxml import ElementTree
|
13
13
|
from dissect.hypervisor.util import vmtar
|
14
14
|
from dissect.sql import sqlite3
|
15
|
-
from flow.record.fieldtypes import path
|
16
15
|
|
17
16
|
try:
|
18
17
|
from dissect.hypervisor.util.envelope import Envelope, KeyStore
|
@@ -25,7 +24,7 @@ from dissect.target.filesystem import Filesystem, VirtualFilesystem
|
|
25
24
|
from dissect.target.filesystems import tar
|
26
25
|
from dissect.target.helpers.record import TargetRecordDescriptor
|
27
26
|
from dissect.target.plugin import OperatingSystem, arg, export, internal
|
28
|
-
from dissect.target.plugins.os.unix.
|
27
|
+
from dissect.target.plugins.os.unix._os import UnixPlugin
|
29
28
|
from dissect.target.target import Target
|
30
29
|
|
31
30
|
VirtualMachineRecord = TargetRecordDescriptor(
|
@@ -36,7 +35,7 @@ VirtualMachineRecord = TargetRecordDescriptor(
|
|
36
35
|
)
|
37
36
|
|
38
37
|
|
39
|
-
class ESXiPlugin(
|
38
|
+
class ESXiPlugin(UnixPlugin):
|
40
39
|
"""ESXi OS plugin
|
41
40
|
|
42
41
|
ESXi partitioning varies between versions. Generally, specific partition numbers have special meaning.
|
@@ -159,7 +158,7 @@ class ESXiPlugin(LinuxPlugin):
|
|
159
158
|
root = ElementTree.fromstring(inv_file.read_text("utf-8"))
|
160
159
|
for entry in root.iter("ConfigEntry"):
|
161
160
|
yield VirtualMachineRecord(
|
162
|
-
path=path
|
161
|
+
path=self.target.fs.path(entry.findtext("vmxCfgPath")),
|
163
162
|
_target=self.target,
|
164
163
|
)
|
165
164
|
|
@@ -241,7 +240,7 @@ def _mount_filesystems(target: Target, sysvol: Filesystem, cfg: dict[str, str]):
|
|
241
240
|
osdata_fs = None
|
242
241
|
locker_fs = None
|
243
242
|
for fs in target.filesystems:
|
244
|
-
if fs.
|
243
|
+
if fs.__type__ == "fat":
|
245
244
|
fs.volume.seek(512)
|
246
245
|
magic, uuid1, uuid2, uuid3, uuid4 = struct.unpack("<16sIIH6s", fs.volume.read(32))
|
247
246
|
if magic != b"VMWARE FAT16 ":
|
@@ -273,7 +272,7 @@ def _mount_filesystems(target: Target, sysvol: Filesystem, cfg: dict[str, str]):
|
|
273
272
|
target.fs.symlink(f"/vmfs/volumes/{fs_uuid}", "/store")
|
274
273
|
target.fs.symlink("/store", "/locker")
|
275
274
|
|
276
|
-
elif fs.
|
275
|
+
elif fs.__type__ == "vmfs":
|
277
276
|
target.fs.mount(f"/vmfs/volumes/{fs.vmfs.uuid}", fs)
|
278
277
|
target.fs.symlink(f"/vmfs/volumes/{fs.vmfs.uuid}", f"/vmfs/volumes/{fs.vmfs.label}")
|
279
278
|
|
@@ -20,6 +20,8 @@ class GenericPlugin(Plugin):
|
|
20
20
|
|
21
21
|
last_seen = 0
|
22
22
|
for f in var_log.iterdir():
|
23
|
+
if not f.exists():
|
24
|
+
continue
|
23
25
|
if f.stat().st_mtime > last_seen:
|
24
26
|
last_seen = f.stat().st_mtime
|
25
27
|
|
@@ -30,16 +32,18 @@ class GenericPlugin(Plugin):
|
|
30
32
|
def install_date(self) -> Optional[datetime]:
|
31
33
|
"""Return the likely install date of the operating system."""
|
32
34
|
|
35
|
+
# Although this purports to be a generic function for Unix targets,
|
36
|
+
# these paths are Linux specific.
|
33
37
|
files = [
|
34
38
|
# Debian
|
35
39
|
"/var/log/installer/install-journal.txt",
|
36
40
|
"/var/log/installer/syslog",
|
41
|
+
"/var/lib/dpkg/arch",
|
37
42
|
# RedHat
|
38
43
|
"/root/anaconda-ks.cfg",
|
39
44
|
# Generic
|
40
45
|
"/etc/hostname",
|
41
46
|
"/etc/machine-id",
|
42
|
-
"/var/lib/dpkg/arch",
|
43
47
|
]
|
44
48
|
dates = []
|
45
49
|
|
@@ -11,7 +11,7 @@ from dissect.target.helpers.record import UnixUserRecord, create_extended_descri
|
|
11
11
|
from dissect.target.plugin import Plugin, export, internal
|
12
12
|
|
13
13
|
CommandHistoryRecord = create_extended_descriptor([UserRecordDescriptorExtension])(
|
14
|
-
"
|
14
|
+
"unix/history",
|
15
15
|
[
|
16
16
|
("datetime", "ts"),
|
17
17
|
("string", "command"),
|
@@ -35,6 +35,7 @@ class CommandHistoryPlugin(Plugin):
|
|
35
35
|
("python", ".python_history"),
|
36
36
|
("sqlite", ".sqlite_history"),
|
37
37
|
("zsh", ".zsh_history"),
|
38
|
+
("ash", ".ash_history"),
|
38
39
|
)
|
39
40
|
|
40
41
|
def __init__(self, target: Target):
|
@@ -69,13 +69,20 @@ class LinuxPlugin(UnixPlugin, LinuxNetworkManager):
|
|
69
69
|
|
70
70
|
@export(property=True)
|
71
71
|
def version(self) -> str:
|
72
|
-
|
72
|
+
distrib_description = self._os_release.get("DISTRIB_DESCRIPTION", "")
|
73
|
+
name = self._os_release.get("NAME", "") or self._os_release.get("DISTRIB_ID", "")
|
74
|
+
version = (
|
75
|
+
self._os_release.get("VERSION", "")
|
76
|
+
or self._os_release.get("VERSION_ID", "")
|
77
|
+
or self._os_release.get("DISTRIB_RELEASE", "")
|
78
|
+
)
|
79
|
+
|
80
|
+
if len(f"{name} {version}") > len(distrib_description):
|
81
|
+
return f"{name} {version}"
|
82
|
+
|
83
|
+
else:
|
73
84
|
return distrib_description
|
74
85
|
|
75
|
-
name = self._os_release.get("NAME") or self._os_release.get("DISTRIB_ID")
|
76
|
-
version = self._os_release.get("VERSION") or self._os_release.get("DISTRIB_RELEASE")
|
77
|
-
return f"{name} {version}"
|
78
|
-
|
79
86
|
@export(property=True)
|
80
87
|
def os(self) -> str:
|
81
88
|
return OperatingSystem.LINUX.value
|
@@ -0,0 +1,112 @@
|
|
1
|
+
from itertools import chain
|
2
|
+
from typing import Iterator
|
3
|
+
|
4
|
+
from dissect.target.exceptions import FileNotFoundError, UnsupportedPluginError
|
5
|
+
from dissect.target.helpers.record import TargetRecordDescriptor
|
6
|
+
from dissect.target.plugin import Plugin, export, internal
|
7
|
+
|
8
|
+
RECORD_NAME = "linux/service"
|
9
|
+
|
10
|
+
DEFAULT_ELEMENTS = [
|
11
|
+
("datetime", "ts"),
|
12
|
+
("string", "type"),
|
13
|
+
("string", "name"),
|
14
|
+
("path", "source"),
|
15
|
+
]
|
16
|
+
|
17
|
+
LinuxServiceRecord = TargetRecordDescriptor(RECORD_NAME, DEFAULT_ELEMENTS)
|
18
|
+
|
19
|
+
|
20
|
+
class ServicesPlugin(Plugin):
|
21
|
+
SYSTEMD_PATHS = [
|
22
|
+
"/etc/systemd/system",
|
23
|
+
"/lib/systemd/system",
|
24
|
+
"/usr/lib/systemd/system",
|
25
|
+
]
|
26
|
+
|
27
|
+
INITD_PATHS = ["/etc/rc.d/init.d", "/etc/init.d"]
|
28
|
+
|
29
|
+
def check_compatible(self) -> None:
|
30
|
+
if not any([self.target.fs.path(p).exists() for p in self.SYSTEMD_PATHS + self.INITD_PATHS]):
|
31
|
+
raise UnsupportedPluginError("No supported service directories found")
|
32
|
+
|
33
|
+
@export(record=LinuxServiceRecord)
|
34
|
+
def services(self) -> Iterator[LinuxServiceRecord]:
|
35
|
+
"""Return information about all installed systemd and init.d services.
|
36
|
+
|
37
|
+
References:
|
38
|
+
- https://geeksforgeeks.org/what-is-init-d-in-linux-service-management
|
39
|
+
- http://0pointer.de/blog/projects/systemd-for-admins-3.html
|
40
|
+
- https://www.freedesktop.org/software/systemd/man/systemd.syntax.html
|
41
|
+
"""
|
42
|
+
|
43
|
+
return chain(self.systemd(), self.initd())
|
44
|
+
|
45
|
+
@internal
|
46
|
+
def systemd(self) -> Iterator[LinuxServiceRecord]:
|
47
|
+
ignored_suffixes = [".wants", ".requires", ".d"]
|
48
|
+
|
49
|
+
for systemd_path in self.SYSTEMD_PATHS:
|
50
|
+
path = self.target.fs.path(systemd_path)
|
51
|
+
if not path.exists() or not path.is_dir():
|
52
|
+
continue
|
53
|
+
|
54
|
+
for service_file in path.iterdir():
|
55
|
+
if should_ignore_file(service_file.name, ignored_suffixes):
|
56
|
+
continue
|
57
|
+
|
58
|
+
try:
|
59
|
+
parsed_file = self.target.config_tree(service_file, as_dict=True)
|
60
|
+
config = {}
|
61
|
+
types = []
|
62
|
+
for segment, configuration in parsed_file.items():
|
63
|
+
for key, value in configuration.items():
|
64
|
+
_value = value or None
|
65
|
+
_key = f"{segment}_{key}"
|
66
|
+
types.append(("string", _key))
|
67
|
+
config.update({_key: _value})
|
68
|
+
except FileNotFoundError:
|
69
|
+
# The service is registered but the symlink is broken.
|
70
|
+
yield LinuxServiceRecord(
|
71
|
+
ts=service_file.stat(follow_symlinks=False).st_mtime,
|
72
|
+
type="systemd",
|
73
|
+
name=service_file.name,
|
74
|
+
source=service_file,
|
75
|
+
_target=self.target,
|
76
|
+
)
|
77
|
+
continue
|
78
|
+
|
79
|
+
record = TargetRecordDescriptor(RECORD_NAME, DEFAULT_ELEMENTS + types)
|
80
|
+
yield record(
|
81
|
+
ts=service_file.stat().st_mtime,
|
82
|
+
type="systemd",
|
83
|
+
name=service_file.name,
|
84
|
+
source=service_file,
|
85
|
+
**config,
|
86
|
+
_target=self.target,
|
87
|
+
)
|
88
|
+
|
89
|
+
@internal
|
90
|
+
def initd(self) -> Iterator[LinuxServiceRecord]:
|
91
|
+
ignored_suffixes = ["README"]
|
92
|
+
|
93
|
+
for initd_path in self.INITD_PATHS:
|
94
|
+
path = self.target.fs.path(initd_path)
|
95
|
+
|
96
|
+
if not path.exists():
|
97
|
+
continue
|
98
|
+
for file_ in path.iterdir():
|
99
|
+
if should_ignore_file(file_.name, ignored_suffixes):
|
100
|
+
continue
|
101
|
+
|
102
|
+
yield LinuxServiceRecord(
|
103
|
+
ts=file_.stat().st_mtime,
|
104
|
+
type="initd",
|
105
|
+
name=file_.name,
|
106
|
+
source=file_,
|
107
|
+
_target=self.target,
|
108
|
+
)
|
109
|
+
|
110
|
+
|
111
|
+
def should_ignore_file(needle: str, haystack: list) -> bool:
|
112
|
+
return needle.endswith(tuple(haystack))
|
@@ -28,17 +28,17 @@ class ZypperPlugin(plugin.Plugin):
|
|
28
28
|
Example log format::
|
29
29
|
|
30
30
|
2022-12-16 12:56:23|command|root@ec9fa6d67dda|'zypper' 'install' 'unzip'|
|
31
|
-
2022-12-16 12:56:23|install|update-alternatives|1.21.8-1.4|x86_64||repo-oss|b4d6389437e306d6104559c82d09fce15c4486fbc7fd215cc33d265ff729aaf1|
|
31
|
+
2022-12-16 12:56:23|install|update-alternatives|1.21.8-1.4|x86_64||repo-oss|b4d6389437e306d6104559c82d09fce15c4486fbc7fd215cc33d265ff729aaf1|
|
32
32
|
# 2022-12-16 12:56:23 unzip-6.00-41.1.x86_64.rpm installed ok
|
33
33
|
# Additional rpm output:
|
34
34
|
# update-alternatives: using /usr/bin/unzip-plain to provide /usr/bin/unzip (unzip) in auto mode
|
35
35
|
#
|
36
|
-
2022-12-16 12:56:23|install|unzip|6.00-41.1|x86_64|root@ec9fa6d67dda|repo-oss|d7e42c9d83f97cf3b7eceb4d3fa64e445a33a7a33f387366734c444d5571cb3a|
|
36
|
+
2022-12-16 12:56:23|install|unzip|6.00-41.1|x86_64|root@ec9fa6d67dda|repo-oss|d7e42c9d83f97cf3b7eceb4d3fa64e445a33a7a33f387366734c444d5571cb3a|
|
37
37
|
2022-12-16 12:57:50|command|root@ec9fa6d67dda|'zypper' 'remove' 'unzip'|
|
38
38
|
# 2022-12-16 12:57:50 unzip-6.00-41.1.x86_64 removed ok
|
39
39
|
# Additional rpm output:
|
40
|
-
# update-alternatives: warning: alternative /usr/bin/unzipsfx-plain (part of link group unzipsfx) doesn't exist; removing from list of alternatives
|
41
|
-
# update-alternatives: warning: alternative /usr/bin/zipgrep-plain (part of link group zipgrep) doesn't exist; removing from list of alternatives
|
40
|
+
# update-alternatives: warning: alternative /usr/bin/unzipsfx-plain (part of link group unzipsfx) doesn't exist; removing from list of alternatives
|
41
|
+
# update-alternatives: warning: alternative /usr/bin/zipgrep-plain (part of link group zipgrep) doesn't exist; removing from list of alternatives
|
42
42
|
#
|
43
43
|
2022-12-16 12:57:50|remove |unzip|6.00-41.1|x86_64|root@ec9fa6d67dda|
|
44
44
|
2022-12-16 12:58:49|command|root@ec9fa6d67dda|'zypper' 'install' 'unzip'|
|
@@ -5,7 +5,7 @@ from dissect.target.helpers.record import TargetRecordDescriptor
|
|
5
5
|
from dissect.target.plugin import Plugin, export
|
6
6
|
|
7
7
|
UnixKeyboardRecord = TargetRecordDescriptor(
|
8
|
-
"
|
8
|
+
"unix/keyboard",
|
9
9
|
[
|
10
10
|
("string", "layout"),
|
11
11
|
("string", "model"),
|
@@ -64,6 +64,8 @@ class LocalePlugin(Plugin):
|
|
64
64
|
@export(property=True)
|
65
65
|
def language(self):
|
66
66
|
"""Get the configured locale(s) of the system."""
|
67
|
+
# Although this purports to be a generic function for Unix targets,
|
68
|
+
# these paths are Linux specific.
|
67
69
|
locale_paths = ["/etc/default/locale", "/etc/locale.conf"]
|
68
70
|
|
69
71
|
found_languages = []
|
@@ -5,7 +5,6 @@ import zstandard
|
|
5
5
|
from dissect.cstruct import Instance, cstruct
|
6
6
|
from dissect.util import ts
|
7
7
|
from dissect.util.compression import lz4
|
8
|
-
from flow.record.fieldtypes import path
|
9
8
|
|
10
9
|
from dissect.target import Target
|
11
10
|
from dissect.target.exceptions import UnsupportedPluginError
|
@@ -394,6 +393,8 @@ class JournalPlugin(Plugin):
|
|
394
393
|
- https://github.com/systemd/systemd/blob/9203abf79f1d05fdef9b039e7addf9fc5a27752d/man/systemd.journal-fields.xml
|
395
394
|
""" # noqa: E501
|
396
395
|
|
396
|
+
path_function = self.target.fs.path
|
397
|
+
|
397
398
|
for _path in self.journal_paths:
|
398
399
|
fh = _path.open()
|
399
400
|
|
@@ -409,7 +410,7 @@ class JournalPlugin(Plugin):
|
|
409
410
|
message=entry.get("message"),
|
410
411
|
message_id=entry.get("message_id"),
|
411
412
|
priority=get_optional(entry.get("priority"), int),
|
412
|
-
code_file=get_optional(entry.get("code_file"),
|
413
|
+
code_file=get_optional(entry.get("code_file"), path_function),
|
413
414
|
code_line=get_optional(entry.get("code_line"), int),
|
414
415
|
code_func=entry.get("code_func"),
|
415
416
|
errno=get_optional(entry.get("errno"), int),
|
@@ -427,12 +428,12 @@ class JournalPlugin(Plugin):
|
|
427
428
|
uid=get_optional(entry.get("uid"), int),
|
428
429
|
gid=get_optional(entry.get("gid"), int),
|
429
430
|
comm=entry.get("comm"),
|
430
|
-
exe=get_optional(entry.get("exe"),
|
431
|
+
exe=get_optional(entry.get("exe"), path_function),
|
431
432
|
cmdline=entry.get("cmdline"),
|
432
433
|
cap_effective=entry.get("cap_effective"),
|
433
434
|
audit_session=get_optional(entry.get("audit_session"), int),
|
434
435
|
audit_loginuid=get_optional(entry.get("audit_loginuid"), int),
|
435
|
-
systemd_cgroup=get_optional(entry.get("systemd_cgroup"),
|
436
|
+
systemd_cgroup=get_optional(entry.get("systemd_cgroup"), path_function),
|
436
437
|
systemd_slice=entry.get("systemd_slice"),
|
437
438
|
systemd_unit=entry.get("systemd_unit"),
|
438
439
|
systemd_user_unit=entry.get("systemd_user_unit"),
|
@@ -451,8 +452,8 @@ class JournalPlugin(Plugin):
|
|
451
452
|
kernel_device=entry.get("kernel_device"),
|
452
453
|
kernel_subsystem=entry.get("kernel_subsystem"),
|
453
454
|
udev_sysname=entry.get("udev_sysname"),
|
454
|
-
udev_devnode=get_optional(entry.get("udev_devnode"),
|
455
|
-
udev_devlink=get_optional(entry.get("udev_devlink"),
|
455
|
+
udev_devnode=get_optional(entry.get("udev_devnode"), path_function),
|
456
|
+
udev_devlink=get_optional(entry.get("udev_devlink"), path_function),
|
456
457
|
journal_hostname=entry.get("hostname"),
|
457
458
|
filepath=_path,
|
458
459
|
_target=self.target,
|
@@ -9,7 +9,7 @@ from dissect.target.helpers.record import TargetRecordDescriptor
|
|
9
9
|
from dissect.target.plugin import Plugin, export
|
10
10
|
|
11
11
|
PackageManagerLogRecord = TargetRecordDescriptor(
|
12
|
-
"
|
12
|
+
"unix/log/packagemanager",
|
13
13
|
[
|
14
14
|
("datetime", "ts"),
|
15
15
|
("string", "package_manager"),
|
@@ -61,7 +61,7 @@ class PackageManagerPlugin(Plugin):
|
|
61
61
|
for entry in self.TOOLS:
|
62
62
|
try:
|
63
63
|
self._plugins.append(getattr(self.target, entry))
|
64
|
-
except Exception:
|
64
|
+
except Exception:
|
65
65
|
target.log.exception(f"Failed to load tool plugin: {entry}")
|
66
66
|
|
67
67
|
def check_compatible(self) -> None:
|
@@ -77,5 +77,5 @@ class PackageManagerPlugin(Plugin):
|
|
77
77
|
|
78
78
|
@export(record=PackageManagerLogRecord)
|
79
79
|
def logs(self) -> Iterator[PackageManagerLogRecord]:
|
80
|
-
"""Returns logs from
|
80
|
+
"""Returns logs from all available Unix package managers."""
|
81
81
|
yield from self._func("logs")
|
@@ -77,7 +77,8 @@ class WindowsPlugin(OSPlugin):
|
|
77
77
|
self.target.fs.mount(drive, volume.fs)
|
78
78
|
break
|
79
79
|
except Exception as e:
|
80
|
-
self.target.log.warning("Failed to map drive letters"
|
80
|
+
self.target.log.warning("Failed to map drive letters")
|
81
|
+
self.target.log.debug("", exc_info=e)
|
81
82
|
|
82
83
|
@export(property=True)
|
83
84
|
def hostname(self) -> Optional[str]:
|
@@ -1,7 +1,6 @@
|
|
1
1
|
from datetime import datetime, timezone
|
2
2
|
|
3
3
|
from dissect.util.ts import wintimestamp
|
4
|
-
from flow.record.fieldtypes import path
|
5
4
|
|
6
5
|
from dissect.target.exceptions import RegistryKeyNotFoundError, UnsupportedPluginError
|
7
6
|
from dissect.target.helpers import regutil
|
@@ -220,7 +219,7 @@ class AmcachePluginOldMixin:
|
|
220
219
|
created_timestamp=parse_win_timestamp(subkey_data.get("created_timestamp")),
|
221
220
|
mtime_regf=subkey.timestamp,
|
222
221
|
reference=int(subkey.name, 16),
|
223
|
-
path=path
|
222
|
+
path=self.target.fs.path(subkey_data["full_path"]) if subkey_data.get("full_path") else None,
|
224
223
|
language_code=subkey_data.get("language_code"),
|
225
224
|
digests=[None, subkey_data["sha1"][-40:] if subkey_data.get("sha1") else None, None],
|
226
225
|
program_id=subkey_data.get("program_id"),
|
@@ -265,7 +264,7 @@ class AmcachePluginOldMixin:
|
|
265
264
|
language_code=entry_data.get("LanguageCode"),
|
266
265
|
entry_type=entry_data.get("EntryType"),
|
267
266
|
uninstall_key=entry_data.get("UninstallKey"),
|
268
|
-
path=path
|
267
|
+
path=self.target.fs.path(file_path_entry),
|
269
268
|
product_code=entry_data.get("ProductCode"),
|
270
269
|
package_code=entry_data.get("PackageCode"),
|
271
270
|
msi_package_code=entry_data.get("MsiPackageCode"),
|
@@ -284,7 +283,7 @@ class AmcachePluginOldMixin:
|
|
284
283
|
language_code=entry_data.get("LanguageCode"),
|
285
284
|
entry_type=entry_data.get("EntryType"),
|
286
285
|
uninstall_key=entry_data.get("UninstallKey"),
|
287
|
-
path=path
|
286
|
+
path=self.target.fs.path(file_entry),
|
288
287
|
product_code=entry_data.get("ProductCode"),
|
289
288
|
package_code=entry_data.get("PackageCode"),
|
290
289
|
msi_package_code=entry_data.get("MsiPackageCode"),
|
@@ -416,7 +415,7 @@ class AmcachePlugin(AmcachePluginOldMixin, Plugin):
|
|
416
415
|
program_instance_id=entry_data.get("ProgramInstanceId"),
|
417
416
|
publisher=entry_data.get("Publisher"),
|
418
417
|
registry_key_path=entry_data.get("RegistryKeyPath"),
|
419
|
-
root_dir_path=path
|
418
|
+
root_dir_path=self.target.fs.path(entry_data.get("RootDirPath")),
|
420
419
|
source=entry_data.get("Source"),
|
421
420
|
uninstall_string=entry_data.get("UninstallString"),
|
422
421
|
type=entry_data.get("Type"),
|
@@ -467,7 +466,7 @@ class AmcachePlugin(AmcachePluginOldMixin, Plugin):
|
|
467
466
|
mtime_regf=entry.timestamp,
|
468
467
|
program_id=entry_data.get("ProgramId"),
|
469
468
|
digests=[None, sha1_digest, None],
|
470
|
-
path=path
|
469
|
+
path=self.target.fs.path(entry_data.get("LowerCaseLongPath")),
|
471
470
|
link_date=parse_win_datetime(entry_data.get("LinkDate")),
|
472
471
|
hash_path=entry_data.get("LongPathHash"),
|
473
472
|
name=entry_data.get("Name"),
|
@@ -492,8 +491,8 @@ class AmcachePlugin(AmcachePluginOldMixin, Plugin):
|
|
492
491
|
|
493
492
|
yield BinaryAppcompatRecord(
|
494
493
|
mtime_regf=entry.timestamp,
|
495
|
-
driver_name=path
|
496
|
-
inf=path
|
494
|
+
driver_name=self.target.fs.path(entry_data.get("DriverName")),
|
495
|
+
inf=self.target.fs.path(entry_data.get("Inf")),
|
497
496
|
driver_version=entry_data.get("DriverVersion"),
|
498
497
|
product=entry_data.get("Product"),
|
499
498
|
product_version=entry_data.get("ProductVersion"),
|
@@ -515,7 +514,7 @@ class AmcachePlugin(AmcachePluginOldMixin, Plugin):
|
|
515
514
|
for entry in self.read_key_subkeys(key):
|
516
515
|
yield ShortcutAppcompatRecord(
|
517
516
|
mtime_regf=entry.timestamp,
|
518
|
-
path=path
|
517
|
+
path=self.target.fs.path(entry.value("ShortCutPath").value),
|
519
518
|
_target=self.target,
|
520
519
|
)
|
521
520
|
|
@@ -637,7 +636,7 @@ class AmcachePlugin(AmcachePluginOldMixin, Plugin):
|
|
637
636
|
parts = line.rstrip().split("|")
|
638
637
|
yield AppLaunchAppcompatRecord(
|
639
638
|
ts=datetime.strptime(parts[-1], "%Y-%m-%d %H:%M:%S.%f").replace(tzinfo=timezone.utc),
|
640
|
-
path=path
|
639
|
+
path=self.target.fs.path(parts[0]),
|
641
640
|
_target=self.target,
|
642
641
|
)
|
643
642
|
|
@@ -1,5 +1,5 @@
|
|
1
1
|
from asn1crypto import algos, core
|
2
|
-
from flow.record.fieldtypes import digest
|
2
|
+
from flow.record.fieldtypes import digest
|
3
3
|
|
4
4
|
from dissect.target.exceptions import UnsupportedPluginError
|
5
5
|
from dissect.target.helpers.record import TargetRecordDescriptor
|
@@ -118,7 +118,7 @@ class CatrootPlugin(Plugin):
|
|
118
118
|
|
119
119
|
yield CatrootRecord(
|
120
120
|
digest=fdigest,
|
121
|
-
hint=path
|
121
|
+
hint=self.target.fs.path(filehint) if filehint else None,
|
122
122
|
source=f,
|
123
123
|
_target=self.target,
|
124
124
|
)
|