FireAI 安全博客

作者 FireAI Security & Research Team · 发布于

从终端检查任何 Mac 应用程序的签名和公证

从终端检查任何 Mac 应用程序的签名和公证

当您第一次打开下载的应用程序时,Gatekeeper 会自动运行此检查,并且大多数时候您永远不会看到详细信息 - 会出现一个对话框,您单击它即可完成。这个实验室的目的是了解 Gatekeeper 看到了什么:哪个开发人员签署了该应用程序、该签名是否完整、Apple 的公证服务是否对其进行了检查,以及运行后允许执行哪些操作。这里的每个命令都附带 Xcode 的命令行工具(xcode-select --install 如果您还没有它们),并且每个命令都是只读的 - 您正在检查而不是更改应用程序。

签名本身:codesign -dvvv

codesign 是 Apple 用于创建和检查代码签名的工具。 -d 标志显示有关路径中签名代码的信息,并且根据其手册页,“增加详细级别会产生更多输出” - 因此 -dvvv(显示,三个详细级别)可以通过一个命令让您了解完整情况:

终端 — 完整的签名详细信息
codesign -dvvv /Applications/Example.app
# example output, trimmed to the fields that matter
Executable=/Applications/Example.app/Contents/MacOS/Example
Identifier=com.example.app
Format=app bundle with Mach-O universal (x86_64 arm64)
CodeDirectory v=20500 size=... flags=0x10000(runtime) hashes=...
Signature size=4741
Authority=Developer ID Application: Example Software LLC (ABCDE12345)
Authority=Developer ID Certification Authority
Authority=Apple Root CA
Team Identifier=ABCDE12345
Runtime Version=14.0.0

每次都要读取四个字段。 Authority 是证书链:正常的外部应用程序应通过Developer ID Certification Authority 以Apple Root CA 结尾,顶行命名开发人员。 Team Identifier 是 Apple 为开发者帐户提供的十个字符 ID — 用于在您认为来自同一家公司的应用程序之间进行比较的值,因为它在不同版本之间不会发生变化。 flags=0x10000(runtime) 表示启用了强化运行时,这是 Apple 进行公证所需的一组附加限制(例如阻止代码注入进程)。并且根本没有任何Authority行——只有Signature=adhoc——意味着该应用程序是未签名或自签名的,没有任何与Apple的链接。

签名是否仍与文件匹配:--verify --deep --strict

签名是在签名时对一组特定字节的承诺。 --verify 检查该承诺是否仍然有效 - 根据手册页,它确认“这些路径上的代码已签名,签名有效,并且所有密封组件均未更改”。对于应用程序包来说,两个额外的标志很重要,应用程序包是一个充满嵌套资源、框架和帮助程序可执行文件的目录,而不是单个文件:

终端——深度、严格的验证
codesign --verify --deep --strict --verbose=2 /Applications/Example.app
/Applications/Example.app: valid on disk
/Applications/Example.app: satisfies its Designated Requirement

--deep 很重要,因为根据手册页,嵌套内容的验证默认“仅限于可能无法检测到嵌套代码更改的浅层调查”——深度模式递归地验证每个嵌入式框架和辅助工具,而不仅仅是外部包。 --strict 添加了 Apple 认为足够重要、默认情况下不启用的额外检查,包括捆绑包内的任何符号链接“指向其捆绑包内的密封文件”,拒绝指向应用程序外部或未密封的内容的符号链接——这是一种将未签名的有效负载走私到其他合法签名的捆绑包中的已知技巧。如果任一检查失败,您将看到 code failed to satisfy specified code requirement 或一条注释,准确命名哪个嵌套项目与最初密封的项目不匹配 — 阅读该行,它会命名该文件。

阅读权利

权利是应用程序签名授予它的特定权限 - 相机访问权限、未沙盒访问网络的能力、禁用库验证等。 codesign -d --entitlements - 提取它们;根据手册页,“嵌入式权利数据将被同样提取并写入”给定路径,- 表示标准输出:

终端——应用程序声明的权利
codesign -d --entitlements - /Applications/Example.app
# example output, trimmed
<key>com.apple.security.cs.disable-library-validation</key>
<true/>
<key>com.apple.security.network.client</key>
<true/>
<key>com.apple.security.device.camera</key>
<true/>

大多数权利并不引人注目,并且符合应用程序明显需要的内容 - 请求摄像头访问的视频通话应用程序并不是一个发现。值得暂停的是disable-library-validation:这意味着应用程序将从其自己的签名包外部加载代码,这是某些基于插件的软件的正常需求,并且比大多数应用程序所需的范围更宽。这是一个需要注意的细节,而不是一个自动的危险信号,而且它正是那种你不询问就看不到的细节。

苹果的公证服务是否真的检查过:spctl 和stapler

