dissect.target 3.20.dev41__py3-none-any.whl → 3.20.dev44__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
dissect/target/loader.py CHANGED
@@ -205,4 +205,5 @@ register("velociraptor", "VelociraptorLoader")
205
205
  register("smb", "SmbLoader")
206
206
  register("cb", "CbLoader")
207
207
  register("cyber", "CyberLoader")
208
+ register("proxmox", "ProxmoxLoader")
208
209
  register("multiraw", "MultiRawLoader") # Should be last
@@ -0,0 +1,68 @@
1
+ from __future__ import annotations
2
+
3
+ import re
4
+ from pathlib import Path
5
+
6
+ from dissect.target import container
7
+ from dissect.target.loader import Loader
8
+ from dissect.target.target import Target
9
+
10
+ RE_VOLUME_ID = re.compile(r"(?:file=)?([^:]+):([^,]+)")
11
+
12
+
13
+ class ProxmoxLoader(Loader):
14
+ """Loader for Proxmox VM configuration files.
15
+
16
+ Proxmox uses volume identifiers in the format of ``storage_id:volume_id``. The ``storage_id`` maps to a
17
+ storage configuration in ``/etc/pve/storage.cfg``. The ``volume_id`` is the name of the volume within
18
+ that configuration.
19
+
20
+ This loader currently does not support parsing the storage configuration, so it will attempt to open the
21
+ volume directly from the same directory as the configuration file, or from ``/dev/pve/`` (default LVM config).
22
+ If the volume is not found, it will log a warning.
23
+ """
24
+
25
+ def __init__(self, path: Path, **kwargs):
26
+ path = path.resolve()
27
+ super().__init__(path)
28
+ self.base_dir = path.parent
29
+
30
+ @staticmethod
31
+ def detect(path: Path) -> bool:
32
+ if path.suffix.lower() != ".conf":
33
+ return False
34
+
35
+ with path.open("rb") as fh:
36
+ lines = fh.read(512).split(b"\n")
37
+ needles = [b"cpu:", b"memory:", b"name:"]
38
+ return all(any(needle in line for line in lines) for needle in needles)
39
+
40
+ def map(self, target: Target) -> None:
41
+ with self.path.open("rt") as fh:
42
+ for line in fh:
43
+ if not (line := line.strip()):
44
+ continue
45
+
46
+ key, value = line.split(":", 1)
47
+ value = value.strip()
48
+
49
+ if key.startswith(("scsi", "sata", "ide", "virtio")) and key[-1].isdigit():
50
+ # https://pve.proxmox.com/wiki/Storage
51
+ if match := RE_VOLUME_ID.match(value):
52
+ storage_id, volume_id = match.groups()
53
+
54
+ # TODO: parse the storage information from /etc/pve/storage.cfg
55
+ # For now, let's try a few assumptions
56
+ disk_path = None
57
+ if (path := self.base_dir.joinpath(volume_id)).exists():
58
+ disk_path = path
59
+ elif (path := self.base_dir.joinpath("/dev/pve/").joinpath(volume_id)).exists():
60
+ disk_path = path
61
+
62
+ if disk_path:
63
+ try:
64
+ target.disks.add(container.open(disk_path))
65
+ except Exception:
66
+ target.log.exception("Failed to open disk: %s", disk_path)
67
+ else:
68
+ target.log.warning("Unable to find disk: %s:%s", storage_id, volume_id)
dissect/target/plugin.py CHANGED
@@ -57,17 +57,18 @@ log = logging.getLogger(__name__)
57
57
 
58
58
 
59
59
  class OperatingSystem(StrEnum):
60
- LINUX = "linux"
61
- WINDOWS = "windows"
62
- ESXI = "esxi"
60
+ ANDROID = "android"
63
61
  BSD = "bsd"
