dissect.target 3.17.dev37__py3-none-any.whl → 3.18__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
Files changed (79) hide show
  1. dissect/target/exceptions.py +4 -0
  2. dissect/target/filesystem.py +0 -10
  3. dissect/target/helpers/cache.py +3 -1
  4. dissect/target/helpers/hashutil.py +0 -40
  5. dissect/target/helpers/protobuf.py +6 -10
  6. dissect/target/helpers/ssh.py +3 -4
  7. dissect/target/loaders/mqtt.py +147 -2
  8. dissect/target/loaders/raw.py +7 -0
  9. dissect/target/plugins/apps/av/mcafee.py +3 -0
  10. dissect/target/plugins/apps/av/sophos.py +6 -0
  11. dissect/target/plugins/apps/av/symantec.py +6 -0
  12. dissect/target/plugins/apps/av/trendmicro.py +8 -3
  13. dissect/target/plugins/apps/browser/chromium.py +12 -0
  14. dissect/target/plugins/apps/browser/firefox.py +13 -1
  15. dissect/target/plugins/apps/browser/iexplore.py +6 -0
  16. dissect/target/plugins/apps/container/docker.py +1 -1
  17. dissect/target/plugins/os/unix/etc/__init__.py +0 -0
  18. dissect/target/plugins/os/unix/etc/etc.py +77 -0
  19. dissect/target/plugins/os/unix/history.py +1 -1
  20. dissect/target/plugins/os/unix/linux/cmdline.py +3 -0
  21. dissect/target/plugins/os/unix/linux/environ.py +3 -0
  22. dissect/target/plugins/os/unix/linux/processes.py +3 -0
  23. dissect/target/plugins/os/unix/linux/sockets.py +15 -0
  24. dissect/target/plugins/os/unix/locate/gnulocate.py +1 -2
  25. dissect/target/plugins/os/unix/locate/mlocate.py +3 -4
  26. dissect/target/plugins/os/unix/locate/plocate.py +34 -25
  27. dissect/target/plugins/os/unix/log/atop.py +6 -4
  28. dissect/target/plugins/os/unix/log/journal.py +5 -4
  29. dissect/target/plugins/os/unix/log/lastlog.py +2 -3
  30. dissect/target/plugins/os/unix/log/utmp.py +6 -7
  31. dissect/target/plugins/os/windows/_os.py +17 -6
  32. dissect/target/plugins/os/windows/activitiescache.py +3 -0
  33. dissect/target/plugins/os/windows/adpolicy.py +3 -4
  34. dissect/target/plugins/os/windows/catroot.py +6 -0
  35. dissect/target/plugins/os/windows/credhist.py +1 -2
  36. dissect/target/plugins/os/windows/datetime.py +3 -4
  37. dissect/target/plugins/os/windows/defender.py +221 -5
  38. dissect/target/plugins/os/windows/defender_helpers/__init__.py +0 -0
  39. dissect/target/plugins/os/windows/defender_helpers/defender_patterns.py +282 -0
  40. dissect/target/plugins/os/windows/defender_helpers/defender_records.py +191 -0
  41. dissect/target/plugins/os/windows/dpapi/blob.py +1 -2
  42. dissect/target/plugins/os/windows/dpapi/master_key.py +2 -3
  43. dissect/target/plugins/os/windows/lnk.py +3 -0
  44. dissect/target/plugins/os/windows/log/etl.py +9 -0
  45. dissect/target/plugins/os/windows/log/evt.py +3 -0
  46. dissect/target/plugins/os/windows/log/evtx.py +3 -0
  47. dissect/target/plugins/os/windows/log/pfro.py +3 -0
  48. dissect/target/plugins/os/windows/log/schedlgu.py +5 -2
  49. dissect/target/plugins/os/windows/notifications.py +1 -2
  50. dissect/target/plugins/os/windows/prefetch.py +32 -27
  51. dissect/target/plugins/os/windows/recyclebin.py +13 -8
  52. dissect/target/plugins/os/windows/regf/appxdebugkeys.py +3 -0
  53. dissect/target/plugins/os/windows/regf/auditpol.py +4 -5
  54. dissect/target/plugins/os/windows/regf/bam.py +5 -3
  55. dissect/target/plugins/os/windows/regf/cit.py +1 -2
  56. dissect/target/plugins/os/windows/regf/clsid.py +3 -0
  57. dissect/target/plugins/os/windows/regf/firewall.py +3 -0
  58. dissect/target/plugins/os/windows/regf/muicache.py +3 -0
  59. dissect/target/plugins/os/windows/regf/recentfilecache.py +6 -4
  60. dissect/target/plugins/os/windows/regf/regf.py +6 -0
  61. dissect/target/plugins/os/windows/regf/runkeys.py +3 -0
  62. dissect/target/plugins/os/windows/regf/shellbags.py +9 -7
  63. dissect/target/plugins/os/windows/regf/shimcache.py +5 -3
  64. dissect/target/plugins/os/windows/regf/trusteddocs.py +3 -0
  65. dissect/target/plugins/os/windows/regf/usb.py +3 -0
  66. dissect/target/plugins/os/windows/regf/userassist.py +8 -6
  67. dissect/target/plugins/os/windows/sam.py +7 -5
  68. dissect/target/plugins/os/windows/services.py +3 -0
  69. dissect/target/plugins/os/windows/task_helpers/tasks_job.py +3 -4
  70. dissect/target/plugins/os/windows/wer.py +3 -0
  71. dissect/target/target.py +6 -1
  72. dissect/target/tools/shell.py +30 -19
  73. {dissect.target-3.17.dev37.dist-info → dissect.target-3.18.dist-info}/METADATA +49 -24
  74. {dissect.target-3.17.dev37.dist-info → dissect.target-3.18.dist-info}/RECORD +79 -74
  75. {dissect.target-3.17.dev37.dist-info → dissect.target-3.18.dist-info}/WHEEL +1 -1
  76. {dissect.target-3.17.dev37.dist-info → dissect.target-3.18.dist-info}/COPYRIGHT +0 -0
  77. {dissect.target-3.17.dev37.dist-info → dissect.target-3.18.dist-info}/LICENSE +0 -0
  78. {dissect.target-3.17.dev37.dist-info → dissect.target-3.18.dist-info}/entry_points.txt +0 -0
  79. {dissect.target-3.17.dev37.dist-info → dissect.target-3.18.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,77 @@
1
+ import fnmatch
2
+ import logging
3
+ import re
4
+ from pathlib import Path
5
+ from typing import Iterator
6
+
7
+ from dissect.target import Target
8
+ from dissect.target.helpers.record import TargetRecordDescriptor
9
+ from dissect.target.plugin import arg, export
10
+ from dissect.target.plugins.general.config import (
11
+ ConfigurationEntry,
12
+ ConfigurationTreePlugin,
13
+ )
14
+
15
+ UnixConfigTreeRecord = TargetRecordDescriptor(
16
+ "unix/config",
17
+ [
18
+ ("path", "source"),
19
+ ("path", "path"),
20
+ ("string", "key"),
21
+ ("string[]", "value"),
22
+ ],
23
+ )
24
+
25
+ log = logging.getLogger(__name__)
26
+
27
+
28
+ class EtcTree(ConfigurationTreePlugin):
29
+ __namespace__ = "etc"
30
+
31
+ def __init__(self, target: Target):
32
+ super().__init__(target, "/etc")
33
+
34
+ def _sub(self, items: ConfigurationEntry, entry: Path, pattern: str) -> Iterator[UnixConfigTreeRecord]:
35
+ index = 0
36
+ config_entry = items
37
+ if not isinstance(items, dict):
38
+ items = items.as_dict()
39
+
40
+ for raw_key, value in items.items():
41
+ key = re.sub(r"[\n\r\t]", "", raw_key)
42
+ path = Path(entry) / Path(key)
43
+
44
+ if isinstance(value, dict):
45
+ yield from self._sub(value, path, pattern)
46
+ continue
47
+
48
+ if not isinstance(value, list):
49
+ value = [str(value)]
50
+
51
+ if fnmatch.fnmatch(path, pattern):
52
+ data = {
53
+ "_target": self.target,
54
+ "source": self.target.fs.path(config_entry.entry.path),
55
+ "path": path,
56
+ "key": key,
57
+ "value": value,
58
+ }
59
+ if value == [""]:
60
+ data["key"] = index
61
+ data["value"] = [key]
62
+ index += 1
63
+
64
+ yield UnixConfigTreeRecord(**data)
65
+
66
+ @export(record=UnixConfigTreeRecord)
67
+ @arg("--glob", dest="pattern", required=False, default="*", type=str, help="Glob-style pattern to search for")
68
+ def etc(self, pattern: str) -> Iterator[UnixConfigTreeRecord]:
69
+ for entry, subs, items in self.config_fs.walk("/"):
70
+ for item in items:
71
+ try:
72
+ config_object = self.get(str(Path(entry) / Path(item)))
73
+ if isinstance(config_object, ConfigurationEntry):
74
+ yield from self._sub(config_object, Path(entry) / Path(item), pattern)
75
+ except Exception:
76
+ log.warning("Could not open configuration item: %s", item)
77
+ pass
@@ -52,7 +52,7 @@ class CommandHistoryPlugin(Plugin):
52
52
  for user_details in self.target.user_details.all_with_home():
53
53
  for shell, history_relative_path in self.COMMAND_HISTORY_RELATIVE_PATHS:
54
54
  history_path = user_details.home_path.joinpath(history_relative_path)
55
- if history_path.exists():
55
+ if history_path.is_file():
56
56
  history_files.append((shell, history_path, user_details.user))
57
57
  return history_files
58
58
 
@@ -29,6 +29,9 @@ class CmdlinePlugin(Plugin):
29
29
  Think of this output as the command line that the process wants you to see.
30
30
 
31
31
  Yields CmdlineRecord with the following fields:
32
+
33
+ .. code-block:: text
34
+
32
35
  hostname (string): The target hostname.
33
36
  domain (string): The target domain.
34
37
  ts (datetime): The starttime of the process.
@@ -27,6 +27,9 @@ class EnvironPlugin(Plugin):
27
27
  the environ(7) variable directly), this plugin will not reflect those changes.
