macOS 应用以 .app Bundle 形式封装,确保安全执行是其核心设计。每个 Bundle 不仅是资源容器,更是经代码签名与沙盒强化后的安全单元。本文聚焦单一技术点:从目录结构到内核加载的安全链路,结合实用命令与参数,提供可落地审计清单。
Bundle 目录结构剖析
macOS 应用 Bundle 是一个目录,遵循严格约定。核心路径为 Contents/ 下:
- Info.plist:元数据枢纽,包含 CFBundleExecutable(指向 MacOS/ 可执行体)、CFBundleIdentifier(唯一 ID)、LSMinimumSystemVersion 等。缺失或篡改将触发 Gatekeeper 阻挡。
- MacOS/:存放主 Mach-O 可执行体,如 MyApp,常为 fat binary 支持 arm64/x86_64。
- Resources/:图像、nib、lproj 本地化资源。
- Frameworks/:嵌入动态库,已签名。
- _CodeSignature/:签名 blob 目录,CodeResources 文件哈希资源树。
- embedded.provisioning.profile:可选 Provisioning Profile,绑定 Team ID 与 entitlements(macOS 非强制)。
使用 find MyApp.app -type f | head -20 快速树状扫描;plutil -p MyApp.app/Contents/Info.plist 解析 plist,验证 Bundle ID 与签名匹配。
证据:标准 Bundle 规范要求所有嵌套代码(如 Frameworks/*.framework)递归签名,否则 codesign --verify --deep 失败。
Mach-O 可执行体布局
主可执行体为 Mach-O 格式(非 ELF)。用 file MyApp.app/Contents/MacOS/MyApp 确认:"Mach-O universal binary with 2 architectures"。
关键布局:
- Header:magic=0xFEEDFACF (universal),ncmds 负载命令数。
- Load Commands:otool -l 显示 LC_SEGMENT_64 (__TEXT, __DATA)、LC_UUID、LC_CODE_SIGNATURE(偏移签名 blob)。
- Sections:__text(代码)、__cstring(字符串)、__entitlements(嵌入权限 XML,签名后)。
参数:otool -h MyApp 查看 header;otool -l | grep -A5 CODE_SIGNATURE 定位签名负载,offset/size 指向 blob。
落地:开发时用 ld -rpath @executable_path/../Frameworks 嵌入相对路径,避免 DYLD_LIBRARY_PATH 绕过。
代码签名链验证
签名是 CMS/PKCS#7 结构,嵌入 Mach-O blob,经 dyld 加载时验证。链路:Developer ID → Apple WWDR → Root CA。
命令审计:
codesign -dvvv --verbose=4 MyApp.app
输出:Format=bundle with Mach-O,Identifier、TeamIdentifier、Authority=Developer ID Application: XXX (TEAMID),Sealed Resources rules=13 files=XXX。
深验:codesign --verify --deep --strict MyApp.app 检查嵌套;失败常见:资源哈希不匹配或未签名 dylib。
风险:Hardened Runtime (flags=0x10000 (runtime)) 启用 PAC 等防护,codesign -d --entitlements :- MyApp.app dump XML。
Entitlements 解析与沙盒
Entitlements 是签名嵌入的 plist,授予权限如网络 / 文件访问。核心:<key>com.apple.security.app-sandbox</key><true/> 启用沙盒。
Dump:codesign -d --entitlements xml1 MyApp.app/Contents/MacOS/MyApp > ents.xml,查看 com.apple.security.* 键。
沙盒 profile:运行时 sandbox-exec 生成,限制文件到~/Library/Containers/com.bundleid/Data,网络 out-only(无 server)。
监控:log stream --predicate 'subsystem == "com.apple.sandbox"' 捕获 violations,如 denied {file-read-data} /private/var。
参数清单:
- com.apple.security.network.client: true(出站)
- com.apple.security.files.user-selected.read-write: true(NSOpenPanel)
- com.apple.security.temporary-exception.*: 慎用,仅调试。
Launchd/XPC 服务集成
安全执行常涉后台服务。XPC 服务在 Contents/XPCServices/*.xpcservice,子 Bundle 继承父签名。
launchd plist:~/Library/LaunchAgents/com.bundle.service.plist,指定 ProgramArguments、MachServices com.bundle.xpc。
集成:主 app 用 xpc_connection_create 连接,系统校验签名 / 权限。参数:KeepAlive true,RunAtLoad false,避免 DoS。
审计:launchctl print gui/$(id -u)/com.bundle.service 查看状态;服务 Mach-O 同上验证。
安全审计清单(可落地)
- 结构完整:
pkgutil --check-signature MyApp.app无 warning。 - 签名链:
spctl --assess --verbose MyApp.app→ accepted source=Developer ID。 - 嵌套代码:逐 Frameworks/Plugins/ 运行 codesign -dvvv。
- Entitlements 最小:grep sandbox.xml 无 all-files/unrestricted。
- 沙盒测试:sandbox-exec -f ents.sb MyApp.app/Contents/MacOS/MyApp --test,模拟 violations。
- 公证:
xcrun notarytool submit MyApp.dmg --keychain-profilestaple ticket。 - 回滚:签名失效 → codesign --remove-signature 重签;沙盒违规 → entitlements 精简 + TCC 授权。
参数阈值:签名过期 <90 天警报;violation>5 / 分钟 → 进程 kill。
此链路确保 tamper-proof 执行,适用于自建 / 分发 app。实际如 Safari.app,层层嵌套却无缝。
资料来源:
- Apple Entitlements 文档:https://developer.apple.com/documentation/bundleresources/entitlements
- TN3125 Code Signing:https://developer.apple.com/documentation/technotes/tn3125-inside-code-signing-provisioning-profiles
- codesign (1) man page 与 otool (1)。
(正文约 1250 字)