有效的签名只能证明应用程序自开发者签名以来没有被更改过——它没有说明苹果是否已经看过它。这就是公证所增加的内容。苹果自己的文档将自动公证服务描述为扫描“您的软件中是否存在恶意组件,检查代码签名问题,然后快速将结果返回给您”。 当它通过时,“公证服务会生成一张票证,供您装订到您的软件中;公证服务还会在线发布该票证,Gatekeeper 可以在其中找到它。”

spctl --assess 是向 Gatekeeper 自己的策略引擎询问其结论而不是自己推断的实用方法。根据其手册页,--assess“对给定的文件执行评估”,以及-v/--verbose(重复以获取更多详细信息)被简单地描述为请求“更详细的输出”:

终端——Gatekeeper自己的评估
spctl --assess -vv /Applications/Example.app
/Applications/Example.app: accepted
source=Notarized Developer ID

source=Notarized Developer ID 是您想要看到的结果 - 这意味着 Gatekeeper 找到了有效的开发者 ID 签名和公证票,无论该票是钉在应用程序上还是在网上找到的。 source=Unnotarized Developer ID 的结果意味着该应用程序已签名,但 Apple 尚未(或尚未)对其进行公证,而平坦的 rejected 意味着 Gatekeeper 将阻止其以默认配置启动。

为了具体检查主食,xcrun stapler validate 会查找物理附加到应用程序的票证,而不是要求 Gatekeeper 在线查找它 - 对于确认应用程序在没有互联网连接的情况下仍能正确评估非常有用:

终端 — 检查装订好的机票
xcrun stapler validate /Applications/Example.app
Processing: /Applications/Example.app
The validate action worked!

隔离标志:首次运行检查的来源

苹果的平台安全指南解释说,“Gatekeeper 还会跟踪下载软件编写的文件的来源”,并“在首次打开下载的软件之前请求用户批准”。其背后的机制是一个扩展属性com.apple.quarantine,Safari、Mail 和其他应用程序将其附加到从网络保存的任何内容。 xattr -l 列出了它,并且根据手册页,此选项“导致显示属性名称和相应的值”:

终端 — 检查文件是否仍标记为已下载
xattr -l ~/Downloads/Example.dmg
com.apple.quarantine: 0081;65e1a2b3;Safari;

根本没有输出意味着该文件不带有隔离标志 - 要么是本地创建的,通过未设置属性的路径到达的(某些归档工具和包管理器会跳过它),要么是使用 xattr -d com.apple.quarantine 手动删除该标志。最后一个案例值得了解的原因不同:这是一种记录在案的方式,人们完全绕过 Gatekeeper 的首次运行检查,对他们信任或认为他们信任的文件进行检查,值得深思熟虑,而不是从一组不熟悉的终端指令中粘贴。

有效的比较:两个应用程序声称同一开发者

将所有这些结合在一起的具体方法是:假设您有一个应用程序的两份副本,两者都声称来自同一家公司,一份来自开发人员自己的网站,一份来自某人发送给您的链接。在两者上运行 codesign -dvvv 并比较 Team Identifier 行 - 它是与特定 Apple 开发人员帐户绑定的十个字符的字符串,与显示名称或捆绑包标识符不同,第二方无法在不访问该帐户签名证书的情况下随意复制它。如果两个副本显示不同的团队标识符,则您查看的不是同一应用程序的两个版本;而是同一应用程序的两个版本。您正在查看两个不同的签名者,其中之一不是下载所声称的签名者。在可疑副本上使用spctl --assess -vv - 声称与经过公证的原件相同的副本上不匹配或不存在的公证来源是确认,而不仅仅是线索。

阅读整个图片,以及实际的危险信号

  • 根本没有 Authority 链,或者一条不以 Apple Root CA 结尾的链——该应用程序背后没有负责任的开发者身份。
  • codesign --verify 失败,尤其是在一条消息命名特定嵌套文件时 - 包内的某些内容在签名后发生了变化。
  • spctl --assess 返回rejected,或者codesign -dvvv 中的团队标识符与您期望的该产品的开发人员不匹配。
  • 隔离标志在您未自行下载的文件上被明显剥离,或者通过不寻常的渠道到达(脚本、电子邮件附件重命名为看起来像其他东西)。
  • 完全开放的权利——完整的磁盘访问、禁用的库验证、不受限制的网络访问——在其声明的目的显然不需要它们的应用程序上。

这些检查都不会关注应用程序在运行并连接到网络后实际执行的操作 - 这是一个不同类型的问题,通过观察其流量而不是其签名来回答。干净的签名和有效的公证票是真正的、有意义的底线:他们说苹果已经看到了这组确切的字节,并且在检查时没有发现代码签名问题。它们是信任应用程序的开始,而不是结束。

FireAI 和 HisnLabs 在其中扮演的角色

A clean signature and a stapled ticket say an app hasn’t been tampered with since Apple checked it — they say nothing about what it connects to afterward, which is the question FireAI’s per-app rules and on-device review are built to keep answering, signature by signature, connection by connection.

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

你可以阅读它背后的技术决策,或试用 FireAI 17 天:HisnLabs 出品的 FireAI。

来源