一、概述
macOS 没有注册表。系统与应用配置散落成一个个属性列表文件(plist),这就是它的"注册表等价物"。
~/Library/Preferences/com.apple.finder.plist 装的是 Finder 的全部配置,落在的位置相当于 Windows 的 HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer。
从 Windows 过来的人会连续踩空,两处。
同一逻辑配置在磁盘上可能是二进制格式。
| 格式 |
开头 |
可读性 |
常见位置 |
| XML |
<?xml version="1.0" ...?> |
人可读 |
手写配置、.strings 资源 |
| 二进制 |
bplist00(8 字节魔数) |
不可读 |
绝大多数系统与应用偏好 |
| JSON |
—(plutil 可输出) |
可读 |
脚本中间格式 |
实测本机 com.apple.finder.plist 就是 binary plist。直接 cat 或 grep 全部无效——本篇要先解决的就是这个。
同一份偏好可能存在三份副本,分不要在 Preferences/com.x.y.plist、Preferences/ByHost/com.x.y.<UUID>.plist、以及内存中的 cfprefsd 缓存里。
只查一个就下结论,是本篇最常见的误判来源。
在取证流程中的位置:本篇是 macOS 取证的配置层入口,在完成只读挂载或物理镜像之后、进入应用数据与日志分析之前做。它产出的键值对是后续时间线重建的锚点之一(多数 plist 带 修改日期 字段),也是判断"用户改过什么设置"的直接依据。上游依赖是 Mac 取证工件地图(~/Library/ 下面有哪些目录、各自装什么)与 Spotlight 索引与元数据(用 mdfind 按 bundle id 定位偏好文件,比全盘遍历快得多)。cfprefsd 缓存的分析属内存取证,参考 内存证据的可采性与报告;改包与权限痕迹走 Quarantine 与 Gatekeeper。
本篇处理的证据形态(先确认文件类型,再谈读法):
| 位置 |
文件名形态 |
取证时要注意 |
~/Library/Preferences/ |
com.bundle.id.plist |
bundle id 权威值在 CFBundleIdentifier 键里,文件名对不上时以键为准 |
~/Library/Preferences/ByHost/ |
com.bundle.id.<UUID>.plist |
同一 bundle 按用户与主机域各存一份,UUID 后缀必须和用户目录对应起来 |
~/Library/Managed Preferences/ |
同上命名的 plist |
MDM 下发,优先级最高、改不了也删不掉;这里出现的值是管理员设的,不是用户设的 |
/Library/Preferences/、/Library/Managed Preferences/ |
系统级 plist |
与用户级同名文件并存时,优先级按 macOS 规则,不按修改时间 |
~/Library/Containers/<bundle-id>/Data/Library/Preferences/ |
沙盒应用私有 plist |
10.14 之后的应用大量落在这里,不扫等于漏掉整个应用生态 |
二进制 plist 里有几个可用的定长锚点:文件头 8 字节 bplist00、文件末尾的 trailer 结构(给出偏移表与对象表的位置)、布尔值固定用 0x08 / 0x09、CFString 的 tag 是 0x5A。手工解析时按这些走,不要去猜整体长度,也不要把二进制 plist 当 UTF-8 去 strings 后就下结论——中文键值会被切断。
某个应用当前与历史上的配置状态(哪些键被写过)、某项功能是否被显式关闭过、是否有 MDM 或安装配置文件在托管这台机器、同一 bundle 在不同用户下的配置是否不一致。
键值只说明配置状态,不说明执行事实——关掉某项功能的键,不等于对应行为没发生;磁盘上也不留"是谁改的",值可能来自用户点击、应用自动迁移或 MDM 下发。plist 的 修改日期 是最后一次写入该文件的时刻,不是这个键被改动的时间,批量导入配置会把整个文件的日期刷成同一时刻。ByHost 与主文件不一致时判断不了哪份更新,合并规则受 cfprefsd 运行时状态影响。阴性结果必须限定范围:"在 ~/Library/Preferences/ 与 ByHost/ 下未发现该 bundle id 的偏好文件",未扫的 Managed Preferences、系统级目录与容器内 plist 属于未覆盖范围。另外 defaults read 读的是 cfprefsd 合并后的视图,直接改磁盘文件会被它覆盖回去——改证据的经典死路。plist 里也不存访问频率这类运行时数据,那些在 ~/Library/Application Support/ 与统一日志里,不要在 plist 里没找到就认为某项功能没被用过。
读者前提:需要能分辨 binary plist 与 XML plist 的文件头,并接受"先转成 XML 或用 plutil 输出 JSON 再分析"这一步是强制的;需要理解 cfprefsd 同时是偏好设置的读路径与合并路径,这是三份副本优先级问题的根源;知道 plist 的键名在不同 macOS 大版本之间会增删迁移,老案例里的键在新系统上可能根本不存在。
二、核心原理
2.1 二进制 plist 的结构
plist 本质就是键值对序列化,能装字符串、整数、布尔、日期、数据、数组、字典。二进制格式的布局:
偏移 0 8 字节魔数 "bplist00"
偏移 8 对象表(字符串 / 整数 / 字典 / 数组 ...)
末尾-32 Trailer:偏移表位置、对象引用大小、顶层对象偏移、对象总数
更靠末尾 Offset Table:偏移表(★ 与 Trailer 一起在文件尾部)
Trailer 与偏移表在文件末尾,这与多数格式相反。
后果很直接:文件尾部一旦被截断,前面内容再完整也无法解析——plutil -lint 报错往往就指向这里,是判断 plist 是否被截断或篡改的关键检查点。
xxd -l 8 com.apple.finder.plist
# → 00000000: 6270 6c69 7374 3030 bplist00
file com.apple.finder.plist
# → Apple binary property list
2.2 plutil:取证的主力工具
plutil 是 Apple 自带工具,能读两种格式,是离线分析的首选。
| 命令 |
作用 |
取证用途 |
plutil -p file |
人类可读打印(=> 风格) |
快速查看;输出格式不稳定,不适合机器解析 |
plutil -lint file |
语法检查 |
验证是否完整、是否被截断 |
plutil -convert xml1 -o - file |
转 XML 输出到 stdout |
把二进制转成可 grep 的文本(最高频) |
plutil -extract <key> <fmt> -o - file |
提取某个键 |
精确取值,如 -extract Downloads raw |
plutil -type <key> file |
取键的类型 |
判断值是数组还是字典 |
⚠️ -convert 若不给 -o,会直接写回原文件。 分析原始证据时必须用 -o -(stdout)或 -o /tmp/x.xml(工作副本),绝不能原地转换。
2.3 三个存储位置与优先级
① ~/Library/Preferences/com.vendor.app.plist ← 通用配置(应用主配置)
② ~/Library/Preferences/ByHost/com.vendor.app.<UUID>.plist ← 绑定到某台具体机器的配置
③ cfprefsd 守护进程的内存缓存 ← 运行时视图
| 位置 |
何时使用 |
取证含义 |
Preferences/ |
默认配置 |
应用的主配置来源 |
Preferences/ByHost/ |
配置需与机器绑定 |
文件名含本机 Hardware UUID |
cfprefsd 缓存 |
运行时 |
命令看到的值未必等于磁盘上的值 |
ByHost 的取证价值常被低估。 先看文件名结构:
com.adobe.headlightscc.3132C17B-F189-5363-B717-8ACDE9BAF01D.plist
└──────── 应用 bundle ID ────────┘ └──── 本机 Hardware UUID ────┘
这个 UUID 就是本机 Hardware UUID(system_profiler SPHardwareDataType 可查)。本机实测 3132C17B-F189-5363-B717-8ACDE9BAF01D,与 ~/Library/Keychains/ 下同名目录一致。
所以 ByHost 的文件名能直接回答两个问题:
- 确认这些偏好属于本机,而不是从其他机器拷贝而来
- 发现异常:若 ByHost 里出现多个不同 UUID,说明该用户曾在这台机器上导入过其他机器的配置
2.4 defaults 与磁盘文件的对应关系
defaults 域 |
对应文件 |
defaults read <domain> |
~/Library/Preferences/<domain>.plist |
defaults read NSGlobalDomain |
~/Library/Preferences/.GlobalPreferences.plist |
defaults read com.apple.finder |
~/Library/Preferences/com.apple.finder.plist |
defaults -currentHost read <domain> |
走 ByHost/ 目录 |
defaults write / defaults delete 会修改系统配置,对原始证据绝不可用,取证只用 defaults read。
而且它读的是本机的 cfprefsd,对镜像里的 plist 通常无效——读镜像文件应直接用 plutil。
2.5 cfprefsd 对取证的三个具体影响
| 问题 |
说明 |
对策 |
| 命令读到的可能是缓存值 |
defaults read 走 cfprefsd,与磁盘 plist 存在时间差 |
以磁盘 plist 为准,defaults 仅交叉参考 |
| 关机不保证立即落盘 |
正常关机流程会刷缓存;强制断电可能丢失最近的偏好变更 |
缺失的变更不等于没发生设置 |
| 强制断电留下不一致态 |
缓存里的新值未写回,磁盘上是旧值 |
磁盘值是"最后一次成功落盘"的状态 |
实操结论:报告里引用偏好设置时必须注明读取方式(plutil 读盘还是 defaults 读缓存)。
两个值不一致本身就是有价值的发现——可能意味着程序崩溃被强杀、偏好被外部工具直接改盘、或有人手工编辑了 plist。
2.6 .GlobalPreferences.plist 与 Mobile Documents
.GlobalPreferences.plist 是 NSGlobalDomain 的落盘位置,以点号开头,ls 不带 -a 看不到。实测内容(节选):
plutil -p ~/Library/Preferences/.GlobalPreferences.plist | grep -E 'Apple(Locale|Languages|InterfaceStyle)'
# "AppleInterfaceStyle" => "Dark"
# "AppleLanguages" => [ "zh-Hans-CN" ]
# "AppleLocale" => "zh_CN"
| 键 |
取证价值 |
AppleLanguages / AppleLocale |
用户语言环境,可佐证"是否本人操作" |
AppleInterfaceStyle |
深色 / 浅色模式 |
AppleMeasurementUnits / AppleMetricUnits |
公制 / 英制 |
AppleICUForce24HourTime |
12 / 24 小时制——影响日志时间显示的解读 |
~/Library/Mobile Documents/(macOS 10.15+;10.14 时代在 ~/Mobile Documents/)是 iCloud Drive 的本地同步目录。
其下 ~apple 是实际内容区(~ 是 Apple 用来转义 / 的规则,不是文件名的正常字符)。
| 现象 |
正确解释 |
~apple/CloudDocs/ |
系统管理的内部结构,不是用户文档 |
出现 .icloud 后缀文件 |
仅云端有副本、本地未下载,不代表文件已删除 |
| 文件 mtime 异常新 |
iCloud 同步会更新 mtime,不等于编辑时间 |
三、操作步骤
3.1 第 1 步:确认格式与完整性
P=~/Library/Preferences/com.vendor.app.plist
file "$P" # → Apple binary property list 或 XML document
xxd -l 8 "$P" # bplist00 = 二进制;3c3f786d6c = "<?xml" = XML
plutil -lint "$P" # → xxx.plist: OK(语法完整)
plutil -lint 通过只是"文件完整"的必要验证。
报出 Encountered unknown tag 之类的错误,说明文件可能被截断或改写——这本身是重要发现。
3.2 第 2 步:读取与提取
plutil -p "$P" # 人类可读全量打印
plutil -convert xml1 -o - "$P" | grep -A5 -i 'ServerURL' # ★ 转 XML 后 grep
plutil -extract Downloads raw -o - "$P" # 精确提取某个键
plutil -type Downloads "$P" # 查看键的类型
3.3 第 3 步:三位置完整清点
EV=/mnt/mac-data/Users/liwei
ls -1 "$EV/Library/Preferences"/*.plist 2>/dev/null | wc -l # ① 通用偏好数量
ls -1 "$EV/Library/Preferences/ByHost"/*.plist 2>/dev/null | wc -l # ② ByHost 偏好
# ③ ByHost 文件名中的 UUID(<bundle-id>.<UUID>.plist)
ls -1 "$EV/Library/Preferences/ByHost"/*.plist 2>/dev/null \
| sed -E 's|.*/||; s|\.plist$||; s|.*\.||' | sort -u
# → 应只有一个 UUID;出现多个即为拷机 / 迁移痕迹
3.4 第 4 步:重点应用配置
# Safari 下载记录(10.14+ 随沙盒迁入容器,两处都查)
find "$EV/Library" -name 'Downloads.plist' -path '*Safari*' 2>/dev/null
plutil -p "$EV/Library/Containers/com.apple.Safari/Data/Library/Safari/Downloads.plist" 2>/dev/null
# 后台项目录(现代登录项,10.14+)
plutil -p /var/db/com.apple.backgroundtaskmanagement/Contents/Info.plist 2>/dev/null | head -40
# 网络配置
plutil -p /mnt/mac-data/Library/Preferences/SystemConfiguration/NetworkInterfaces.plist
# IM 客户端
for f in "$EV"/com.tencent.xinWeChat.plist "$EV"/org.telegram.desktop.plist; do
[ -f "$f" ] && { echo "--- $f"; plutil -p "$f" | head -20; }
done
3.5 第 5 步:验证
| 验证对象 |
方法 |
通过标准 |
| plist 完整未截断 |
plutil -lint |
输出 : OK |
| 格式判断正确 |
file + xxd -l 8 |
bplist00 或 <?xml |
| 转换未改原件 |
转换前后 shasum 比对 |
完全一致 |
| ByHost 归属本机 |
与 Hardware UUID 比对 |
相同则归属本机 |
defaults 与 plutil 一致 |
两者读同一键 |
不一致本身就是发现 |
shasum -a 256 "$P" > /tmp/pre.sha256
plutil -convert xml1 -o - "$P" > /tmp/view.xml
shasum -a 256 -c /tmp/pre.sha256 # → OK,证明未改动原文件
四、常见陷阱
★ 标记表示该操作会导致原始检材永久灭失或不可逆改变,执行前必须停下来确认。
4.1 格式与完整性
陷阱 1:★ plutil -convert 不带 -o,原地改写检材里的二进制 plist
现象:为把二进制 plist 转成可 grep 的文本,直接执行 plutil -convert xml1 com.vendor.app.plist,然后 grep 该文件。
原因:-convert 若不给 -o,会直接写回原文件。 分析者习惯把它当成"输出到屏幕"的转换命令(因为 -p 确实是打印),而 -convert 的默认行为是