62
+ CITRIX = "citrix-netscaler"
63
+ ESXI = "esxi"
64
+ FORTIOS = "fortios"
65
+ IOS = "ios"
66
+ LINUX = "linux"
64
67
  OSX = "osx"
68
+ PROXMOX = "proxmox"
65
69
  UNIX = "unix"
66
- ANDROID = "android"
67
70
  VYOS = "vyos"
68
- IOS = "ios"
69
- FORTIOS = "fortios"
70
- CITRIX = "citrix-netscaler"
71
+ WINDOWS = "windows"
71
72
 
72
73
 
73
74
  def export(*args, **kwargs) -> Callable:
@@ -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,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
- def loaders(self) -> None:
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
- print("Available loaders:")
25
- for loader_name, loader_description in sorted(loaders_info.items()):
26
- print(f"{INDENT_STEP}{loader_name} - {loader_description}")
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}")
@@ -1,5 +1,8 @@
1
+ from __future__ import annotations
2
+
3
+ import json
1
4
  import textwrap
2
- from typing import Dict, List, Type, Union
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: Union[Dict, Plugin],
57
+ structure_dict: dict | Plugin,
54
58
  print_docs: bool,
55
- indentation_step=0,
56
- ) -> List[str]:
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: Dict,
85
+ structure_dict: dict,
82
86
  print_docs: bool,
83
87
  indentation_step: int,
84
- ) -> List[str]:
88
+ ) -> list[str]:
85
89
  """Returns a list of indented descriptions."""
86
90
 
87
91
  output_descriptions = []
@@ -105,10 +109,17 @@ class PluginListPlugin(Plugin):
105
109
 
106
110
  @export(output="none", cache=False)
107
111
  @arg("--docs", dest="print_docs", action="store_true")
108
- def plugins(self, plugins: list[dict] = None, print_docs: bool = False) -> None:
109
- """Print all registered plugins to stdout."""
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()))
110
122
 
111
- categorized_plugins = dict(sorted(categorize_plugins(plugins).items()))
112
123
  plugin_descriptions = output_plugin_description_recursive(categorized_plugins, print_docs)
113
124
 
114
125
  plugins_list = textwrap.indent(
@@ -142,4 +153,32 @@ class PluginListPlugin(Plugin):
142
153
  "Failed to load:",
143
154
  failed_list,
144
155
  ]
145
- print("\n".join(output_lines))
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
+ }
@@ -23,6 +23,7 @@ class UnixPlugin(OSPlugin):
23
23
  def __init__(self, target: Target):
24
24
  super().__init__(target)
25
25
  self._add_mounts()
26
+ self._add_devices()
26
27
  self._hostname_dict = self._parse_hostname_string()
27
28
  self._hosts_dict = self._parse_hosts_string()
28
29
  self._os_release = self._parse_os_release()
@@ -247,6 +248,17 @@ class UnixPlugin(OSPlugin):
247
248
  self.target.log.debug("Mounting %s (%s) at %s", fs, fs.volume, mount_point)
248
249
  self.target.fs.mount(mount_point, fs)
249
250
 
251
+ def _add_devices(self) -> None:
252
+ """Add some virtual block devices to the target.
253
+
254
+ Currently only adds LVM devices.
255
+ """
256
+ vfs = self.target.fs.append_layer()
257
+
258
+ for volume in self.target.volumes:
259
+ if volume.vs and volume.vs.__type__ == "lvm":
260
+ vfs.map_file_fh(f"/dev/{volume.raw.vg.name}/{volume.raw.name}", volume)
261
+
250
262
  def _parse_os_release(self, glob: str | None = None) -> dict[str, str]:
