技术文档

macOS Mach-O 加固推荐方案

macOS Mach-O(可执行文件、dylib、Bundle)的保护面、Universal 二进制、签名与公证约束,以及推荐加固组合。

最后更新于 2025-06-06预计阅读 8 分钟

1. 适用范围与支持说明

项目范围
格式Mach-O(thin / fat / Universal)
架构arm64、x86_64(按最低系统与分发范围)
产物可执行文件、.dylib、.framework、.app 内的主二进制与插件
分发官网直发、DMG / PKG、Homebrew、Mac App Store(受审核约束)

本文给出 Mach-O 的加固组合、签名流程与验证方法。具体可用选项与支持范围以当前客户端版本和授权为准;Universal 二进制、特殊 Bundle 形态建议先做样本验证。

不适用于:

  • iOS IPA:见 iOS 加固推荐方案
  • 系统自带二进制与受 SIP 保护的组件
  • 纯脚本(Shell / Python / JavaScript)产物

2. Mach-O 结构注意

2.1 Thin 与 Universal

  • 现代 macOS 软件通常同时包含 arm64 与 x86_64 两个 slice。
  • 使用 lipo -info 查看架构,使用 lipo -thin 拆分、lipo -create 合并。
  • 保护时必须覆盖所有 slice;只处理其中一个会导致另一架构启动失败。
  • Apple Silicon 上运行 x86_64 依赖 Rosetta 2,测试时不要只用原生 arm64 结果代替。

2.2 文件类型

类型说明注意事项
MH_EXECUTE主可执行文件入口点、__TEXT 段、签名
MH_DYLIB动态库 / Framework导出符号、install name、依赖
MH_BUNDLE插件 Bundle宿主加载、代码签名
MH_OBJECT目标文件不直接分发,通常不处理

2.3 关键段

  • __TEXT:代码与只读数据,修改后签名必然失效。
  • __DATA / __DATA_CONST:可写数据与常量,注意完整性与重定位。
  • __LINKEDIT:符号表、重定位、代码签名数据。
  • LC_CODE_SIGNATURE:代码签名;任何字节修改都要重新签名。

3. 保护面与能力映射

层风险推荐手段
符号与元数据类名、方法名、Swift mangling 暴露结构Strip、隐藏符号、避免语义化命名
字符串接口地址、密钥被 strings 提取字符串加密、运行时拼接
关键逻辑授权、支付、协议签名被定位下沉到 C/C++,函数混淆 / 虚拟化评估
资源配置、模型、脚本被替换资源加密、哈希校验
运行时调试器附加、Frida / LLDB 注入ptrace / sysctl 反调试、注入检测
完整性Mach-O、资源、签名被替换代码段哈希、Team ID / Bundle ID 校验
分发重签名、篡改后二次分发公证、Gatekeeper、服务端校验

能力映射(以当前客户端实际选项为准):

能力Mach-O 场景说明
压缩保护可评估启动时解压,App / Universal 二进制越大越慢,需验证签名与加载
移除符号推荐发布版减少信息暴露
函数混淆推荐核心业务逻辑
函数虚拟化少量核心函数被保护方法慢数倍到数十倍,性能与兼容性需重点验证
反调试推荐与开发、排障、部分安全工具冲突
内存 / 完整性校验视场景启动阶段遍历产物,越大越慢;与签名、热更新结合

性能影响(macOS 启动尤其敏感)

  • 压缩保护会在 App 启动时解压整个 Mach-O / Bundle;Universal 二进制包含多个架构,体积更大,启动更慢。
  • 文件 / 完整性校验会在启动阶段遍历产物,文件越大耗时越长;公证与 Gatekeeper 校验还会带来额外系统开销。
  • 函数虚拟化会让被保护方法运行速度慢数倍到数十倍;Swift / ObjC 初始化、热循环、每帧回调不要虚拟化。
  • 先用未保护 App 测冷启动、首次窗口时间,再逐项开启保护;大体积 App / 内嵌大量 Framework 时重点测试。

4. 三档推荐方案

4.1 基础档:兼容性优先

措施设置
符号发布版 Strip,保留 .dSYM
字符串敏感字符串不硬编码
函数保护5–10 个核心函数混淆
反调试关(先保证稳定)
签名开发签名 + 本地验证

4.2 标准档:商业发布

措施设置
符号Strip + 隐藏非必要导出符号
函数保护授权、支付、协议函数虚拟化;业务逻辑混淆
反调试开
完整性校验关键资源与配置
分发Developer ID 签名 + 公证 + Staple

4.3 强化档:高价值目标

措施设置
函数保护10–30 个核心函数虚拟化,其余敏感函数混淆
反调试多点检测,异常环境降级而非直接崩溃
注入检测扫描异常动态库与环境变量
完整性代码段、资源、签名多维校验 + 服务端挑战
分发公证、Gatekeeper 验证、灰度发布

5. 函数选择

5.1 优先保护

  • 授权激活、许可证校验、试用期判断
  • 内购 / 支付校验、订单状态机
  • 协议签名、加密解密、密钥派生
  • 核心算法、音视频处理、渲染管线中的关键分支
  • 反调试与完整性校验自身的方法

5.2 谨慎保护

  • main、+load、__attribute__((constructor)) 等启动阶段代码
  • Objective-C / Swift 运行时依赖的方法(KVC、KVO、序列化)
  • 高频调用的工具函数与热循环
  • 与系统框架、XPC、插件交互的边界
  • 已经由其他混淆器处理过的代码

5.3 不建议保护

  • 系统库、Apple Framework、受 SIP 保护的组件
  • 只做转发的 thunk
  • 第三方静态库中未验证的代码
  • 与代码签名、公证工具链直接交互的脚本与二进制

