
经常有同行问我一个问题客户只给了一个 IPA 包源码早就不在了还能不能做 iOS 成品包加固我第一次接手这种需求时心里是打鼓的因为按常规思路加固不都是从源码工程的编译阶段开始的吗手里只有一个成品包似乎连下手的地方都找不到。但真正把一个企业签名的 IPA 拆开、逐层过完之后我得说只有 IPA 的情况下iOS 成品包加固能做的事情远比想象中多只是解题思路和源码级加固完全不一样。这篇文章适合三类人看源码遗失或交接断档、只剩安装包的开发者负责企业内部应用加固和分发整改的运维或安全同学以及单纯想搞明白“一个 .app 里到底装了什么”的 iOS 技术爱好者。先说结论无源码加固的核心不是“改代码”而是“把能改的层改扎实把不能改的层看守住”。配置层、资源层可以放心动手二进制层以审计为主最后所有改动都要落回重签和真机回归。下面是我实操过的完整记录。1. 先摸透三层结构只有 IPA 时你实际上在加固什么“只有 IPA”这句话最容易被误解的地方是很多人把 IPA 当成一个拆不开的成品黑盒。其实 IPA 就是改了后缀的 zip 压缩包拆开后里面是 Payload 目录再往里走是xxx.app这个 bundle。一个典型的 .app 结构长这样unzip App.ipa -d ipa_unpack find ipa_unpack/Payload -maxdepth 2 -type d # 典型输出 # ipa_unpack/Payload/YourApp.app # ipa_unpack/Payload/YourApp.app/Frameworks # ipa_unpack/Payload/YourApp.app/PlugIns把 .app 拆成三个层次看后面每一步加固都会落到对应层上层次主要改动能力风险是否需要重签配置层Info.plist / entitlements收紧 ATS、清理后台模式、调整文件共享开关低误改会影响具体功能是资源层数据库 / 证书 / 本地化文件等删除冗余文件、检索密钥泄漏、清理脏文件低删除被依赖文件会引发运行时问题是二进制层Mach-O符号剥离、静态审计高改动逻辑类内容风险极大是三层的操作空间和风险完全不同所以实操顺序也基本固定先配置层再资源层最后二进制层。别倒过来不然很容易在最高风险的层上做低收益的折腾。1.1 最快判断这个包“能不能动”的两条命令拿到 IPA 先别急着改先用五分钟确认这个包的来源和签名类型这决定了后面所有操作的合法性边界。# 第一条查看签名信息 codesign -dv --verbose2 Payload/YourApp.app # 第二条检查二进制是否带系统加密标记 otool -l Payload/YourApp.app/YourApp | grep -A5 LC_ENCRYPTION_INFO_64用企业证书、Ad Hoc 证书、Development 证书签出来的包通常LC_ENCRYPTION_INFO_64里的cryptid为 0说明没有系统级加密这类包可以合法地做重签和整改。如果cryptid为 1说明这个包带有 Apple 的 DRM 保护机制多半是从 App Store 渠道来的。对于这类包我的原则是只做只读分析绝不修改和重新分发。任何试图移除该保护机制的操作都违反条款还有法律风险不在“自己加固”的讨论范围内。判断完签名类型还要看一眼描述文件里带的权限。用下面命令可以读出 embedded.mobileprovision 里的关键内容security cms -D -i Payload/YourApp.app/embedded.mobileprovision provision.plist重点读application-identifier、get-task-allow、ProvisionsAllDevices这几个字段。get-task-allow为 true 表示允许调试器附加生产包如果还是 true说明当时的构建配置有问题。这是第一个可以立刻修的问题——但它是写在 entitlements 里的改完同样要重签。2. 第一道工序Info.plist 与资源层的“体检式”整改配置层和资源层的改动安全系数最高而且很多隐患恰恰藏在这里。我先处理这两层相当于给包做一次全面体检。2.1 Info.plist 里最容易出问题的几个开关先用 plutil 把 Info.plist 转成可读格式plutil -p Payload/YourApp.app/Info.plist重点检查这几项。ATS 配置如果看到NSAppTransportSecurity下挂着NSAllowsArbitraryLoads1这就是一个典型的“允许所有明文网络”配置。处理方式不是无脑改成 NO而是先确认业务里还有没有 http 接口。如果应用已经完全走 https就直接收紧如果还有个别 http 域名用NSExceptionDomains只放行已知域名。这个改动不用碰代码逻辑但能显著缩小网络层的暴露面。文件共享开关UIFileSharingEnabled和LSSupportsOpeningDocumentsInPlace如果为 true用户可以通过系统的“文件”App 直接看到应用沙盒里的文档目录。企业应用如果没有“导出文件”的需求建议关掉。这是很多人容易忽略的、面向用户的数据泄漏口。注册的 URL Scheme看CFBundleURLTypes里注册的 scheme。如果注册了类似yourapp://这种敏感 scheme意味着任意安装的 App 都能尝试唤起你的应用并传递参数。唤起后的参数解析逻辑在代码里没法改但至少要把这个暴露面记录下来后续在客户端侧加参数校验时有个清单可依。后台模式UIBackgroundModes里的每一项都对应一个后台运行能力声明。多余的声明不仅容易被审核盯上也会成为安全事故排查时的干扰项能去掉就去掉。2.2 资源层的“垃圾清理”和密钥排查资源层最容易犯的错是把生产包打成开发目录。拿出一个生产包用 find 把所有文件列出来经常能发现一堆不该出现在线上应用的资产find Payload/YourApp.app -type f | sed s|.*/|| | sort重点找这几类东西证书与私钥文件.p12、.cer、.key、.pem、.der。一旦被打进包里等于把凭据送给了每一个拿到 IPA 的人。内部配置与文档.md、.txt、config.json、debug.plist这类文件里经常写着内部地址、测试账号、数据库连接串。未加密的数据库sqlite、realm文件如果整包带着说明业务数据要么没加密要么加密逻辑极弱至少要评估里面存了什么。本地化字符串文件.lproj里的 Localizable.strings 常常藏着 API 域名、内部路径、错误提示里的敏感信息值得全文搜一遍。实际操作里我会顺手在资源目录里搜几类关键词grep -rniE (api[_-]?key|secret|password|BEGIN (RSA |CERTIFICATE)) \ Payload/YourApp.app \ --include*.plist --include*.json --include*.strings -l搜出来的东西不用急着删。我的经验是先建立一份清单区分“可以安全移除”和“删除会导致功能异常”两类。比如某张宣传图被错误打进 bundle移除没有副作用但某个内置的推送证书如果被业务在启动时读取移除就会导致推送功能失效。这需要结合崩溃报告或业务方的确认来判断千万不要为了“干净”一刀切。任何资源层改动都会改变 bundle 内容所以后续必须重签。3. 第二道工序Mach-O 二进制的静态体检与修改边界配置层和资源层处理完后重头戏是那个 Mach-O 可执行文件。这里必须先把预期管理好没有源码二进制层的“加固”绝大多数是审计而不是改造。3.1 给二进制做体检的几条命令# 查看二进制架构与类型 file Payload/YourApp.app/YourApp # 查看链接了哪些动态库 otool -L Payload/YourApp.app/YourApp # 查看导出的符号 nm -gU Payload/YourApp.app/YourApp | head -50 # 检索硬编码的敏感字符串 strings Payload/YourApp.app/YourApp | grep -iE (api[_-]?key|secret|token|password)经验之谈很多企业包为了排查线上问题编译时没把符号剥离干净nm -gU能看到较多全局符号otool -ov甚至能枚举出 ObjC 的类名和方法列表。这类信息对逆向者来说就是“源代码地图”。如果确认包没剥离符号在重签之前可以用 strip 做一次剥离strip -x Payload/YourApp.app/YourApp # 移除本地符号但一定要清楚代价strip 会修改二进制破坏原有签名所以必须在重签流程里做而且剥离后崩溃日志的可读性下降需要和稳定性监控方案做权衡。我个人的建议是只在“线上稳定性监控完备、团队能接受符号化成本”时才做这步否则宁可保留符号也不要给自己制造排查障碍。3.2 二进制层“能改”与“不能改”的分界线能改的符号剥离以及上一节说的 Info.plist 键值调整。这些属于“不碰逻辑”的改动改动后重签即可。不能改的任何涉及指令逻辑的改动。比如想给启动流程加一段防调试代码、想加密某个硬编码字符串这些操作的本质是“改代码”。没有源码时只能靠注入动态库或直接 patch 机器码来实现但对一个生产包来说风险极高注入的代码和原逻辑的兼容性没有编译期保证一个线程安全问题就能让线上应用频繁崩溃注入动态库之后Apple 对签名和审核的合规要求也基本无法满足。这类操作我不建议在生产包里做更不建议拿它去替代正规的安全工程。换句话说二进制层的正确动作是“知道里面有什么”而不是“强行把它变成别的东西”。硬编码密钥、明文协议这类问题单靠改一个 IPA 是无解的正确的处理方式是回源码修、发新版然后把“禁止硬编码密钥”写进 CI 检查。4. 把改动落回真机重签、安装与回归的完整链路改完任何东西都要重签这是铁律。重签不是跑一条命令那么简单顺序错了或者 entitlements 丢了应用装到真机上可能直接闪退甚至连启动都进不去。4.1 重签名的正确顺序我常用的流程分四步先签嵌套组件再签主应用最后整体验证。# 第一步导出原包 entitlements作为重签基准 codesign -d --entitlements - Payload/YourApp.app entitlements.plist 2/dev/null || true # 第二步逐个签名 Frameworks 和 PlugIns从内到外 codesign -f -s 你的证书名称 Payload/YourApp.app/Frameworks/*.framework codesign -f -s 你的证书名称 Payload/YourApp.app/PlugIns/*.appex # 第三步签名主应用并带上 entitlements codesign -f -s 你的证书名称 --entitlements entitlements.plist Payload/YourApp.app # 第四步整体验证 codesign --verify --deep --strict Payload/YourApp.app这四步里最容易翻车的点有三个签名顺序必须先内后外最后签主 bundle。顺序反了嵌套框架的签名校验会失败。entitlements 匹配application-identifier必须和描述文件里的 App ID 一致aps-environment决定推送通道这些值不能手改。我见过有人图省事直接删掉 entitlements 重签结果 App 启动后 Keychain 访问一片报错因为 Keychain 访问组的 entitlement 丢了。描述文件重签用的证书要匹配描述文件Ad Hoc 类型还要确认目标设备 UUID 在ProvisionedDevices列表里不然安装时会被拒绝。注意修改 bundle ID 这种事情在重签阶段千万不要做。bundle ID 一变Keychain 数据、推送通道、内购记录、Universal Links 全部会失效代价远超收益。重签通过后顺手把整个 .app 的哈希打一份基线留给日后排查find Payload/YourApp.app -type f -exec shasum -a 256 {} \; | shasum -a 2564.2 真机安装与回归清单重签完成后把 Payload 目录压回 zip 并改名 .ipa就可以安装到真机上了。安装方式可以是 Apple Configurator、Xcode 的 Devices 窗口或者 ideviceinstaller 这类命令行工具。安装前确认两件事设备已开启开发者模式并且已信任对应的开发者证书。回归测试不是随便点几下就完事至少要覆盖这几条路径应用能否正常启动有没有启动即闪退的现象。登录态是否还在Keychain 数据有没有丢。推送能否正常到达特别是企业应用常用的静默推送。内购或支付流程是否正常这类功能对 entitlements 极其敏感。如果有 Widget、Share Extension 这类插件要在真实场景里各跑一遍。重签理论上不该改逻辑但签名和 entitlements 层面的问题只会在真机上暴露所以这步不能省。5. 边界地图无源码做不到的和绝对不能碰的“只有 IPA 能做什么”这个问题一半的答案其实是“不能做什么”。把边界想清楚能省下大量无效工作。5.1 没有源码就做不到的加固项编译期代码混淆字符串加密、控制流平坦化、逻辑混淆这些能力都寄生在编译器流程里对已有二进制无能为力。业务逻辑层的防篡改如果攻击者定位到了某个关键判断想改的是“逻辑”这已经接近逆向重写不在加固范畴。全面的代码虚拟化需要定制编译器工具链同样依赖源码工程。安全的密钥管理密钥如果硬编码在代码里必须从源码侧改用 Keychain、Secure Enclave 或服务端方案改包解决不了。说到这里必须承认一个扎心的事实无源码加固解决的是“暴露面积”不是“代码强度”。它能帮你收敛配置、清理资源、关掉调试后门但不能把一个本来就不安全的包变成安全的包。5.2 合规层面不能碰的红线没有权利和授权不要对第三方的包做任何修改或重签。不要绕过授权校验、内购流程或软件许可任何以“加固”为名实为破解的动作都在红线内。不要对来自 App Store 的加密包做移除保护后再分发这类操作违反条款且有法律风险。不要以为“改了包本地能跑”就等于“没问题”App Store 上架的完整包必须从源码工程构建重打包产物过不了审核。企业证书重签后的分发必须在自己公司或客户的授权范围内。证书滥用导致开发者账号被封是真实发生过的事管理上要认真对待。6. 实操后的经验清单最后把这几年的实操心得整理成一份清单算是给后面接手的人留个底。先做基线再动手动手改包前记录原始文件的哈希、文件列表、entitlements。后面无论做 diff 还是出事故往回查都靠它。最小化改动每次只改一个层面改完立刻重签验证。不要同时改 Info.plist、资源、符号再重新压包否则出了问题你根本定位不到是哪一步导致的。保留重签脚本把 codesign 流程写成脚本放进版本库。即使下次又只剩一个包恢复链路也能在两分钟内跑完。善用崩溃日志重签后的首个版本一定要在真实设备上跑够登录、支付、推送这些核心路径。重签不该改逻辑但签名和 entitlements 层面的问题只会在真机上暴露。别把无源码加固当终点如果还能找到源码或 CI 流水线的任何碎片——比如 .dSYM、构建脚本、旧版本仓库——都要尽力找回。真正的加固应该在编译期做无源码操作只是补救措施。最后再分享一个我常对团队说的话无源码加固像给已经装修好的房子加锁能把门锁、窗户、门禁都检查一遍但改不了房子的承重结构。把配置层和资源层的洞补好把签名链路管住已经能拦住大部分漫无目的的扫描式攻击至于更高强度的保护还是得靠恢复源码、把安全能力前置到开发流程里。真到了哪一天你手里的包又能从源码完整构建了再回头看看这次的补课记录你会发现它没有白折腾——那些检查和整改项本身就是一份现成的安全验收清单。