dissect.target 3.20.dev27__py3-none-any.whl → 3.20.dev29__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/helpers/configutil.py +74 -0
- dissect/target/helpers/regutil.py +41 -21
- dissect/target/plugins/apps/other/env.py +56 -0
- {dissect.target-3.20.dev27.dist-info → dissect.target-3.20.dev29.dist-info}/METADATA +1 -1
- {dissect.target-3.20.dev27.dist-info → dissect.target-3.20.dev29.dist-info}/RECORD +10 -9
- {dissect.target-3.20.dev27.dist-info → dissect.target-3.20.dev29.dist-info}/COPYRIGHT +0 -0
- {dissect.target-3.20.dev27.dist-info → dissect.target-3.20.dev29.dist-info}/LICENSE +0 -0
- {dissect.target-3.20.dev27.dist-info → dissect.target-3.20.dev29.dist-info}/WHEEL +0 -0
- {dissect.target-3.20.dev27.dist-info → dissect.target-3.20.dev29.dist-info}/entry_points.txt +0 -0
- {dissect.target-3.20.dev27.dist-info → dissect.target-3.20.dev29.dist-info}/top_level.txt +0 -0
@@ -2,6 +2,7 @@ from __future__ import annotations
|
|
2
2
|
|
3
3
|
import io
|
4
4
|
import json
|
5
|
+
import logging
|
5
6
|
import re
|
6
7
|
import sys
|
7
8
|
from collections import deque
|
@@ -47,6 +48,9 @@ except ImportError:
|
|
47
48
|
HAS_TOML = False
|
48
49
|
|
49
50
|
|
51
|
+
log = logging.getLogger(__name__)
|
52
|
+
|
53
|
+
|
50
54
|
def _update_dictionary(current: dict[str, Any], key: str, value: Any) -> None:
|
51
55
|
if prev_value := current.get(key):
|
52
56
|
if isinstance(prev_value, dict):
|
@@ -465,6 +469,76 @@ class Toml(ConfigurationParser):
|
|
465
469
|
raise ConfigurationParsingError("Failed to parse file, please install tomli.")
|
466
470
|
|
467
471
|
|
472
|
+
class Env(ConfigurationParser):
|
473
|
+
"""Parses ``.env`` file contents according to Docker and bash specification.
|
474
|
+
|
475
|
+
Does not apply interpolation of substituted values, eg. ``foo=${bar}`` and does not attempt
|
476
|
+
to parse list or dict strings. Does not support dynamic env files, eg. `` foo=`bar` ``. Also
|
477
|
+
does not support multi-line key/value assignments (yet).
|
478
|
+
|
479
|
+
Resources:
|
480
|
+
- https://docs.docker.com/compose/environment-variables/variable-interpolation/#env-file-syntax
|
481
|
+
- https://github.com/theskumar/python-dotenv/blob/main/src/dotenv/parser.py
|
482
|
+
"""
|
483
|
+
|
484
|
+
RE_KV = re.compile(r"^(?P<key>.+?)=(?P<value>(\".+?\")|(\'.+?\')|(.*?))?(?P<comment> \#.+?)?$")
|
485
|
+
|
486
|
+
def __init__(self, comments: bool = True, *args, **kwargs) -> None:
|
487
|
+
super().__init__(*args, **kwargs)
|
488
|
+
self.comments = comments
|
489
|
+
self.parsed_data: dict | tuple[dict, str | None] = {}
|
490
|
+
|
491
|
+
def parse_file(self, fh: TextIO) -> None:
|
492
|
+
for line in fh.readlines():
|
493
|
+
# Blank lines are ignored.
|
494
|
+
# Lines beginning with ``#`` are processed as comments and ignored.
|
495
|
+
if not line or line[0] == "#" or "=" not in line:
|
496
|
+
continue
|
497
|
+
|
498
|
+
# Each line represents a key-value pair. Values can optionally be quoted.
|
499
|
+
# Inline comments for unquoted values must be preceded with a space.
|
500
|
+
# Value may be empty.
|
501
|
+
match = self.RE_KV.match(line)
|
502
|
+
|
503
|
+
# Line could be invalid
|
504
|
+
if not match:
|
505
|
+
log.warning("Could not parse line in %s: '%s'", fh, line)
|
506
|
+
continue
|
507
|
+
|
508
|
+
key = match.groupdict()["key"]
|
509
|
+
value = match.groupdict().get("value") or ""
|
510
|
+
value = value.strip()
|
511
|
+
comment = match.groupdict().get("comment")
|
512
|
+
comment = comment.replace(" # ", "", 1) if comment else None
|
513
|
+
|
514
|
+
# Surrounding whitespace characters are removed, unless quoted.
|
515
|
+
if value and ((value[0] == '"' and value[-1] == '"') or (value[0] == "'" and value[-1] == "'")):
|
516
|
+
is_quoted = True
|
517
|
+
value = value.strip("\"'")
|
518
|
+
else:
|
519
|
+
is_quoted = False
|
520
|
+
value = value.strip()
|
521
|
+
|
522
|
+
# Unquoted values may start with a quote if they are properly escaped.
|
523
|
+
if not is_quoted and value[:2] in ["\\'", '\\"']:
|
524
|
+
value = value[1:]
|
525
|
+
|
526
|
+
# Interpret boolean values
|
527
|
+
if value.lower() in ["1", "true"]:
|
528
|
+
value = True
|
529
|
+
elif value.lower() in ["0", "false"]:
|
530
|
+
value = False
|
531
|
+
|
532
|
+
# Interpret integer values
|
533
|
+
if isinstance(value, str) and re.match(r"^[0-9]{1,}$", value):
|
534
|
+
value = int(value)
|
535
|
+
|
536
|
+
if key.strip() in self.parsed_data:
|
537
|
+
log.warning("Duplicate environment key '%s' in file %s", key.strip(), fh)
|
538
|
+
|
539
|
+
self.parsed_data[key.strip()] = (value, comment) if self.comments else value
|
540
|
+
|
541
|
+
|
468
542
|
class ScopeManager:
|
469
543
|
"""A (context)manager for dictionary scoping.
|
470
544
|
|
@@ -3,9 +3,10 @@ from __future__ import annotations
|
|
3
3
|
|
4
4
|
import fnmatch
|
5
5
|
import re
|
6
|
-
import struct
|
7
6
|
from collections import defaultdict
|
8
7
|
from datetime import datetime
|
8
|
+
from enum import IntEnum
|
9
|
+
from functools import cached_property
|
9
10
|
from io import BytesIO
|
10
11
|
from pathlib import Path
|
11
12
|
from typing import BinaryIO, Iterator, Optional, TextIO, Union
|
@@ -28,6 +29,19 @@ ValueType = Union[int, str, bytes, list[str]]
|
|
28
29
|
"""The possible value types that can be returned from the registry."""
|
29
30
|
|
30
31
|
|
32
|
+
class RegistryValueType(IntEnum):
|
33
|
+
NONE = regf.REG_NONE
|
34
|
+
SZ = regf.REG_SZ
|
35
|
+
EXPAND_SZ = regf.REG_EXPAND_SZ
|
36
|
+
BINARY = regf.REG_BINARY
|
37
|
+
DWORD = regf.REG_DWORD
|
38
|
+
DWORD_BIG_ENDIAN = regf.REG_DWORD_BIG_ENDIAN
|
39
|
+
MULTI_SZ = regf.REG_MULTI_SZ
|
40
|
+
FULL_RESOURCE_DESCRIPTOR = regf.REG_FULL_RESOURCE_DESCRIPTOR
|
41
|
+
RESOURCE_REQUIREMENTS_LIST = regf.REG_RESOURCE_REQUIREMENTS_LIST
|
42
|
+
QWORD = regf.REG_QWORD
|
43
|
+
|
44
|
+
|
31
45
|
class RegistryHive:
|
32
46
|
"""Base class for registry hives."""
|
33
47
|
|
@@ -405,8 +419,8 @@ class VirtualValue(RegistryValue):
|
|
405
419
|
return self._value
|
406
420
|
|
407
421
|
@property
|
408
|
-
def type(self) ->
|
409
|
-
return
|
422
|
+
def type(self) -> RegistryValueType:
|
423
|
+
return RegistryValueType.NONE
|
410
424
|
|
411
425
|
|
412
426
|
class HiveCollection(RegistryHive):
|
@@ -683,8 +697,8 @@ class RegfValue(RegistryValue):
|
|
683
697
|
return self.kv.value
|
684
698
|
|
685
699
|
@property
|
686
|
-
def type(self) ->
|
687
|
-
return self.kv.type
|
700
|
+
def type(self) -> RegistryValueType:
|
701
|
+
return RegistryValueType(self.kv.type)
|
688
702
|
|
689
703
|
|
690
704
|
class RegFlex:
|
@@ -750,17 +764,22 @@ class RegFlexKey(VirtualKey):
|
|
750
764
|
|
751
765
|
class RegFlexValue(VirtualValue):
|
752
766
|
def __init__(self, hive: RegistryHive, name: str, value: ValueType):
|
753
|
-
self._parsed_value = None
|
754
767
|
super().__init__(hive, name, value)
|
755
768
|
|
769
|
+
@cached_property
|
770
|
+
def _parse(self) -> tuple[RegistryValueType, ValueType]:
|
771
|
+
return parse_flex_value(self._value)
|
772
|
+
|
756
773
|
@property
|
757
774
|
def value(self) -> ValueType:
|
758
|
-
|
759
|
-
|
760
|
-
|
775
|
+
return self._parse[1]
|
776
|
+
|
777
|
+
@property
|
778
|
+
def type(self) -> RegistryValueType:
|
779
|
+
return self._parse[0]
|
761
780
|
|
762
781
|
|
763
|
-
def parse_flex_value(value: str) -> ValueType:
|
782
|
+
def parse_flex_value(value: str) -> tuple[RegistryValueType, ValueType]:
|
764
783
|
"""Parse values from text registry exports.
|
765
784
|
|
766
785
|
Args:
|
@@ -770,31 +789,31 @@ def parse_flex_value(value: str) -> ValueType:
|
|
770
789
|
NotImplementedError: If ``value`` is not of a supported type for parsing.
|
771
790
|
"""
|
772
791
|
if value.startswith('"'):
|
773
|
-
return value.strip('"')
|
792
|
+
return RegistryValueType.SZ, value.strip('"')
|
774
793
|
|
775
794
|
vtype, _, value = value.partition(":")
|
776
795
|
if vtype == "dword":
|
777
|
-
return
|
796
|
+
return RegistryValueType.DWORD, int.from_bytes(bytes.fromhex(value), "big", signed=True)
|
778
797
|
elif "hex" in vtype:
|
779
798
|
value = bytes.fromhex(value.replace(",", ""))
|
780
799
|
if vtype == "hex":
|
781
|
-
return value
|
800
|
+
return RegistryValueType.BINARY, value
|
782
801
|
|
783
802
|
# hex(T)
|
784
803
|
# These values match regf type values
|
785
804
|
vtype = int(vtype[4:5], 16)
|
786
805
|
if vtype == regf.REG_NONE:
|
787
|
-
|
806
|
+
decoded = value if value else None
|
788
807
|
elif vtype == regf.REG_SZ:
|
789
|
-
|
808
|
+
decoded = regf.try_decode_sz(value)
|
790
809
|
elif vtype == regf.REG_EXPAND_SZ:
|
791
|
-
|
810
|
+
decoded = regf.try_decode_sz(value)
|
792
811
|
elif vtype == regf.REG_BINARY:
|
793
|
-
|
812
|
+
decoded = value
|
794
813
|
elif vtype == regf.REG_DWORD:
|
795
|
-
|
814
|
+
decoded = int.from_bytes(value, "little", signed=False)
|
796
815
|
elif vtype == regf.REG_DWORD_BIG_ENDIAN:
|
797
|
-
|
816
|
+
decoded = int.from_bytes(value, "big", signed=False)
|
798
817
|
elif vtype == regf.REG_MULTI_SZ:
|
799
818
|
d = BytesIO(value)
|
800
819
|
|
@@ -806,11 +825,12 @@ def parse_flex_value(value: str) -> ValueType:
|
|
806
825
|
|
807
826
|
r.append(s)
|
808
827
|
|
809
|
-
|
828
|
+
decoded = r
|
810
829
|
elif vtype == regf.REG_QWORD:
|
811
|
-
|
830
|
+
decoded = int.from_bytes(value, "big", signed=False)
|
812
831
|
else:
|
813
832
|
raise NotImplementedError(f"Registry flex value type {vtype}")
|
833
|
+
return RegistryValueType(vtype), decoded
|
814
834
|
|
815
835
|
|
816
836
|
def has_glob_magic(pattern: str) -> bool:
|
@@ -0,0 +1,56 @@
|
|
1
|
+
from typing import Iterator
|
2
|
+
|
3
|
+
from dissect.target.helpers.configutil import Env
|
4
|
+
from dissect.target.helpers.record import TargetRecordDescriptor
|
5
|
+
from dissect.target.plugin import Plugin, arg, export
|
6
|
+
|
7
|
+
EnvironmentFileRecord = TargetRecordDescriptor(
|
8
|
+
"application/other/file/environment",
|
9
|
+
[
|
10
|
+
("datetime", "ts_mtime"),
|
11
|
+
("string", "key"),
|
12
|
+
("string", "value"),
|
13
|
+
("string", "comment"),
|
14
|
+
("path", "path"),
|
15
|
+
],
|
16
|
+
)
|
17
|
+
|
18
|
+
|
19
|
+
class EnvironmentFilePlugin(Plugin):
|
20
|
+
"""Environment file plugin."""
|
21
|
+
|
22
|
+
def check_compatible(self) -> None:
|
23
|
+
# `--env-path` is provided at runtime
|
24
|
+
pass
|
25
|
+
|
26
|
+
@export(record=EnvironmentFileRecord)
|
27
|
+
@arg("--env-path", help="path to scan environment files in")
|
28
|
+
@arg("--extension", help="extension of files to scan", default="env")
|
29
|
+
def envfile(self, env_path: str, extension: str = "env") -> Iterator[EnvironmentFileRecord]:
|
30
|
+
"""Yield environment variables found in ``.env`` files at the provided path."""
|
31
|
+
|
32
|
+
if not env_path:
|
33
|
+
self.target.log.error("No ``--path`` provided!")
|
34
|
+
|
35
|
+
if not (path := self.target.fs.path(env_path)).exists():
|
36
|
+
self.target.log.error("Provided path %s does not exist!", path)
|
37
|
+
|
38
|
+
for file in path.glob("**/*." + extension):
|
39
|
+
if not file.is_file():
|
40
|
+
continue
|
41
|
+
|
42
|
+
mtime = file.lstat().st_mtime
|
43
|
+
|
44
|
+
with file.open("r") as fh:
|
45
|
+
parser = Env(comments=True)
|
46
|
+
parser.read_file(fh)
|
47
|
+
|
48
|
+
for key, (value, comment) in parser.parsed_data.items():
|
49
|
+
yield EnvironmentFileRecord(
|
50
|
+
ts_mtime=mtime,
|
51
|
+
key=key,
|
52
|
+
value=value,
|
53
|
+
comment=comment,
|
54
|
+
path=file,
|
55
|
+
_target=self.target,
|
56
|
+
)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: dissect.target
|
3
|
-
Version: 3.20.
|
3
|
+
Version: 3.20.dev29
|
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
|
@@ -46,7 +46,7 @@ dissect/target/filesystems/zip.py,sha256=BeNj23DOYfWuTm5V1V419ViJiMfBrO1VA5gP6rl
|
|
46
46
|
dissect/target/helpers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
47
47
|
dissect/target/helpers/cache.py,sha256=TXlJBdFRz6V9zKs903am4Yawr0maYw5kZY0RqklDQJM,8568
|
48
48
|
dissect/target/helpers/config.py,sha256=RMHnIuKJHINHiLrvKN3EyA0jFA1o6-pbeaycG8Pgrp8,2596
|
49
|
-
dissect/target/helpers/configutil.py,sha256=
|
49
|
+
dissect/target/helpers/configutil.py,sha256=mO2XwhzLhGjFQzg_zC8SNi24CQhye1fhkYlH5Q5HFm8,31365
|
50
50
|
dissect/target/helpers/cyber.py,sha256=WnJlk-HqAETmDAgLq92JPxyDLxvzSoFV_WrO-odVKBI,16805
|
51
51
|
dissect/target/helpers/descriptor_extensions.py,sha256=uT8GwznfDAiIgMM7JKKOY0PXKMv2c0GCqJTCkWFgops,2605
|
52
52
|
dissect/target/helpers/docs.py,sha256=J5U65Y3yOTqxDEZRCdrEmO63XQCeDzOJea1PwPM6Cyc,5146
|
@@ -62,7 +62,7 @@ dissect/target/helpers/polypath.py,sha256=h8p7m_OCNiQljGwoZh5Aflr9H2ot6CZr6WKq1O
|
|
62
62
|
dissect/target/helpers/protobuf.py,sha256=b4DsnqrRLrefcDjx7rQno-_LBcwtJXxuKf5RdOegzfE,1537
|
63
63
|
dissect/target/helpers/record.py,sha256=7Se6ZV8cvwEaGSjRd9bKhVnUAn4W4KR2eqP6AbQhTH4,5892
|
64
64
|
dissect/target/helpers/record_modifier.py,sha256=O_Jj7zOi891HIyAYjxxe6LFPYETHdMa5lNjo4NA_T_w,3969
|
65
|
-
dissect/target/helpers/regutil.py,sha256=
|
65
|
+
dissect/target/helpers/regutil.py,sha256=ti-ht2N9UxbMjhUBP2bybY76_dAvbCz0txPBszvSKVw,28171
|
66
66
|
dissect/target/helpers/shell_application_ids.py,sha256=hYxrP-YtHK7ZM0ectJFHfoMB8QUXLbYNKmKXMWLZRlA,38132
|
67
67
|
dissect/target/helpers/shell_folder_ids.py,sha256=Behhb8oh0kMxrEk6YYKYigCDZe8Hw5QS6iK_d2hTs2Y,24978
|
68
68
|
dissect/target/helpers/utils.py,sha256=K3xVq9D0FwIhTBAuiWN8ph7Pq2GABgG3hOz-3AmKuEA,4244
|
@@ -127,6 +127,7 @@ dissect/target/plugins/apps/browser/firefox.py,sha256=mZBBagFfIdiz9kUyK4Hi989I4g
|
|
127
127
|
dissect/target/plugins/apps/browser/iexplore.py,sha256=g_xw0toaiyjevxO8g9XPCOqc-CXZp39FVquRhPFGdTE,8801
|
128
128
|
dissect/target/plugins/apps/container/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
129
129
|
dissect/target/plugins/apps/container/docker.py,sha256=LTsZplaECSfO1Ysp_Y-9WsnNocsreu_iHO8fbSif3g0,16221
|
130
|
+
dissect/target/plugins/apps/other/env.py,sha256=_I12S_wjyT18WlUJ5cWOy5OTI140AheH6tq743iiyys,1874
|
130
131
|
dissect/target/plugins/apps/remoteaccess/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
131
132
|
dissect/target/plugins/apps/remoteaccess/anydesk.py,sha256=IdijK3F6ppaB_IgKL-xDljlEbb8l9S2U0xSWKqK9xRs,4294
|
132
133
|
dissect/target/plugins/apps/remoteaccess/remoteaccess.py,sha256=DWXkRDVUpFr1icK2fYwSXdZD204Xz0yRuO7rcJOwIwc,825
|
@@ -368,10 +369,10 @@ dissect/target/volumes/luks.py,sha256=OmCMsw6rCUXG1_plnLVLTpsvE1n_6WtoRUGQbpmu1z
|
|
368
369
|
dissect/target/volumes/lvm.py,sha256=wwQVR9I3G9YzmY6UxFsH2Y4MXGBcKL9aayWGCDTiWMU,2269
|
369
370
|
dissect/target/volumes/md.py,sha256=7ShPtusuLGaIv27SvEETtgsuoQyAa4iAAeOR1NEaajI,1689
|
370
371
|
dissect/target/volumes/vmfs.py,sha256=-LoUbn9WNwTtLi_4K34uV_-wDw2W5hgaqxZNj4UmqAQ,1730
|
371
|
-
dissect.target-3.20.
|
372
|
-
dissect.target-3.20.
|
373
|
-
dissect.target-3.20.
|
374
|
-
dissect.target-3.20.
|
375
|
-
dissect.target-3.20.
|
376
|
-
dissect.target-3.20.
|
377
|
-
dissect.target-3.20.
|
372
|
+
dissect.target-3.20.dev29.dist-info/COPYRIGHT,sha256=m-9ih2RVhMiXHI2bf_oNSSgHgkeIvaYRVfKTwFbnJPA,301
|
373
|
+
dissect.target-3.20.dev29.dist-info/LICENSE,sha256=DZak_2itbUtvHzD3E7GNUYSRK6jdOJ-GqncQ2weavLA,34523
|
374
|
+
dissect.target-3.20.dev29.dist-info/METADATA,sha256=5QkftvpVFTGaJunkmrOrcd8izTwayNKC5WbTqXhcMn8,12897
|
375
|
+
dissect.target-3.20.dev29.dist-info/WHEEL,sha256=GV9aMThwP_4oNCtvEC2ec3qUYutgWeAzklro_0m4WJQ,91
|
376
|
+
dissect.target-3.20.dev29.dist-info/entry_points.txt,sha256=BWuxAb_6AvUAQpIQOQU0IMTlaF6TDht2AIZK8bHd-zE,492
|
377
|
+
dissect.target-3.20.dev29.dist-info/top_level.txt,sha256=Mn-CQzEYsAbkxrUI0TnplHuXnGVKzxpDw_po_sXpvv4,8
|
378
|
+
dissect.target-3.20.dev29.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
{dissect.target-3.20.dev27.dist-info → dissect.target-3.20.dev29.dist-info}/entry_points.txt
RENAMED
File without changes
|
File without changes
|