28
28
 
29
29
  Yields EnvironmentVariableRecord with the following fields:
30
+
31
+ .. code-block:: text
32
+
30
33
  hostname (string): The target hostname.
31
34
  domain (string): The target domain.
32
35
  ts (datetime): The modification timestamp of the processes' environ file.
@@ -29,6 +29,9 @@ class ProcProcesses(Plugin):
29
29
  Each ``/proc/[pid]`` subdirectory contains various pseudo-files.
30
30
 
31
31
  Yields ProcProcessRecord with the following fields:
32
+
33
+ .. code-block:: text
34
+
32
35
  hostname (string): The target hostname.
33
36
  domain (string): The target domain.
34
37
  ts (datetime): The start time of the process.
@@ -78,6 +78,9 @@ class NetSocketPlugin(Plugin):
78
78
  """This plugin yields the packet sockets and available stats associated with them.
79
79
 
80
80
  Yields PacketSocketRecord with the following fields:
81
+
82
+ .. code-block:: text
83
+
81
84
  hostname (string): The target hostname.
82
85
  domain (string): The target domain.
83
86
  protocol (int): The captured protocol i.e. 0003 is ETH_P_ALL
@@ -101,6 +104,9 @@ class NetSocketPlugin(Plugin):
101
104
  """This plugin yields the unix sockets and available stats associated with them.
