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.
- dissect/target/exceptions.py +4 -0
- dissect/target/filesystem.py +0 -10
- dissect/target/helpers/cache.py +3 -1
- dissect/target/helpers/hashutil.py +0 -40
- dissect/target/helpers/protobuf.py +6 -10
- dissect/target/helpers/ssh.py +3 -4
- dissect/target/loaders/mqtt.py +147 -2
- dissect/target/loaders/raw.py +7 -0
- dissect/target/plugins/apps/av/mcafee.py +3 -0
- dissect/target/plugins/apps/av/sophos.py +6 -0
- dissect/target/plugins/apps/av/symantec.py +6 -0
- dissect/target/plugins/apps/av/trendmicro.py +8 -3
- dissect/target/plugins/apps/browser/chromium.py +12 -0
- dissect/target/plugins/apps/browser/firefox.py +13 -1
- dissect/target/plugins/apps/browser/iexplore.py +6 -0
- dissect/target/plugins/apps/container/docker.py +1 -1
- dissect/target/plugins/os/unix/etc/__init__.py +0 -0
- dissect/target/plugins/os/unix/etc/etc.py +77 -0
- dissect/target/plugins/os/unix/history.py +1 -1
- dissect/target/plugins/os/unix/linux/cmdline.py +3 -0
- dissect/target/plugins/os/unix/linux/environ.py +3 -0
- dissect/target/plugins/os/unix/linux/processes.py +3 -0
- dissect/target/plugins/os/unix/linux/sockets.py +15 -0
- dissect/target/plugins/os/unix/locate/gnulocate.py +1 -2
- dissect/target/plugins/os/unix/locate/mlocate.py +3 -4
- dissect/target/plugins/os/unix/locate/plocate.py +34 -25
- dissect/target/plugins/os/unix/log/atop.py +6 -4
- dissect/target/plugins/os/unix/log/journal.py +5 -4
- dissect/target/plugins/os/unix/log/lastlog.py +2 -3
- dissect/target/plugins/os/unix/log/utmp.py +6 -7
- dissect/target/plugins/os/windows/_os.py +17 -6
- dissect/target/plugins/os/windows/activitiescache.py +3 -0
- dissect/target/plugins/os/windows/adpolicy.py +3 -4
- dissect/target/plugins/os/windows/catroot.py +6 -0
- dissect/target/plugins/os/windows/credhist.py +1 -2
- dissect/target/plugins/os/windows/datetime.py +3 -4
- dissect/target/plugins/os/windows/defender.py +221 -5
- dissect/target/plugins/os/windows/defender_helpers/__init__.py +0 -0
- dissect/target/plugins/os/windows/defender_helpers/defender_patterns.py +282 -0
- dissect/target/plugins/os/windows/defender_helpers/defender_records.py +191 -0
- dissect/target/plugins/os/windows/dpapi/blob.py +1 -2
- dissect/target/plugins/os/windows/dpapi/master_key.py +2 -3
- dissect/target/plugins/os/windows/lnk.py +3 -0
- dissect/target/plugins/os/windows/log/etl.py +9 -0
- dissect/target/plugins/os/windows/log/evt.py +3 -0
- dissect/target/plugins/os/windows/log/evtx.py +3 -0
- dissect/target/plugins/os/windows/log/pfro.py +3 -0
- dissect/target/plugins/os/windows/log/schedlgu.py +5 -2
- dissect/target/plugins/os/windows/notifications.py +1 -2
- dissect/target/plugins/os/windows/prefetch.py +32 -27
- dissect/target/plugins/os/windows/recyclebin.py +13 -8
- dissect/target/plugins/os/windows/regf/appxdebugkeys.py +3 -0
- dissect/target/plugins/os/windows/regf/auditpol.py +4 -5
- dissect/target/plugins/os/windows/regf/bam.py +5 -3
- dissect/target/plugins/os/windows/regf/cit.py +1 -2
- dissect/target/plugins/os/windows/regf/clsid.py +3 -0
- dissect/target/plugins/os/windows/regf/firewall.py +3 -0
- dissect/target/plugins/os/windows/regf/muicache.py +3 -0
- dissect/target/plugins/os/windows/regf/recentfilecache.py +6 -4
- dissect/target/plugins/os/windows/regf/regf.py +6 -0
- dissect/target/plugins/os/windows/regf/runkeys.py +3 -0
- dissect/target/plugins/os/windows/regf/shellbags.py +9 -7
- dissect/target/plugins/os/windows/regf/shimcache.py +5 -3
- dissect/target/plugins/os/windows/regf/trusteddocs.py +3 -0
- dissect/target/plugins/os/windows/regf/usb.py +3 -0
- dissect/target/plugins/os/windows/regf/userassist.py +8 -6
- dissect/target/plugins/os/windows/sam.py +7 -5
- dissect/target/plugins/os/windows/services.py +3 -0
- dissect/target/plugins/os/windows/task_helpers/tasks_job.py +3 -4
- dissect/target/plugins/os/windows/wer.py +3 -0
- dissect/target/target.py +6 -1
- dissect/target/tools/shell.py +30 -19
- {dissect.target-3.17.dev37.dist-info → dissect.target-3.18.dist-info}/METADATA +49 -24
- {dissect.target-3.17.dev37.dist-info → dissect.target-3.18.dist-info}/RECORD +79 -74
- {dissect.target-3.17.dev37.dist-info → dissect.target-3.18.dist-info}/WHEEL +1 -1
- {dissect.target-3.17.dev37.dist-info → dissect.target-3.18.dist-info}/COPYRIGHT +0 -0
- {dissect.target-3.17.dev37.dist-info → dissect.target-3.18.dist-info}/LICENSE +0 -0
- {dissect.target-3.17.dev37.dist-info → dissect.target-3.18.dist-info}/entry_points.txt +0 -0
- {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.
|
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.
|
@@ -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
|
23
|
+
int8 pad0[2]; /* 32-bit total alignment */
|
24
24
|
char root_database;
|
25
25
|
char config_block[conf_size];
|
26
|
-
int8
|
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
|
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
|
-
|
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 =
|
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 =
|
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) ->
|
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
|
-
|
126
|
-
|
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
|
-
|
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
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
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
|
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[
|
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
|
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[
|
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.
|
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
|
-
|
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 :
|
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
|
-
|
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 =
|
124
|
+
entry = c_utmp.entry(byte_stream)
|
126
125
|
|
127
126
|
r_type = ""
|
128
|
-
if entry.ut_type in
|
129
|
-
r_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
|
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(),
|
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:",
|
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
|
-
|
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
|
-
|
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.
|
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.
|
@@ -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.
|
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:
|
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.
|