Reading width
Wide uses the full column for everything, text, diagrams, code, and exercises. Narrow keeps the standard reading width.
Text size
Scales the body text. Headings and code blocks keep their size.
In this section
What macOS Endpoint Investigation Is: The Evidence a Mac Keeps
If you have worked a Windows or Linux endpoint, you already know how to reason from evidence to a finding. macOS changes the evidence, not the reasoning. This section teaches the foundation everything else rests on: which stores a Mac keeps, what each one actually records, and how you read them. The artifacts below are real, not described.
Scenario
A managed-detection alert fires at 02:14 on NE-VANCE-MBP, a design technologist's MacBook Pro at Northgate Engineering running macOS 26 Tahoe. The alert says an unsigned binary launched from a user directory and opened a network connection. The machine is FileVault-encrypted, on Apple Silicon, and the user is asleep three time zones away. You have a logical triage collection pulled while the machine was still unlocked. Your first instinct is to look for Prefetch. There is none. Your job is to prove what ran, where it came from, whether the system trusted it, and what it did, from stores you may never have opened.
Estimated time: 30 minutes.
A Mac records its history in different stores
A Windows host records execution in Prefetch and Amcache, persistence in the registry, system events in the event log, and filesystem change in the MFT and USN journal. You learn those once and reach for them on every case. macOS records the same categories of activity, but in entirely different stores, in different formats, with different rules about what is written at all.
The first skill is matching the question to the store. Provenance and execution live in extended attributes, the quarantine database, install receipts, and Unified Logging, not in a single execution oracle. Persistence lives in launchd property lists and the Background Task Management database, not the registry. System activity lives in Unified Logging, one high-volume binary store, not in discrete channels. Filesystem change lives in FSEvents and APFS snapshots. User behavior lives in KnowledgeC and Apple's Biome.
One difference deserves stating plainly up front, because it shapes every execution question on a Mac: there is no single execution oracle. Windows hands you Prefetch, which on its own often proves a binary ran, with a count and timestamps. macOS has no equivalent. Execution is assembled from provenance, policy assessment, the log, install receipts, and usage. That is not a gap in your tooling. It is the shape of the platform, and it is why corroboration is not optional here.
The stores also come in formats you have to recognize, because the format decides which tool opens it. Property lists hold configuration and persistence in a binary encoding you convert to text before reading. SQLite databases back the behavioral stores like KnowledgeC and the browsers. Unified Logging writes a compressed binary trace, the tracev3 format, that only Apple's own tooling reads cleanly. The newest Biome streams use a record format called SEGB that most commercial tools cannot parse yet.
The investigative questions carry over from Windows. The stores that answer them do not.
The rest of this section walks the stores that answer the scenario's questions, reading the real artifact in each. Take the unsigned binary the alert flagged, sitting at /Users/j.vance/Downloads/DesignReview.dmg, and follow the evidence.
Where a file came from
When a file arrives through a browser, mail client, or messaging app, macOS tags it with an extended attribute named com.apple.quarantine. That tag is your download-origin evidence, the macOS equivalent of the Windows Zone.Identifier stream, and it survives on the file until something explicitly strips it.
# Read the quarantine tag on the working copy of the suspect file
% xattr -p com.apple.quarantine DesignReview.dmg
0083;68a1f4c0;Safari;9F2A1C7E-4B83-4D2A-9E51-7C3D8A1F2B60
The four semicolon-separated fields are the value of the tag. The first is a flags field. The second is the download time as a hexadecimal epoch, which here decodes to the early hours of March 14. The third names the agent that wrote the file, Safari. The fourth is an event identifier you can correlate with the LSQuarantine database for the full download record. In one read you have established the file was downloaded by the browser, and when.
The flags field is worth understanding rather than skipping. It encodes whether the file has been assessed and approved, so a change in those flags is itself a signal that a user cleared the warning and ran the file. The same attribute that records the download later records the approval, which is why quarantine is both a provenance source and a weak execution signal.
The companion attribute records the source URL. Spotlight tracks it as kMDItemWhereFroms, which you read with mdls.
# Where did it come from? Spotlight keeps the source URLs
% mdls -name kMDItemWhereFroms DesignReview.dmg
kMDItemWhereFroms = (
"https://files.dropshare-cdn.com/d/DesignReview.dmg",
"https://mail.google.com/"
)
Now the provenance is concrete: the file was downloaded by Safari from a content-delivery host, referred from webmail, in the early hours. None of that proves it executed. It proves where it came from. On macOS those are separate questions answered by separate stores, which is the habit you have to build coming from Windows, where a single artifact often answers both.
Whether the system trusted it, and whether it ran
Before a downloaded, quarantined binary runs, Gatekeeper assesses it. You can replay that assessment with spctl, and inspect the signature with codesign. The two together tell you what the system would have decided.
# Would Gatekeeper have allowed it? What does its signature say?
% spctl --assess --type execute -vv DesignReview.app
DesignReview.app: rejected
source=no usable signature
% codesign -dv DesignReview.app
code object is not signed at all
Gatekeeper would have rejected it: no usable signature, unsigned. So if it ran, it ran because a user explicitly overrode the block, which is itself a finding about how the intrusion started. Establishing that it actually executed is the part with no single answer on macOS. You assemble it: the quarantine flags change once a file is approved, install receipts record package installs, and Unified Logging may carry a process record. The log is queried with log show and a predicate.
Keep the two checks straight, because they answer different questions. codesign reports what the binary claims about itself: who signed it, with what identity, and whether the seal is intact. spctl reports what the system policy would do with that claim: allow, reject, or demand notarization. A binary can carry a valid signature and still be rejected by policy, and an unsigned one like this is rejected outright. Reading both tells you not only that it was untrusted, but why.
# Pull any logged activity for the process from the collected log archive
% log show system_logs.logarchive --predicate 'process == "DesignReview"' --info
Timestamp Type Process Message
2026-03-14 02:14:07.221+0000 Default DesignReview spawned, parent Terminal (pid 501)
2026-03-14 02:14:09.880+0000 Default DesignReview network connection to 203.0.113.44:443
That is the execution and the network beat in one place, with a parent of Terminal, which tells you a user typed something. Unified Logging is high-volume and rolls, so its absence proves nothing, a point the next sections return to. When it is present, it is strong.
What it left to survive a reboot
An attacker who wants to persist on a Mac registers a launchd job or a background item. The classic mechanism is a property list in a LaunchAgents or LaunchDaemons directory. You read a plist with plutil, which renders the binary format as readable text.
# Render the suspect LaunchAgent as readable text
% plutil -p ~/Library/LaunchAgents/com.designsync.helper.plist
{
"Label" => "com.designsync.helper"
"ProgramArguments" => [
0 => "/Users/j.vance/Library/Application Support/.designsync/helper"
]
"RunAtLoad" => 1
"KeepAlive" => 1
}
Read that the way an investigator does. RunAtLoad means it starts at login. KeepAlive means the system relaunches it if it dies. ProgramArguments points at a hidden directory under Application Support. A legitimate agent looks similar, so the plist alone is a lead, not a verdict. What turns it into evidence is the Background Task Management database, which on modern macOS is the authoritative record of every login item and launchd job, including ones a plist on disk might no longer reflect.
# Dump the Background Task Management database (the modern persistence record)
% sudo sfltool dumpbtm
Item #1
UUID: 7C3D8A1F-2B60-4E51-9F2A-1C7E4B834D2A
Name: helper
Developer Name: (unknown)
Type: legacy daemon
Disposition: enabled, allowed, visible
Identifier: com.designsync.helper
Executable: /Users/j.vance/Library/Application Support/.designsync/helper
The BTM record corroborates the plist from an independent store: same identifier, same executable, registered and enabled, with no known developer. Two stores recording the same persistence through different mechanisms is the difference between a lead and a finding, and that corroboration habit is the spine of macOS work.
The Background Task Management database matters because it consolidated what used to be scattered. Before it, you chased login items, launchd plists, and helper registrations across several locations, any of which an attacker could leave stale or inconsistent. BTM is the system's own single ledger of every background item, which makes it both a faster first stop and a cross-check against what the on-disk plists claim.
What the user was doing
Here macOS gives you something Windows has no real equivalent for. The KnowledgeC database and Apple's Biome record pattern-of-life: which application held focus, for how long, when the screen locked, when a device connected. For the scenario, it answers whether a person was actually at the keyboard when the binary spawned from Terminal.
# Application focus around the alert, parsed from KnowledgeC
% python3 apollo.py --db knowledgeC.db --stream /app/inFocus
2026-03-14 02:09:51 app/inFocus com.apple.Safari duration 252s
2026-03-14 02:14:03 app/inFocus com.apple.Terminal duration 540s
2026-03-14 02:23:11 app/inFocus com.apple.finder duration 38s
The focus record places Safari active when the file was downloaded, then Terminal in focus across the window the binary spawned and connected out. That is a person, at the keyboard, downloading and then running something from a terminal. On a Windows host you would reconstruct that from scattered fragments. On a Mac it is a near-continuous record, and learning to read it is a large part of what makes this discipline different.
KnowledgeC is a SQLite database the system fills for its own features, predictive suggestions and Screen Time among them, which is exactly why it is valuable to an investigator. It was never meant as an audit log, so it records behavior the user had no reason to think was being kept. Biome extends the same idea with finer-grained streams, and on Tahoe those include the individual menu commands a user issued.
When a store is empty
The hardest macOS skill is reading an absence correctly. On Windows, a hole in the event log over an intrusion window is usually evidence of clearing. On macOS, a missing artifact has four distinct explanations: the activity did not happen, the OS version does not record it, retention rolled it off, or someone removed it. Those are four different findings, and the platform changes which apply every release.
Take the execution record. If the Unified Logging query had come back empty, that would not clear the binary. Log retention on a busy machine is often measured in days, so an event from two weeks before collection may simply have rolled off, while the quarantine tag and the BTM registration, which are not subject to that rotation, still stand. You read the gap in one store against what the durable stores show, never in isolation.
Anti-Pattern
Reading a missing artifact as proof that nothing happened.
A Windows examiner who finds no execution record often concludes the binary did not run. On macOS that reasoning breaks, because the artifact may never have existed. Before you read an absence as meaningful, establish what this version of the OS records, then decide which of the four explanations the evidence supports. The honest answer is sometimes that the platform did not capture it, and saying so is part of the job, not a failure of it.
This is why every version-sensitive artifact in the course carries a note on how Sequoia 15 and Tahoe 26 differ. You do not guess whether something should be there. You establish it, then read the gap.
From stores to a finding
A finding is a statement about what happened, supported by evidence, with a confidence level attached. macOS does not change that definition. It raises the premium on corroboration, because so few macOS artifacts stand alone, and the scenario now assembles into one account.
No single macOS artifact had to carry that. On Windows one strong source can sometimes stand alone. On macOS you build the finding as a corroborated chain from the first store you open, and that is the foundation the rest of the course is built on: read the right store for each question, show what it actually contains, and corroborate before you conclude.
Next: Section 0.2 lays out how the modules are organized around investigation questions and how the course carries both macOS Sequoia 15 and macOS 26 Tahoe.