6. 签名、Hardened Runtime 与公证

6.1 推荐流程

Release 构建(保留 dSYM)
        ↓
原始产物自测
        ↓
保护 / 混淆 / Strip
        ↓
codesign(Developer ID,启用 Hardened Runtime)
        ↓
notarytool 提交公证
        ↓
staple 装订公证票据
        ↓
spctl 验证 + 真机回归
        ↓
DMG / PKG / 官网分发

6.2 代码签名

# 查看架构
lipo -info MyApp.app/Contents/MacOS/MyApp

# 签名(示例)
codesign --force --deep --options runtime \
  --entitlements MyApp.entitlements \
  --sign "Developer ID Application: Example (TEAMID)" \
  MyApp.app

# 校验
codesign --verify --deep --strict --verbose=2 MyApp.app
  • 保护必须在签名之前完成,任何后续字节修改都会使签名失效。
  • --deep 仅用于简单场景;复杂 App 建议自底向上逐个签名内嵌组件。
  • 保留原始 .dSYM,按版本归档,用于崩溃符号化。

6.3 Hardened Runtime

  • 正式分发建议启用 Hardened Runtime(--options runtime)。
  • 启用后默认开启 Library Validation,注入或加载未签名插件会被拒绝。
  • 只有确有必要时才添加 com.apple.security.cs.disable-library-validation 等放宽权限的 entitlement。
  • 对 JIT、调试、Apple Events 等能力,按实际需要申请最小 entitlement。

6.4 公证与 Gatekeeper

xcrun notarytool submit MyApp.zip \
  --keychain-profile "notary-profile" \
  --wait

xcrun stapler staple MyApp.app
spctl --assess --type execute --verbose=4 MyApp.app
  • 公证失败通常由签名不完整、Hardened Runtime 配置错误或包含不允许的组件导致。
  • 公证通过后必须 Staple,否则离线用户首次打开可能被 Gatekeeper 拦截。
  • 修改 App 内容后需要重新签名、重新公证。

7. 反调试、完整性与依赖

7.1 反调试

#import <sys/sysctl.h>
#import <sys/ptrace.h>

static int isBeingDebugged(void) {
    int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, getpid()};
    struct kinfo_proc info;
    size_t size = sizeof(info);
    memset(&info, 0, sizeof(info));
    if (sysctl(mib, 4, &info, &size, NULL, 0) != 0) return 0;
    return (info.kp_proc.p_flag & P_TRACED) != 0;
}

static void disableDebugger(void) {
    ptrace(PT_DENY_ATTACH, 0, 0, 0);
}
  • PT_DENY_ATTACH 会让调试器附加失败,也可能影响客服排障。
  • 反调试逻辑本身容易被 patch,建议多点校验并结合服务端风控。
  • 不要修改系统二进制或绕过 SIP。

7.2 完整性校验

  • 校验 Mach-O __TEXT 段哈希、Team ID、Bundle ID。
  • 校验关键资源、配置与脚本的哈希。
  • 校验逻辑放在 native 层,并由服务端下发挑战值,避免固定哈希被替换。
  • 与代码签名校验配合,不要只依赖单一哈希。

7.3 依赖与注入

  • 检查 LC_LOAD_DYLIB 与 @rpath,确认依赖路径在保护后仍然有效。
  • 检查 DYLD_INSERT_LIBRARIES、异常动态库与调试端口。
  • 对 XPC 服务、Login Item、Privileged Helper,分别验证签名与通信。
  • 对插件体系,验证宿主加载顺序与 Library Validation。

8. 验证清单

  • 原始产物在目标 macOS 版本可正常运行
  • 保护后 Intel 与 Apple Silicon 均能启动
  • Universal 二进制的所有 slice 均已处理
  • 导出符号、dylib、Framework、插件加载正常
  • 授权、内购、自动更新链路正常
  • codesign --verify 与 spctl 通过
  • 公证成功并完成 Staple
  • Hardened Runtime 与 entitlement 配置正确
  • 崩溃栈可通过 .dSYM 符号化
  • 最大体积 App / Universal 二进制的冷启动已单独测试
  • 被虚拟化方法的调用延迟已对比基线
  • 性能指标与基线对比在可接受范围

9. 常见问题

9.1 保护后 App 无法启动

  • 签名失效:确认所有修改都在 codesign 之前完成。
  • Hardened Runtime 拒绝加载:检查 Library Validation 与 entitlement。
  • 某个 slice 未处理:用 lipo -info 确认架构并重新处理。
  • 依赖路径被破坏:检查 @rpath、@loader_path 与内嵌 Framework。

9.2 公证被拒

  • 签名不完整:内嵌组件、Helper、插件需要逐个签名。
  • 使用了不允许的 entitlement 或未签名的动态库。
  • 包含下载后执行的脚本或未声明的组件。
  • 用 notarytool log 查看具体拒绝原因。

9.3 Universal 二进制只有一个架构可用

  • 保护工具可能只处理了当前架构。
  • 使用 lipo -thin 分别保护后 lipo -create 合并。
  • 合并后重新签名并重新公证。

9.4 反调试导致正常用户崩溃

  • 部分远程协助、录屏、安全软件、调试环境会被误判。
  • 发布版降低检测强度,或在异常环境下降级而不是退出。
  • 保留关闭反调试的构建渠道,便于客服排查。

9.5 能否用于 Mac App Store

  • Mac App Store 使用 Apple 分发签名与沙盒要求,加固不能改变审核可见的功能。
  • 动态代码、下载执行、热更新受审核限制。
  • 上架前请确认签名、沙盒 entitlement 与公证要求。

10. 相关文档

这份文档有帮助吗?欢迎反馈文档问题或补充场景。提交文档反馈