# 在 macOS 上寻找持久性：LaunchAgents、登录项和后台任务

> 每个位置代码都可以注册以在 Mac 上重新启动后继续存在，从终端检查：LaunchAgents、LaunchDaemons、登录项、cron、配置文件 - 以及 FireAI 适合和不适合的地方。

HisnLabs Security & Research Team · Published 2026-09-21
Canonical: https://hisnlabs.com/zh/blog/macos-persistence-hunting

“持久性”是针对一个特定问题的安全术语：运行一次的代码如何安排在重新启动或登录后自动再次运行，而无需任何人手动重新启动它？ macOS 为合法软件提供了许多经过批准的方法来做到这一点 - 更新检查器、菜单栏同步客户端、打印机驱动程序的帮助程序 - 并且这些机制中的每一种都同样适用于您根本不希望运行的东西。这是对实际需要查看的地方的游览，使用真实的命令，并诚实地说明了像 FireAI 这样的以网络为中心的工具属于和不属于该图片的位置。

## LaunchAgents 和 LaunchDaemons：两个大的

macOS 几乎通过 `launchd` 启动所有内容，由少量已知目录中的属性列表 (`.plist`) 文件驱动。 MITRE ATT&CK 的技术 T1543.001 清楚地描述了该机制：登录时，每个用户 `launchd` 进程从用户和系统 LaunchAgents 目录加载 plist，`RunAtLoad` 设置为 true 的 plist 会在加载时自动执行 - 无论是谁把它放在那里，都不需要采取进一步的操作。该技术列出了三个重要的位置：`/System/Library/LaunchAgents`、`/Library/LaunchAgents` 和`~/Library/LaunchAgents`。当您滚动浏览这些文件的列表时，MITRE 注意到一些值得记住的事情：为持久性而安装的代理通常是“伪装[d]...使用类似于合法操作系统或软件组件的名称”——看起来像 `com.apple.something.plist` 的文件值得再看一眼，因为它试图不获取一个文件。

相关技术 T1543.004 涵盖的 LaunchDaemons 是系统范围的、无需登录的版本：它们以 root 身份运行，在启动时从 `/System/Library/LaunchDaemons/` 或 `/Library/LaunchDaemons/` 开始。因为在那里安装一个文件首先需要管理权限，所以 MITRE 将守护进程路由作为一种将初始特权立足点转换为重新启动后仍可生存的东西的方式，从那时起以根级别访问权限运行 — 这也是为什么出现在 `/Library/LaunchDaemons` 中的新的、不熟悉的文件比出现在用户自己的 LaunchAgents 文件夹中的信号更重。

*终端 — 列出实际注册的内容*

```console
$ ls -la ~/Library/LaunchAgents /Library/LaunchAgents /Library/LaunchDaemons
# compare this list against what you remember installing; anything you don't
# recognise is worth reading with: plutil -p /path/to/the.plist
```

磁盘上存在的文件和实际加载的作业是两个不同的问题，`launchctl print` 回答了第二个问题。它的手册页将其描述为打印“有关指定服务或域的信息”——指向像`system/`或`gui/501/`这样的域（501是用户的UID），它列出了当前在该上下文中加载的每个服务和端点，以及每个服务和端点的状态：

*终端——现在实际加载的内容*

```console
$ launchctl print gui/$(id -u)
# example output, trimmed — a real run lists every loaded agent for your session
	"com.apple.someAgent" => {
		active count = 1
		path = /Library/LaunchAgents/com.apple.someAgent.plist
		state = running
	}
```

> **Note:** launchctl 手册页明确指出此输出“在任何意义上都不是 API”，并且其确切格式可能会在 macOS 版本之间发生变化，恕不另行通知 - 将其视为要阅读的内容，而不是编写脚本的内容。

## 登录项和后台任务管理

其中大部分面向用户的界面是“系统设置”的“登录项”窗格，值得您亲眼检查，而不仅仅是命令行。苹果的支持指南直接描述了这一点：你可以“选择登录时自动打开的登录项”，在那里添加或删除它们，并单独允许或拒绝“在应用程序未打开时执行任务，例如检查软件更新或同步数据”的应用程序 - 第二类涵盖不是完整登录项但仍无人值守运行的后台助手。