102
105
 
103
106
  Yields UnixSocketRecord with the following fields:
107
+
108
+ .. code-block:: text
109
+
104
110
  hostname (string): The target hostname.
105
111
  domain (string): The target domain.
106
112
  protocol (string): The protocol used by the socket.
@@ -117,6 +123,9 @@ class NetSocketPlugin(Plugin):
117
123
  """This plugin yields the raw and raw6 sockets and available stats associated with them.
118
124
 
119
125
  Yields NetSocketRecord with the following fields:
126
+
127
+ .. code-block:: text
128
+
120
129
  hostname (string): The target hostname.
121
130
  domain (string): The target domain.
122
131
  protocol (string): The protocol used by the socket.
@@ -140,6 +149,9 @@ class NetSocketPlugin(Plugin):
140
149
  """This plugin yields the udp and udp6 sockets and available stats associated with them.
141
150
 
142
151
  Yields NetSocketRecord with the following fields:
152
+
153
+ .. code-block:: text
154
+
143
155
  hostname (string): The target hostname.
144
156
  domain (string): The target domain.
145
157
  protocol (string): The protocol used by the socket.
@@ -163,6 +175,9 @@ class NetSocketPlugin(Plugin):
163
175
  """This plugin yields the tcp and tcp6 sockets and available stats associated with them.
164
176
 
165
177
  Yields NetSocketRecord with the following fields:
178
+
179
+ .. code-block:: text
180
+
166
181
  hostname (string): The target hostname.
167
182
  domain (string): The target domain.
168
183
  protocol (string): The protocol used by the socket.
@@ -26,8 +26,7 @@ GNULocateRecord = TargetRecordDescriptor(
26
26
  ],
27
27
  )
28
28
 
29
- c_gnulocate = cstruct()
30
- c_gnulocate.load(gnulocate_def)
29
+ c_gnulocate = cstruct().load(gnulocate_def)
31
30
 
32
31
 
33
32
  class GNULocateFile:
@@ -20,10 +20,10 @@ struct header_config {
20
20
  int32 conf_size;
21
21
  int8 version; /* file format version */
22
22
  int8 require_visibility;
23
- int8 pad[2]; /* 32-bit total alignment */
23
+ int8 pad0[2]; /* 32-bit total alignment */
24
24
  char root_database;
25
25
  char config_block[conf_size];
26
- int8 pad;
26
+ int8 pad1;
27
27
  };
28
28
 
29
29
  enum DBE_TYPE: uint8 { /* database entry type */
@@ -68,8 +68,7 @@ MLocateRecord = TargetRecordDescriptor(
68
68
  ],
69
69
  )
70
70
 
71
- c_mlocate = cstruct(endian=">")
72
- c_mlocate.load(mlocate_def)
71
+ c_mlocate = cstruct(endian=">").load(mlocate_def)
73
72
 
74
73
 
75
74
  class MLocateFile:
@@ -1,8 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import platform
4
- from io import BytesIO
5
- from typing import BinaryIO, Iterable
4
+ from typing import BinaryIO, Iterator
6
5
 
7
6
  from dissect.cstruct import cstruct
8
7
  from dissect.util.stream import RangeStream
@@ -13,7 +12,11 @@ from dissect.target.plugin import export
13
12
  from dissect.target.plugins.os.unix.locate.locate import BaseLocatePlugin
14
13
 
15
14
  try:
16
- import zstandard # noqa
15
+ from zstandard import (
16
+ DECOMPRESSION_RECOMMENDED_OUTPUT_SIZE,
17
+ ZstdCompressionDict,
18
+ ZstdDecompressor,
19
+ )
17
20
 
18
21
  HAS_ZSTD = True
19
22
  except ImportError:
@@ -32,7 +35,7 @@ struct header {
32
35
  uint64_t filename_index_offset_bytes;
33
36
 
34
37
  /* Version 1 and up only. */
35
- uint32_t max_version;
38
+ uint32_t max_version; // Nominally 1 or 2, but can be increased if more features are added in a backward-compatible way.
36
39
  uint32_t zstd_dictionary_length_bytes;
37
40
  uint64_t zstd_dictionary_offset_bytes;
38
41
 
@@ -44,6 +47,7 @@ struct header {
44
47
  uint64_t conf_block_length_bytes;
45
48
  uint64_t conf_block_offset_bytes;
46
49
 
50
+ // Only if max_version >= 2.
47
51
  uint8_t check_visibility;
48
52
  char padding[7]; /* padding for alignment */
49
53
  };
@@ -51,7 +55,7 @@ struct header {
51
55
  struct file {
52
56
  char path[];
53
57
  };
54
- """
58
+ """ # noqa : E501
55
59
 
56
60
  PLocateRecord = TargetRecordDescriptor(
57
61
  "linux/locate/plocate",
@@ -61,8 +65,7 @@ PLocateRecord = TargetRecordDescriptor(
61
65
  ],
62
66
  )
63
67
 
64
- c_plocate = cstruct()
65
- c_plocate.load(plocate_def)
68
+ c_plocate = cstruct().load(plocate_def)
66
69
 
67
70
 
68
71
  class PLocateFile:
@@ -104,40 +107,46 @@ class PLocateFile:
104
107
  self.dict_data = None
105
108
 
106
109
  if self.header.zstd_dictionary_offset_bytes:
107
- self.dict_data = zstandard.ZstdCompressionDict(self.fh.read(self.header.zstd_dictionary_length_bytes))
110
+ self.dict_data = ZstdCompressionDict(self.fh.read(self.header.zstd_dictionary_length_bytes))
108
111
 
109
112
  self.compressed_length_bytes = (
110
113
  self.header.filename_index_offset_bytes - self.HEADER_SIZE - self.header.zstd_dictionary_length_bytes
111
114
  )
112
- self.ctx = zstandard.ZstdDecompressor(dict_data=self.dict_data)
115
+ self.ctx = ZstdDecompressor(dict_data=self.dict_data)
113
116
  self.buf = RangeStream(self.fh, self.fh.tell(), self.compressed_length_bytes)
114
117
 
115
- def __iter__(self) -> Iterable[PLocateFile]:
118
+ def __iter__(self) -> Iterator[PLocateFile]:
116
119
  # NOTE: This is a workaround for a PyPy bug
117
120
  # We don't know what breaks, but PyPy + zstandard = unhappy times
118
121
  # You just get random garbage data back instead of the decompressed data
119
122
  # This weird dance of using a decompressobj and unused data is the only way that seems to work
120
123
  # It's more expensive on memory, but at least it doesn't break
121
124
  if platform.python_implementation() == "PyPy":
122
- obj = self.ctx.decompressobj()
123
125
  buf = self.buf.read()
124
126
 
125
- tmp = obj.decompress(buf)
126
- while unused_data := obj.unused_data:
127
- obj = self.ctx.decompressobj()
128
- tmp += obj.decompress(unused_data)
127
+ def reader(ctx: ZstdDecompressor) -> Iterator[bytes]:
128
+ obj = ctx.decompressobj()
129
129
 
130
- reader = BytesIO(tmp)
130
+ yield obj.decompress(buf)
131
+ while unused_data := obj.unused_data:
132
+ obj = self.ctx.decompressobj()
133
+ yield obj.decompress(unused_data)
134
+
135
+ it = reader(self.ctx)
131
136
  else:
132
- reader = self.ctx.stream_reader(self.buf)
133
-
134
- with reader:
135
- try:
136
- while True:
137
- file = c_plocate.file(reader)
138
- yield file.path.decode(errors="surrogateescape")
139
- except EOFError:
140
- return
137
+ # NOTE: The end of a zstandard frame does not include a final `0x00`.
138
+ # This causes the c_plocate `file` struct to parse the last path and the first path on the next frame as one
139
+ # since cstruct will read it across frame boundaries waiting for a `0x00`.
140
+ def reader() -> Iterator[bytes]:
141
+ with self.ctx.stream_reader(self.buf) as reader:
142
+ while chunk := reader.read(DECOMPRESSION_RECOMMENDED_OUTPUT_SIZE):
143
+ yield chunk
144
+
145
+ it = reader()
146
+
147
+ for chunk in it:
148
+ for path in chunk.split(b"\x00"):
149
+ yield path.decode(errors="surrogateescape")
141
150
 
142
151
  def filename_index(self) -> bytes:
143
152
  """Return the filename index of the plocate.db file."""
@@ -2,7 +2,7 @@ import zlib
2
2
  from io import BytesIO
3
3
  from typing import BinaryIO, Iterator
4
4
 
5
- from dissect.cstruct import Instance, cstruct
5
+ from dissect.cstruct import cstruct
6
6
 
7
7
  from dissect.target.exceptions import UnsupportedPluginError
8
8
  from dissect.target.helpers.record import TargetRecordDescriptor
@@ -178,8 +178,7 @@ struct tstat {
178
178
  };
179
179
  """ # noqa: E501
