技术文档
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. 相关文档
这份文档有帮助吗?欢迎反馈文档问题或补充场景。提交文档反馈