自 macOS Ventura 以来，该设置窗格下方的系统在安全社区中通常被称为后台任务管理 (BTM)：一种在注册时跟踪每个启动代理、启动守护程序和登录项的服务，这让系统设置可以向您显示实时、集中的列表，而无需您手动搜索三个 plist 目录。有一个未记录的命令行工具`sfltool`，一些研究人员使用它通过`sfltool dumpbtm`更直接地查询数据库——苹果没有为其提供手册页，并且它的输出格式不能保证保持稳定，因此将其视为一种研究好奇心，在您自己的机器上尝试，而不是围绕它构建工作流程。查看相同信息的受支持、稳定的方式仍然是“系统设置”中的“登录项”窗格，或者用于特定作业的实时状态的`launchctl print`。

## cron：更老、更安静、仍然存在

launchd 长期以来一直是 Apple 的首选调度程序，但较旧的 Unix `cron` 守护进程仍然发布并运行其中调度的任何内容。 crontab 手册页直接描述了文件格式：每行有五个时间/日期字段 - 分钟、小时、月份、月份、星期几 - 后面跟着要运行的命令，在某些系统上可以使用 `@reboot` 和类似的简写字符串来代替这五个字段。 `crontab -l`，根据 crontab(1) 手册页，将为当前用户“在标准输出上显示当前 crontab”：

*终端 — 检查你自己的和 root 的 crontab*

```console
$ crontab -l
$ sudo crontab -l -u root
```

在当今的大多数 Mac 上，两者的空结果都是正常的 - 这正是其中的任何内容值得关注的原因。 cron 乏味且很少被检查，这正是它仍然在事件记录中作为后备持久性位置出现的原因。

## 配置配置文件：通过书面记录持久保存

配置文件可以安装 LaunchDaemon、授予隐私权限或在一组 Mac 上推送设置 - 合法地，这就是 MDM（移动设备管理）的工作原理。非法的是，配置文件是一种记录的方式，可以在不直接接触 plist 文件的情况下保留更改。 `profiles` 命令行工具列出了已安装的内容：`profiles list` 显示已安装的配置文件，并且正如其手册页所述，使用 `-all` 以 root 身份运行它将“列出系统上的所有配置配置文件”，而不仅仅是当前用户的配置文件。

*终端 — Mac 上的每个配置文件*

```console
$ sudo profiles list -all
$ sudo profiles show -all
```

没有 MDM 注册的个人 Mac 通常应该没有，或者只有您特意安装的（VPN 配置、工作配置文件）。您不记得安装的配置文件值得在删除之前进行调查，因为 `profiles` 也支持在该步骤中通过密码保护进行删除。

## 授权插件和长尾

除了上述四大功能之外，还有一长串较小、较旧的机制：目录服务和授权插件、Spotlight 导入器、QuickLook 生成器、Dock 磁贴插件以及每次打开新终端会话时运行的 shell 启动文件。这就是专用工具比手动检查更胜一筹的地方。例如，Objective-See 的 KnockKnock 一次性枚举了 20 多个类别的持久性位置，包括启动代理和守护程序、登录项、浏览器扩展、cron 作业、内核和系统扩展以及授权和目录服务插件，并显示在每一类中找到的代码签名状态。它的伴侣 BlockBlock 获取相同的位置列表并持续监视它们，在有新记录时发出警报；根据它自己的描述，它“监视常见的持久性位置，并在添加新的持久性组件时发出警报”，显示负责的流程及其签名状态，并让您当场允许或阻止。

## Shell 启动文件：安静的包罗万象

还有一个位置值得直接查看，因为它不需要 plist，也根本不需要特权安装：shell 配置文件。每次打开匹配的新终端会话时，`~/.zshrc`、`~/.zprofile` 和 `~/.bash_profile` 都会运行，并且单个附加行（管道到脚本、导出被劫持的 `PATH`、启动后台进程）足以在每次打开终端时重新建立立足点，无需将任何内容加载到 `launchd` 中，也无需向 `launchctl print` 显示任何内容。 Objective-See 的 KnockKnock 在其扫描中包含了这一类别，与启动代理和登录项一起作为 shell 配置文件列出，其原因与本文中的原因相同：它足够常见，也足够平淡，值得检查而不是假设。