180
180
 
181
- c_atop = cstruct()
182
- c_atop.load(atop_def)
181
+ c_atop = cstruct().load(atop_def)
183
182
  c_atop.load(atop_tstat_def, align=True)
184
183
 
185
184
  AtopRecord = TargetRecordDescriptor(
@@ -226,7 +225,7 @@ class AtopFile:
226
225
  self.header = c_atop.rawheader(self.fh)
227
226
  self.version = self.version()
228
227
 
229
- def __iter__(self) -> Iterator[Instance]:
228
+ def __iter__(self) -> Iterator[c_atop.tstat]:
230
229
  while True:
231
230
  try:
232
231
  record = c_atop.rawrecord(self.fh)
@@ -270,6 +269,9 @@ class AtopPlugin(Plugin):
270
269
  - https://diablohorn.com/2022/11/17/parsing-atop-files-with-python-dissect-cstruct/
271
270
 
272
271
  Yields AtopRecord with fields:
272
+
273
+ .. code-block:: text
274
+
273
275
  hostname (string): The target hostname.
274
276
  process (string): The process name.
275
277
  cmdline (string): The command-line of the process.
@@ -1,8 +1,10 @@
1
+ from __future__ import annotations
2
+
1
3
  import lzma
2
4
  from typing import BinaryIO, Callable, Iterator
3
5
 
4
6
  import zstandard
5
- from dissect.cstruct import Instance, cstruct
7
+ from dissect.cstruct import cstruct
6
8
  from dissect.util import ts
7
9
  from dissect.util.compression import lz4
8
10
 
@@ -252,8 +254,7 @@ struct EntryArrayObject_Compact {
252
254
  };
253
255
  """ # noqa: E501
254
256
 
255
- c_journal = cstruct()
256
- c_journal.load(journal_def)
257
+ c_journal = cstruct().load(journal_def)
257
258
 
258
259
 
259
260
  def get_optional(value: str, to_type: Callable):
@@ -314,7 +315,7 @@ class JournalFile:
314
315
 
315
316
  return key, value
316
317
 
317
- def __iter__(self) -> Iterator[Instance]:
318
+ def __iter__(self) -> Iterator[dict[str, int | str]]:
318
319
  "Iterate over the entry objects to read payloads."
319
320
 
320
321
  for offset in self.entry_object_offsets():
@@ -1,6 +1,6 @@
1
1
  from typing import BinaryIO
2
2
 
3
- from dissect import cstruct
3
+ from dissect.cstruct import cstruct
4
4
  from dissect.util import ts
5
5
 
6
6
  from dissect.target.exceptions import FileNotFoundError, UnsupportedPluginError
@@ -36,8 +36,7 @@ struct entry {
36
36
  };
37
37
  """
38
38
 
39
- c_lastlog = cstruct.cstruct()
40
- c_lastlog.load(lastlog_def)
39
+ c_lastlog = cstruct().load(lastlog_def)
41
40
 
42
41
 
43
42
  class LastLogFile:
@@ -39,14 +39,14 @@ WtmpRecord = TargetRecordDescriptor(
39
39
  ],
40
40
  )
