Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
898b07a
some working stuff
qmadev Mar 16, 2026
1e8d95c
lint stuff
qmadev Mar 16, 2026
6db60f2
add groups and systemlog
qmadev Mar 22, 2026
7e4c235
Merge branch 'main' into feature/macos-plugins
qmadev Mar 22, 2026
a33ccab
add empty test files
qmadev Mar 22, 2026
bc39d35
Fix macOS parsers and add tests
EmperorValkorion Apr 3, 2026
5a31d6e
Update comments
EmperorValkorion Apr 7, 2026
4742b4a
Remove unnecessary print
EmperorValkorion Apr 7, 2026
4a9acd1
Add macOS .plist parsers
EmperorValkorion Apr 21, 2026
44c2850
Merge remote-tracking branch 'upstream/main' into feature/macos-plugins
EmperorValkorion Apr 21, 2026
57debef
Move parse_timestamp function to helper file and remove DynamicDescri…
EmperorValkorion Apr 22, 2026
f2fc33d
add macOS SQLite parsers
EmperorValkorion Apr 30, 2026
f5ee807
Merge remote-tracking branch 'upstream/main' into feature/macos-plugins
EmperorValkorion Apr 30, 2026
820629c
add macOS persistence parsers
EmperorValkorion May 7, 2026
d1d73c8
add macOS parser for at jobs
EmperorValkorion May 7, 2026
ba73adb
Use exsiting unix cronjobs parser instead
EmperorValkorion May 8, 2026
175eda3
Cleaned up installog and systemlog parsers
EmperorValkorion May 8, 2026
d12c07c
add command field for at jobs
EmperorValkorion May 11, 2026
6ffa21e
Improve log plugins & tests
EmperorValkorion May 13, 2026
2fd1cba
Plugin renames
EmperorValkorion May 13, 2026
17da16e
Add parser for WiFi logs
EmperorValkorion May 13, 2026
285cd0b
Return None instead of skipping lines without timestamp in year_rollo…
EmperorValkorion May 14, 2026
188326d
Merge branch 'yrh-multiline-fix' into feature/macos-plugins
EmperorValkorion May 14, 2026
ca71719
Only iterate over logs once
EmperorValkorion May 14, 2026
4c76edf
Improve build_sqlite_records helper function
EmperorValkorion May 18, 2026
0c09f0b
Changed at jobs test file
EmperorValkorion May 18, 2026
90a832c
add fsck_apfs_log plugin
EmperorValkorion May 18, 2026
2488a8b
parse ASL logs
EmperorValkorion May 26, 2026
da8c071
Process feedback & general improvements
EmperorValkorion May 26, 2026
cace2ae
Update dissect/target/plugins/os/unix/bsd/darwin/macos/airport_prefer…
EmperorValkorion May 26, 2026
220c262
Update airport_preferences.py
EmperorValkorion May 26, 2026
0f2e889
minor improvements
EmperorValkorion May 26, 2026
4a4895c
minor imrpovements
EmperorValkorion May 27, 2026
20e87cc
helper function for finding bundle files
EmperorValkorion May 27, 2026
4d85beb
Improve plugins + tests and add docstrings
EmperorValkorion Jun 4, 2026
3fa433a
Add docstrings for find_bundle_files() and parse_timestamp() helpers
EmperorValkorion Jun 4, 2026
2df67f5
Add docstrings for build_records helpers
EmperorValkorion Jun 4, 2026
ecc58e5
Fix typo in docstring
EmperorValkorion Jun 4, 2026
4930701
Removed unused mocks
EmperorValkorion Jun 5, 2026
f9a0fe2
Add Duet KnowledgeC plugin
EmperorValkorion Jun 5, 2026
0c0890d
Add Duet InteractionC Plugin
EmperorValkorion Jun 8, 2026
aa8757c
Minor fixes & additional comments for build_sqlite_records
EmperorValkorion Jun 8, 2026
736ae05
Merge remote-tracking branch 'upstream/main' into feature/macos-plugins
EmperorValkorion Jun 8, 2026
e607bc6
add notes & call_history plugins
EmperorValkorion Jun 9, 2026
7704d03
Add Safari favicons and per site preferences plugins
EmperorValkorion Jun 9, 2026
de3d9d3
Add safari_preferences plugin
EmperorValkorion Jun 9, 2026
52ff082
Add some more fields to LauncherRecord from the launchd manual
EmperorValkorion Jun 10, 2026
3658cb6
Replace true/false varints with booleans
EmperorValkorion Jun 10, 2026
5709272
Add macOS Safari plugins
EmperorValkorion Jun 10, 2026
8eda477
Replace DynamicDescriptor for TargetRecordDescriptors in safari_recen…
EmperorValkorion Jun 10, 2026
057814b
Improve prefix_row docstring
EmperorValkorion Jun 11, 2026
4c99f9b
Move the periodic TODO comment to periodic.py
EmperorValkorion Jun 11, 2026
4603625
Improved some plugins with better test data
EmperorValkorion Jun 11, 2026
f059838
Add traceability to plist records yielded from SQLite rows
EmperorValkorion Jun 11, 2026
b033eb1
Add value mapping
EmperorValkorion Jun 11, 2026
9ab673d
Also parse NoteStore.sqlite in notes.py
EmperorValkorion Jun 12, 2026
a52abc4
Add FSEvents plugin
EmperorValkorion Jun 16, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions dissect/target/container.py
Original file line number Diff line number Diff line change
Expand Up @@ -258,3 +258,4 @@ def open(item: list | str | BinaryIO | Path, *args, **kwargs) -> Container:
register("hds", "HdsContainer")
register("split", "SplitContainer")
register("fortifw", "FortiFirmwareContainer")
register("asif", "AsifContainer")
4 changes: 2 additions & 2 deletions dissect/target/helpers/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ def readinto(buffer: bytearray, fh: BinaryIO) -> int:

