Documentation & Tools →
Sign In
In this section

The macOS Forensic Toolstack: mac_apt, APOLLO, ccl-segb, and the Native Commands

Module 0

You proved a finding in 0.1 by reading stores one at a time. Doing that across a whole collection, at speed, is what the toolstack is for. A Mac scatters its record across half a dozen formats and no single tool reads them all well. Matching the parser to the format, and knowing when to distrust the parser and drop to the raw store, is the skill that separates a fast case from an afternoon lost to a store you cannot open.

Scenario

You have the NE-VANCE-MBP collection mounted on your analysis Mac. Inside are a binary property list that opens as garbage in a text editor, several SQLite databases, a .tracev3 log archive, and a SEGB-encoded Biome stream. None of them open in the tools you reached for on Windows. You need the whole landscape fast, then the two or three stores the alert points at, and every reading has to survive a second analyst checking it. Which tool opens which, and how do you keep the reading honest when a parser and the raw store disagree?

Estimated time: 20 minutes.

Match the tool to the format

The collection is not one kind of file. Property lists hold configuration and persistence in a binary encoding you convert before reading. SQLite databases back the behavioral stores and the browsers. Unified Logging writes the compressed tracev3 trace that only Apple's tooling reads cleanly. The newest Biome streams use the SEGB record format that most commercial suites cannot parse yet. The format decides the tool, so reading the format off a file is the first move on every artifact.

The analysis stack here is almost entirely open-source, and that is deliberate. Free tools carry no licensing friction onto a case and keep pace with a platform that reorganizes its stores every year. More important, you can read their code. When a parser claims an artifact says something, you can confirm the claim against the source rather than trust a black box. Commercial tools still earn their place in acquisition, where the engineering is genuinely hard, but the reading is done with tools you can open up.

Acquisition has its own short list. Aftermath runs locally or through MDM and pulls persistence items, the TCC databases, shell history, and logs into one archive. Fuji performs a logical image of modern Intel and Apple Silicon Macs into a DMG you can mount, and RECON ITR is the commercial option that boots the machine into a triage environment and handles the T2 chip and FileVault.

Section 0.5 and Module 3 take the acquisition decision in full. What matters now is what you do with the evidence once you have it, because every tool below assumes a collection already in hand.

Start with breadth

The first analysis pass is breadth, and the workhorse is mac_apt. It is a Python framework that takes a full disk image, a mounted volume, or a targeted artifact set and runs modular plugins across them, writing one SQLite table per plugin into a single mac_apt.db. You point it at the collection and let it map the whole landscape before you commit to a theory.

# Process the mounted collection in one pass; sqlite is the default output
% python mac_apt.py MOUNTED /Volumes/NE-VANCE-MBP /cases/vance/macapt ALL
MAIN-INFO-Started macOS Artifact Parsing Tool, version 1.12.0.dev
MAIN-INFO-Dates and times are in UTC unless the artifact stores local time
MAIN-INFO-Running plugin SAFARI
MAIN-INFO-Running plugin QUARANTINE
MAIN-INFO-Running plugin UNIFIEDLOGS
MAIN-INFO-Running plugin FSEVENTS
MAIN-INFO-Running plugin TERMSESSIONS
MAIN-INFO-Finished. Output written to mac_apt.db

Each plugin name is a category of evidence. QUARANTINE reads the download-provenance database and the Gatekeeper reject file. UNIFIEDLOGS parses the tracev3 store. FSEVENTS turns the filesystem changelog into rows. TERMSESSIONS recovers shell history for every user. You open mac_apt.db in any SQLite viewer and read across them, which is how you find the store your investigation question points at without opening every file by hand.

mac_apt

What: Python, plugin-based macOS artifact processing framework, the breadth-first workhorse of the stack.

Input: E01, DD, DMG, VMDK, AFF4, SPARSE, a mounted volume, or a single artifact file.

Output: One SQLite table per plugin in a single mac_apt.db, with CSV and spreadsheet exports on request.

Covers: Safari, quarantine, unified logs, FSEvents, Spotlight, recent items, install history, users, networking, and more.

Limit: Broad, not infinite. It predates the newest Tahoe stores and lags Apple's format changes, so treat the pass as a map, not the territory.

Two practical notes save you time. The default output is the SQLite database, while the optional TSV exports are UTF-16 little-endian, which trips up naive command-line parsing but opens cleanly in a spreadsheet. The unified-log plugin is also heavy: a forty-gigabyte image can produce a UnifiedLogs.db over ten gigabytes on its own. When you already know the question, you run named plugins rather than ALL and finish in minutes instead of an afternoon.

mac_apt is broad, not complete. It does not parse every store, and a tracev3 or Biome change in this year's release often lands before the framework catches up. That is the reason the breadth pass is where you start an investigation, not where you end it. MacRipper is a gentler alternative for a quick extract if you are new to the platform, with a GUI and a CLI that write parsed artifacts to text or CSV.

Read a store directly

When the breadth pass flags a store, you open it yourself with the native SQLite client. The privacy database, TCC.db, is a good example, because it answers a question Windows never asks: which applications were granted access to the camera, the microphone, the whole filesystem, or the accessibility API that can read keystrokes. There is a system database and one per user, and on an image you read them straight off disk.