41
41
 
42
- c_utmp = """
42
+ utmp_def = """
43
43
  #define UT_LINESIZE 32
44
44
  #define UT_NAMESIZE 32
45
45
  #define UT_HOSTSIZE 256
46
46
 
47
47
  typedef uint32 pid_t;
48
48
 
49
- enum Type : char {
49
+ enum Type : uint8_t {
50
50
  EMPTY = 0x0,
51
51
  RUN_LVL = 0x1,
52
52
  BOOT_TIME = 0x2,
@@ -84,8 +84,7 @@ struct entry {
84
84
  };
85
85
  """ # noqa: E501
86
86
 
87
- utmp = cstruct()
88
- utmp.load(c_utmp)
87
+ c_utmp = cstruct().load(utmp_def)
89
88
 
90
89
  UTMP_ENTRY = namedtuple(
91
90
  "UTMPRecord",
@@ -122,11 +121,11 @@ class UtmpFile:
122
121
 
123
122
  while True:
124
123
  try:
125
- entry = utmp.entry(byte_stream)
124
+ entry = c_utmp.entry(byte_stream)
126
125
 
127
126
  r_type = ""
128
- if entry.ut_type in utmp.Type.reverse:
129
- r_type = utmp.Type.reverse[entry.ut_type]
127
+ if entry.ut_type in c_utmp.Type:
128
+ r_type = c_utmp.Type(entry.ut_type).name
130
129
 
131
130
  ut_host = entry.ut_host.decode(errors="surrogateescape").strip("\x00")
132
131
  ut_addr = None
@@ -21,7 +21,7 @@ class WindowsPlugin(OSPlugin):
21
21
  self.add_mounts()
22
22
 
23
23
  target.props["sysvol_drive"] = next(
24
- (mnt for mnt, fs in target.fs.mounts.items() if fs is target.fs.mounts["sysvol"] and mnt != "sysvol"),
24
+ (mnt for mnt, fs in target.fs.mounts.items() if fs is target.fs.mounts.get("sysvol") and mnt != "sysvol"),
25
25
  None,
26
26
  )
27
27
 
@@ -78,13 +78,16 @@ class WindowsPlugin(OSPlugin):
78
78
  self.target.log.warning("Failed to map drive letters")
79
79
  self.target.log.debug("", exc_info=e)
80
80
 
81
+ sysvol_drive = self.target.fs.mounts.get("sysvol")
81
82
  # Fallback mount the sysvol to C: if we didn't manage to mount it to any other drive letter
82
- if operator.countOf(self.target.fs.mounts.values(), self.target.fs.mounts["sysvol"]) == 1:
83
+ if sysvol_drive and operator.countOf(self.target.fs.mounts.values(), sysvol_drive) == 1:
83
84
  if "c:" not in self.target.fs.mounts:
84
85
  self.target.log.debug("Unable to determine drive letter of sysvol, falling back to C:")
85
- self.target.fs.mount("c:", self.target.fs.mounts["sysvol"])
86
+ self.target.fs.mount("c:", sysvol_drive)
86
87
  else:
87
88
  self.target.log.warning("Unknown drive letter for sysvol")
89
+ else:
90
+ self.target.log.warning("No sysvol drive found")
88
91
 
89
92
  @export(property=True)
90
93
  def hostname(self) -> Optional[str]:
@@ -244,13 +247,21 @@ class WindowsPlugin(OSPlugin):
244
247
  if any(map(lambda value: value is not None, version_parts.values())):
245
248
  version = []
246
249
 
250
+ nt_version = _part_str(version_parts, "CurrentVersion")
251
+ build_version = _part_str(version_parts, "CurrentBuildNumber")
247
252
  prodcut_name = _part_str(version_parts, "ProductName")
248
- version.append(prodcut_name)
249
253
 
250
- nt_version = _part_str(version_parts, "CurrentVersion")
254
+ # CurrentBuildNumber >= 22000 on NT 10.0 indicates Windows 11.
255
+ # https://learn.microsoft.com/en-us/windows/release-health/windows11-release-information
256
+ try:
257
+ if nt_version == "10.0" and int(build_version) >= 22_000:
258
+ prodcut_name = prodcut_name.replace("Windows 10", "Windows 11")
259
+ except ValueError:
260
+ pass
261
+
262
+ version.append(prodcut_name)
251
263
  version.append(f"(NT {nt_version})")
252
264
 
253
- build_version = _part_str(version_parts, "CurrentBuildNumber")
254
265
  ubr = version_parts["UBR"]
255
266
  if ubr:
256
267
  build_version = f"{build_version}.{ubr}"
@@ -77,6 +77,9 @@ class ActivitiesCachePlugin(Plugin):
77
77
  - https://salt4n6.com/2018/05/03/windows-10-timeline-forensic-artefacts/
78
78
 
79
79
  Yields ActivitiesCacheRecords with the following fields:
80
+
81
+ .. code-block:: text
82
+
80
83
  hostname (string): The target hostname.
81
84
  domain (string): The target domain.
82
85
  start_time (datetime): StartTime field.
@@ -1,7 +1,7 @@
1
1
  from struct import unpack
2
2
 
3
3
  from defusedxml import ElementTree
4
- from dissect import cstruct
4
+ from dissect.cstruct import cstruct
5
5
  from dissect.regf.c_regf import (
6
6
  REG_BINARY,
7
7
  REG_DWORD,
@@ -18,14 +18,13 @@ from dissect.target.exceptions import UnsupportedPluginError
18
18
  from dissect.target.helpers.record import TargetRecordDescriptor
19
19
  from dissect.target.plugin import Plugin, export
20
20
 
21
- c_def = """
21
+ policy_def = """
22
22
  struct registry_policy_header {
23
23
  uint32 signature;
24
24
  uint32 version;
25
25
  };
26
26
  """
27
- c_adpolicy = cstruct.cstruct()
28
- c_adpolicy.load(c_def)
27
+ c_adpolicy = cstruct().load(policy_def)
29
28
 
30
29
  ADPolicyRecord = TargetRecordDescriptor(
31
30
  "windows/adpolicy",
@@ -105,6 +105,9 @@ class CatrootPlugin(Plugin):
105
105
  - https://docs.microsoft.com/en-us/windows-hardware/drivers/install/catalog-files
106
106
 
107
107
  Yields CatrootRecords with the following fields:
108
+
109
+ .. code-block:: text
110
+
108
111
  hostname (string): The target hostname.
109
112
  domain (string): The target domain.
110
113
  digest (digest): The parsed digest.
@@ -210,6 +213,9 @@ class CatrootPlugin(Plugin):
210
213
  - https://docs.microsoft.com/en-us/windows-hardware/drivers/install/catalog-files
211
214
 
212
215
  Yields CatrootRecords with the following fields:
216
+
217
+ .. code-block:: text
218
+
213
219
  hostname (string): The target hostname.
214
220
  domain (string): The target domain.
215
221
  digest (digest): The parsed digest.
@@ -53,8 +53,7 @@ struct entry {
53
53
  };
54
54
  """
55
55
 
56
- c_credhist = cstruct()
57
- c_credhist.load(credhist_def)
56
+ c_credhist = cstruct().load(credhist_def)
58
57
 
59
58
 
60
59
  @dataclass
@@ -3,7 +3,7 @@ from collections import namedtuple
3
3
  from datetime import datetime, timedelta, timezone, tzinfo
4
4
  from typing import Dict, Tuple
5
5
 
6
- from dissect import cstruct
6
+ from dissect.cstruct import cstruct
7
7
 
8
8
  from dissect.target.exceptions import (
9
9
  RegistryError,
@@ -34,8 +34,7 @@ typedef struct _REG_TZI_FORMAT {
34
34
  SYSTEMTIME DaylightDate;
35
35
  } REG_TZI_FORMAT;
36
36
  """
37
- c_tz = cstruct.cstruct()
38
- c_tz.load(tz_def)
37
+ c_tz = cstruct().load(tz_def)
39
38
 
40
39
 
41
40
  # Althoug calendar.SUNDAY is only officially documented since Python 3.10, it
@@ -63,7 +62,7 @@ ZERO = timedelta(0)
63
62
  HOUR = timedelta(hours=1)
64
63
 
65
64
 
66
- def parse_systemtime_transition(systemtime: cstruct.Instance, year: int) -> datetime:
65
+ def parse_systemtime_transition(systemtime: c_tz._SYSTEMTIME, year: int) -> datetime:
67
66
  """Return the transition datetime for a given year using the SYSTEMTIME of a STD or DST transition date.
68
67
 
69
68
  The SYSTEMTIME date of a TZI structure needs to be used to calculate the actual date for a given year.