def year_rollover_helper(
path: Path, re_ts: str | re.Pattern, ts_format: str, tzinfo: tzinfo = timezone.utc
) -> Iterator[tuple[datetime, str]]:
) -> Iterator[tuple[datetime | None, str]]:
"""Helper function for determining the correct timestamps for log files without year notation.

Supports compressed files by using :func:`open_decompress`.
Expand Down Expand Up @@ -142,7 +142,7 @@ def year_rollover_helper(
if not warned:
log.warning("No timestamp found in one of the lines in %s!", path)
warned = True
log.debug("Skipping line: %s", line)
yield None, line
continue

# We have to append the current_year to strptime instead of adding it using replace later.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
from __future__ import annotations

import plistlib
from typing import TYPE_CHECKING

from dissect.target.exceptions import UnsupportedPluginError
from dissect.target.helpers.record import TargetRecordDescriptor
from dissect.target.plugin import Plugin, export

if TYPE_CHECKING:
from collections.abc import Iterator

from dissect.target import Target

AirportPreferencesRecord = TargetRecordDescriptor(
"macos/airport_preferences",
[
("varint", "counter"),
("string", "device_uuid"),
("string[]", "preferred_order"),
("varint", "version_number"),
("path", "source"),
],
)


class AirportPreferencesPlugin(Plugin):
"""macOS AirPort (WiFi) preferences plugin.

Contains WiFi network information.

References:
- https://apple.stackexchange.com/questions/301346/how-can-i-better-sort-and-set-wifi-network-preferences-on-mac
"""

PATH = "/Library/Preferences/SystemConfiguration/com.apple.airport.preferences.plist"

def __init__(self, target: Target):
super().__init__(target)
self.file = self.target.fs.path(self.PATH) if self.target.fs.path(self.PATH).exists() else None

def check_compatible(self) -> None:
if not self.file:
raise UnsupportedPluginError("No com.apple.airport.preferences.plist file found")

@export(record=AirportPreferencesRecord)
def airport_preferences(self) -> Iterator[AirportPreferencesRecord]:
"""Return macOS AirPort (Wi-Fi) preferences.

Yields AirportPreferencesRecord with the following fields:

.. code-block:: text

counter (varint): The Counter key of the plist.
device_uuid (string): UUID of the device.
preferred_order (string[]): Ordered list of known Wi-Fi network SSIDs.
version_number (varint): The version number of the plist.
source (path): Path to the com.apple.airport.preferences.plist file.
"""
plist = plistlib.load(self.file.open())

yield AirportPreferencesRecord(
counter=plist.get("Counter"),
device_uuid=plist.get("DeviceUUID"),
preferred_order=plist.get("PreferredOrder"),
version_number=plist.get("Version"),
source=self.file,
_target=self.target,
)
126 changes: 126 additions & 0 deletions dissect/target/plugins/os/unix/bsd/darwin/macos/at_jobs.py
Comment thread
EmperorValkorion marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
from __future__ import annotations

from typing import TYPE_CHECKING

from dissect.target.exceptions import UnsupportedPluginError
from dissect.target.helpers.record import TargetRecordDescriptor
from dissect.target.plugin import Plugin, export

if TYPE_CHECKING:
from collections.abc import Iterator

from dissect.target.target import Target

AtJobsRecord = TargetRecordDescriptor(
"macos/at_jobs",
[
("string", "queue"),
("varint", "seq"),
("datetime", "execution_time"),
("string", "command"),
("path", "source"),
],
)


class AtJobsPlugin(Plugin):
"""macOS at jobs plugin.