# Read the privacy-grant store straight from the image
% cd "/Volumes/NE-VANCE-MBP/Library/Application Support/com.apple.TCC"
% sqlite3 TCC.db "SELECT service, client, auth_value, datetime(last_modified,'unixepoch') FROM access WHERE auth_value=2"
kTCCServiceMicrophone|us.zoom.us|2|2026-01-09 14:22:31
kTCCServiceSystemPolicyAllFiles|com.apple.Terminal|2|2026-03-14 02:11:53
kTCCServiceAccessibility|/Users/j.vance/Library/Application Support/.designsync/helper|2|2026-03-14 02:14:48

Read the rows like an analyst, not a list. An auth_value of 2 means the grant is enforced, and client_type tells you whether the client is named by bundle identifier or, as in the third row, by an absolute path to an executable. The first row is ordinary: Zoom holds the microphone, granted back in January.

The next two rows are the case. Terminal was granted Full Disk Access at 02:11, two minutes before the binary ran, which is how the operator reached protected files from a script. The helper at the hidden .designsync path holds the Accessibility grant, written at 02:14:48, seconds after execution. Accessibility lets a process read keystrokes from every other application, so that single row states a keylogging capability in plain text.

The last_modified epoch is the forensic value here. It timestamps exactly when each grant changed, and both timestamps drop straight onto the timeline you started in 0.1. One detail decides where you read this store: there is a system database under /Library and a per-user one under each home directory, and on a live Mac both are SIP-protected and need Full Disk Access to open. On the image you mounted, that protection does not apply, so you read them directly.

Anti-Pattern

Treating the breadth tool's output as the finding.

A broad processor is fast because it makes assumptions about formats, and on a platform that moves its stores every year those assumptions go stale. When the output looks wrong, when the case turns on a single record, or when the Mac runs a release the tool predates, you open the raw store yourself and confirm. The pass tells you where to look. It does not get to tell you what you found.

Some stores have only one parser

A few stores have no good general substitute, and knowing which is part of the toolstack itself. APOLLO is the standard parser for KnowledgeC and the Biome streams, correlating raw pattern-of-life data into a timeline of application focus and device activity: which app was frontmost and for how long, when the screen locked and woke, and intervals of user presence.

That is the store that puts a person at the keyboard during a window of activity, which is why it carries weight in insider and account-takeover cases alike. iLEAPP, better known on iOS, reads Apple system logs, property lists, and KnowledgeC on a Mac too and writes a readable report.

Two stores defeat the mainstream suites today. ccl-segb parses the SEGB format behind Apple's newer Biome streams, including the Tahoe App.MenuItem stream that records individual menu selections. FSEventsParser turns the raw FSEvents changelog into a timeline of file creation, modification, and deletion. When a store has exactly one parser that reads it, that parser is not a preference. It is the only door, and recognizing the format tells you which door to use.

The native toolchain is the arbiter

The most valuable tools are already on every Mac, and usually inside the collection you were handed. log show and log collect read Unified Logging. plutil converts and reads property lists. mdls and mdfind query Spotlight. codesign and spctl inspect a signature and report what Gatekeeper would decide. sfltool dumpbtm dumps the Background Task Management persistence store. These commands sit with no third-party interpretation between you and the data, which is exactly why they settle a dispute.

# mac_apt's unified-log plugin returned no rows for this process on Tahoe.
# Apple's own tool reads the same .tracev3 store correctly.
% log show vance.logarchive --predicate 'process == "helper"' --info --style compact
2026-03-14 02:14:09.880  helper  network connection to 203.0.113.44:443

When an automated parser returns nothing, or returns something that does not match the raw store, the native command is your fallback and usually your arbiter. It is always present, always current with the installed OS, and never blocked by a parser that has not caught up to this year's release. Learn the native commands even when a GUI is faster, because the GUI is only ever as current as its last update and the OS does not wait for it.

How the stack fits together

The workflow runs in one direction. You acquire or collect the evidence, process it broadly with mac_apt to see the whole picture, go deep on the stores the alert points at with the targeted parsers, and verify anything that matters against the native commands.

The stack, grouped by the job it does Acquire Aftermath, Fuji, RECON ITR, macOS Triage Tool Process broadly mac_apt for breadth, MacRipper for a quick extract Parse the stores APOLLO, iLEAPP, ccl-segb, FSEventsParser Verify (native) log, plutil, mdls, codesign, spctl, sfltool

Acquire, process broadly, parse the stores that matter, verify against native output.

On NE-VANCE-MBP that direction produced a clean reading. The breadth pass surfaced the quarantine and unified-log tables; the direct store read pulled the two TCC grants and their timestamps; the native log archive settled what the parser had missed. Three layers, each checking the one before it.

Toolstack Decision: NE-VANCE-MBP

Breadth: mac_apt across the mounted collection, to find which stores hold relevant rows before committing to any one of them.

Depth: sqlite3 directly against TCC.db for the privacy grants, and APOLLO against KnowledgeC for the focus timeline, because those stores reward a targeted read.

Arbiter: log show on the native archive, used to confirm the one process record the breadth parser dropped. The native read is what the finding cites.

The operational reference module collects these tools in one place for fast recall. Build a stack you understand, not a folder of binaries you were told to trust.

Next: Section 0.4 looks at where macOS investigation actually gets used, from incident response to insider cases, so the toolstack has a clear destination.