251
263
  """Parse files containing Unix version information.
252
264
 
@@ -0,0 +1,141 @@
1
+ from __future__ import annotations
2
+
3
+ import stat
4
+ from io import BytesIO
5
+ from typing import BinaryIO
6
+
7
+ from dissect.sql import sqlite3
8
+ from dissect.util.stream import BufferedStream
9
+
10
+ from dissect.target.filesystem import (
11
+ Filesystem,
12
+ VirtualDirectory,
13
+ VirtualFile,
14
+ VirtualFilesystem,
15
+ )
16
+ from dissect.target.helpers import fsutil
17
+ from dissect.target.plugins.os.unix._os import OperatingSystem, export
18
+ from dissect.target.plugins.os.unix.linux.debian._os import DebianPlugin
19
+ from dissect.target.target import Target
20
+
21
+
22
+ class ProxmoxPlugin(DebianPlugin):
23
+ @classmethod
24
+ def detect(cls, target: Target) -> Filesystem | None:
25
+ for fs in target.filesystems:
26
+ if fs.exists("/etc/pve") or fs.exists("/var/lib/pve"):
27
+ return fs
28
+
29
+ return None
30
+
31
+ @classmethod
32
+ def create(cls, target: Target, sysvol: Filesystem) -> ProxmoxPlugin:
33
+ obj = super().create(target, sysvol)
34
+
35
+ if (config_db := target.fs.path("/var/lib/pve-cluster/config.db")).exists():
36
+ with config_db.open("rb") as fh:
37
+ vfs = _create_pmxcfs(fh, obj.hostname)
38
+
39
+ target.fs.mount("/etc/pve", vfs)
40
+
41
+ return obj
42
+
43
+ @export(property=True)
44
+ def version(self) -> str:
45
+ """Returns Proxmox VE version with underlying OS release."""
46
+
47
+ for pkg in self.target.dpkg.status():
48
+ if pkg.name == "proxmox-ve":
49
+ distro_name = self._os_release.get("PRETTY_NAME", "")
50
+ return f"{pkg.name} {pkg.version} ({distro_name})"
51
+
52
+ @export(property=True)
53
+ def os(self) -> str:
54
+ return OperatingSystem.PROXMOX.value
55
+
56
+
57
+ DT_DIR = 4
58
+ DT_REG = 8
59
+
60
+
61
+ def _create_pmxcfs(fh: BinaryIO, hostname: str | None = None) -> VirtualFilesystem:
62
+ # https://pve.proxmox.com/wiki/Proxmox_Cluster_File_System_(pmxcfs)
63
+ db = sqlite3.SQLite3(fh)
64
+
65
+ entries = {row.inode: row for row in db.table("tree")}
66
+
67
+ vfs = VirtualFilesystem()
68
+ for entry in entries.values():
69
+ if entry.type == DT_DIR:
70
+ cls = ProxmoxConfigDirectoryEntry
71
+ elif entry.type == DT_REG:
72
+ cls = ProxmoxConfigFileEntry
73
+ else:
74
+ raise ValueError(f"Unknown pmxcfs file type: {entry.type}")
75
+
76
+ parts = []
77
+ current = entry
78
+ while current.parent != 0:
79
+ parts.append(current.name)
80
+ current = entries[current.parent]
81
+ parts.append(current.name)
82
+
83
+ path = "/".join(parts[::-1])
84
+ vfs.map_file_entry(path, cls(vfs, path, entry))
85
+
86
+ if hostname:
87
+ node_root = vfs.path(f"nodes/{hostname}")
88
+ vfs.symlink(str(node_root), "local")
89
+ vfs.symlink(str(node_root / "lxc"), "lxc")
90
+ vfs.symlink(str(node_root / "openvz"), "openvz")
91
+ vfs.symlink(str(node_root / "qemu-server"), "qemu-server")
92
+
93
+ # TODO: .version, .members, .vmlist, maybe .clusterlog and .rrd?
94
+
95
+ return vfs
96
+
97
+
98
+ class ProxmoxConfigFileEntry(VirtualFile):
99
+ def open(self) -> BinaryIO:
100
+ return BufferedStream(BytesIO(self.entry.data or b""))
101
+
102
+ def lstat(self) -> fsutil.stat_result:
103
+ # ['mode', 'addr', 'dev', 'nlink', 'uid', 'gid', 'size', 'atime', 'mtime', 'ctime']
104
+ return fsutil.stat_result(
105
+ [
106
+ stat.S_IFREG | 0o640,
107
+ self.entry.inode,
108
+ id(self.fs),
109
+ 1,
110
+ 0,
111
+ 0,
112
+ len(self.entry.data) if self.entry.data else 0,
113
+ 0,
114
+ self.entry.mtime,
115
+ 0,
116
+ ]
117
+ )
118
+
119
+
120
+ class ProxmoxConfigDirectoryEntry(VirtualDirectory):
121
+ def __init__(self, fs: VirtualFilesystem, path: str, entry: sqlite3.Row):
122
+ super().__init__(fs, path)
123
+ self.entry = entry
124
+
125
+ def lstat(self) -> fsutil.stat_result:
126
+ """Return the stat information of the given path, without resolving links."""
127
+ # ['mode', 'addr', 'dev', 'nlink', 'uid', 'gid', 'size', 'atime', 'mtime', 'ctime']
128
+ return fsutil.stat_result(
129
+ [
130
+ stat.S_IFDIR | 0o755,
131
+ self.entry.inode,
132
+ id(self.fs),
133
+ 1,
134
+ 0,
135
+ 0,
136
+ 0,
137
+ 0,
138
+ self.entry.mtime,
139
+ 0,
140
+ ]
141
+ )
@@ -0,0 +1,29 @@
1
+ from typing import Iterator
2
+
3
+ from dissect.target.exceptions import UnsupportedPluginError
4
+ from dissect.target.helpers.record import TargetRecordDescriptor
5
+ from dissect.target.plugin import Plugin, export
6
+
7
+ VirtualMachineRecord = TargetRecordDescriptor(
8
+ "proxmox/vm",
9
+ [
10
+ ("string", "path"),
11
+ ],
12
+ )
13
+
14
+
15
+ class VirtualMachinePlugin(Plugin):
16
+ """Plugin to list Proxmox virtual machines."""
17
+
18
+ def check_compatible(self) -> None:
19
+ if self.target.os != "proxmox":
20
+ raise UnsupportedPluginError("Not a Proxmox operating system")
21
+
22
+ @export(record=VirtualMachineRecord)
23
+ def vmlist(self) -> Iterator[VirtualMachineRecord]:
24
+ """List Proxmox virtual machines on this node."""
25
+ for config in self.target.fs.path("/etc/pve/qemu-server").iterdir():
26
+ yield VirtualMachineRecord(
27
+ path=config,
28
+ _target=self.target,
29
+ )
@@ -169,31 +169,40 @@ def main():
169
169
  # Show the list of available plugins for the given optional target and optional
170
170
  # search pattern, only display plugins that can be applied to ANY targets
171
171
  if args.list:
172
- collected_plugins = {}
172
+ collected_plugins = []
173
173
 
174
174
  if targets:
175
175
  for plugin_target in Target.open_all(targets, args.children):
176
176
  funcs, _ = find_plugin_functions(plugin_target, args.list, compatibility=True, show_hidden=True)
177
177
  for func in funcs:
178
- collected_plugins[func.path] = func.plugin_desc
178
+ collected_plugins.append(func)
179
179
  else:
180
180
  funcs, _ = find_plugin_functions(Target(), args.list, compatibility=False, show_hidden=True)
181
181
  for func in funcs:
182
- collected_plugins[func.path] = func.plugin_desc
182
+ collected_plugins.append(func)
183
183
 
184
- # Display in a user friendly manner
185
184
  target = Target()
186
185
  fparser = generate_argparse_for_bound_method(target.plugins, usage_tmpl=USAGE_FORMAT_TMPL)
187
186
  fargs, rest = fparser.parse_known_args(rest)
188
187
 
188
+ # Display in a user friendly manner
189
189
  if collected_plugins:
190
- target.plugins(list(collected_plugins.values()))
190
+ if args.json:
191
+ print('{"plugins": ', end="")
192
+ target.plugins(collected_plugins, as_json=args.json)
191
193
 
192
194
  # No real targets specified, show the available loaders
193
195
  if not targets:
194
196
  fparser = generate_argparse_for_bound_method(target.loaders, usage_tmpl=USAGE_FORMAT_TMPL)
195
197
  fargs, rest = fparser.parse_known_args(rest)
196
- target.loaders(**vars(fargs))
198
+ del fargs.as_json
199
+ if args.json:
200
+ print(', "loaders": ', end="")
201
+ target.loaders(**vars(fargs), as_json=args.json)
202
+
203
+ if args.json:
204
+ print("}")
205
+
197
206
  parser.exit()
198
207
 
199
208
  if not targets:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: dissect.target
3
- Version: 3.20.dev41
3
+ Version: 3.20.dev44
4
4
  Summary: This module ties all other Dissect modules together, it provides a programming API and command line tools which allow easy access to various data sources inside disk images or file collections (a.k.a. targets)
5
5
  Author-email: Dissect Team <dissect@fox-it.com>
6
6
  License: Affero General Public License v3
@@ -2,8 +2,8 @@ dissect/target/__init__.py,sha256=Oc7ounTgq2hE4nR6YcNabetc7SQA40ldSa35VEdZcQU,63
2
2
  dissect/target/container.py,sha256=0YcwcGmfJjhPXUB6DEcjWEoSuAtTDxMDpoTviMrLsxM,9353
3
3
  dissect/target/exceptions.py,sha256=ULi7NXlqju_d8KENEL3aimmfKTFfbNssfeWhAnOB654,2972
4
4
  dissect/target/filesystem.py,sha256=__p2p72B6mKgIiAOj85EC3ESdZ8gjgkm2pt1KKym3h0,60743
5
- dissect/target/loader.py,sha256=11B3W9IpfdTU9GJiKONGffuGeICog7J9ypXZ6mffsBE,7337
6
- dissect/target/plugin.py,sha256=bXWfFOX9B4p1u7T7crASf0jjPrSp8X0wu_jHze2-t_g,50658
5
+ dissect/target/loader.py,sha256=ZlCI7ZyPpysuSKndOiRz_rrGb30_jLMdFD6qObY0Vzg,7374
6
+ dissect/target/plugin.py,sha256=iUc7OmQJ0wwYJeR7L4VBNJ0AjgYWV69FN0NHgWYaLYI,50682
7
7
  dissect/target/report.py,sha256=06uiP4MbNI8cWMVrC1SasNS-Yg6ptjVjckwj8Yhe0Js,7958
8
8
  dissect/target/target.py,sha256=-UoFO_fqS6XPf77RDHSV4gNRi95wwwpgWq7zIhNVUGk,32719
9
9
  dissect/target/volume.py,sha256=aQZAJiny8jjwkc9UtwIRwy7nINXjCxwpO-_UDfh6-BA,15801
@@ -93,6 +93,7 @@ dissect/target/loaders/overlay.py,sha256=tj99HKvNG5_JbGfb1WCv4KNSbXXSnEcPQY5XT-J
93
93
  dissect/target/loaders/ovf.py,sha256=ELMq6J2y6cPKbp7pjWAqMMnFYefWxXNqzIiAQdvGGXQ,1061
94
94
  dissect/target/loaders/phobos.py,sha256=XtxF7FZXfZrXJruFUZUQzxlREyfc86dTxph7BNoNMvw,2277
95
95
  dissect/target/loaders/profile.py,sha256=5ylgmzEEGyBFW3izvb-BZ7dGByXN9OFyRnnggR98P9w,1667
96
+ dissect/target/loaders/proxmox.py,sha256=HP7yCrXze1DmMud7730OPMIwUAEaeoXK0YO6i2XSunM,2778
96
97
  dissect/target/loaders/pvm.py,sha256=b-PvHNTbRVdOnf7-OR5dbikbDTCFlW85b-9Z8PEL2Cs,406
97
98
  dissect/target/loaders/pvs.py,sha256=dMqdYSBQtH9QLM3tdu0mokLBcn73edg_HUtYtqrdi6E,955
98
99
  dissect/target/loaders/raw.py,sha256=tleNWoO0BkC32ExBIPVOpzrQHXXHChZXoZr02oYuC8A,674
@@ -163,6 +164,7 @@ dissect/target/plugins/child/docker.py,sha256=frBZ8UUzbtkT9VrK1fwUzXDAdkHESdPCb-
163
164
  dissect/target/plugins/child/esxi.py,sha256=EHpBRv5dizvJVIhtVR7frkUnI9GR7lrbSWPGoOpKhRw,753
164
165
  dissect/target/plugins/child/hyperv.py,sha256=R2qVeu4p_9V53jO-65znN0LwX9v3FVA-9jbbtOQcEz8,2236
165
166
  dissect/target/plugins/child/parallels.py,sha256=jeBT_NvTQbQBaUjqGWTy2I5Q5OWlrogoyWHRXjOhLis,2255
167
+ dissect/target/plugins/child/proxmox.py,sha256=qjeact60fpUXsOciAIJ776fpcnkO6hTZjeUyx92I2HQ,755
166
168
  dissect/target/plugins/child/qemu.py,sha256=vNzQwzFO964jYaI67MlX8vpWyHxpegjIU5F29zHKOGI,791
167
169
  dissect/target/plugins/child/virtuozzo.py,sha256=raXaDTyGPY5JuNgyUOn_qzHaPa0sokG3nGZ5_7eDPyA,1303
168
170
  dissect/target/plugins/child/vmware_workstation.py,sha256=eLvgi_aFUaMN1tC5X4RN85nKQdsuRQygrFYtNMAERTc,2030
@@ -186,15 +188,15 @@ dissect/target/plugins/general/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5
186
188
  dissect/target/plugins/general/config.py,sha256=Mdy9uhWn4OJ96zfXpLgjVifV5SrViqHnpSnKhC1mjZE,3432
187
189
  dissect/target/plugins/general/default.py,sha256=8W_9JV3jKEeETlyTrB25sACoIIFmmO8wlVU5Zoi51W0,1425
188
190
  dissect/target/plugins/general/example.py,sha256=mYAbhtfQmUBj2L2C1DFt9bWpI7rQLJwCIYUsNLcA_pc,6053
189
- dissect/target/plugins/general/loaders.py,sha256=B-9GA4ftiFIpxbc8BjliTNpQREN3lvNlUhJf2N3fhjM,887
191
+ dissect/target/plugins/general/loaders.py,sha256=z_t55Q1XNjmTOxq0E4tCwpZ-utFyxiLKyAJIFgJMlJs,1508
190
192
  dissect/target/plugins/general/network.py,sha256=J8aMfUJ7dgwqpaXzZpHHyOUYg-cPef2Qaa3krUj-A-Q,3225
191
193
  dissect/target/plugins/general/osinfo.py,sha256=oU-vmMiA-oaSEQWTSyn6-yQiH2sLQT6aTQHRd0677wo,1415
192
- dissect/target/plugins/general/plugins.py,sha256=pn6uwfzbLnVTBpel5_GRPOFU3XEAMzbsiFY1UWKkWjc,4575
194
+ dissect/target/plugins/general/plugins.py,sha256=9KJ70YvYwBfxt19C9yISv8YE4mOdHNvP16fTCTHC68U,6033
193
195
  dissect/target/plugins/general/scrape.py,sha256=Fz7BNXflvuxlnVulyyDhLpyU8D_hJdH6vWVtER9vjTg,6651
194
196
  dissect/target/plugins/general/users.py,sha256=yy9gvRXfN9BT71v4Xqo5hpwfgN9he9Otu8TBPZ_Tegs,3009
195
197
  dissect/target/plugins/os/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
196
198
  dissect/target/plugins/os/unix/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
197
- dissect/target/plugins/os/unix/_os.py,sha256=I-waC7Hn9rMJRI2JUDreLnU_LS54Zj3hix7BfJLV-xg,14651
199
+ dissect/target/plugins/os/unix/_os.py,sha256=prvcuIkJ1jmbb_g1CtgHmmJUcGjFaZn3yU6VPNMJ27k,15061
198
200
  dissect/target/plugins/os/unix/applications.py,sha256=AUgZRP35FzswGyFyChj2o4dfGO34Amc6nqHgiMEaqdI,3129
199
201
  dissect/target/plugins/os/unix/cronjobs.py,sha256=tgWQ3BUZpfyvRzodMwGtwFUdPjZ17k7ZRbZ9Q8wmXPk,3393
200
202
  dissect/target/plugins/os/unix/datetime.py,sha256=gKfBdPyUirt3qmVYfOJ1oZXRPn8wRzssbZxR_ARrtk8,1518
@@ -242,6 +244,9 @@ dissect/target/plugins/os/unix/linux/debian/_os.py,sha256=GI19ZqcyfZ1mUYg2NCx93H
242
244
  dissect/target/plugins/os/unix/linux/debian/apt.py,sha256=uKfW77pbgxYSe9g6hpih19-xfgvCB954Ygx21j-ydzk,4388
243
245
  dissect/target/plugins/os/unix/linux/debian/dpkg.py,sha256=6A3mb77NREdDWDTFrmcfCF_XxHMmD4_5eHqHEl_7Vqg,5816
244
246
  dissect/target/plugins/os/unix/linux/debian/snap.py,sha256=YVz1N54eQsj2_22LUJIj6Gg-huwT4xDEcOAZn9Tvgw4,3023
247
+ dissect/target/plugins/os/unix/linux/debian/proxmox/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
248
+ dissect/target/plugins/os/unix/linux/debian/proxmox/_os.py,sha256=OrQsPcz3fERVnSJKRMXwWQCoVN6CFuC-pJIVHtU-g9M,4257
249
+ dissect/target/plugins/os/unix/linux/debian/proxmox/vm.py,sha256=Z5IDA5Oot4P2hh65n5cgUMUi_TMIgSexIO2ngOgxPo0,911
245
250
  dissect/target/plugins/os/unix/linux/debian/vyos/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
246
251
  dissect/target/plugins/os/unix/linux/debian/vyos/_os.py,sha256=TPjcfv1n68RCe3Er4aCVQwQDCZwJT-NLvje3kPjDfhk,1744
247
252
  dissect/target/plugins/os/unix/linux/fortios/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -356,7 +361,7 @@ dissect/target/tools/fsutils.py,sha256=q0t9gFwKHaPr2Ya-MN2o4LsYledde7kp2DZZTd8ro
356
361
  dissect/target/tools/info.py,sha256=8nnbqFUYeo4NLPE7ORcTBcDL-TioGB2Nqc1TKcu5qdY,5715
357
362
  dissect/target/tools/logging.py,sha256=5ZnumtMWLyslxfrUGZ4ntRyf3obOOhmn8SBjKfdLcEg,4174
358
363
  dissect/target/tools/mount.py,sha256=8GRYnu4xEmFBHxuIZAYhOMyyTGX8fat1Ou07DNiUnW4,3945
359
- dissect/target/tools/query.py,sha256=e-yAN9zdQjuOiTuoOQoo17mVEQGGcOgaA9YkF4GYpkM,15394
364
+ dissect/target/tools/query.py,sha256=OYWVmCx2nFx85x1r8Y6D17UdUIi8PJm304xBfT-H8vs,15605
360
365
  dissect/target/tools/reg.py,sha256=FDsiBBDxjWVUBTRj8xn82vZe-J_d9piM-TKS3PHZCcM,3193
361
366
  dissect/target/tools/shell.py,sha256=qY-JIwFQKBHTbqOiFxeO9OYeOlesQlx0r8PHghSAV8I,54207
362
367
  dissect/target/tools/utils.py,sha256=JJZDSso1CEK2sv4Z3HJNgqxH6G9S5lbmV-C3h-XmcMo,12035
@@ -373,10 +378,10 @@ dissect/target/volumes/luks.py,sha256=OmCMsw6rCUXG1_plnLVLTpsvE1n_6WtoRUGQbpmu1z
373
378
  dissect/target/volumes/lvm.py,sha256=wwQVR9I3G9YzmY6UxFsH2Y4MXGBcKL9aayWGCDTiWMU,2269
374
379
  dissect/target/volumes/md.py,sha256=7ShPtusuLGaIv27SvEETtgsuoQyAa4iAAeOR1NEaajI,1689
375
380
  dissect/target/volumes/vmfs.py,sha256=-LoUbn9WNwTtLi_4K34uV_-wDw2W5hgaqxZNj4UmqAQ,1730
376
- dissect.target-3.20.dev41.dist-info/COPYRIGHT,sha256=m-9ih2RVhMiXHI2bf_oNSSgHgkeIvaYRVfKTwFbnJPA,301
377
- dissect.target-3.20.dev41.dist-info/LICENSE,sha256=DZak_2itbUtvHzD3E7GNUYSRK6jdOJ-GqncQ2weavLA,34523
378
- dissect.target-3.20.dev41.dist-info/METADATA,sha256=L31RLE9aRvflCgG4WQ89JHQgFlD-1TbAlxgnXJFtY4U,12897
379
- dissect.target-3.20.dev41.dist-info/WHEEL,sha256=P9jw-gEje8ByB7_hXoICnHtVCrEwMQh-630tKvQWehc,91
380
- dissect.target-3.20.dev41.dist-info/entry_points.txt,sha256=BWuxAb_6AvUAQpIQOQU0IMTlaF6TDht2AIZK8bHd-zE,492
381
- dissect.target-3.20.dev41.dist-info/top_level.txt,sha256=Mn-CQzEYsAbkxrUI0TnplHuXnGVKzxpDw_po_sXpvv4,8
382
- dissect.target-3.20.dev41.dist-info/RECORD,,
381
+ dissect.target-3.20.dev44.dist-info/COPYRIGHT,sha256=m-9ih2RVhMiXHI2bf_oNSSgHgkeIvaYRVfKTwFbnJPA,301
382
+ dissect.target-3.20.dev44.dist-info/LICENSE,sha256=DZak_2itbUtvHzD3E7GNUYSRK6jdOJ-GqncQ2weavLA,34523
383
+ dissect.target-3.20.dev44.dist-info/METADATA,sha256=Sgsho9JR5_66txpbY0u-KInNUTMmgflhCY23idL5Iuo,12897
384
+ dissect.target-3.20.dev44.dist-info/WHEEL,sha256=P9jw-gEje8ByB7_hXoICnHtVCrEwMQh-630tKvQWehc,91
385
+ dissect.target-3.20.dev44.dist-info/entry_points.txt,sha256=BWuxAb_6AvUAQpIQOQU0IMTlaF6TDht2AIZK8bHd-zE,492
386
+ dissect.target-3.20.dev44.dist-info/top_level.txt,sha256=Mn-CQzEYsAbkxrUI0TnplHuXnGVKzxpDw_po_sXpvv4,8
387
+ dissect.target-3.20.dev44.dist-info/RECORD,,