The at utility schedules commands to be executed at a later time.

References:
- https://man.freebsd.org/cgi/man.cgi?query=at&sektion=1&format=html
- github.com/freebsd/freebsd-src/blob/main/usr.bin/at/at.c
"""

PATHS = ("/usr/lib/cron/jobs/*",)

def __init__(self, target: Target):
super().__init__(target)
self.at_jobs_files = self._find_files()

def check_compatible(self) -> None:
if not (self.at_jobs_files):
raise UnsupportedPluginError("No At Jobs files found")

def _find_files(self) -> set:
files = set()

for pattern in self.PATHS:
for path in self.target.fs.glob(pattern):
files.add(self.target.fs.path(path))

return files

@export(record=AtJobsRecord)
def at_jobs(self) -> Iterator[AtJobsRecord]:
"""Return macOS `at` job records.

Yields AtJobsRecord with the following fields:

.. code-block:: text

queue (string): Queue identifier derived from the job filename.
seq (varint): Sequence number derived from the job filename.
execution_time (datetime): Execution time derived from the job filename.
command (string): Command contents extracted from the job file.
source (path): Path to the `at` job file.

The job filename typically follows the structure:

QSSSSSTTTTTTTT

Where:
Q = queue identifier
S = sequence number (hexadecimal)
T = execution time (hexadecimal, in minutes)

Lines following the environment setup (typically after 'export OLDPWD') are treated
as the command content.
"""
for file in self.at_jobs_files:
name = file.name

if name in (".SEQ", ".lockfile"):
continue

if len(name) < 6:
continue

queue = name[0]
seq = int(name[1:6], 16)
time_hex = name[6:]

execution_time = None
try:
minutes = int(time_hex, 16)
execution_time = minutes * 60
except ValueError:
pass
Comment thread
EmperorValkorion marked this conversation as resolved.

command_line = False
command = ""
with file.open("r") as f:
for line in f:
if command_line:
command += line
else:
line = line.strip()

if not line or line.startswith(("#", "export")):
continue

line = line.split("#", 1)[0].strip()

if "export OLDPWD" in line:
command_line = True
Comment thread
EmperorValkorion marked this conversation as resolved.

command = command.rstrip("\n")

yield AtJobsRecord(
queue=queue,
seq=seq,
execution_time=execution_time,
command=command,
source=file,
)
170 changes: 170 additions & 0 deletions dissect/target/plugins/os/unix/bsd/darwin/macos/authorization_rules.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
from __future__ import annotations

from typing import TYPE_CHECKING

from dissect.target.exceptions import UnsupportedPluginError
from dissect.target.helpers.record import TargetRecordDescriptor
from dissect.target.plugin import Plugin, export
from dissect.target.plugins.os.unix.bsd.darwin.macos.helpers.build_records import build_sqlite_records

if TYPE_CHECKING:
from collections.abc import Iterator

from dissect.target import Target

AuthorizationRulesRecord = TargetRecordDescriptor(
"macos/authorization_rules",
[
("string[]", "tables"),
("varint", "rules_id"),
("string", "rules_name"),
("varint", "rules_type"),
("varint", "rules_class"),
("string", "rules_group"),
("varint", "rules_kofn"),
("varint", "rules_timeout"),
("varint", "rules_flags"),
("string", "rules_tries"),
("varint", "rules_version"),
("datetime", "rules_created"),
("datetime", "rules_modified"),
("string", "rules_hash"),
("string", "rules_identifier"),
("bytes", "rules_requirement"),
("string", "rules_comment"),
("string[]", "rules_delegates_map"),
("datetime", "rules_history_timestamp"),
("string", "rules_history_source"),
("varint", "rules_history_operation"),
("varint", "mechanisms_map_m_id"),
("varint", "mechanisms_map_ord"),
("string", "mechanisms_plugin"),
("string", "mechanisms_param"),
("varint", "mechanisms_privileged"),
("path", "source"),
],
)

ConfigTableRecord = TargetRecordDescriptor(
"macos/authorization_rules/config",
[
("string", "table"),
("string", "key"),
("string", "value"),
("path", "source"),
],
)

SQLiteSequenceTableRecord = TargetRecordDescriptor(
"macos/authorization_rules/sqlite_sequence",
[
("string", "table"),
("string", "name"),
("varint", "seq"),
("path", "source"),
],
)

AuthorizationRulesRecords = (
AuthorizationRulesRecord,
ConfigTableRecord,
SQLiteSequenceTableRecord,
)

joins = (
{"table1": "rules", "key1": "name", "table2": "rules_history", "key2": "rule", "join": "iterate"},
{"table1": "rules", "key1": "version", "table2": "rules_history", "key2": "version", "join": "ignore"},
{"table1": "rules", "key1": "id", "table2": "mechanisms_map", "key2": "r_id", "join": "iterate"},
{"table1": "mechanisms_map", "key1": "m_id", "table2": "mechanisms", "key2": "id", "join": "iterate"},
{"table1": "rules", "key1": "id", "table2": "delegates_map", "key2": "r_id", "join": "nested"},
)

CONVERT_TIMESTAMPS = {
"rules_created": "2001",
"rules_modified": "2001",
}


class AuthorizationRulesPlugin(Plugin):
"""macOS authorization rules plugin.

