Skip to content

Commit 7841482

Browse files
authored
Merge branch 'main' into py313-support
2 parents 3fe6279 + a8a085c commit 7841482

File tree

14 files changed

+301
-29
lines changed

14 files changed

+301
-29
lines changed

dissect/target/plugins/apps/other/env.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,11 @@ def envfile(self, env_path: str, extension: str = "env") -> Iterator[Environment
3131

3232
if not env_path:
3333
self.target.log.error("No ``--path`` provided!")
34+
return
3435

3536
if not (path := self.target.fs.path(env_path)).exists():
3637
self.target.log.error("Provided path %s does not exist!", path)
38+
return
3739

3840
for file in path.glob("**/*." + extension):
3941
if not file.is_file():

dissect/target/plugins/os/unix/log/journal.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -315,9 +315,18 @@ def __iter__(self) -> Iterator[dict[str, int | str]]:
315315
offset = self.header.entry_array_offset
316316
while offset != 0:
317317
self.fh.seek(offset)
318+
object_type = self.fh.read(1)[0]
318319

319-
if self.fh.read(1)[0] != c_journal.ObjectType.OBJECT_ENTRY_ARRAY:
320-
raise ValueError(f"Expected OBJECT_ENTRY_ARRAY at offset {offset}")
320+
if object_type == c_journal.ObjectType.OBJECT_UNUSED:
321+
self.target.log.warning(
322+
"ObjectType OBJECT_UNUSED encountered for next OBJECT_ENTRY_ARRAY offset at 0x%X. "
323+
"This indicates allocated space in the journal file which is not used yet.",
324+
offset,
325+
)
326+
break
327+
328+
elif object_type != c_journal.ObjectType.OBJECT_ENTRY_ARRAY:
329+
raise ValueError(f"Expected OBJECT_ENTRY_ARRAY or OBJECT_UNUSED at offset {offset}")
321330

322331
if self.header.incompatible_flags & c_journal.IncompatibleFlag.HEADER_INCOMPATIBLE_COMPACT:
323332
entry_array_object = c_journal.EntryArrayObject_Compact(self.fh)

dissect/target/plugins/os/windows/credential/credhist.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ def _parse(self) -> Iterator[CredHistEntry]:
125125
yield CredHistEntry(
126126
version=entry.dwVersion,
127127
guid=UUID(bytes_le=entry.guidLink),
128-
user_sid=read_sid(entry.pSid),
128+
user_sid=read_sid(entry.pSid) if entry.pSid else None,
129129
hash_alg=HashAlgorithm.from_id(entry.algHash),
130130
cipher_alg=cipher_alg,
131131
sha1=None,

dissect/target/plugins/os/windows/generic.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -611,11 +611,12 @@ def sid(self) -> Iterator[ComputerSidRecord]:
611611

612612
try:
613613
key = self.target.registry.key("HKLM\\SECURITY\\Policy\\PolMachineAccountS")
614+
raw_sid = key.value("(Default)").value
614615

615616
yield ComputerSidRecord(
616617
ts=key.timestamp,
617618
sidtype="Domain",
618-
sid=read_sid(key.value("(Default)").value),
619+
sid=read_sid(raw_sid) if raw_sid else None,
619620
_target=self.target,
620621
)
621622
except (RegistryError, struct.error):
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
from typing import Iterator
2+
3+
from dissect.util.ts import wintimestamp
4+
from flow.record.fieldtypes import windows_path
5+
6+
from dissect.target.exceptions import UnsupportedPluginError
7+
from dissect.target.helpers.descriptor_extensions import (
8+
RegistryRecordDescriptorExtension,
9+
UserRecordDescriptorExtension,
10+
)
11+
from dissect.target.helpers.record import create_extended_descriptor
12+
from dissect.target.helpers.regutil import RegistryKey, RegistryValueNotFoundError
13+
from dissect.target.plugin import Plugin, export
14+
from dissect.target.target import Target
15+
16+
CamRecord = create_extended_descriptor([RegistryRecordDescriptorExtension, UserRecordDescriptorExtension])(
17+
"windows/registry/cam",
18+
[
19+
("datetime", "ts"),
20+
("string", "device"),
21+
("string", "app_name"),
22+
("path", "path"),
23+
("datetime", "last_started"),
24+
("datetime", "last_stopped"),
25+
("varint", "duration"),
26+
],
27+
)
28+
29+
30+
class CamPlugin(Plugin):
31+
"""Plugin that iterates various Capability Access Manager registry key locations."""
32+
33+
CONSENT_STORES = [
34+
"HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\CapabilityAccessManager\\ConsentStore",
35+
"HKLM\\Software\\Microsoft\\Windows\\CurrentVersion\\CapabilityAccessManager\\ConsentStore",
36+
]
37+
38+
def __init__(self, target: Target):
39+
super().__init__(target)
40+
self.app_regf_keys = self._find_apps()
41+
42+
def _find_apps(self) -> list[RegistryKey]:
43+
apps = []
44+
for store in self.target.registry.keys(self.CONSENT_STORES):
45+
for key in store.subkeys():
46+
apps.append(key)
47+
48+
return apps
49+
50+
def check_compatible(self) -> None:
51+
if not self.app_regf_keys:
52+
raise UnsupportedPluginError("No Capability Access Manager keys found")
53+
54+
def yield_apps(self) -> Iterator[RegistryKey]:
55+
for app in self.app_regf_keys:
56+
for key in app.subkeys():
57+
if key.name == "NonPackaged": # NonPackaged registry key has more apps, so yield those apps
58+
yield from key.subkeys()
59+
else:
60+
yield key
61+
62+
@export(record=CamRecord)
63+
def cam(self) -> Iterator[CamRecord]:
64+
"""Iterate Capability Access Manager key locations.
65+
66+
The Capability Access Manager keeps track of processes that access I/O devices, like the webcam or microphone.
67+
Applications are divided into packaged and non-packaged applications meaning Microsoft or
68+
non-Microsoft applications.
69+
70+
References:
71+
- https://docs.velociraptor.app/exchange/artifacts/pages/windows.registry.capabilityaccessmanager/
72+
- https://svch0st.medium.com/can-you-track-processes-accessing-the-camera-and-microphone-7e6885b37072
73+
74+
Yields ``CamRecord`` with the following fields:
75+
76+
.. code-block:: text
77+
78+
hostname (string): The target hostname.
79+
domain (string): The target domain.
80+
ts (datetime): The modification timestamp of the registry key.
81+
device (string): Name of the device privacy permission where asked for.
82+
app_name (string): The name of the application.
83+
path (path): The possible path to the application.
84+
last_started (datetime): When the application last started using the device.
85+
last_stopped (datetime): When the application last stopped using the device.
86+
duration (datetime): How long the application used the device (seconds).
87+
"""
88+
89+
for key in self.yield_apps():
90+
last_started = None
91+
last_stopped = None
92+
duration = None
93+
94+
try:
95+
last_started = wintimestamp(key.value("LastUsedTimeStart").value)
96+
except RegistryValueNotFoundError:
97+
self.target.log.warning("No LastUsedTimeStart for application: %s", key.name)
98+
99+
try:
100+
last_stopped = wintimestamp(key.value("LastUsedTimeStop").value)
101+
except RegistryValueNotFoundError:
102+
self.target.log.warning("No LastUsedTimeStop for application: %s", key.name)
103+
104+
if last_started and last_stopped:
105+
duration = (last_stopped - last_started).seconds
106+
107+
yield CamRecord(
108+
ts=key.ts,
109+
device=key.path.split("\\")[-2],
110+
app_name=key.name,
111+
path=windows_path(key.name.replace("#", "\\")) if "#" in key.name else None,
112+
last_started=last_started,
113+
last_stopped=last_stopped,
114+
duration=duration,
115+
_target=self.target,
116+
_key=key,
117+
_user=self.target.registry.get_user(key),
118+
)

dissect/target/plugins/os/windows/task_helpers/tasks_xml.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from __future__ import annotations
2+
13
import warnings
24
from typing import Iterator, Optional
35
from xml.etree.ElementTree import Element
@@ -157,8 +159,8 @@ def strip_namespace(self, data: Element) -> Element:
157159
return data
158160

159161
def get_element(
160-
self, xml_path: str, xml_data: Optional[Element] = None, attribute: Optional[str] = None
161-
) -> Optional[str]:
162+
self, xml_path: str, xml_data: Element | None = None, attribute: Optional[str] = None
163+
) -> str | None:
162164
"""Get the value of the specified XML element.
163165
164166
Args:
@@ -179,7 +181,7 @@ def get_element(
179181

180182
return data.text
181183

182-
def get_raw(self, xml_path: Optional[str] = None) -> str:
184+
def get_raw(self, xml_path: str | None = None) -> str:
183185
"""Get the raw XML data of the specified element.
184186
185187
Args:

dissect/target/plugins/os/windows/tasks.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,16 @@
11
from __future__ import annotations
22

3-
import logging
4-
import warnings
5-
from typing import Iterator, Union
3+
from typing import Iterator
64

75
from flow.record import GroupedRecord
86

97
from dissect.target import Target
10-
from dissect.target.exceptions import UnsupportedPluginError
8+
from dissect.target.exceptions import InvalidTaskError, UnsupportedPluginError
119
from dissect.target.helpers.record import DynamicDescriptor, TargetRecordDescriptor
1210
from dissect.target.plugin import Plugin, export
1311
from dissect.target.plugins.os.windows.task_helpers.tasks_job import AtTask
1412
from dissect.target.plugins.os.windows.task_helpers.tasks_xml import ScheduledTasks
1513

16-
warnings.simplefilter(action="ignore", category=FutureWarning)
17-
log = logging.getLogger(__name__)
18-
1914
TaskRecord = TargetRecordDescriptor(
2015
"filesystem/windows/task",
2116
[
@@ -118,7 +113,7 @@ def check_compatible(self) -> None:
118113
raise UnsupportedPluginError("No task files")
119114

120115
@export(record=DynamicDescriptor(["path", "datetime"]))
121-
def tasks(self) -> Iterator[Union[TaskRecord, GroupedRecord]]:
116+
def tasks(self) -> Iterator[TaskRecord | GroupedRecord]:
122117
"""Return all scheduled tasks on a Windows system.
123118
124119
On a Windows system, a scheduled task is a program or script that is executed on a specific time or at specific
@@ -132,7 +127,12 @@ def tasks(self) -> Iterator[Union[TaskRecord, GroupedRecord]]:
132127
"""
133128
for task_file in self.task_files:
134129
if not task_file.suffix or task_file.suffix == ".xml":
135-
task_objects = ScheduledTasks(task_file).tasks
130+
try:
131+
task_objects = ScheduledTasks(task_file).tasks
132+
except InvalidTaskError as e:
133+
self.target.log.warning("Invalid task file encountered: %s", task_file)
134+
self.target.log.debug("", exc_info=e)
135+
continue
136136
else:
137137
task_objects = [AtTask(task_file, self.target)]
138138

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
version https://git-lfs.github.com/spec/v1
2+
oid sha256:bd34a41863a93619bff6389d760dd3417652dc42083776bd8a13fa6a0725178e
3+
size 8388608
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
version https://git-lfs.github.com/spec/v1
2+
oid sha256:eb19305d131360a8b6ea15f8db3c906cb0bb10c92e2b29a3155cbbcf67cc53c2
3+
size 8388608
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
version https://git-lfs.github.com/spec/v1
2+
oid sha256:d5dfb012b37018e9c39826ab0f44cd583045964c417e19bbaacbaefa74122da0
3+
size 16777216

0 commit comments

Comments
 (0)