dissect.target 3.19.dev57__py3-none-any.whl → 3.20__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 +1 -1
- dissect/target/exceptions.py +6 -5
- dissect/target/filesystem.py +2 -2
- dissect/target/filesystems/btrfs.py +14 -5
- dissect/target/filesystems/config.py +5 -1
- dissect/target/filesystems/extfs.py +5 -4
- dissect/target/filesystems/fat.py +22 -16
- dissect/target/filesystems/ffs.py +11 -4
- dissect/target/filesystems/jffs.py +12 -7
- dissect/target/filesystems/ntfs.py +22 -6
- dissect/target/filesystems/overlay.py +14 -4
- dissect/target/filesystems/smb.py +3 -3
- dissect/target/filesystems/squashfs.py +4 -4
- dissect/target/filesystems/vmfs.py +4 -4
- dissect/target/filesystems/xfs.py +15 -8
- dissect/target/helpers/compat/path_common.py +5 -5
- dissect/target/helpers/configutil.py +128 -32
- dissect/target/helpers/cyber.py +2 -0
- dissect/target/helpers/data/windowsZones.xml +19 -23
- dissect/target/helpers/docs.py +1 -1
- dissect/target/helpers/keychain.py +2 -0
- dissect/target/helpers/mount.py +2 -1
- dissect/target/helpers/record.py +29 -2
- dissect/target/helpers/record_modifier.py +5 -1
- dissect/target/helpers/regutil.py +56 -26
- dissect/target/loader.py +1 -1
- dissect/target/loaders/mqtt.py +104 -9
- dissect/target/loaders/proxmox.py +68 -0
- dissect/target/loaders/vma.py +1 -1
- dissect/target/loaders/xva.py +1 -1
- dissect/target/plugin.py +24 -21
- dissect/target/plugins/apps/av/mcafee.py +2 -0
- dissect/target/plugins/apps/av/sophos.py +2 -0
- dissect/target/plugins/apps/av/trendmicro.py +2 -0
- dissect/target/plugins/apps/browser/chromium.py +27 -6
- dissect/target/plugins/apps/container/docker.py +48 -32
- dissect/target/plugins/apps/editor/__init__.py +0 -0
- dissect/target/plugins/apps/editor/editor.py +23 -0
- dissect/target/plugins/apps/{texteditor → editor}/windowsnotepad.py +40 -31
- dissect/target/plugins/apps/other/__init__.py +0 -0
- dissect/target/plugins/apps/other/env.py +56 -0
- dissect/target/plugins/apps/shell/powershell.py +6 -2
- dissect/target/plugins/apps/shell/wget.py +91 -0
- dissect/target/plugins/apps/ssh/openssh.py +2 -0
- dissect/target/plugins/apps/ssh/opensshd.py +2 -0
- dissect/target/plugins/apps/virtualization/__init__.py +0 -0
- dissect/target/plugins/apps/virtualization/vmware_workstation.py +61 -0
- dissect/target/plugins/apps/vpn/wireguard.py +9 -9
- dissect/target/plugins/apps/webhosting/cpanel.py +2 -0
- dissect/target/plugins/apps/webserver/caddy.py +2 -0
- dissect/target/plugins/apps/webserver/nginx.py +2 -0
- dissect/target/plugins/child/esxi.py +3 -1
- dissect/target/plugins/child/parallels.py +68 -0
- dissect/target/plugins/child/proxmox.py +23 -0
- dissect/target/plugins/child/virtuozzo.py +12 -8
- dissect/target/plugins/child/vmware_workstation.py +23 -8
- dissect/target/plugins/filesystem/acquire_hash.py +2 -1
- dissect/target/plugins/filesystem/icat.py +15 -11
- dissect/target/plugins/filesystem/ntfs/mft.py +10 -6
- dissect/target/plugins/filesystem/ntfs/mft_timeline.py +3 -1
- dissect/target/plugins/filesystem/ntfs/usnjrnl.py +2 -0
- dissect/target/plugins/filesystem/ntfs/utils.py +3 -1
- dissect/target/plugins/filesystem/unix/suid.py +4 -1
- dissect/target/plugins/filesystem/walkfs.py +2 -0
- dissect/target/plugins/general/example.py +2 -2
- dissect/target/plugins/general/loaders.py +18 -5
- dissect/target/plugins/general/network.py +20 -5
- dissect/target/plugins/general/osinfo.py +1 -0
- dissect/target/plugins/general/plugins.py +53 -10
- dissect/target/plugins/os/unix/_os.py +70 -44
- dissect/target/plugins/os/unix/applications.py +78 -0
- dissect/target/plugins/os/unix/bsd/citrix/history.py +2 -0
- dissect/target/plugins/os/unix/bsd/osx/_os.py +4 -21
- dissect/target/plugins/os/unix/bsd/osx/network.py +92 -0
- dissect/target/plugins/os/unix/bsd/osx/user.py +4 -0
- dissect/target/plugins/os/unix/cronjobs.py +8 -4
- dissect/target/plugins/os/unix/etc/etc.py +4 -0
- dissect/target/plugins/os/unix/generic.py +2 -0
- dissect/target/plugins/os/unix/history.py +27 -25
- dissect/target/plugins/os/unix/linux/_os.py +8 -10
- dissect/target/plugins/os/unix/linux/cmdline.py +2 -0
- dissect/target/plugins/os/unix/linux/debian/apt.py +4 -1
- dissect/target/plugins/os/unix/linux/debian/dpkg.py +3 -3
- dissect/target/plugins/os/unix/linux/debian/proxmox/__init__.py +0 -0
- dissect/target/plugins/os/unix/linux/debian/proxmox/_os.py +141 -0
- dissect/target/plugins/os/unix/linux/debian/proxmox/vm.py +29 -0
- dissect/target/plugins/os/unix/linux/debian/snap.py +79 -0
- dissect/target/plugins/os/unix/linux/environ.py +2 -0
- dissect/target/plugins/os/unix/linux/fortios/_os.py +74 -63
- dissect/target/plugins/os/unix/linux/fortios/generic.py +2 -0
- dissect/target/plugins/os/unix/linux/fortios/locale.py +2 -0
- dissect/target/plugins/os/unix/linux/modules.py +2 -0
- dissect/target/plugins/os/unix/linux/netstat.py +2 -0
- dissect/target/{helpers → plugins/os/unix/linux}/network_managers.py +11 -9
- dissect/target/plugins/os/unix/linux/processes.py +2 -0
- dissect/target/plugins/os/unix/linux/redhat/yum.py +4 -1
- dissect/target/plugins/os/unix/linux/services.py +5 -3
- dissect/target/plugins/os/unix/linux/sockets.py +2 -0
- dissect/target/plugins/os/unix/linux/suse/zypper.py +4 -1
- dissect/target/plugins/os/unix/locale.py +2 -0
- dissect/target/plugins/os/unix/locate/gnulocate.py +4 -2
- dissect/target/plugins/os/unix/locate/mlocate.py +2 -0
- dissect/target/plugins/os/unix/locate/plocate.py +3 -1
- dissect/target/plugins/os/unix/log/atop.py +2 -0
- dissect/target/plugins/os/unix/log/audit.py +3 -1
- dissect/target/plugins/os/unix/log/auth.py +351 -38
- dissect/target/plugins/os/unix/log/journal.py +123 -101
- dissect/target/plugins/os/unix/log/lastlog.py +5 -3
- dissect/target/plugins/os/unix/log/messages.py +51 -27
- dissect/target/plugins/os/unix/log/utmp.py +52 -71
- dissect/target/plugins/os/unix/packagemanager.py +5 -38
- dissect/target/plugins/os/unix/shadow.py +3 -1
- dissect/target/plugins/os/unix/trash.py +132 -0
- dissect/target/plugins/os/windows/_os.py +22 -41
- dissect/target/plugins/os/windows/activitiescache.py +9 -4
- dissect/target/plugins/os/windows/adpolicy.py +2 -1
- dissect/target/plugins/os/windows/amcache.py +16 -13
- dissect/target/plugins/os/windows/defender.py +4 -3
- dissect/target/plugins/os/windows/dpapi/keyprovider/credhist.py +3 -0
- dissect/target/plugins/os/windows/dpapi/keyprovider/empty.py +3 -0
- dissect/target/plugins/os/windows/dpapi/keyprovider/keychain.py +3 -0
- dissect/target/plugins/os/windows/dpapi/keyprovider/lsa.py +3 -0
- dissect/target/plugins/os/windows/env.py +1 -2
- dissect/target/plugins/os/windows/exchange/exchange.py +6 -4
- dissect/target/plugins/os/windows/generic.py +68 -19
- dissect/target/plugins/os/windows/lnk.py +2 -0
- dissect/target/plugins/os/windows/locale.py +9 -3
- dissect/target/plugins/os/windows/log/etl.py +5 -4
- dissect/target/plugins/os/windows/log/evt.py +12 -8
- dissect/target/plugins/os/windows/log/evtx.py +9 -7
- dissect/target/plugins/os/windows/log/mssql.py +103 -0
- dissect/target/plugins/os/windows/log/pfro.py +2 -1
- dissect/target/plugins/os/windows/network.py +380 -0
- dissect/target/plugins/os/windows/notifications.py +6 -4
- dissect/target/plugins/os/windows/prefetch.py +7 -2
- dissect/target/plugins/os/windows/regf/7zip.py +9 -1
- dissect/target/plugins/os/windows/regf/applications.py +62 -0
- dissect/target/plugins/os/windows/regf/auditpol.py +2 -1
- dissect/target/plugins/os/windows/regf/bam.py +3 -1
- dissect/target/plugins/os/windows/regf/cit.py +14 -12
- dissect/target/plugins/os/windows/regf/clsid.py +6 -3
- dissect/target/plugins/os/windows/regf/firewall.py +2 -1
- dissect/target/plugins/os/windows/regf/mru.py +9 -8
- dissect/target/plugins/os/windows/regf/nethist.py +6 -3
- dissect/target/plugins/os/windows/regf/recentfilecache.py +3 -1
- dissect/target/plugins/os/windows/regf/regf.py +5 -1
- dissect/target/plugins/os/windows/regf/shellbags.py +351 -345
- dissect/target/plugins/os/windows/regf/shimcache.py +1 -1
- dissect/target/plugins/os/windows/regf/usb.py +2 -1
- dissect/target/plugins/os/windows/regf/userassist.py +2 -1
- dissect/target/plugins/os/windows/registry.py +11 -0
- dissect/target/plugins/os/windows/services.py +3 -2
- dissect/target/plugins/os/windows/startupinfo.py +7 -2
- dissect/target/plugins/os/windows/syscache.py +5 -2
- dissect/target/plugins/os/windows/tasks.py +1 -1
- dissect/target/plugins/os/windows/thumbcache.py +11 -5
- dissect/target/plugins/os/windows/ual.py +12 -9
- dissect/target/plugins/os/windows/wer.py +21 -6
- dissect/target/plugins/os/windows/wua_history.py +0 -1
- dissect/target/target.py +13 -8
- dissect/target/tools/dump/utils.py +4 -0
- dissect/target/tools/fsutils.py +1 -1
- dissect/target/tools/info.py +1 -1
- dissect/target/tools/mount.py +15 -5
- dissect/target/tools/query.py +15 -9
- dissect/target/tools/shell.py +98 -9
- dissect/target/tools/utils.py +7 -7
- dissect/target/volume.py +4 -4
- {dissect.target-3.19.dev57.dist-info → dissect.target-3.20.dist-info}/METADATA +6 -2
- {dissect.target-3.19.dev57.dist-info → dissect.target-3.20.dist-info}/RECORD +176 -160
- {dissect.target-3.19.dev57.dist-info → dissect.target-3.20.dist-info}/WHEEL +1 -1
- dissect/target/helpers/targetd.py +0 -58
- dissect/target/loaders/targetd.py +0 -223
- dissect/target/plugins/apps/texteditor/texteditor.py +0 -13
- dissect/target/plugins/os/unix/etc.py +0 -9
- /dissect/target/plugins/apps/{texteditor → database}/__init__.py +0 -0
- {dissect.target-3.19.dev57.dist-info → dissect.target-3.20.dist-info}/COPYRIGHT +0 -0
- {dissect.target-3.19.dev57.dist-info → dissect.target-3.20.dist-info}/LICENSE +0 -0
- {dissect.target-3.19.dev57.dist-info → dissect.target-3.20.dist-info}/entry_points.txt +0 -0
- {dissect.target-3.19.dev57.dist-info → dissect.target-3.20.dist-info}/top_level.txt +0 -0
@@ -1,3 +1,5 @@
|
|
1
|
+
from typing import Iterator
|
2
|
+
|
1
3
|
from dissect.target.exceptions import UnsupportedPluginError
|
2
4
|
from dissect.target.helpers.record import ChildTargetRecord
|
3
5
|
from dissect.target.plugin import ChildTargetPlugin
|
@@ -12,7 +14,7 @@ class ESXiChildTargetPlugin(ChildTargetPlugin):
|
|
12
14
|
if self.target.os != "esxi":
|
13
15
|
raise UnsupportedPluginError("Not an ESXi operating system")
|
14
16
|
|
15
|
-
def list_children(self):
|
17
|
+
def list_children(self) -> Iterator[ChildTargetRecord]:
|
16
18
|
for vm in self.target.vm_inventory():
|
17
19
|
yield ChildTargetRecord(
|
18
20
|
type=self.__type__,
|
@@ -0,0 +1,68 @@
|
|
1
|
+
from pathlib import Path
|
2
|
+
from typing import Iterator
|
3
|
+
|
4
|
+
from dissect.target.exceptions import UnsupportedPluginError
|
5
|
+
from dissect.target.helpers.fsutil import TargetPath
|
6
|
+
from dissect.target.helpers.record import ChildTargetRecord
|
7
|
+
from dissect.target.plugin import ChildTargetPlugin
|
8
|
+
from dissect.target.target import Target
|
9
|
+
|
10
|
+
PARALLELS_USER_PATHS = [
|
11
|
+
"Parallels",
|
12
|
+
"Documents/Parallels",
|
13
|
+
"Library/Group Containers/*.com.parallels.desktop.appstore/Shared/Parallels",
|
14
|
+
]
|
15
|
+
|
16
|
+
PARALLELS_SYSTEM_PATHS = [
|
17
|
+
"/Users/Shared/Parallels",
|
18
|
+
]
|
19
|
+
|
20
|
+
|
21
|
+
def find_pvms(target: Target) -> Iterator[TargetPath]:
|
22
|
+
"""Finds virtual machines located in default folders on a macOS target.
|
23
|
+
|
24
|
+
Resources:
|
25
|
+
- https://kb.parallels.com/117333
|
26
|
+
"""
|
27
|
+
for user_details in target.user_details.all_with_home():
|
28
|
+
for parallels_path in PARALLELS_SYSTEM_PATHS:
|
29
|
+
if (path := target.fs.path(parallels_path)).exists():
|
30
|
+
yield from iter_vms(path)
|
31
|
+
|
32
|
+
for parallels_path in PARALLELS_USER_PATHS:
|
33
|
+
if "*" in parallels_path:
|
34
|
+
start_path, pattern = parallels_path.split("*", 1)
|
35
|
+
for path in user_details.home_path.joinpath(start_path).rglob("*" + pattern):
|
36
|
+
yield from iter_vms(path)
|
37
|
+
else:
|
38
|
+
if (path := user_details.home_path.joinpath(parallels_path)).exists():
|
39
|
+
yield from iter_vms(path)
|
40
|
+
|
41
|
+
|
42
|
+
def iter_vms(path: Path) -> Iterator[TargetPath]:
|
43
|
+
"""Glob for .pvm folders in the provided folder."""
|
44
|
+
for file in path.rglob("*.pvm"):
|
45
|
+
if file.is_dir():
|
46
|
+
yield file
|
47
|
+
|
48
|
+
|
49
|
+
class ParallelsChildTargetPlugin(ChildTargetPlugin):
|
50
|
+
"""Child target plugin that yields Parallels Desktop VM files."""
|
51
|
+
|
52
|
+
__type__ = "parallels"
|
53
|
+
|
54
|
+
def __init__(self, target: Target):
|
55
|
+
super().__init__(target)
|
56
|
+
self.pvms = list(find_pvms(target))
|
57
|
+
|
58
|
+
def check_compatible(self) -> None:
|
59
|
+
if not self.pvms:
|
60
|
+
raise UnsupportedPluginError("No Parallels pvm file(s) found")
|
61
|
+
|
62
|
+
def list_children(self) -> Iterator[ChildTargetRecord]:
|
63
|
+
for pvm in self.pvms:
|
64
|
+
yield ChildTargetRecord(
|
65
|
+
type=self.__type__,
|
66
|
+
path=pvm,
|
67
|
+
_target=self.target,
|
68
|
+
)
|
@@ -0,0 +1,23 @@
|
|
1
|
+
from typing import Iterator
|
2
|
+
|
3
|
+
from dissect.target.exceptions import UnsupportedPluginError
|
4
|
+
from dissect.target.helpers.record import ChildTargetRecord
|
5
|
+
from dissect.target.plugin import ChildTargetPlugin
|
6
|
+
|
7
|
+
|
8
|
+
class ProxmoxChildTargetPlugin(ChildTargetPlugin):
|
9
|
+
"""Child target plugin that yields from the VM listing."""
|
10
|
+
|
11
|
+
__type__ = "proxmox"
|
12
|
+
|
13
|
+
def check_compatible(self) -> None:
|
14
|
+
if self.target.os != "proxmox":
|
15
|
+
raise UnsupportedPluginError("Not a Proxmox operating system")
|
16
|
+
|
17
|
+
def list_children(self) -> Iterator[ChildTargetRecord]:
|
18
|
+
for vm in self.target.vmlist():
|
19
|
+
yield ChildTargetRecord(
|
20
|
+
type=self.__type__,
|
21
|
+
path=vm.path,
|
22
|
+
_target=self.target,
|
23
|
+
)
|
@@ -1,3 +1,5 @@
|
|
1
|
+
from typing import Iterator
|
2
|
+
|
1
3
|
from dissect.target.exceptions import UnsupportedPluginError
|
2
4
|
from dissect.target.helpers.record import ChildTargetRecord
|
3
5
|
from dissect.target.plugin import ChildTargetPlugin
|
@@ -9,13 +11,15 @@ class VirtuozzoChildTargetPlugin(ChildTargetPlugin):
|
|
9
11
|
Virtuozzo conatiners are by default registered in the folder ``vz/root/$VEID``,
|
10
12
|
where VEID will be substituted with the actual container UUID.
|
11
13
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
14
|
+
.. code-block::
|
15
|
+
|
16
|
+
/
|
17
|
+
etc/
|
18
|
+
var/
|
19
|
+
vz/
|
20
|
+
root/
|
21
|
+
<container-uuid>/
|
22
|
+
<container-uuid>/
|
19
23
|
|
20
24
|
References:
|
21
25
|
- https://docs.virtuozzo.com/virtuozzo_hybrid_server_7_command_line_reference/managing-system/configuration-files.html
|
@@ -29,7 +33,7 @@ class VirtuozzoChildTargetPlugin(ChildTargetPlugin):
|
|
29
33
|
if not self.target.fs.path(self.PATH).exists():
|
30
34
|
raise UnsupportedPluginError("No Virtuozzo path found")
|
31
35
|
|
32
|
-
def list_children(self):
|
36
|
+
def list_children(self) -> Iterator[ChildTargetRecord]:
|
33
37
|
for container in self.target.fs.path(self.PATH).iterdir():
|
34
38
|
yield ChildTargetRecord(
|
35
39
|
type=self.__type__,
|
@@ -1,29 +1,44 @@
|
|
1
|
+
from typing import Iterator
|
2
|
+
|
1
3
|
from dissect.target.exceptions import UnsupportedPluginError
|
4
|
+
from dissect.target.helpers.fsutil import TargetPath
|
2
5
|
from dissect.target.helpers.record import ChildTargetRecord
|
3
6
|
from dissect.target.plugin import ChildTargetPlugin
|
7
|
+
from dissect.target.target import Target
|
8
|
+
|
9
|
+
INVENTORY_PATHS = [
|
10
|
+
# Windows
|
11
|
+
"AppData/Roaming/VMware/inventory.vmls",
|
12
|
+
# Linux
|
13
|
+
".vmware/inventory.vmls",
|
14
|
+
]
|
15
|
+
|
16
|
+
|
17
|
+
def find_vm_inventory(target: Target) -> Iterator[TargetPath]:
|
18
|
+
"""Search for inventory.vmls files in user home folders.
|
4
19
|
|
20
|
+
Does not support older vmAutoStart.xml or vmInventory.xml formats."""
|
5
21
|
|
6
|
-
def find_vm_inventory(target):
|
7
22
|
for user_details in target.user_details.all_with_home():
|
8
|
-
|
9
|
-
|
10
|
-
|
23
|
+
for inv_path in INVENTORY_PATHS:
|
24
|
+
if (inv_file := user_details.home_path.joinpath(inv_path)).exists():
|
25
|
+
yield inv_file
|
11
26
|
|
12
27
|
|
13
|
-
class
|
28
|
+
class VmwareWorkstationChildTargetPlugin(ChildTargetPlugin):
|
14
29
|
"""Child target plugin that yields from VMware Workstation VM inventory."""
|
15
30
|
|
16
31
|
__type__ = "vmware_workstation"
|
17
32
|
|
18
|
-
def __init__(self, target):
|
33
|
+
def __init__(self, target: Target):
|
19
34
|
super().__init__(target)
|
20
35
|
self.inventories = list(find_vm_inventory(target))
|
21
36
|
|
22
37
|
def check_compatible(self) -> None:
|
23
|
-
if not
|
38
|
+
if not self.inventories:
|
24
39
|
raise UnsupportedPluginError("No VMWare inventories found")
|
25
40
|
|
26
|
-
def list_children(self):
|
41
|
+
def list_children(self) -> Iterator[ChildTargetRecord]:
|
27
42
|
for inv in self.inventories:
|
28
43
|
for line in inv.open("rt"):
|
29
44
|
line = line.strip()
|
@@ -1,5 +1,6 @@
|
|
1
1
|
import csv
|
2
2
|
import gzip
|
3
|
+
from typing import Iterator
|
3
4
|
|
4
5
|
from dissect.target.exceptions import UnsupportedPluginError
|
5
6
|
from dissect.target.helpers.record import TargetRecordDescriptor
|
@@ -27,7 +28,7 @@ class AcquireHashPlugin(Plugin):
|
|
27
28
|
raise UnsupportedPluginError("No hash file found")
|
28
29
|
|
29
30
|
@export(record=AcquireHashRecord)
|
30
|
-
def acquire_hashes(self):
|
31
|
+
def acquire_hashes(self) -> Iterator[AcquireHashRecord]:
|
31
32
|
"""Return file hashes collected by Acquire.
|
32
33
|
|
33
34
|
An Acquire file container contains a file hashes csv when the hashes module was used. The content of this csv
|
@@ -1,3 +1,5 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
1
3
|
import shutil
|
2
4
|
import sys
|
3
5
|
|
@@ -27,24 +29,26 @@ class ICatPlugin(Plugin):
|
|
27
29
|
)
|
28
30
|
@arg("--ads", type=str, default="", help="Alternate Data Stream name")
|
29
31
|
@export(output="none")
|
30
|
-
def icat(self, inum, fs, ads):
|
32
|
+
def icat(self, inum: int, fs: int | None, ads: str) -> None:
|
31
33
|
"""Output the contents of a file based on its MFT segment or inode number. Supports Alternate Data Streams
|
32
34
|
|
33
35
|
Example:
|
34
|
-
|
35
|
-
|
36
|
+
.. code-block::
|
37
|
+
|
38
|
+
# outputs contents of segment defaults to 'sysvol'
|
39
|
+
target-query <TARGET> -f icat --segment 96997
|
36
40
|
|
37
|
-
|
38
|
-
|
41
|
+
# outputs contents of inode defaults to '/'
|
42
|
+
target-query <TARGET> -f icat --inode 50947
|
39
43
|
|
40
|
-
|
41
|
-
|
44
|
+
# outputs contents of segment's ADS
|
45
|
+
target-query <TARGET> -f icat --segment 96997 --ads Zone.Identifier
|
42
46
|
|
43
|
-
|
44
|
-
|
47
|
+
# outputs contents of segment in filesystem 3 of target
|
48
|
+
target-query <TARGET> -f icat --fs 3 --segment 96997
|
45
49
|
|
46
|
-
|
47
|
-
|
50
|
+
# outputs contents of inode in filesystem 2 of target
|
51
|
+
target-query <TARGET> -f icat --fs 2 --inode 50947
|
48
52
|
"""
|
49
53
|
|
50
54
|
open_as = None
|
@@ -105,6 +105,7 @@ FilesystemMACBRecord = TargetRecordDescriptor(
|
|
105
105
|
("filesize", "filesize"),
|
106
106
|
("boolean", "resident"),
|
107
107
|
("boolean", "inuse"),
|
108
|
+
("boolean", "ads"),
|
108
109
|
("string", "volume_uuid"),
|
109
110
|
],
|
110
111
|
)
|
@@ -122,6 +123,8 @@ COMPACT_RECORD_TYPES = {
|
|
122
123
|
|
123
124
|
|
124
125
|
class MftPlugin(Plugin):
|
126
|
+
"""NTFS MFT plugin."""
|
127
|
+
|
125
128
|
def __init__(self, target):
|
126
129
|
super().__init__(target)
|
127
130
|
self.ntfs_filesystems = {index: fs for index, fs in enumerate(self.target.filesystems) if fs.__type__ == "ntfs"}
|
@@ -151,7 +154,7 @@ class MftPlugin(Plugin):
|
|
151
154
|
"--macb",
|
152
155
|
group="fmt",
|
153
156
|
action="store_true",
|
154
|
-
help="compacts
|
157
|
+
help="compacts MFT timestamps into MACB bitfield (format: MACB[standard|ads]/MACB[filename])",
|
155
158
|
)
|
156
159
|
def mft(
|
157
160
|
self, compact: bool = False, fs: int | None = None, start: int = 0, end: int = -1, macb: bool = False
|
@@ -342,12 +345,13 @@ def macb_aggr(records: list[Record]) -> Iterator[Record]:
|
|
342
345
|
for record in records:
|
343
346
|
found = False
|
344
347
|
|
345
|
-
|
346
|
-
|
348
|
+
offset = 0
|
349
|
+
if not getattr(record, "ads", False):
|
350
|
+
offset = int(record._desc.name == "filesystem/ntfs/mft/filename") * 5
|
347
351
|
|
348
|
-
field = "MACB".find(record.ts_type) +
|
352
|
+
field = "MACB".find(record.ts_type) + offset
|
349
353
|
for macb in macbs:
|
350
|
-
if macb.ts == record.ts:
|
354
|
+
if macb.ts == record.ts and macb.path == record.path:
|
351
355
|
macb.macb = macb_set(macb.macb, field, record.ts_type)
|
352
356
|
found = True
|
353
357
|
break
|
@@ -356,7 +360,7 @@ def macb_aggr(records: list[Record]) -> Iterator[Record]:
|
|
356
360
|
continue
|
357
361
|
|
358
362
|
macb = FilesystemMACBRecord.init_from_record(record)
|
359
|
-
macb.macb = "
|
363
|
+
macb.macb = "..../...."
|
360
364
|
macb.macb = macb_set(macb.macb, field, record.ts_type)
|
361
365
|
|
362
366
|
macbs.append(macb)
|
@@ -93,6 +93,8 @@ def format_info(
|
|
93
93
|
|
94
94
|
|
95
95
|
class MftTimelinePlugin(Plugin):
|
96
|
+
"""NTFS MFT timeline plugin."""
|
97
|
+
|
96
98
|
def check_compatible(self) -> None:
|
97
99
|
ntfs_filesystems = [fs for fs in self.target.filesystems if fs.__type__ == "ntfs"]
|
98
100
|
if not len(ntfs_filesystems):
|
@@ -100,7 +102,7 @@ class MftTimelinePlugin(Plugin):
|
|
100
102
|
|
101
103
|
@export(output="yield")
|
102
104
|
@arg("--ignore-dos", action="store_true", help="ignore DOS file names")
|
103
|
-
def mft_timeline(self, ignore_dos: bool = False):
|
105
|
+
def mft_timeline(self, ignore_dos: bool = False) -> Iterator[str]:
|
104
106
|
"""Return the MFT records of all NTFS filesystems in a human readable format (unsorted).
|
105
107
|
|
106
108
|
The Master File Table (MFT) contains metadata about every file and folder on a NFTS filesystem.
|
@@ -13,12 +13,14 @@ DRIVE_LETTER_RE = re.compile(r"[a-zA-Z]:")
|
|
13
13
|
|
14
14
|
|
15
15
|
class InformationType(Enum):
|
16
|
+
"""Valid information types"""
|
17
|
+
|
16
18
|
STANDARD_INFORMATION = auto()
|
17
19
|
FILE_INFORMATION = auto()
|
18
20
|
ALTERNATE_DATA_STREAM = auto()
|
19
21
|
|
20
22
|
|
21
|
-
def get_drive_letter(target: Target, filesystem: NtfsFilesystem):
|
23
|
+
def get_drive_letter(target: Target, filesystem: NtfsFilesystem) -> str:
|
22
24
|
"""Retrieve the drive letter from the loaded mounts
|
23
25
|
|
24
26
|
When the drive letter is not available for that filesystem it returns empty.
|
@@ -1,4 +1,5 @@
|
|
1
1
|
import stat
|
2
|
+
from typing import Iterator
|
2
3
|
|
3
4
|
from dissect.target.exceptions import UnsupportedPluginError
|
4
5
|
from dissect.target.helpers.record import TargetRecordDescriptor
|
@@ -12,12 +13,14 @@ SuidRecord = TargetRecordDescriptor(
|
|
12
13
|
|
13
14
|
|
14
15
|
class SuidPlugin(Plugin):
|
16
|
+
"""Unix SUID binary plugin."""
|
17
|
+
|
15
18
|
def check_compatible(self) -> None:
|
16
19
|
if not self.target.has_function("walkfs") or self.target.os == "windows":
|
17
20
|
raise UnsupportedPluginError("Unsupported plugin")
|
18
21
|
|
19
22
|
@export(record=SuidRecord)
|
20
|
-
def suid_binaries(self):
|
23
|
+
def suid_binaries(self) -> Iterator[SuidRecord]:
|
21
24
|
"""Return all SUID binaries.
|
22
25
|
|
23
26
|
A SUID binary allows all users to run it with the permissions of its owner. This means that a
|
@@ -27,6 +27,8 @@ FilesystemRecord = TargetRecordDescriptor(
|
|
27
27
|
|
28
28
|
|
29
29
|
class WalkFSPlugin(Plugin):
|
30
|
+
"""Filesystem agnostic walkfs plugin."""
|
31
|
+
|
30
32
|
def check_compatible(self) -> None:
|
31
33
|
if not len(self.target.filesystems):
|
32
34
|
raise UnsupportedPluginError("No filesystems to walk")
|
@@ -69,7 +69,7 @@ class ExamplePlugin(Plugin):
|
|
69
69
|
"""
|
70
70
|
pass
|
71
71
|
|
72
|
-
@export
|
72
|
+
@export(output="default")
|
73
73
|
@arg("--flag", action="store_true", help="optional example flag")
|
74
74
|
def example(self, flag: bool = False) -> str:
|
75
75
|
"""Example plugin function.
|
@@ -117,7 +117,7 @@ class ExamplePlugin(Plugin):
|
|
117
117
|
To include registry or user information in a record, you must create a new record descriptor using
|
118
118
|
:func:`~dissect.target.helpers.record.create_extended_descriptor` with
|
119
119
|
:class:`~dissect.target.helpers.descriptor_extensions.RegistryRecordDescriptorExtension` and/or
|
120
|
-
:class:`~dissect.target.helpers.descriptor_extensions.UserRecordDescriptorExtension as extensions.
|
120
|
+
:class:`~dissect.target.helpers.descriptor_extensions.UserRecordDescriptorExtension` as extensions.
|
121
121
|
"""
|
122
122
|
for key in self.target.registry.keys("HKCU\\SOFTWARE"):
|
123
123
|
user = self.target.registry.get_user(key)
|
@@ -1,6 +1,8 @@
|
|
1
|
+
import json
|
2
|
+
|
1
3
|
from dissect.target.helpers.docs import INDENT_STEP, get_docstring
|
2
4
|
from dissect.target.loader import LOADERS_BY_SCHEME
|
3
|
-
from dissect.target.plugin import Plugin, export
|
5
|
+
from dissect.target.plugin import Plugin, arg, export
|
4
6
|
|
5
7
|
|
6
8
|
class LoaderListPlugin(Plugin):
|
@@ -10,7 +12,12 @@ class LoaderListPlugin(Plugin):
|
|
10
12
|
pass
|
11
13
|
|
12
14
|
@export(output="none")
|
13
|
-
|
15
|
+
# NOTE: We would prefer to re-use arguments across plugins from argparse in query.py, but that is not possible yet.
|
16
|
+
# For now we use --as-json, but in the future this should be changed to inherit --json from target-query.
|
17
|
+
# https://github.com/fox-it/dissect.target/pull/841
|
18
|
+
# https://github.com/fox-it/dissect.target/issues/889
|
19
|
+
@arg("--as-json", dest="as_json", action="store_true")
|
20
|
+
def loaders(self, as_json: bool = False) -> None:
|
14
21
|
"""List the available loaders."""
|
15
22
|
|
16
23
|
loaders_info = {}
|
@@ -21,6 +28,12 @@ class LoaderListPlugin(Plugin):
|
|
21
28
|
except ImportError:
|
22
29
|
continue
|
23
30
|
|
24
|
-
|
25
|
-
|
26
|
-
|
31
|
+
loaders = sorted(loaders_info.items())
|
32
|
+
|
33
|
+
if as_json:
|
34
|
+
print(json.dumps([{"name": name, "description": desc} for name, desc in loaders]), end="")
|
35
|
+
|
36
|
+
else:
|
37
|
+
print("Available loaders:")
|
38
|
+
for loader_name, loader_description in loaders:
|
39
|
+
print(f"{INDENT_STEP}{loader_name} - {loader_description}")
|
@@ -3,6 +3,7 @@ from __future__ import annotations
|
|
3
3
|
from typing import Any, Iterator, Union
|
4
4
|
|
5
5
|
from flow.record.fieldtypes.net import IPAddress, IPNetwork
|
6
|
+
from flow.record.fieldtypes.net.ipv4 import Address, addr_long, addr_str, mask_to_bits
|
6
7
|
|
7
8
|
from dissect.target.helpers.record import (
|
8
9
|
MacInterfaceRecord,
|
@@ -16,6 +17,8 @@ InterfaceRecord = Union[UnixInterfaceRecord, WindowsInterfaceRecord, MacInterfac
|
|
16
17
|
|
17
18
|
|
18
19
|
class NetworkPlugin(Plugin):
|
20
|
+
"""Generic implementation for network interfaces plugin."""
|
21
|
+
|
19
22
|
__namespace__ = "network"
|
20
23
|
|
21
24
|
def __init__(self, target: Target):
|
@@ -40,6 +43,7 @@ class NetworkPlugin(Plugin):
|
|
40
43
|
|
41
44
|
@export(record=InterfaceRecord)
|
42
45
|
def interfaces(self) -> Iterator[InterfaceRecord]:
|
46
|
+
"""Yield interfaces."""
|
43
47
|
# Only search for the interfaces once
|
44
48
|
if self._interface_list is None:
|
45
49
|
self._interface_list = list(self._interfaces())
|
@@ -48,19 +52,23 @@ class NetworkPlugin(Plugin):
|
|
48
52
|
|
49
53
|
@export
|
50
54
|
def ips(self) -> list[IPAddress]:
|
51
|
-
|
55
|
+
"""Return IP addresses as list of :class:`IPAddress`."""
|
56
|
+
return list(set(self._get_record_type("ip")))
|
52
57
|
|
53
58
|
@export
|
54
59
|
def gateways(self) -> list[IPAddress]:
|
55
|
-
|
60
|
+
"""Return gateways as list of :class:`IPAddress`."""
|
61
|
+
return list(set(self._get_record_type("gateway")))
|
56
62
|
|
57
63
|
@export
|
58
64
|
def macs(self) -> list[str]:
|
59
|
-
|
65
|
+
"""Return MAC addresses as list of :class:`str`."""
|
66
|
+
return list(set(self._get_record_type("mac")))
|
60
67
|
|
61
68
|
@export
|
62
|
-
def dns(self) -> list[str]:
|
63
|
-
|
69
|
+
def dns(self) -> list[str | IPAddress]:
|
70
|
+
"""Return DNS addresses as list of :class:`str`."""
|
71
|
+
return list(set(self._get_record_type("dns")))
|
64
72
|
|
65
73
|
@internal
|
66
74
|
def with_ip(self, ip_addr: str) -> Iterator[InterfaceRecord]:
|
@@ -80,3 +88,10 @@ class NetworkPlugin(Plugin):
|
|
80
88
|
for interface in self.interfaces():
|
81
89
|
if any(ip_addr in cidr for ip_addr in interface.ip):
|
82
90
|
yield interface
|
91
|
+
|
92
|
+
def calculate_network(self, ips: int | Address, subnets: int | Address) -> Iterator[str]:
|
93
|
+
for ip, subnet_mask in zip(ips, subnets):
|
94
|
+
subnet_mask_int = addr_long(subnet_mask)
|
95
|
+
cidr = mask_to_bits(subnet_mask_int)
|
96
|
+
network_address = addr_str(addr_long(ip) & subnet_mask_int)
|
97
|
+
yield f"{network_address}/{cidr}"
|
@@ -22,6 +22,7 @@ class OSInfoPlugin(plugin.Plugin):
|
|
22
22
|
|
23
23
|
@plugin.export(record=OSInfoRecord)
|
24
24
|
def osinfo(self) -> Iterator[Union[OSInfoRecord, GroupedRecord]]:
|
25
|
+
"""Yield grouped records with target OS info."""
|
25
26
|
for os_func in self.target._os.__functions__:
|
26
27
|
if os_func in ["is_compatible", "get_all_records"]:
|
27
28
|
continue
|
@@ -1,5 +1,8 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
import json
|
1
4
|
import textwrap
|
2
|
-
from typing import
|
5
|
+
from typing import Iterator, Type
|
3
6
|
|
4
7
|
from dissect.target import plugin
|
5
8
|
from dissect.target.helpers.docs import INDENT_STEP, get_plugin_overview
|
@@ -23,7 +26,8 @@ def categorize_plugins(plugins_selection: list[dict] = None) -> dict:
|
|
23
26
|
return output_dict
|
24
27
|
|
25
28
|
|
26
|
-
def get_exported_plugins():
|
29
|
+
def get_exported_plugins() -> list:
|
30
|
+
"""Returns list of exported plugins."""
|
27
31
|
return [p for p in plugin.plugins() if len(p["exports"])]
|
28
32
|
|
29
33
|
|
@@ -50,10 +54,10 @@ def update_dict_recursive(source_dict: dict, updated_dict: dict) -> dict:
|
|
50
54
|
|
51
55
|
|
52
56
|
def output_plugin_description_recursive(
|
53
|
-
structure_dict:
|
57
|
+
structure_dict: dict | Plugin,
|
54
58
|
print_docs: bool,
|
55
|
-
indentation_step=0,
|
56
|
-
) ->
|
59
|
+
indentation_step: int = 0,
|
60
|
+
) -> list[str]:
|
57
61
|
"""Create plugin overview with identations."""
|
58
62
|
|
59
63
|
if isinstance(structure_dict, type) and issubclass(structure_dict, Plugin):
|
@@ -78,10 +82,10 @@ def get_plugin_description(
|
|
78
82
|
|
79
83
|
|
80
84
|
def get_description_dict(
|
81
|
-
structure_dict:
|
85
|
+
structure_dict: dict,
|
82
86
|
print_docs: bool,
|
83
87
|
indentation_step: int,
|
84
|
-
) ->
|
88
|
+
) -> list[str]:
|
85
89
|
"""Returns a list of indented descriptions."""
|
86
90
|
|
87
91
|
output_descriptions = []
|
@@ -98,13 +102,24 @@ def get_description_dict(
|
|
98
102
|
|
99
103
|
|
100
104
|
class PluginListPlugin(Plugin):
|
105
|
+
"""Plugin list plugin (so meta)."""
|
106
|
+
|
101
107
|
def check_compatible(self) -> None:
|
102
108
|
pass
|
103
109
|
|
104
110
|
@export(output="none", cache=False)
|
105
111
|
@arg("--docs", dest="print_docs", action="store_true")
|
106
|
-
|
107
|
-
|
112
|
+
# NOTE: We would prefer to re-use arguments across plugins from argparse in query.py, but that is not possible yet.
|
113
|
+
# For now we use --as-json, but in the future this should be changed to inherit --json from target-query.
|
114
|
+
# https://github.com/fox-it/dissect.target/pull/841
|
115
|
+
# https://github.com/fox-it/dissect.target/issues/889
|
116
|
+
@arg("--as-json", dest="as_json", action="store_true")
|
117
|
+
def plugins(self, plugins: list[Plugin] = None, print_docs: bool = False, as_json: bool = False) -> None:
|
118
|
+
"""Print all available plugins."""
|
119
|
+
|
120
|
+
dict_plugins = list({p.path: p.plugin_desc for p in plugins}.values())
|
121
|
+
categorized_plugins = dict(sorted(categorize_plugins(dict_plugins).items()))
|
122
|
+
|
108
123
|
plugin_descriptions = output_plugin_description_recursive(categorized_plugins, print_docs)
|
109
124
|
|
110
125
|
plugins_list = textwrap.indent(
|
@@ -138,4 +153,32 @@ class PluginListPlugin(Plugin):
|
|
138
153
|
"Failed to load:",
|
139
154
|
failed_list,
|
140
155
|
]
|
141
|
-
|
156
|
+
|
157
|
+
if as_json:
|
158
|
+
out = {"loaded": list(generate_plugins_json(plugins))}
|
159
|
+
|
160
|
+
if failed_plugins := plugin.failed():
|
161
|
+
out["failed"] = [
|
162
|
+
{"module": p["module"], "stacktrace": "".join(p["stacktrace"])} for p in failed_plugins
|
163
|
+
]
|
164
|
+
|
165
|
+
print(json.dumps(out), end="")
|
166
|
+
|
167
|
+
else:
|
168
|
+
print("\n".join(output_lines))
|
169
|
+
|
170
|
+
|
171
|
+
def generate_plugins_json(plugins: list[Plugin]) -> Iterator[dict]:
|
172
|
+
"""Generates JSON output of a list of :class:`Plugin`s."""
|
173
|
+
|
174
|
+
for p in plugins:
|
175
|
+
func = getattr(p.class_object, p.method_name)
|
176
|
+
description = getattr(func, "__doc__", None)
|
177
|
+
summary = description.split("\n\n", 1)[0].strip() if description else None
|
178
|
+
|
179
|
+
yield {
|
180
|
+
"name": p.name,
|
181
|
+
"output": p.output_type,
|
182
|
+
"description": summary,
|
183
|
+
"path": p.path,
|
184
|
+
}
|