The database located in /var/db/auth.db is used to store permissions to perform sensitive operations.

References:
- https://angelica.gitbook.io/hacktricks/macos-hardening/macos-security-and-privilege-escalation/macos-security-protections/macos-authorizations-db-and-authd
"""

PATH = "/var/db/auth.db"

def __init__(self, target: Target):
super().__init__(target)
self.file = self.target.fs.path(self.PATH) if self.target.fs.path(self.PATH).exists() else None

def check_compatible(self) -> None:
if not self.file:
raise UnsupportedPluginError("No auth.db file found")

@export(record=AuthorizationRulesRecords)
def authorization_rules(self) -> Iterator[AuthorizationRulesRecords]:
"""Return macOS authorization rules from the auth.db database.

The /var/db/auth.db database stores authorization rules used by macOS
to determine whether a client is allowed to perform privileged operations.

Yields the following record types:

.. code-block:: text

AuthorizationRulesRecord:
tables (string[]): Names of source tables contributing to the record.
rules_id (varint): Unique identifier for the rule.
rules_name (string): Unique name used to identify the authorization rule.
rules_type (varint): Rule type value.
rules_class (varint): Rule class defining how the rule is evaluated.
rules_group (string): User group associated with the rule.
rules_kofn (varint): "k-of-n" parameter indicating how many subrules must be satisfied.
rules_timeout (varint): Duration in seconds before the authorization expires.
rules_flags (varint): Flags modifying rule behavior.
rules_tries (string): Maximum number of allowed authorization attempts.
rules_version (varint): Version of the rule.
rules_created (datetime): Timestamp when the rule was created.
rules_modified (datetime): Timestamp of the last modification.
rules_hash (string): Hash value used to verify rule integrity.
rules_identifier (string): Identifier string used for external reference.
rules_requirement (bytes): Serialized data defining rule requirements and mechanisms.
rules_comment (string): Human-readable description of the rule.
rules_delegates_map (string[]): Delegate mappings for the rule.
rules_history_timestamp (datetime): Timestamp from the rules_history table.
rules_history_source (string): Source of the history entry (e.g. authd).
rules_history_operation (varint): Operation type recorded in rules_history.
mechanisms_map_m_id (varint): Mechanism identifier from mechanisms_map.
mechanisms_map_ord (varint): Order of the mechanism within the rule.
mechanisms_plugin (string): Mechanism plugin name.
mechanisms_param (string): Mechanism parameter value.
mechanisms_privileged (varint): Indicates whether the mechanism runs with privileges.
source (path): Path to the auth.db database file.

ConfigTableRecord:
table (string): Name of the source table (config).
key (string): Configuration key.
value (string): Stored value associated with the key.
source (path): Path to the auth.db database file.

SQLiteSequenceTableRecord:
table (string): Name of the source table (sqlite_sequence).
name (string): Name of the table for which the sequence applies.
seq (varint): Current autoincrement value for the table.
source (path): Path to the auth.db database file.

Records are constructed by joining data from the rules,
rules_history, mechanisms_map, mechanisms, and
delegates_map tables.

Multiple records may be produced for a single rule when multiple
mechanisms or delegate mappings exist.
"""
yield from build_sqlite_records(
self, (self.file,), AuthorizationRulesRecords, joins, convert_timestamps=CONVERT_TIMESTAMPS
)

# TODO: Add prompts & buttons tables
Loading