macOS and Linux essentials · Lesson 1 of 5
macOS: layout, plists and accounts
11 min read
A Mac keeps most of its configuration and much of its user activity in ordinary files: property lists, small databases and extended attributes, spread across a directory layout that differs from Windows and Linux. This lesson maps the directories an examiner works in, explains how to read property lists and the attributes macOS attaches to downloaded files, and shows where the system records its version, names, time zone, accounts and install history. It also explains why modern Macs are usually collected logically. Lesson 1.7 covered APFS volumes and Lesson 3.1 the Apple time formats; both are assumed here.
The directory layout
On macOS 10.15 Catalina and later the visible tree combines the read-only System volume and the Data volume (Lesson 1.7). /etc, /var and /tmp are symbolic links to /private/etc, /private/var and /private/tmp, so a triage collection or parser may show either form.
| Path | What it holds | Examiner notes |
|---|---|---|
/System | The operating system, on the sealed System volume | Read-only; holds SystemVersion.plist |
/Applications | Applications installed by users and the App Store | Apple's built-in apps are in /System/Applications since Catalina |
/Library | System-wide settings and support files for all users | Preferences, Application Support, Logs, launch items (Lesson 6.5) |
/Users/<name> | Home folders | Desktop, Documents, Downloads and the hidden Library |
~/Library/Preferences | Per-user application settings, one plist per app | Named after the bundle identifier, e.g. com.apple.finder.plist |
~/Library/Application Support | Per-app data and databases | Many browser and chat databases (Module 4) |
~/Library/Containers/<bundle-id> | Data of sandboxed apps | Data/ mirrors a home folder, so the app's preferences are in Data/Library/Preferences |
~/Library/Group Containers/<group-id> | Data shared by apps of one developer | e.g. group.com.apple.notes holds the Notes database |
~/Library/Caches, ~/Library/Logs | Caches and per-user application logs | Volatile but often overlooked |
/private/var/db | System databases | Local accounts (dslocal), receipts, .AppleSetupDone, Unified Log stores (diagnostics, uuidtext) |
/private/var/log | Text logs | install.log, system.log |
/private/var/folders | Per-user temporary and cache folders | /private/var/folders/<2 chars>/<random>/ with T (temp), C (cache) and 0 subfolders |
/private/etc | Unix configuration | hosts, sudoers, the localtime link |
Sandboxed apps (all App Store apps, and built-in apps such as Safari on current releases) keep their data in their container, so look there before concluding that an app left no preferences.
Hidden files and extended attributes
Finder hides names that begin with a dot and items carrying the hidden BSD flag (set with chflags hidden, shown by ls -lO); ~/Library is hidden this way. Forensic tools show both.
Extended attributes (xattrs) are named pieces of metadata stored with a file, outside its content. xattr -l <file> lists names and values. Two matter in almost every case:
com.apple.quarantine: added by quarantine-aware apps (browsers, mail and messaging clients, AirDrop) to downloaded files. Its value is a text stringflags;timestamp;agent;UUID: four hex digits of flags, the time in hexadecimal Unix seconds, the name of the downloading application, and a UUID that links to the user's quarantine events database~/Library/Preferences/com.apple.LaunchServices.QuarantineEventsV2, which can hold the download URL.com.apple.metadata:kMDItemWhereFroms: the source URLs of a download, usually the file URL and the referring page. Its value is a binary property list containing an array of strings.
Extended attributes exist only on file systems that support them, such as APFS and HFS+. When a Mac copies a file to FAT32 or exFAT, macOS stores the attributes in a separate AppleDouble file named ._<filename> (magic 00 05 16 07); ._ files on a USB drive therefore show that a Mac wrote to it. Most other copies (uploads, many archive tools, copy tools that do not preserve xattrs even onto file systems that support them) silently drop them. Collect into an image or container format that preserves xattrs, or record them with xattr -l before copying.
Property lists
A property list (plist) serializes a tree of typed values: dictionaries, arrays, strings, integers, reals, Booleans, dates and raw data. It plays the role of a Registry key, but each plist is a separate file. Three forms matter:
| Form | How to recognize it | Notes |
|---|---|---|
| XML | Starts with <?xml and a plist DOCTYPE | Dates as ISO 8601 text, data as Base64 |
| Binary | First 8 bytes bplist00 | Most system and app plists; 32-byte trailer at the end points to an offset table |
| JSON | Starts with { or [ | An export option of plutil; cannot represent dates or data, so conversions of such plists fail |
On a Mac, plutil -p <file> prints any plist readably, and plutil -convert xml1 -o out.xml <file> writes an XML copy. Without -o, plutil -convert rewrites the file in place, so run it only on working copies. On Linux or Windows, Python's plistlib reads XML and binary plists.
In a binary plist each object begins with a marker byte: the high four bits give the type and the low four bits a length or size code. 0x5n is an ASCII string of n characters, 0x6n a UTF-16 string, 0x1n an integer, 0x23 an 8-byte real, 0x33 a date, 0x4n raw data, 0x8n a UID reference, 0xAn an array and 0xDn a dictionary. A date is an 8-byte big-endian double of Mac Absolute Time.
Two nestings are common. A data value can itself start with bplist00: a plist inside a plist that must be decoded again. And many apps serialize objects with NSKeyedArchiver: the plist has the keys $archiver (value NSKeyedArchiver), $version, $top and $objects, and values are scattered through the $objects array and joined by UID references. Read these with a deserializing parser rather than by eye.
Key system plists
| Question | File or path | Key |
|---|---|---|
| macOS version and build | /System/Library/CoreServices/SystemVersion.plist | ProductVersion, ProductBuildVersion |
| Computer names | /Library/Preferences/SystemConfiguration/preferences.plist | System/System/ComputerName, System/System/HostName (if set), System/Network/HostNames/LocalHostName |
| Time zone | /private/etc/localtime (symbolic link) | Link target, e.g. /var/db/timezone/zoneinfo/Europe/Berlin |
| Time zone city chosen | /Library/Preferences/.GlobalPreferences.plist | com.apple.TimeZonePref.Last_Selected_City |
| Last user at the login window | /Library/Preferences/com.apple.loginwindow.plist | lastUserName |
| Automatic login | /Library/Preferences/com.apple.loginwindow.plist | autoLoginUser (password obfuscated in /private/etc/kcpassword) |
| Known Wi-Fi networks | /Library/Preferences/com.apple.wifi.known-networks.plist | One entry per network, wifi.network.ssid.<SSID>, with AddedAt, JoinedByUserAt, JoinedBySystemAt |
| Bluetooth devices | /Library/Preferences/com.apple.Bluetooth.plist | Paired device addresses and names |
| Local accounts | /private/var/db/dslocal/nodes/Default/users/<name>.plist | uid, home, generateduid, accountPolicyData |
| Administrators | /private/var/db/dslocal/nodes/Default/groups/admin.plist | users, groupmembers |
| Installs and updates | /Library/Receipts/InstallHistory.plist | date, displayName, displayVersion, processName |
SystemVersion.plist is on the System volume and may be missing from a collection of the Data volume; InstallHistory.plist then gives the installed versions. The Wi-Fi file replaced the KnownNetworks dictionary of /Library/Preferences/SystemConfiguration/com.apple.airport.preferences.plist during the macOS 11 Big Sur cycle; on older systems read that file instead. The time zone is the link target, not the content of the zone file.
Accounts
macOS keeps local accounts in the directory service's local node, one binary plist per account under /private/var/db/dslocal/nodes/Default/users/, readable only by root on a live system. Every attribute is an array, even single values: uid is ["501"] as a string. Useful keys are name (short name), realname (full name), uid, gid (20, staff, for normal users), home, shell, generateduid (a UUID that identifies the account in group membership), authentication_authority and:
accountPolicyData: a nested plist withcreationTime,passwordLastSetTime,failedLoginCountandfailedLoginTimestamp. These times are Unix seconds stored as reals, not Mac Absolute Time. The failure counter resets after a correct password. For logon history, use the Unified Logs (Lesson 6.2).ShadowHashData: a nested plist with the password hash,SALTED-SHA512-PBKDF2(entropy,salt,iterations) since OS X 10.8. It proves an account has a local password; recovering passwords is outside this course.
The first account created in Setup Assistant gets UID 501, the next 502. Accounts whose names start with _ are system service accounts with low UIDs. In groups/admin.plist (GID 80), users lists short names and groupmembers lists generateduid values; check both. On a live Mac, dscl . -list /Users UniqueID and dscl . -read /Users/<name> show the same data. A mobile account is a locally cached copy of a directory (e.g. Active Directory) account; its authentication_authority contains LocalCachedUser, and its UID usually comes from the directory and is far above 501.
Install date and updates
/private/var/db/.AppleSetupDone is an empty file created when Setup Assistant finishes, so its timestamps indicate when the system was set up; later setups after OS upgrades may rewrite it, so corroborate it with the first user's creationTime and the earliest InstallHistory.plist entries. Each entry in InstallHistory.plist records one installation: macOS updates (processName such as softwareupdated), App Store apps and installer packages. /private/var/db/receipts and /private/var/log/install.log corroborate them.
Collecting from a modern Mac
On T2 and Apple silicon Macs, a physical image away from the original hardware is unreadable (Lesson 1.7), and a full image of the unlocked live system is often impractical. Examiners therefore make a logical triage collection from the running, unlocked Mac: sudo log collect --output <dest>.logarchive for the Unified Logs, plus file copies of /Users, /Library and /private/var made by a tool granted Full Disk Access in Privacy & Security. Without it, privacy protections (TCC) silently block Mail, Messages, Safari and other protected data. Document what could not be read and hash the output. Free examples: Aftermath (open-source collection and analysis), mac_apt (parses images and collected folders), APOLLO (pattern-of-life databases, Lesson 6.2) and plutil.
Worked example: decoding plist values and a quarantine string
loginwindow. In a binary com.apple.loginwindow.plist the objects 5C 6C 61 73 74 55 73 65 72 4E 61 6D 65 and 54 61 6C 65 78 appear. Marker 0x5C is an ASCII string of 12 characters: lastUserName. Marker 0x54 is an ASCII string of 4: alex. The dictionary pairs them, so lastUserName = alex.
InstallHistory date. An entry with displayName "macOS Sequoia 15.1" has a date object 33 41 C6 6C 84 94 80 00 00. Marker 0x33 is a date; the next eight bytes are a big-endian double:
python3 -c "import struct; print(struct.unpack('>d', bytes.fromhex('41c66c8494800000')))"prints752421161.0.- Unix time = 752,421,161 + 978,307,200 = 1,730,728,361.
python3 -c "import datetime as d; print(d.datetime.fromtimestamp(1730728361, d.timezone.utc))"prints2024-11-04 13:52:41+00:00, so 2024-11-04T13:52:41Z.
Quarantine. xattr -l on ~/Downloads/tool.dmg shows com.apple.quarantine: 0083;65f2c1a3;Safari; followed by a UUID.
- 0x65F2C1A3 = 6×16^7 + 5×16^6 + 15×16^5 + 2×16^4 + 12×16^3 + 1×16^2 + 10×16 + 3 = 1,710,408,099.
python3 -c "import datetime as d; print(d.datetime.fromtimestamp(0x65f2c1a3, d.timezone.utc))"prints2024-03-14 09:21:39+00:00, so Safari quarantined the file at 2024-03-14T09:21:39Z.
Corroborate with the matching UUID row in QuarantineEventsV2, whose times are Mac Absolute Time, and with the file's APFS creation time. The quarantine time is set by the downloading app and is not proof that the file was ever opened.
Exam focus: Expect to match a question to its source: version inSystemVersion.plist, names inpreferences.plist, last user incom.apple.loginwindow.plist, accounts and admin membership underdslocal, installs inInstallHistory.plist. Be ready to identify a binary plist bybplist00, decode a plist date as Mac Absolute Time, convert the hex Unix time in a quarantine string, and explain why extended attributes disappear when files are copied to file systems without xattr support.
Key points
- User evidence sits in
/Users/<name>/Library(Preferences, Application Support, Containers, Group Containers) and under/private/var(db,log,folders); sandboxed apps keep their data in their container. com.apple.quarantineholds flags, a hex Unix timestamp, the downloading agent and a UUID;kMDItemWhereFromsholds the source URLs. Both survive only where xattrs are supported or carried in._AppleDouble files.- Plists come in XML and binary (
bplist00) forms; binary dates are big-endian Mac Absolute doubles; nested plists and NSKeyedArchiver archives need a second decoding step.plutil -convertwithout-ooverwrites the file. - Local accounts are per-user plists in
dslocal: the first user is UID 501, admin membership is ingroups/admin.plist, andaccountPolicyDatatimes are Unix seconds. - Modern Macs are usually collected logically from the live, unlocked system with Full Disk Access and
log collect.