dissect.target 3.19.dev58__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.dev58.dist-info → dissect.target-3.20.dist-info}/METADATA +6 -2
- {dissect.target-3.19.dev58.dist-info → dissect.target-3.20.dist-info}/RECORD +176 -160
- {dissect.target-3.19.dev58.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.dev58.dist-info → dissect.target-3.20.dist-info}/COPYRIGHT +0 -0
- {dissect.target-3.19.dev58.dist-info → dissect.target-3.20.dist-info}/LICENSE +0 -0
- {dissect.target-3.19.dev58.dist-info → dissect.target-3.20.dist-info}/entry_points.txt +0 -0
- {dissect.target-3.19.dev58.dist-info → dissect.target-3.20.dist-info}/top_level.txt +0 -0
@@ -1,11 +1,13 @@
|
|
1
|
-
"""
|
1
|
+
"""Registry related abstractions"""
|
2
|
+
|
2
3
|
from __future__ import annotations
|
3
4
|
|
4
5
|
import fnmatch
|
5
6
|
import re
|
6
|
-
import struct
|
7
7
|
from collections import defaultdict
|
8
8
|
from datetime import datetime
|
9
|
+
from enum import IntEnum
|
10
|
+
from functools import cached_property
|
9
11
|
from io import BytesIO
|
10
12
|
from pathlib import Path
|
11
13
|
from typing import BinaryIO, Iterator, Optional, TextIO, Union
|
@@ -28,6 +30,19 @@ ValueType = Union[int, str, bytes, list[str]]
|
|
28
30
|
"""The possible value types that can be returned from the registry."""
|
29
31
|
|
30
32
|
|
33
|
+
class RegistryValueType(IntEnum):
|
34
|
+
NONE = regf.REG_NONE
|
35
|
+
SZ = regf.REG_SZ
|
36
|
+
EXPAND_SZ = regf.REG_EXPAND_SZ
|
37
|
+
BINARY = regf.REG_BINARY
|
38
|
+
DWORD = regf.REG_DWORD
|
39
|
+
DWORD_BIG_ENDIAN = regf.REG_DWORD_BIG_ENDIAN
|
40
|
+
MULTI_SZ = regf.REG_MULTI_SZ
|
41
|
+
FULL_RESOURCE_DESCRIPTOR = regf.REG_FULL_RESOURCE_DESCRIPTOR
|
42
|
+
RESOURCE_REQUIREMENTS_LIST = regf.REG_RESOURCE_REQUIREMENTS_LIST
|
43
|
+
QWORD = regf.REG_QWORD
|
44
|
+
|
45
|
+
|
31
46
|
class RegistryHive:
|
32
47
|
"""Base class for registry hives."""
|
33
48
|
|
@@ -296,6 +311,7 @@ class VirtualKey(RegistryKey):
|
|
296
311
|
self._class_name = class_name
|
297
312
|
self._values: dict[str, RegistryValue] = {}
|
298
313
|
self._subkeys: dict[str, RegistryKey] = {}
|
314
|
+
self._timestamp: datetime = None
|
299
315
|
self.top: RegistryKey = None
|
300
316
|
super().__init__(hive=hive)
|
301
317
|
|
@@ -325,11 +341,19 @@ class VirtualKey(RegistryKey):
|
|
325
341
|
return self._path
|
326
342
|
|
327
343
|
@property
|
328
|
-
def timestamp(self) -> datetime:
|
344
|
+
def timestamp(self) -> datetime | None:
|
329
345
|
if self.top:
|
330
346
|
return self.top.timestamp
|
347
|
+
|
348
|
+
if self._timestamp:
|
349
|
+
return self._timestamp
|
350
|
+
|
331
351
|
return None
|
332
352
|
|
353
|
+
@timestamp.setter
|
354
|
+
def timestamp(self, ts: datetime) -> None:
|
355
|
+
self._timestamp = ts
|
356
|
+
|
333
357
|
def subkey(self, subkey: str) -> RegistryKey:
|
334
358
|
try:
|
335
359
|
return self._subkeys[subkey.lower()]
|
@@ -405,8 +429,8 @@ class VirtualValue(RegistryValue):
|
|
405
429
|
return self._value
|
406
430
|
|
407
431
|
@property
|
408
|
-
def type(self) ->
|
409
|
-
return
|
432
|
+
def type(self) -> RegistryValueType:
|
433
|
+
return RegistryValueType.NONE
|
410
434
|
|
411
435
|
|
412
436
|
class HiveCollection(RegistryHive):
|
@@ -622,7 +646,7 @@ class RegfHive(RegistryHive):
|
|
622
646
|
try:
|
623
647
|
return RegfKey(self, self.hive.open(key))
|
624
648
|
except regf.RegistryKeyNotFoundError as e:
|
625
|
-
raise RegistryKeyNotFoundError(key
|
649
|
+
raise RegistryKeyNotFoundError(key) from e
|
626
650
|
|
627
651
|
|
628
652
|
class RegfKey(RegistryKey):
|
@@ -652,7 +676,7 @@ class RegfKey(RegistryKey):
|
|
652
676
|
try:
|
653
677
|
return RegfKey(self.hive, self.key.subkey(subkey))
|
654
678
|
except regf.RegistryKeyNotFoundError as e:
|
655
|
-
raise RegistryKeyNotFoundError(subkey
|
679
|
+
raise RegistryKeyNotFoundError(subkey) from e
|
656
680
|
|
657
681
|
def subkeys(self) -> list[RegistryKey]:
|
658
682
|
return [RegfKey(self.hive, k) for k in self.key.subkeys()]
|
@@ -661,7 +685,7 @@ class RegfKey(RegistryKey):
|
|
661
685
|
try:
|
662
686
|
return RegfValue(self.hive, self.key.value(value))
|
663
687
|
except regf.RegistryValueNotFoundError as e:
|
664
|
-
raise RegistryValueNotFoundError(value
|
688
|
+
raise RegistryValueNotFoundError(value) from e
|
665
689
|
|
666
690
|
def values(self) -> list[RegistryValue]:
|
667
691
|
return [RegfValue(self.hive, v) for v in self.key.values()]
|
@@ -683,8 +707,8 @@ class RegfValue(RegistryValue):
|
|
683
707
|
return self.kv.value
|
684
708
|
|
685
709
|
@property
|
686
|
-
def type(self) ->
|
687
|
-
return self.kv.type
|
710
|
+
def type(self) -> RegistryValueType:
|
711
|
+
return RegistryValueType(self.kv.type)
|
688
712
|
|
689
713
|
|
690
714
|
class RegFlex:
|
@@ -750,17 +774,22 @@ class RegFlexKey(VirtualKey):
|
|
750
774
|
|
751
775
|
class RegFlexValue(VirtualValue):
|
752
776
|
def __init__(self, hive: RegistryHive, name: str, value: ValueType):
|
753
|
-
self._parsed_value = None
|
754
777
|
super().__init__(hive, name, value)
|
755
778
|
|
779
|
+
@cached_property
|
780
|
+
def _parse(self) -> tuple[RegistryValueType, ValueType]:
|
781
|
+
return parse_flex_value(self._value)
|
782
|
+
|
756
783
|
@property
|
757
784
|
def value(self) -> ValueType:
|
758
|
-
|
759
|
-
|
760
|
-
|
785
|
+
return self._parse[1]
|
786
|
+
|
787
|
+
@property
|
788
|
+
def type(self) -> RegistryValueType:
|
789
|
+
return self._parse[0]
|
761
790
|
|
762
791
|
|
763
|
-
def parse_flex_value(value: str) -> ValueType:
|
792
|
+
def parse_flex_value(value: str) -> tuple[RegistryValueType, ValueType]:
|
764
793
|
"""Parse values from text registry exports.
|
765
794
|
|
766
795
|
Args:
|
@@ -770,31 +799,31 @@ def parse_flex_value(value: str) -> ValueType:
|
|
770
799
|
NotImplementedError: If ``value`` is not of a supported type for parsing.
|
771
800
|
"""
|
772
801
|
if value.startswith('"'):
|
773
|
-
return value.strip('"')
|
802
|
+
return RegistryValueType.SZ, value.strip('"')
|
774
803
|
|
775
804
|
vtype, _, value = value.partition(":")
|
776
805
|
if vtype == "dword":
|
777
|
-
return
|
806
|
+
return RegistryValueType.DWORD, int.from_bytes(bytes.fromhex(value), "big", signed=True)
|
778
807
|
elif "hex" in vtype:
|
779
808
|
value = bytes.fromhex(value.replace(",", ""))
|
780
809
|
if vtype == "hex":
|
781
|
-
return value
|
810
|
+
return RegistryValueType.BINARY, value
|
782
811
|
|
783
812
|
# hex(T)
|
784
813
|
# These values match regf type values
|
785
814
|
vtype = int(vtype[4:5], 16)
|
786
815
|
if vtype == regf.REG_NONE:
|
787
|
-
|
816
|
+
decoded = value if value else None
|
788
817
|
elif vtype == regf.REG_SZ:
|
789
|
-
|
818
|
+
decoded = regf.try_decode_sz(value)
|
790
819
|
elif vtype == regf.REG_EXPAND_SZ:
|
791
|
-
|
820
|
+
decoded = regf.try_decode_sz(value)
|
792
821
|
elif vtype == regf.REG_BINARY:
|
793
|
-
|
822
|
+
decoded = value
|
794
823
|
elif vtype == regf.REG_DWORD:
|
795
|
-
|
824
|
+
decoded = int.from_bytes(value, "little", signed=False)
|
796
825
|
elif vtype == regf.REG_DWORD_BIG_ENDIAN:
|
797
|
-
|
826
|
+
decoded = int.from_bytes(value, "big", signed=False)
|
798
827
|
elif vtype == regf.REG_MULTI_SZ:
|
799
828
|
d = BytesIO(value)
|
800
829
|
|
@@ -806,11 +835,12 @@ def parse_flex_value(value: str) -> ValueType:
|
|
806
835
|
|
807
836
|
r.append(s)
|
808
837
|
|
809
|
-
|
838
|
+
decoded = r
|
810
839
|
elif vtype == regf.REG_QWORD:
|
811
|
-
|
840
|
+
decoded = int.from_bytes(value, "big", signed=False)
|
812
841
|
else:
|
813
842
|
raise NotImplementedError(f"Registry flex value type {vtype}")
|
843
|
+
return RegistryValueType(vtype), decoded
|
814
844
|
|
815
845
|
|
816
846
|
def has_glob_magic(pattern: str) -> bool:
|
dissect/target/loader.py
CHANGED
@@ -177,7 +177,6 @@ def open(item: Union[str, Path], *args, **kwargs) -> Loader:
|
|
177
177
|
register("local", "LocalLoader")
|
178
178
|
register("remote", "RemoteLoader")
|
179
179
|
register("mqtt", "MQTTLoader")
|
180
|
-
register("targetd", "TargetdLoader")
|
181
180
|
register("asdf", "AsdfLoader")
|
182
181
|
register("tar", "TarLoader")
|
183
182
|
register("vmx", "VmxLoader")
|
@@ -206,4 +205,5 @@ register("velociraptor", "VelociraptorLoader")
|
|
206
205
|
register("smb", "SmbLoader")
|
207
206
|
register("cb", "CbLoader")
|
208
207
|
register("cyber", "CyberLoader")
|
208
|
+
register("proxmox", "ProxmoxLoader")
|
209
209
|
register("multiraw", "MultiRawLoader") # Should be last
|
dissect/target/loaders/mqtt.py
CHANGED
@@ -1,9 +1,11 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
|
+
import argparse
|
3
4
|
import atexit
|
4
5
|
import logging
|
5
6
|
import math
|
6
7
|
import os
|
8
|
+
import re
|
7
9
|
import ssl
|
8
10
|
import sys
|
9
11
|
import time
|
@@ -280,7 +282,7 @@ class Broker:
|
|
280
282
|
factor = 1
|
281
283
|
|
282
284
|
def __init__(
|
283
|
-
self, broker:
|
285
|
+
self, broker: str, port: str, key: str, crt: str, ca: str, case: str, username: str, password: str, **kwargs
|
284
286
|
):
|
285
287
|
self.broker_host = broker
|
286
288
|
self.broker_port = int(port)
|
@@ -341,9 +343,19 @@ class Broker:
|
|
341
343
|
self.mqtt_client.subscribe(f"{self.case}/{host}/DISKS")
|
342
344
|
self.mqtt_client.subscribe(f"{self.case}/{host}/READ/#")
|
343
345
|
if self.command is not None:
|
346
|
+
self.mqtt_client.subscribe(f"{self.case}/{host}/CALLID")
|
344
347
|
self.mqtt_client.publish(f"{self.case}/{host}/COMM", self.command.encode("utf-8"))
|
345
348
|
time.sleep(1)
|
346
349
|
|
350
|
+
def _on_call_id(self, hostname: str, payload: bytes) -> None:
|
351
|
+
try:
|
352
|
+
decoded_payload = payload.decode("utf-8")
|
353
|
+
except UnicodeDecodeError as e:
|
354
|
+
log.error(f"Failed to decode payload for hostname {hostname}: {e}")
|
355
|
+
return
|
356
|
+
|
357
|
+
print(decoded_payload)
|
358
|
+
|
347
359
|
def _on_log(self, client: mqtt.Client, userdata: Any, log_level: int, message: str) -> None:
|
348
360
|
log.debug(message)
|
349
361
|
|
@@ -365,6 +377,8 @@ class Broker:
|
|
365
377
|
self._on_read(hostname, tokens, msg.payload)
|
366
378
|
elif response == "ID":
|
367
379
|
self._on_id(hostname, msg.payload)
|
380
|
+
elif response == "CALLID":
|
381
|
+
self._on_call_id(hostname, msg.payload)
|
368
382
|
|
369
383
|
def seek(self, host: str, disk_id: int, offset: int, flength: int, optimization_strategy: int) -> None:
|
370
384
|
length = int(flength / self.factor)
|
@@ -410,16 +424,97 @@ class Broker:
|
|
410
424
|
self.mqtt_client.loop_start()
|
411
425
|
|
412
426
|
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
427
|
+
def strictly_positive(value: str) -> int:
|
428
|
+
"""
|
429
|
+
Validates that the provided value is a strictly positive integer.
|
430
|
+
|
431
|
+
This function is intended to be used as a type for argparse arguments.
|
432
|
+
|
433
|
+
Args:
|
434
|
+
value (str): The value to validate.
|
435
|
+
|
436
|
+
Returns:
|
437
|
+
int: The validated integer value.
|
438
|
+
|
439
|
+
Raises:
|
440
|
+
argparse.ArgumentTypeError: If the value is not a strictly positive integer.
|
441
|
+
"""
|
442
|
+
try:
|
443
|
+
strictly_positive_value = int(value)
|
444
|
+
if strictly_positive_value < 1:
|
445
|
+
raise argparse.ArgumentTypeError("Value must be larger than or equal to 1.")
|
446
|
+
return strictly_positive_value
|
447
|
+
except ValueError:
|
448
|
+
raise argparse.ArgumentTypeError(f"Invalid integer value specified: '{value}'")
|
449
|
+
|
450
|
+
|
451
|
+
def port(value: str) -> int:
|
452
|
+
"""
|
453
|
+
Convert a string value to an integer representing a valid port number.
|
454
|
+
|
455
|
+
This function is intended to be used as a type for argparse arguments.
|
456
|
+
|
457
|
+
Args:
|
458
|
+
value (str): The string representation of the port number.
|
459
|
+
Returns:
|
460
|
+
int: The port number as an integer.
|
461
|
+
Raises:
|
462
|
+
argparse.ArgumentTypeError: If the port number is not an integer or out of the valid range (1-65535).
|
463
|
+
"""
|
464
|
+
|
465
|
+
try:
|
466
|
+
port = int(value)
|
467
|
+
if port < 1 or port > 65535:
|
468
|
+
raise argparse.ArgumentTypeError("Port number must be between 1 and 65535.")
|
469
|
+
return port
|
470
|
+
except ValueError:
|
471
|
+
raise argparse.ArgumentTypeError(f"Invalid port number specified: '{value}'")
|
472
|
+
|
473
|
+
|
474
|
+
def case(value: str) -> str:
|
475
|
+
"""
|
476
|
+
Validates that the given value is a valid case name consisting of
|
477
|
+
alphanumeric characters and underscores only.
|
478
|
+
|
479
|
+
This function is intended to be used as a type for argparse arguments.
|
480
|
+
|
481
|
+
Args:
|
482
|
+
value (str): The case name to validate.
|
483
|
+
|
484
|
+
Returns:
|
485
|
+
str: The validated case name if it matches the required pattern.
|
486
|
+
|
487
|
+
Raises:
|
488
|
+
argparse.ArgumentTypeError: If the case name does not match the required pattern.
|
489
|
+
"""
|
490
|
+
|
491
|
+
if re.match(r"^[a-zA-Z0-9_]+$", value):
|
492
|
+
return value
|
493
|
+
|
494
|
+
raise argparse.ArgumentTypeError(f"Invalid case name specified: '{value}'")
|
495
|
+
|
496
|
+
|
497
|
+
@arg(
|
498
|
+
"--mqtt-peers",
|
499
|
+
type=strictly_positive,
|
500
|
+
dest="peers",
|
501
|
+
default=1,
|
502
|
+
help="minimum number of peers to await for first alias",
|
503
|
+
)
|
504
|
+
@arg(
|
505
|
+
"--mqtt-case",
|
506
|
+
type=case,
|
507
|
+
dest="case",
|
508
|
+
help="case name (broker will determine if you are allowed to access this data)",
|
509
|
+
)
|
510
|
+
@arg("--mqtt-port", type=port, dest="port", default=443, help="broker connection port")
|
511
|
+
@arg("--mqtt-broker", default="localhost", dest="broker", help="broker ip-address")
|
512
|
+
@arg("--mqtt-key", type=Path, dest="key", required=True, help="private key file")
|
513
|
+
@arg("--mqtt-crt", type=Path, dest="crt", required=True, help="client certificate file")
|
514
|
+
@arg("--mqtt-ca", type=Path, dest="ca", required=True, help="certificate authority file")
|
420
515
|
@arg("--mqtt-command", dest="command", help="direct command to client(s)")
|
421
516
|
@arg("--mqtt-diag", action="store_true", dest="diag", help="show MQTT diagnostic information")
|
422
|
-
@arg("--mqtt-username", dest="username", help="Username for connection")
|
517
|
+
@arg("--mqtt-username", dest="username", default="mqtt-loader", help="Username for connection")
|
423
518
|
@arg("--mqtt-password", action="store_true", dest="password", help="Ask for password before connecting")
|
424
519
|
class MQTTLoader(Loader):
|
425
520
|
"""Load remote targets through a broker."""
|
@@ -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/loaders/vma.py
CHANGED
dissect/target/loaders/xva.py
CHANGED
dissect/target/plugin.py
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
"""Dissect plugin system.
|
2
2
|
|
3
|
-
See dissect/target/plugins/general/example.py for an example plugin.
|
3
|
+
See ``dissect/target/plugins/general/example.py`` for an example plugin.
|
4
4
|
"""
|
5
5
|
|
6
6
|
from __future__ import annotations
|
@@ -57,17 +57,18 @@ log = logging.getLogger(__name__)
|
|
57
57
|
|
58
58
|
|
59
59
|
class OperatingSystem(StrEnum):
|
60
|
-
|
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
|
-
|
69
|
-
FORTIOS = "fortios"
|
70
|
-
CITRIX = "citrix-netscaler"
|
71
|
+
WINDOWS = "windows"
|
71
72
|
|
72
73
|
|
73
74
|
def export(*args, **kwargs) -> Callable:
|
@@ -76,18 +77,19 @@ def export(*args, **kwargs) -> Callable:
|
|
76
77
|
Supported keyword arguments:
|
77
78
|
property (bool): Whether this export should be regarded as a property.
|
78
79
|
Properties are implicitly cached.
|
80
|
+
|
79
81
|
cache (bool): Whether the result of this function should be cached.
|
82
|
+
|
80
83
|
record (RecordDescriptor): The :class:`flow.record.RecordDescriptor` for the records that this function yields.
|
81
84
|
If the records are dynamically made, use DynamicRecord instead.
|
82
|
-
output (str): The output type of this function. Can be one of:
|
83
85
|
|
86
|
+
output (str): The output type of this function. Must be one of:
|
84
87
|
- default: Single return value
|
85
88
|
- record: Yields records. Implicit when record argument is given.
|
86
89
|
- yield: Yields printable values.
|
87
90
|
- none: No return value. Plugin is responsible for output formatting and should return ``None``.
|
88
91
|
|
89
92
|
The ``export`` decorator adds some additional private attributes to an exported method or property:
|
90
|
-
|
91
93
|
- ``__output__``: The output type to expect for this function, this is the same as ``output``.
|
92
94
|
- ``__record__``: The type of record to expect, this value is the same as ``record``.
|
93
95
|
- ``__exported__``: set to ``True`` to indicate the method or property is exported.
|
@@ -173,7 +175,7 @@ class Plugin:
|
|
173
175
|
class attribute. Namespacing results in your plugin needing to be prefixed
|
174
176
|
with this namespace when being called. For example, if your plugin has
|
175
177
|
specified ``test`` as namespace and a function called ``example``, you must
|
176
|
-
call your plugin with ``test.example
|
178
|
+
call your plugin with ``test.example``.
|
177
179
|
|
178
180
|
A ``Plugin`` class has the following private class attributes:
|
179
181
|
|
@@ -430,15 +432,13 @@ def register(plugincls: Type[Plugin]) -> None:
|
|
430
432
|
"""Register a plugin, and put related data inside :attr:`PLUGINS`.
|
431
433
|
|
432
434
|
This function uses the following private attributes that are set using decorators:
|
433
|
-
|
434
|
-
|
435
|
-
- ``__internal__``: Set in :func:`internal`.
|
435
|
+
- ``__exported__``: Set in :func:`export`.
|
436
|
+
- ``__internal__``: Set in :func:`internal`.
|
436
437
|
|
437
438
|
Additionally, ``register`` sets the following private attributes on the `plugincls`:
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
- ``__exports__``: A list of all the methods or properties that were explicitly exported.
|
439
|
+
- ``__plugin__``: Always set to ``True``.
|
440
|
+
- ``__functions__``: A list of all the methods and properties that are ``__internal__`` or ``__exported__``.
|
441
|
+
- ``__exports__``: A list of all the methods or properties that were explicitly exported.
|
442
442
|
|
443
443
|
Args:
|
444
444
|
plugincls: A plugin class to register.
|
@@ -807,7 +807,7 @@ def load(plugin_desc: PluginDescriptor) -> Type[Plugin]:
|
|
807
807
|
module = importlib.import_module(module)
|
808
808
|
return getattr(module, plugin_desc["class"])
|
809
809
|
except Exception as e:
|
810
|
-
raise PluginError(f"An exception occurred while trying to load a plugin: {module}"
|
810
|
+
raise PluginError(f"An exception occurred while trying to load a plugin: {module}") from e
|
811
811
|
|
812
812
|
|
813
813
|
def failed() -> list[dict[str, Any]]:
|
@@ -1138,6 +1138,9 @@ class PluginFunction:
|
|
1138
1138
|
method_name: str
|
1139
1139
|
plugin_desc: PluginDescriptor = field(hash=False)
|
1140
1140
|
|
1141
|
+
def __repr__(self) -> str:
|
1142
|
+
return self.path
|
1143
|
+
|
1141
1144
|
|
1142
1145
|
def plugin_function_index(target: Optional[Target]) -> tuple[dict[str, PluginDescriptor], set[str]]:
|
1143
1146
|
"""Returns an index-list for plugins.
|
@@ -1292,7 +1295,7 @@ def find_plugin_functions(
|
|
1292
1295
|
path=index_name,
|
1293
1296
|
class_object=loaded_plugin_object,
|
1294
1297
|
method_name=method_name,
|
1295
|
-
output_type=getattr(fobject, "__output__", "
|
1298
|
+
output_type=getattr(fobject, "__output__", "none"),
|
1296
1299
|
plugin_desc=func,
|
1297
1300
|
)
|
1298
1301
|
)
|
@@ -1337,7 +1340,7 @@ def find_plugin_functions(
|
|
1337
1340
|
path=f"{description['module']}.{funcname}",
|
1338
1341
|
class_object=loaded_plugin_object,
|
1339
1342
|
method_name=funcname,
|
1340
|
-
output_type=getattr(fobject, "__output__", "
|
1343
|
+
output_type=getattr(fobject, "__output__", "none"),
|
1341
1344
|
plugin_desc=description,
|
1342
1345
|
)
|
1343
1346
|
)
|
@@ -608,6 +608,15 @@ def remove_padding(decrypted: bytes) -> bytes:
|
|
608
608
|
|
609
609
|
|
610
610
|
def decrypt_v10(encrypted_password: bytes) -> str:
|
611
|
+
"""Decrypt a version 10 encrypted password.
|
612
|
+
|
613
|
+
Args:
|
614
|
+
encrypted_password: The encrypted password bytes.
|
615
|
+
|
616
|
+
Returns:
|
617
|
+
Decrypted password string.
|
618
|
+
"""
|
619
|
+
|
611
620
|
if not HAS_CRYPTO:
|
612
621
|
raise ValueError("Missing pycryptodome dependency for AES operation")
|
613
622
|
|
@@ -625,12 +634,24 @@ def decrypt_v10(encrypted_password: bytes) -> str:
|
|
625
634
|
|
626
635
|
|
627
636
|
def decrypt_v10_2(encrypted_password: bytes, key: bytes) -> str:
|
628
|
-
"""
|
629
|
-
|
630
|
-
|
631
|
-
|
632
|
-
|
633
|
-
|
637
|
+
"""Decrypt a version 10 type 2 password.
|
638
|
+
|
639
|
+
References:
|
640
|
+
|
641
|
+
.. code-block::
|
642
|
+
|
643
|
+
struct chrome_pass {
|
644
|
+
byte signature[3] = 'v10';
|
645
|
+
byte iv[12];
|
646
|
+
byte ciphertext[EOF];
|
647
|
+
}
|
648
|
+
|
649
|
+
Args:
|
650
|
+
encrypted_password: The encrypted password bytes.
|
651
|
+
key: The encryption key.
|
652
|
+
|
653
|
+
Returns:
|
654
|
+
Decrypted password string.
|
634
655
|
"""
|
635
656
|
|
636
657
|
if not HAS_CRYPTO:
|