*终端——快速阅读，不能替代实际阅读*

```console
$ cat -A ~/.zshrc ~/.zprofile ~/.bash_profile 2>/dev/null | less
# -A shows non-printing characters, which surfaces anything hidden with
# trailing whitespace or a carriage return trying to push it off-screen
```

## FireAI 适合什么地方，它故意不适合什么地方

直接说吧：FireAI 不会扫描 `/Library/LaunchDaemons`，不会读取 plist 文件，并且不会尝试检测新的登录项或配置文件。这是一个与 FireAI 所做的截然不同的学科，专门为其构建的工具（其中包括 KnockKnock 和 BlockBlock）已经很好地完成了这项工作。 FireAI 关注的是持久性之后的步骤，并且这些机制中的每一种如果要对安装它的人有用，最终都需要：网络连接。从网络防火墙的角度来看，每次登录时都会安静地运行但从不与网络通信的 LaunchAgent 是不可见的，而且实际上对攻击者来说用处要小得多。当它打开套接字时，FireAI 的每个应用程序规则就像任何其他进程一样适用于它：一个不熟悉的签名或未签名的二进制文件进行第一次连接时会触发提示，设备上模型的推理以简单的语言显示，并且每个决定都是可见的、可撤销的，并且可以随后作为文本规则导出。

将这两个学科放在一起的诚实方法是：按照与您的风险承受能力相匹配的时间表检查本文中的位置 - 对于大多数人来说每月一次是合理的，如果您安装了大量第三方软件，则每周一次 - 并让网络工具在两者之间承担负载，假设任何悄悄存在的东西最终都必须与它响应的任何人交谈。

## FireAI 和 HisnLabs 在其中扮演的角色

FireAI does not scan for persistence — that is a different job, and tools like KnockKnock and BlockBlock already do it well; what FireAI watches is what that persisted code does the moment it opens a socket, which is the step every one of these mechanisms eventually has to take to be useful to whoever installed it.

FireAI 是 HisnLabs 自己的产品：一款直接在 Mac 上运行的 AI 防火墙。它用通俗易懂的语言显示你的应用建立的每一个连接，并让你决定哪些数据可以离开你的 Mac——它的 AI 在本地运行，因此你的流量永远不会发送给我们或任何其他人。HisnLabs 的安全研究团队负责让这些判断保持准确：归类哪些域名只是普通的遥测、哪些属于真正的服务，追踪连接背后的国家和网络，并用真实的流量模式训练设备端模型（FireAI Pilot 功能）——这一切都不会离开你的 Mac。

你可以阅读它背后的技术决策，或试用 FireAI 17 天：[HisnLabs 出品的 FireAI](https://hisnlabs.com/fireai/zh/download)。

## Sources

- [MITRE ATT&CK T1543.001: Create or Modify System Process: Launch Agent](https://attack.mitre.org/techniques/T1543/001/)
- [MITRE ATT&CK T1543.004: Create or Modify System Process: Launch Daemon](https://attack.mitre.org/techniques/T1543/004/)
- [Objective-See: KnockKnock](https://objective-see.org/products/knockknock.html)
- [Objective-See: BlockBlock](https://objective-see.org/products/blockblock.html)
- [launchctl(1) man page (Xcode man pages mirror)](https://keith.github.io/xcode-man-pages/launchctl.1.html)
- [crontab(5) man page (Xcode man pages mirror)](https://keith.github.io/xcode-man-pages/crontab.5.html)
- [profiles(1) man page (Xcode man pages mirror)](https://keith.github.io/xcode-man-pages/profiles.1.html)
- [Apple Support: Change Login Items settings on Mac](https://support.apple.com/guide/mac-help/change-login-items-settings-mtusr003/mac)
