-
Notifications
You must be signed in to change notification settings - Fork 96
Add macOS plugins #1727
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
EmperorValkorion
wants to merge
57
commits into
fox-it:main
Choose a base branch
from
EmperorValkorion:feature/macos-plugins
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Add macOS plugins #1727
Changes from all commits
Commits
Show all changes
57 commits
Select commit
Hold shift + click to select a range
898b07a
some working stuff
qmadev 1e8d95c
lint stuff
qmadev 6db60f2
add groups and systemlog
qmadev 7e4c235
Merge branch 'main' into feature/macos-plugins
qmadev a33ccab
add empty test files
qmadev bc39d35
Fix macOS parsers and add tests
EmperorValkorion 5a31d6e
Update comments
EmperorValkorion 4742b4a
Remove unnecessary print
EmperorValkorion 4a9acd1
Add macOS .plist parsers
EmperorValkorion 44c2850
Merge remote-tracking branch 'upstream/main' into feature/macos-plugins
EmperorValkorion 57debef
Move parse_timestamp function to helper file and remove DynamicDescri…
EmperorValkorion f2fc33d
add macOS SQLite parsers
EmperorValkorion f5ee807
Merge remote-tracking branch 'upstream/main' into feature/macos-plugins
EmperorValkorion 820629c
add macOS persistence parsers
EmperorValkorion d1d73c8
add macOS parser for at jobs
EmperorValkorion ba73adb
Use exsiting unix cronjobs parser instead
EmperorValkorion 175eda3
Cleaned up installog and systemlog parsers
EmperorValkorion d12c07c
add command field for at jobs
EmperorValkorion 6ffa21e
Improve log plugins & tests
EmperorValkorion 2fd1cba
Plugin renames
EmperorValkorion 17da16e
Add parser for WiFi logs
EmperorValkorion 285cd0b
Return None instead of skipping lines without timestamp in year_rollo…
EmperorValkorion 188326d
Merge branch 'yrh-multiline-fix' into feature/macos-plugins
EmperorValkorion ca71719
Only iterate over logs once
EmperorValkorion 4c76edf
Improve build_sqlite_records helper function
EmperorValkorion 0c09f0b
Changed at jobs test file
EmperorValkorion 90a832c
add fsck_apfs_log plugin
EmperorValkorion 2488a8b
parse ASL logs
EmperorValkorion da8c071
Process feedback & general improvements
EmperorValkorion cace2ae
Update dissect/target/plugins/os/unix/bsd/darwin/macos/airport_prefer…
EmperorValkorion 220c262
Update airport_preferences.py
EmperorValkorion 0f2e889
minor improvements
EmperorValkorion 4a4895c
minor imrpovements
EmperorValkorion 20e87cc
helper function for finding bundle files
EmperorValkorion 4d85beb
Improve plugins + tests and add docstrings
EmperorValkorion 3fa433a
Add docstrings for find_bundle_files() and parse_timestamp() helpers
EmperorValkorion 2df67f5
Add docstrings for build_records helpers
EmperorValkorion ecc58e5
Fix typo in docstring
EmperorValkorion 4930701
Removed unused mocks
EmperorValkorion f9a0fe2
Add Duet KnowledgeC plugin
EmperorValkorion 0c0890d
Add Duet InteractionC Plugin
EmperorValkorion aa8757c
Minor fixes & additional comments for build_sqlite_records
EmperorValkorion 736ae05
Merge remote-tracking branch 'upstream/main' into feature/macos-plugins
EmperorValkorion e607bc6
add notes & call_history plugins
EmperorValkorion 7704d03
Add Safari favicons and per site preferences plugins
EmperorValkorion de3d9d3
Add safari_preferences plugin
EmperorValkorion 52ff082
Add some more fields to LauncherRecord from the launchd manual
EmperorValkorion 3658cb6
Replace true/false varints with booleans
EmperorValkorion 5709272
Add macOS Safari plugins
EmperorValkorion 8eda477
Replace DynamicDescriptor for TargetRecordDescriptors in safari_recen…
EmperorValkorion 057814b
Improve prefix_row docstring
EmperorValkorion 4c99f9b
Move the periodic TODO comment to periodic.py
EmperorValkorion 4603625
Improved some plugins with better test data
EmperorValkorion f059838
Add traceability to plist records yielded from SQLite rows
EmperorValkorion b033eb1
Add value mapping
EmperorValkorion 9ab673d
Also parse NoteStore.sqlite in notes.py
EmperorValkorion a52abc4
Add FSEvents plugin
EmperorValkorion File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
69 changes: 69 additions & 0 deletions
69
dissect/target/plugins/os/unix/bsd/darwin/macos/airport_preferences.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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
126
dissect/target/plugins/os/unix/bsd/darwin/macos/at_jobs.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 | ||
|
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 | ||
|
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
170
dissect/target/plugins/os/unix/bsd/darwin/macos/authorization_rules.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.