plist 与偏好设置

macOS 没有注册表。系统与应用配置散落成一个个属性列表文件(plist),这就是它的"注册表等价物"。

最后更新 2026-10-09版本 v1.0维护 DigiForensics查看历史 0

关键词:plist、bplist00、plutil、defaults、ByHost、GlobalPreferences、cfprefsd、Mobile Documents 难度:入门 前置知识:macOS 目录结构(见01)、XML 基础

一、概述

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,会直接写回原文件。 分析者习惯把它当成"输出到